Merge pull request #29576 from backstage/canon-docs-updates
Canon - Added docs for Menu + Link
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Tabs } from '@/components/Tabs';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
MenuSnippet,
|
||||
ButtonSnippet,
|
||||
LinkSnippet,
|
||||
} from '@/snippets/stories-snippets';
|
||||
import { linkPropDefs } from './props';
|
||||
|
||||
# Link
|
||||
|
||||
A link component that renders a `<a>` element.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<LinkSnippet story="Default" />}
|
||||
code={`<Link href="https://backstage.io">Sign up for Backstage</Link>`}
|
||||
/>
|
||||
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Usage</Tabs.Tab>
|
||||
<Tabs.Tab>Theming</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>
|
||||
<CodeBlock
|
||||
code={`import { Link } from '@backstage/canon';
|
||||
|
||||
<Link href="https://backstage.io">Sign up for Backstage</Link>`}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel>
|
||||
We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with
|
||||
your brand. For additional flexibility, you can use the provided class names for each element listed below.
|
||||
- `canon-Link`
|
||||
- `canon-Link[data-variant='body']`
|
||||
- `canon-Link[data-variant='subtitle']`
|
||||
- `canon-Link[data-variant='caption']`
|
||||
- `canon-Link[data-variant='label']`
|
||||
- `canon-Link[data-weight='regular']`
|
||||
- `canon-Link[data-weight='bold']`
|
||||
</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={linkPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when links have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<LinkSnippet story="AllVariants" />}
|
||||
code={`<Flex gap="4" direction="column">
|
||||
<Link href="https://canon.backstage.io" variant="subtitle" />
|
||||
<Link href="https://canon.backstage.io" variant="body" />
|
||||
<Link href="https://canon.backstage.io" variant="caption" />
|
||||
<Link href="https://canon.backstage.io" variant="label" />
|
||||
</Flex>`}
|
||||
/>
|
||||
|
||||
### Weights
|
||||
|
||||
Here's a view when links have different weights.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<LinkSnippet story="AllWeights" />}
|
||||
code={`<Flex gap="4" direction="column">
|
||||
<Link href="https://canon.backstage.io" weight="regular" />
|
||||
<Link href="https://canon.backstage.io" weight="bold" />
|
||||
</Flex>`}
|
||||
/>
|
||||
@@ -0,0 +1,22 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const linkPropDefs: Record<string, PropDef> = {
|
||||
to: {
|
||||
type: 'string',
|
||||
},
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['subtitle', 'body', 'caption', 'label'],
|
||||
default: 'body',
|
||||
responsive: true,
|
||||
},
|
||||
weight: {
|
||||
type: 'enum',
|
||||
values: ['regular', 'bold'],
|
||||
default: 'regular',
|
||||
responsive: true,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Tabs } from '@/components/Tabs';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { MenuSnippet } from '@/snippets/stories-snippets';
|
||||
import { BaseUI } from '@/components/HeadlessBanners/BaseUI';
|
||||
import {
|
||||
menuRootPropDefs,
|
||||
menuTriggerPropDefs,
|
||||
menuPositionerPropDefs,
|
||||
menuItemPropDefs,
|
||||
} from './props';
|
||||
|
||||
# Menu
|
||||
|
||||
A list of actions in a dropdown, enhanced with keyboard navigation.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<MenuSnippet story="Default" />}
|
||||
code={`<Menu.Root>
|
||||
<Menu.Trigger
|
||||
render={props => (
|
||||
<Button
|
||||
{...props}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
iconEnd="chevron-down"
|
||||
>
|
||||
Menu
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner sideOffset={8} align="start">
|
||||
<Menu.Popup>
|
||||
<Menu.Item>Settings</Menu.Item>
|
||||
<Menu.Item>Invite new members</Menu.Item>
|
||||
<Menu.Item>Download app</Menu.Item>
|
||||
<Menu.Item>Log out</Menu.Item>
|
||||
</Menu.Popup>
|
||||
</Menu.Positioner>
|
||||
</Menu.Portal>
|
||||
</Menu.Root>`}
|
||||
/>
|
||||
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Usage</Tabs.Tab>
|
||||
<Tabs.Tab>Theming</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>
|
||||
<CodeBlock
|
||||
code={`import { Menu } from '@backstage/canon';
|
||||
|
||||
<Menu.Root>
|
||||
<Menu.Trigger />
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner>
|
||||
<Menu.Popup>
|
||||
<Menu.Item />
|
||||
</Menu.Popup>
|
||||
</Menu.Positioner>
|
||||
</Menu.Portal>
|
||||
</Menu.Root>`}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel>
|
||||
We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with
|
||||
your brand. For additional flexibility, you can use the provided class names for each element listed below.
|
||||
- `canon-MenuTrigger`
|
||||
- `canon-MenuBackdrop`
|
||||
- `canon-MenuPositioner`
|
||||
- `canon-MenuPopup`
|
||||
- `canon-MenuArrow`
|
||||
- `canon-MenuItem`
|
||||
- `canon-MenuGroup`
|
||||
- `canon-MenuGroupLabel`
|
||||
- `canon-MenuRadioGroup`
|
||||
- `canon-MenuRadioItem`
|
||||
- `canon-MenuRadioItemIndicator`
|
||||
- `canon-MenuCheckboxItem`
|
||||
- `canon-MenuCheckboxItemIndicator`
|
||||
- `canon-MenuSubmenuTrigger`
|
||||
- `canon-MenuSeparator`
|
||||
</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
|
||||
## API reference
|
||||
|
||||
<BaseUI href="https://base-ui.com/react/components/menu" />
|
||||
|
||||
### Menu.Root
|
||||
|
||||
Groups all parts of the menu. Doesn't render its own HTML element.
|
||||
|
||||
<PropsTable data={menuRootPropDefs} />
|
||||
|
||||
### Menu.Trigger
|
||||
|
||||
The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend
|
||||
using the `render` prop to render a custom trigger.
|
||||
|
||||
<CodeBlock code={`<Menu.Trigger render={props => <Button {...props} />} />`} />
|
||||
|
||||
<PropsTable data={menuTriggerPropDefs} />
|
||||
|
||||
### Menu.Positioner
|
||||
|
||||
Positions the menu popup against the trigger. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={menuPositionerPropDefs} />
|
||||
|
||||
### Menu.Item
|
||||
|
||||
An individual interactive item in the menu. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={menuItemPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Open on hover
|
||||
|
||||
To create a menu that opens on hover, add the `openOnHover` prop to the Root part.
|
||||
You can additionally configure how quickly the menu opens on hover using the `delay` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="OpenOnHover" />}
|
||||
code={`<Menu.Root openOnHover delay={100}>
|
||||
<Menu.Trigger
|
||||
render={props => (
|
||||
<Button
|
||||
{...props}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
iconEnd="chevron-down"
|
||||
>
|
||||
Menu
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner sideOffset={8} align="start">
|
||||
<Menu.Popup>
|
||||
<Menu.Item>Settings</Menu.Item>
|
||||
<Menu.Item>Invite new members</Menu.Item>
|
||||
<Menu.Item>Download app</Menu.Item>
|
||||
<Menu.Item>Log out</Menu.Item>
|
||||
</Menu.Popup>
|
||||
</Menu.Positioner>
|
||||
</Menu.Portal>
|
||||
</Menu.Root>`}
|
||||
/>
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
renderPropDefs,
|
||||
} from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const menuRootPropDefs: Record<string, PropDef> = {
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
open: {
|
||||
type: 'boolean',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(open, event) => void'],
|
||||
},
|
||||
closeParentOnEsc: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
},
|
||||
modal: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
},
|
||||
onOpenChangeComplete: {
|
||||
type: 'enum',
|
||||
values: ['(open) => void'],
|
||||
},
|
||||
disabled: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
openOnHover: {
|
||||
type: 'boolean',
|
||||
},
|
||||
delay: {
|
||||
type: 'number',
|
||||
default: '100',
|
||||
},
|
||||
loop: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
},
|
||||
orientation: {
|
||||
type: 'enum',
|
||||
values: ['horizontal', 'vertical'],
|
||||
default: 'vertical',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuTriggerPropDefs: Record<string, PropDef> = {
|
||||
...renderPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuPositionerPropDefs: Record<string, PropDef> = {
|
||||
align: {
|
||||
type: 'enum',
|
||||
values: ['start', 'center', 'end'],
|
||||
default: 'center',
|
||||
},
|
||||
alignOffset: {
|
||||
type: 'enum',
|
||||
values: ['number', '(data) => number'],
|
||||
default: '0',
|
||||
},
|
||||
side: {
|
||||
type: 'enum',
|
||||
values: ['bottom', 'inline-end', 'inline-start', 'left', 'right', 'top'],
|
||||
default: 'bottom',
|
||||
},
|
||||
sideOffset: {
|
||||
type: 'enum',
|
||||
values: ['number', '(data) => number'],
|
||||
default: '0',
|
||||
},
|
||||
arrowPadding: {
|
||||
type: 'number',
|
||||
default: '5',
|
||||
},
|
||||
anchor: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'React.Ref',
|
||||
'Element',
|
||||
'VirtualElement',
|
||||
'(() => Element | VirtualElement | null)',
|
||||
'null',
|
||||
],
|
||||
},
|
||||
collisionBoundary: {
|
||||
type: 'enum',
|
||||
values: ['clipping-ancestors', 'Element', 'Element[]', 'Rect'],
|
||||
default: 'clipping-ancestors',
|
||||
},
|
||||
collisionPadding: {
|
||||
type: 'enum',
|
||||
values: ['number', 'Rect'],
|
||||
default: '5',
|
||||
},
|
||||
sticky: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
positionMethod: {
|
||||
type: 'enum',
|
||||
values: ['absolute', 'fixed'],
|
||||
default: 'absolute',
|
||||
},
|
||||
trackAnchor: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuItemPropDefs: Record<string, PropDef> = {
|
||||
label: {
|
||||
type: 'string',
|
||||
},
|
||||
onClick: {
|
||||
type: 'enum',
|
||||
values: ['(event) => void'],
|
||||
},
|
||||
closeOnClick: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
},
|
||||
disabled: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -13,6 +13,8 @@ import * as TextFieldStories from '../../../packages/canon/src/components/TextFi
|
||||
import * as TextStories from '../../../packages/canon/src/components/Text/Text.stories';
|
||||
import * as FlexStories from '../../../packages/canon/src/components/Flex/Flex.stories';
|
||||
import * as SelectStories from '../../../packages/canon/src/components/Select/Select.stories';
|
||||
import * as MenuStories from '../../../packages/canon/src/components/Menu/Menu.stories';
|
||||
import * as LinkStories from '../../../packages/canon/src/components/Link/Link.stories';
|
||||
|
||||
export const BoxSnippet = ({ story }: { story: keyof typeof BoxStories }) => {
|
||||
const stories = composeStories(BoxStories);
|
||||
@@ -125,3 +127,17 @@ export const SelectSnippet = ({
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
export const MenuSnippet = ({ story }: { story: keyof typeof MenuStories }) => {
|
||||
const stories = composeStories(MenuStories);
|
||||
const StoryComponent = stories[story as keyof typeof stories];
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
export const LinkSnippet = ({ story }: { story: keyof typeof LinkStories }) => {
|
||||
const stories = composeStories(LinkStories);
|
||||
const StoryComponent = stories[story as keyof typeof stories];
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
@@ -91,6 +91,16 @@ export const components: Page[] = [
|
||||
slug: 'icon-button',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Link',
|
||||
slug: 'link',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Menu',
|
||||
slug: 'menu',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Select',
|
||||
slug: 'select',
|
||||
|
||||
@@ -203,3 +203,11 @@ export const childrenPropDefs: Record<string, PropDef> = {
|
||||
responsive: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const renderPropDefs: Record<string, PropDef> = {
|
||||
render: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactElement', '(props, state) => React.ReactElement'],
|
||||
responsive: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -32,7 +32,12 @@ export const Default: Story = {
|
||||
<>
|
||||
<Menu.Trigger
|
||||
render={props => (
|
||||
<Button {...props} size="small">
|
||||
<Button
|
||||
{...props}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
iconEnd="chevron-down"
|
||||
>
|
||||
Menu
|
||||
</Button>
|
||||
)}
|
||||
@@ -40,9 +45,10 @@ export const Default: Story = {
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner sideOffset={8} align="start">
|
||||
<Menu.Popup>
|
||||
<Menu.Item>Item 1</Menu.Item>
|
||||
<Menu.Item>Item 2</Menu.Item>
|
||||
<Menu.Item>Item 3</Menu.Item>
|
||||
<Menu.Item>Settings</Menu.Item>
|
||||
<Menu.Item>Invite new members</Menu.Item>
|
||||
<Menu.Item>Download app</Menu.Item>
|
||||
<Menu.Item>Log out</Menu.Item>
|
||||
</Menu.Popup>
|
||||
</Menu.Positioner>
|
||||
</Menu.Portal>
|
||||
@@ -57,3 +63,10 @@ export const Open: Story = {
|
||||
open: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const OpenOnHover: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
openOnHover: true,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user