import { useCallback, useState } from "react"; import { pxToRem } from "../../utils"; import { commonProps } from "../../utils/storybook"; import { Box } from "../box"; import { Button } from "../button"; import { Flex } from "../flex"; import { Link } from "../link"; import { Icons } from "../icons"; import { Input } from "../input"; import { Text } from "../text"; import { Breadcrumbs } from "./"; import type { StoryObj, Meta } from "@storybook/react"; function generateRandomString(): string { const minLength = 5; const maxLength = 10; const length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * characters.length); result += characters.charAt(randomIndex); } return result; } const meta: Meta = { title: "Breadcrumbs", component: Breadcrumbs, tags: ["autodocs"], argTypes: { crumbs: { table: { type: { detail: ` {linkElement: React.ReactElement<{href: string | UrlObject; children: string;}>; icon?: React.ReactElement;}[] | {label: string; href: string; linkProps: LinkProps; icon?: React.ReactElement;}[] `, }, }, }, iconPosition: { defaultValue: { summary: "start" }, control: { type: "select", options: ["start", "end"], }, }, divider: { control: { type: "disable", }, }, crumbsListProps: { table: { category: "Anatomy Props", type: { summary: "React.ComponentProps", }, }, }, subCrumbListContainerProps: { table: { category: "Anatomy Props", type: { summary: "React.ComponentProps", }, }, }, crumbItemProps: { table: { category: "Anatomy Props", type: { summary: "React.ComponentProps", }, }, }, subCrumbTriggerProps: { table: { category: "Anatomy Props", type: { summary: "React.ComponentProps", }, }, }, ...commonProps, }, args: { crumbs: [ { label: "Crumb 1", href: "#1" }, { label: "Crumb 2", href: "#2" }, { label: "Crumb 3", href: "#3" }, ], }, }; export default meta; type Story = StoryObj; /** * Pass `crumbs` as `{label: string, href:string}[]` and each crumb will be wrapped with a [Link Component](/docs/link--docs). Each crumb can also optionally define some `LinkProps` */ export const Default: Story = { render: (args) => , }; /** * Pass `crumbs` as array of anchor tags if custom link implementation is needed. */ export const WithLinkElements: Story = { render: (args) => , }; WithLinkElements.args = { crumbs: [ { linkElement: Crumb 1 }, { linkElement: Crumb 2 }, { linkElement: Crumb 3 }, ], }; WithLinkElements.argTypes = { crumbs: { control: false } }; /** * Pass `crumbs` as array of buttons. */ export const WithButtonElements: Story = { render: (args) => , }; WithButtonElements.args = { crumbs: [ { label: "Crumb 1", onClick: () => alert("crumb 1 clicked"), }, { label: "Crumb 2", onClick: () => alert("crumb 2 clicked"), }, { label: "Crumb 3", onClick: () => alert("crumb 3 clicked"), }, ], }; /** * The divider can be customised. Here it is passed as a `` component with a `/` as child. */ export const WithCustomDivider: Story = { render: (args) => , }; WithCustomDivider.args = { divider: ( / ), }; /** * Pass `Icon` to equip each crumb with an icon. If no color is specified the breadcrumbs will color the icon. */ export const WithUncontrolledIcons: Story = { render: (args) => , }; WithUncontrolledIcons.args = { crumbs: [ { linkElement: Crumb 1, Icon: }, { linkElement: Crumb 2, Icon: }, { linkElement: Crumb 3, Icon: }, ], }; WithUncontrolledIcons.argTypes = { crumbs: { control: false } }; /** * Pass `Icon` with an icon color specified to bypass breadcrumbs color handling. */ export const WithControlledIcons: Story = { render: (args) => , }; WithControlledIcons.args = { crumbs: [ { linkElement: Crumb 1, Icon: }, { linkElement: Crumb 2, Icon: }, { linkElement: Crumb 3, Icon: }, ], }; WithControlledIcons.argTypes = { crumbs: { control: false } }; /** * Resize the screen to trigger overflow. */ export const WithOverflow: Story = { render: (args) => ( ), }; WithOverflow.args = { crumbs: [ { label: `${generateRandomString()} 1`, href: "#1", Icon: , }, { label: `${generateRandomString()} 2`, href: "#2", Icon: , }, { label: `${generateRandomString()} 3`, href: "#3", Icon: , }, { label: `${generateRandomString()} 4`, href: "#4", Icon: , }, { label: `${generateRandomString()} 5`, href: "#5", Icon: , }, { label: `${generateRandomString()} 6`, href: "#6", Icon: , }, { label: `${generateRandomString()} 7`, href: "#7", Icon: , }, { label: `${generateRandomString()} 8`, href: "#8", Icon: , }, { label: `${generateRandomString()} 9`, href: "#9", Icon: , }, { label: `${generateRandomString()} 10`, href: "#10", Icon: , }, ], }; WithOverflow.argTypes = { crumbs: { control: false } }; /** * Add and subtract crumbs */ export const WithDynamicCrumbs: Story = { render: (args) => { const [crumbs, setCrumbs] = useState([ { label: `${generateRandomString()} 1`, href: `#1` }, ]); const [px, setPx] = useState(0); const onAdd = useCallback(() => { setCrumbs((prev) => { return [ ...prev, { label: `${generateRandomString()} ${prev.length + 1}`, href: `#${prev.length + 1}` }, ]; }); }, []); const onRemove = useCallback(() => { setCrumbs((prev) => prev.slice(0, -1)); }, []); const onPaddingChange = useCallback((e: React.ChangeEvent) => { setPx(parseInt(e.target.value)); }, []); return ( ); }, }; WithDynamicCrumbs.argTypes = { crumbs: { control: false, }, }; /** * Resize the screen to trigger overflow. */ export const WithOverflowAndCustomLinks: Story = { render: (args) => ( ), }; WithOverflowAndCustomLinks.args = { crumbs: [ { linkElement: {generateRandomString()} 1, Icon: , }, { linkElement: {generateRandomString()} 2, Icon: , }, { linkElement: {generateRandomString()} 3, Icon: , }, { linkElement: {generateRandomString()} 4, Icon: , }, { linkElement: {generateRandomString()} 5, Icon: , }, { linkElement: {generateRandomString()} 6, Icon: , }, { linkElement: {generateRandomString()} 7, Icon: , }, { linkElement: {generateRandomString()} 8, Icon: , }, { linkElement: {generateRandomString()} 9, Icon: , }, { linkElement: {generateRandomString()} 10, Icon: , }, ], }; WithOverflowAndCustomLinks.argTypes = { crumbs: { control: false } }; /** * A crumb can be Icon only. */ export const IconOnly: Story = { render: (args) => , }; IconOnly.args = { crumbs: [ { label: "", Icon: , href: "#1", }, { label: "Crumb 2", href: "#2" }, { label: "Very longggggggggggggggggggggggggg Crumb 3", href: "#3" }, ], }; IconOnly.argTypes = { crumbs: { control: false } }; /** * Assign only the label prop if you want convey to the user that the crumb is non-interactive. */ export const LabelOnlyCrumb: Story = { render: (args) => , }; LabelOnlyCrumb.args = { crumbs: [ { label: "Text Only Crumb 1" }, { label: "Text Only Crumb 2" }, { label: "Text Only Crumb 3" }, ], };