From 40a30551a088bd54623aa2bca39742c0fd0aa84b Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 22 Jan 2026 12:18:59 +0100 Subject: [PATCH] feat(docs-ui): improve documentation components - Add ReactAriaLink component for linking to React Aria docs - Make PropsTable columns configurable - Improve Chip styling to support multiline content - Allow ReactNode in PropDef descriptions Signed-off-by: Johan Persson --- docs-ui/src/components/Chip/styles.module.css | 6 +- .../src/components/PropsTable/PropsTable.tsx | 146 +++++++++++------- .../ReactAriaLink/ReactAriaLink.tsx | 26 ++++ docs-ui/src/components/ReactAriaLink/index.ts | 1 + .../ReactAriaLink/styles.module.css | 15 ++ docs-ui/src/utils/propDefs.ts | 3 +- 6 files changed, 138 insertions(+), 59 deletions(-) create mode 100644 docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx create mode 100644 docs-ui/src/components/ReactAriaLink/index.ts create mode 100644 docs-ui/src/components/ReactAriaLink/styles.module.css diff --git a/docs-ui/src/components/Chip/styles.module.css b/docs-ui/src/components/Chip/styles.module.css index 01ee29311d..80c37d8899 100644 --- a/docs-ui/src/components/Chip/styles.module.css +++ b/docs-ui/src/components/Chip/styles.module.css @@ -1,11 +1,9 @@ .chip { - display: inline-flex; - align-items: center; + display: inline; font-family: monospace; font-size: 13px; border-radius: 6px; - padding: 0px 8px; - height: 24px; + padding: 4px 8px; margin-right: 4px; background-color: #f0f0f0; color: #5d5d5d; diff --git a/docs-ui/src/components/PropsTable/PropsTable.tsx b/docs-ui/src/components/PropsTable/PropsTable.tsx index 86b7b274ef..e1324b88b9 100644 --- a/docs-ui/src/components/PropsTable/PropsTable.tsx +++ b/docs-ui/src/components/PropsTable/PropsTable.tsx @@ -6,77 +6,115 @@ import { TypePopup } from './TypePopup'; import { PropDef } from '@/utils/propDefs'; -// Use the proper PropDef type type PropData = PropDef; -// Modify the PropsTable component to use the new type +type ColumnType = 'prop' | 'type' | 'default' | 'description' | 'responsive'; + +interface ColumnConfig { + key: ColumnType; + width: string; +} + +const defaultColumns: ColumnConfig[] = [ + { key: 'prop', width: '16%' }, + { key: 'type', width: '50%' }, + { key: 'default', width: '20%' }, + { key: 'responsive', width: '14%' }, +]; + +const columnLabels: Record = { + prop: 'Prop', + type: 'Type', + default: 'Default', + description: 'Description', + responsive: 'Responsive', +}; + export const PropsTable = >({ data, + columns = defaultColumns, }: { data: T; + columns?: ColumnConfig[]; }) => { if (!data) return null; + const renderCell = ( + propName: string, + propData: PropData, + column: ColumnType, + ) => { + const enumValues = + Array.isArray(propData.values) && + propData.values.map(t => {t}); + + switch (column) { + case 'prop': + return {propName}; + + case 'type': + return ( +
+ {propData.type === 'string' && string} + {propData.type === 'number' && number} + {propData.type === 'boolean' && boolean} + {propData.type === 'enum' && enumValues} + {propData.type === 'spacing' && ( + <> + 0.5, 1, 1.5, 2, 3, ..., 14 + string + + )} + {propData.type === 'complex' && propData.complexType && ( + + )} + {propData.type === 'enum | string' && ( + <> + {enumValues} + string + + )} +
+ ); + + case 'default': + return propData.default ? {propData.default} : null; + + case 'description': + return propData.description || null; + + case 'responsive': + return {propData.responsive ? 'Yes' : 'No'}; + + default: + return null; + } + }; + return ( - Prop - Type - Default - - Responsive - + {columns.map(col => ( + + {columnLabels[col.key]} + + ))} - {Object.keys(data).map(n => { - const enumValues = - Array.isArray(data[n].values) && - data[n].values.map(t => {t}); - - return ( - - - {n} + {Object.keys(data).map(n => ( + + {columns.map(col => ( + + {renderCell(n, data[n], col.key)} - -
- {data[n].type === 'string' && string} - {data[n].type === 'number' && number} - {data[n].type === 'boolean' && boolean} - {data[n].type === 'enum' && enumValues} - {data[n].type === 'spacing' && ( - <> - 0.5, 1, 1.5, 2, 3, ..., 14 - string - - )} - {data[n].type === 'complex' && data[n].complexType && ( - - )} - {data[n].type === 'enum | string' && ( - <> - {enumValues} - string - - )} -
-
- - {data[n].default ? data[n].default : '-'} - - - {data[n].responsive ? 'Yes' : 'No'} - -
- ); - })} + ))} +
+ ))}
); diff --git a/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx b/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx new file mode 100644 index 0000000000..37656c482d --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx @@ -0,0 +1,26 @@ +import styles from './styles.module.css'; + +interface ReactAriaLinkProps { + /** The React Aria component name (e.g., "Cell", "Table") */ + component: string; + /** The documentation URL */ + href: string; +} + +/** + * Displays a standardized note linking to React Aria documentation. + * + * Usage: + * + */ +export function ReactAriaLink({ component, href }: ReactAriaLinkProps) { + return ( +

+ Inherits all{' '} + + React Aria {component} + {' '} + props. +

+ ); +} diff --git a/docs-ui/src/components/ReactAriaLink/index.ts b/docs-ui/src/components/ReactAriaLink/index.ts new file mode 100644 index 0000000000..8fa7b70e73 --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/index.ts @@ -0,0 +1 @@ +export { ReactAriaLink } from './ReactAriaLink'; diff --git a/docs-ui/src/components/ReactAriaLink/styles.module.css b/docs-ui/src/components/ReactAriaLink/styles.module.css new file mode 100644 index 0000000000..b74436fee3 --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/styles.module.css @@ -0,0 +1,15 @@ +.note { + font-size: 1rem; + color: var(--secondary); + margin-top: 0.5rem; + margin-bottom: 1rem; +} + +.note a { + color: var(--link); + text-decoration: none; +} + +.note a:hover { + text-decoration: underline; +} diff --git a/docs-ui/src/utils/propDefs.ts b/docs-ui/src/utils/propDefs.ts index ed4a07e7ee..2dd4f5c25a 100644 --- a/docs-ui/src/utils/propDefs.ts +++ b/docs-ui/src/utils/propDefs.ts @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import type { Breakpoint } from '@backstage/ui/src/types'; const breakpoints = ['initial', 'xs', 'sm', 'md', 'lg', 'xl'] as Breakpoint[]; @@ -28,7 +29,7 @@ export type PropDef = { default?: string; required?: boolean; responsive?: boolean; - description?: string; + description?: ReactNode; }; export { breakpoints };