diff --git a/docs-ui/src/components/PropsTable/PropsTable.tsx b/docs-ui/src/components/PropsTable/PropsTable.tsx index 8fedeb7c0c..bd54b1e245 100644 --- a/docs-ui/src/components/PropsTable/PropsTable.tsx +++ b/docs-ui/src/components/PropsTable/PropsTable.tsx @@ -2,15 +2,13 @@ import * as Table from '../Table'; import { Chip } from '../Chip'; +import { TypePopup } from './TypePopup'; import { icons } from '../../../../packages/ui'; -// Define a more specific type for the data object -type PropData = { - values?: string | string[]; - responsive?: boolean; - default?: string; - type?: string; -}; +import { PropDef } from '@/utils/propDefs'; + +// Use the proper PropDef type +type PropData = PropDef; // Modify the PropsTable component to use the new type export const PropsTable = >({ @@ -59,6 +57,12 @@ export const PropsTable = >({ string )} + {data[n].type === 'complex' && data[n].complexType && ( + + )} {data[n].type === 'enum | string' && ( <> {enumValues} diff --git a/docs-ui/src/components/PropsTable/TypePopup.module.css b/docs-ui/src/components/PropsTable/TypePopup.module.css new file mode 100644 index 0000000000..294df0b645 --- /dev/null +++ b/docs-ui/src/components/PropsTable/TypePopup.module.css @@ -0,0 +1,79 @@ +.button { + display: inline-flex; + align-items: center; + font-family: monospace; + font-size: 13px; + border-radius: 6px; + padding: 0px 8px; + height: 24px; + margin-right: 4px; + background-color: #f0f0f0; + color: #5d5d5d; + cursor: pointer; + border: none; + outline: none; + box-shadow: none; + text-decoration: underline; +} + +[data-theme='dark'] .button { + background-color: #2c2c2c; + color: #fff; +} + +.popover { + border: 1px solid var(--border); + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 6px; + background: var(--panel); + color: var(--primary); + outline: none; + /* max-width: 400px; */ + transition: transform 200ms, opacity 200ms; + padding: 1rem; + margin-top: 6px; + --origin: translateY(-8px); + + &[data-entering], + &[data-exiting] { + transform: var(--origin); + opacity: 0; + } +} + +.arrow svg { + display: block; + fill: var(--panel); + stroke: var(--border); + stroke-width: 1px; + transform: rotate(180deg); +} + +.name { + font-size: 14px; + font-weight: 500; + margin-bottom: 1rem; +} + +.grid { + display: grid; + grid-template-columns: auto auto 1fr; + gap: 0.5rem 1rem; +} + +.col1 { + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); + padding-top: 0.25rem; +} + +.col2 { + font-size: 13px; +} + +.col3 { + font-size: 13px; + color: var(--text-secondary); + padding-top: 0.25rem; +} diff --git a/docs-ui/src/components/PropsTable/TypePopup.tsx b/docs-ui/src/components/PropsTable/TypePopup.tsx new file mode 100644 index 0000000000..bd0fc5dd76 --- /dev/null +++ b/docs-ui/src/components/PropsTable/TypePopup.tsx @@ -0,0 +1,49 @@ +import { ComplexTypeDef } from '@/utils/propDefs'; +import { Chip } from '../Chip'; +import { + Button, + Dialog, + DialogTrigger, + OverlayArrow, + Popover, +} from 'react-aria-components'; +import styles from './TypePopup.module.css'; + +interface TypePopupProps { + complexType: ComplexTypeDef; + name: string; +} + +export const TypePopup = ({ complexType, name }: TypePopupProps) => { + return ( + + + + + + + + + +
{complexType.name}
+
+ {Object.entries(complexType.properties).map( + ([propName, propDef]) => [ +
+ {propName} + {propDef.required ? '' : '?'} +
, +
+ {propDef.type} +
, +
+ {propDef.description} +
, + ], + )} +
+
+
+
+ ); +}; diff --git a/docs-ui/src/content/components/header.mdx b/docs-ui/src/content/components/header.mdx new file mode 100644 index 0000000000..d59dd79613 --- /dev/null +++ b/docs-ui/src/content/components/header.mdx @@ -0,0 +1,69 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Snippet } from '@/components/Snippet'; +import { HeaderSnippet } from '@/snippets/stories-snippets'; +import { + propDefs, + flexUsageSnippet, + flexDefaultSnippet, + flexFAQ1Snippet, + flexSimpleSnippet, + flexResponsiveSnippet, + flexAlignSnippet, + usage, + defaultSnippet, +} from './header.props'; +import { spacingPropDefs } from '@/utils/propDefs'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={defaultSnippet} +/> + +## Usage + + + +## API reference + + + +The grid component also accepts all the spacing props from the Box component. + + + +## Examples + +### Simple + +A simple example of how to use the Flex component. + + + +### Responsive + +The Flex component also supports responsive values, making it easy to create +responsive designs. + + + +### Align + +The Flex component also supports responsive alignment, making it easy to +create responsive designs. + + + + + + diff --git a/docs-ui/src/content/components/header.props.ts b/docs-ui/src/content/components/header.props.ts new file mode 100644 index 0000000000..ee22fb7d67 --- /dev/null +++ b/docs-ui/src/content/components/header.props.ts @@ -0,0 +1,147 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const propDefs: Record = { + icon: { + type: 'enum', + values: ['ReactNode'], + }, + title: { + type: 'string', + default: 'Your plugin', + }, + titleLink: { + type: 'string', + default: '/', + }, + breadcrumbs: { + type: 'complex', + complexType: { + name: 'Breadcrumb[]', + properties: { + label: { + type: 'string', + required: true, + description: 'Display text for the breadcrumb', + }, + href: { + type: 'string', + required: true, + description: 'URL for the breadcrumb link', + }, + }, + }, + }, + customActions: { + type: 'enum', + values: ['ReactNode'], + }, + menuItems: { + type: 'complex', + complexType: { + name: 'MenuItem[]', + properties: { + label: { + type: 'string', + required: true, + description: 'Display text for the menu item', + }, + value: { + type: 'string', + required: true, + description: 'Unique value for the menu item', + }, + onClick: { + type: '() => void', + required: false, + description: 'Callback function when menu item is clicked', + }, + }, + }, + }, + tabs: { + type: 'complex', + complexType: { + name: 'HeaderTab[]', + properties: { + id: { + type: 'string', + required: true, + description: 'Unique identifier for the tab', + }, + label: { + type: 'string', + required: true, + description: 'Display text for the tab', + }, + href: { + type: 'string', + required: false, + description: 'URL to navigate to when tab is clicked', + }, + matchStrategy: { + type: "'exact' | 'prefix'", + required: false, + description: 'How to match the current route to highlight the tab', + }, + }, + }, + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const usage = `import { Header } from '@backstage/ui'; + +
`; + +export const defaultSnippet = `
+ } /> + } /> + } /> + + } +/>`; + +export const flexSimpleSnippet = ` + Hello World + Hello World + Hello World +`; + +export const flexResponsiveSnippet = ` + Hello World + Hello World + Hello World +`; + +export const flexAlignSnippet = ` + Hello World + Hello World + Hello World +`; diff --git a/docs-ui/src/content/components/tabs.mdx b/docs-ui/src/content/components/tabs.mdx index dd0552b436..26905d4733 100644 --- a/docs-ui/src/content/components/tabs.mdx +++ b/docs-ui/src/content/components/tabs.mdx @@ -3,12 +3,13 @@ import { TabsSnippet } from '@/snippets/stories-snippets'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { - tabsRootPropDefs, - tabsListPropDefs, - tabsTabPropDefs, - tabsPanelPropDefs, + tabsPropDefs, + tabPropDefs, tabsUsageSnippet, tabsDefaultSnippet, + tabsWithTabPanelsSnippet, + tabsWithLinksSnippet, + tabsWithDeeplyNestedRoutesSnippet, } from './tabs.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -31,29 +32,52 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; ## API reference -### Tabs.Root +### Tabs Groups the tabs and the corresponding panels. Renders a `
` element. - + -### Tabs.List - -Groups the individual tab buttons. Renders a `
` element. - - - -### Tabs.Tab +### Tab An individual interactive tab button that toggles the corresponding panel. Renders a `, menuItems, }, decorators: [withRouter], + render: args => ( +
Custom action} /> + ), }; export const WithBreadcrumbs: Story = { @@ -185,11 +187,25 @@ export const WithBreadcrumbs: Story = { export const WithAllComponents: Story = { args: { + title: 'My plugin', + titleLink: '/', menuItems, tabs, breadcrumbs, }, decorators: [withRouter], + render: args => ( +
+ } /> + } /> + } /> + + } + /> + ), }; export const WithLayout: Story = { diff --git a/packages/ui/src/components/Tabs/Tabs.stories.tsx b/packages/ui/src/components/Tabs/Tabs.stories.tsx index 9f6e05e289..a0a1fa75dc 100644 --- a/packages/ui/src/components/Tabs/Tabs.stories.tsx +++ b/packages/ui/src/components/Tabs/Tabs.stories.tsx @@ -95,10 +95,10 @@ export const WithMockedURLTab2: Story = { - + Current URL is mocked to be: /tab2 - + Notice how the "Tab 2" tab is selected (highlighted) because it matches the current path. @@ -273,14 +273,16 @@ export const PrefixMatchingDeepNesting: Story = { - + Current URL: /catalog/users/john/details - + The "Catalog" tab is active because it uses prefix matching and the URL starts with "/catalog". - This works for any level of nesting under "/catalog". + + This works for any level of nesting under "/catalog". + ),