Skip to content

Commit

Permalink
implement Breadcrumb
Browse files Browse the repository at this point in the history
  • Loading branch information
Nemobot committed Feb 9, 2022
1 parent 751310d commit 2f9aaa8
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 5 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"homepage": "https://github.com/buildo/bento-design-system#readme",
"dependencies": {
"@dessert-box/react": "^0.2.0",
"@react-aria/breadcrumbs": "^3.1.5",
"@react-aria/button": "^3.3.4",
"@react-aria/link": "^3.2.0",
"@react-aria/separator": "^3.1.3",
Expand Down
104 changes: 103 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions src/Breadcrumb/createBreadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useBreadcrumbItem, useBreadcrumbs } from "@react-aria/breadcrumbs";
import { useRef, Fragment, FunctionComponent } from "react";
import { IconProps } from "src/Icons/IconProps";
import { IconChevronRight, Label, LinkProps, LocalizedString } from "../";
import { Box, Inline, BentoSprinkles } from "../internal";

type LastItem = {
label: LocalizedString;
};

type Item = LastItem & {
href: string;
};

export type BreadcrumbProps = {
items: [...Item[], LastItem];
};

type BreadcrumbItemProps = LastItem & Partial<Item> & { isCurrent: boolean };

type BreadcrumbConfig = {
separator: FunctionComponent<IconProps>;
separatorSize: IconProps["size"];
space: BentoSprinkles["gap"];
};

export function createBreadcrumb(
Link: FunctionComponent<LinkProps>,
config: BreadcrumbConfig = {
separator: IconChevronRight,
separatorSize: "8",
space: "16",
}
) {
const BreadcrumbItem = createBreadcrumbItem(Link);
const Separator = config.separator;
return function Breadcrumb(props: BreadcrumbProps) {
const children = (
<Box as="ol">
<Inline space={config.space} alignY="center">
{props.items.map((item, idx) => {
const isCurrent = idx === props.items.length - 1;
return (
<Fragment key={idx}>
<BreadcrumbItem isCurrent={isCurrent} {...item} />
{!isCurrent && (
<Box as="span" aria-hidden="true">
<Separator size={config.separatorSize} />
</Box>
)}
</Fragment>
);
})}
</Inline>
</Box>
);
const { navProps } = useBreadcrumbs({ children });
return (
<Box as="nav" {...navProps} color={undefined}>
{children}
</Box>
);
};
}

function createBreadcrumbItem(Link: FunctionComponent<LinkProps>) {
return function BreadcrumbItem({ isCurrent, label, href = "" }: BreadcrumbItemProps) {
const ref = useRef(null);
const { itemProps } = useBreadcrumbItem(
{ children: label, isCurrent, elementType: "div" },
ref
);

return (
<Box as="li" ref={ref}>
{isCurrent ? (
<Label size="large" {...itemProps} color={undefined}>
{label}
</Label>
) : (
<Link
label={label}
href={href}
title={label}
// NOTE(gabro): we need the cast due to a minor inconsistency in the callback type of FocusEventHandler
{...itemProps}
/>
)}
</Box>
);
};
}
10 changes: 10 additions & 0 deletions src/Icons/IconChevronRight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IconProps } from "./IconProps";
import { svgIconProps } from "./svgIconProps";

export function IconChevronRight(props: IconProps) {
return (
<svg {...svgIconProps(props)}>
<path d="M5.627 23.372a2.138 2.138 0 0 0 3.029 0l9.858-9.858a2.14 2.14 0 0 0 0-3.029L8.656.627a2.141 2.141 0 0 0-3.029 3.029l8.334 8.354-8.334 8.334a2.158 2.158 0 0 0 0 3.028Z" />
</svg>
);
}
1 change: 1 addition & 0 deletions src/Icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./IconCheckCircleSolid";
export * from "./IconChevronRight";
export * from "./IconClose";
export * from "./IconInformative";
export * from "./IconNegative";
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./Banner/createBanner";
export * from "./Box/createBentoBox";
export * from "./Breadcrumb/createBreadcrumb";
export * from "./Button/createButton";
export * from "./Divider/Divider";
export * from "./Field/createFormFields";
Expand Down
37 changes: 37 additions & 0 deletions stories/Components/Breadcrumb.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Breadcrumb, unsafeLocalizedString } from "../";
import { createComponentStories } from "../util";

const { defaultExport, createStory } = createComponentStories({
component: Breadcrumb,
args: {},
});

export default defaultExport;

export const breadcrumb = createStory({
items: [
{
label: unsafeLocalizedString("Root"),
href: "https://www.example.com",
},
{
label: unsafeLocalizedString("1st Level"),
href: "https://www.example.com",
},
{
label: unsafeLocalizedString("2nd Level"),
href: "https://www.example.com",
},
{
label: unsafeLocalizedString("3rd Level"),
href: "https://www.example.com",
},
{
label: unsafeLocalizedString("4th Level"),
href: "https://www.example.com",
},
{
label: unsafeLocalizedString("5th Level"),
},
],
});
10 changes: 6 additions & 4 deletions stories/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import "../src/reset.css";
import {
createBanner,
createBentoBox,
createLayoutComponents,
createFormFields,
createBreadcrumb,
createButton,
createBanner,
createToast,
createFormFields,
createLayoutComponents,
createLink,
createToast,
} from "../src";
import { sprinkles } from "./sprinkles.css";

Expand All @@ -18,3 +19,4 @@ export const Button = createButton({});
export const Banner = createBanner({});
export const { Toast, ToastProvider } = createToast(Button, {});
export const Link = createLink();
export const Breadcrumb = createBreadcrumb(Link);

0 comments on commit 2f9aaa8

Please sign in to comment.