From a54ff9c82e3958c08c874173362f6b11a17d6c33 Mon Sep 17 00:00:00 2001 From: Nemobot Date: Wed, 9 Feb 2022 15:45:13 +0100 Subject: [PATCH 1/2] create Link component --- package.json | 1 + pnpm-lock.yaml | 26 +++++++++++ src/Link/Link.css.ts | 9 ++++ src/Link/createLink.tsx | 68 +++++++++++++++++++++++++++++ src/Typography/Label/Label.tsx | 4 +- src/index.ts | 1 + src/reset.css.ts | 6 +++ src/util/atoms.ts | 3 +- src/util/conditions.ts | 6 +-- src/util/link.tsx | 27 ++++++++++++ stories/Components/Link.stories.tsx | 21 +++++++++ stories/index.tsx | 2 + 12 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 src/Link/Link.css.ts create mode 100644 src/Link/createLink.tsx create mode 100644 src/util/link.tsx create mode 100644 stories/Components/Link.stories.tsx diff --git a/package.json b/package.json index dba62a91f..de6481eb6 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dependencies": { "@dessert-box/react": "^0.2.0", "@react-aria/button": "^3.3.4", + "@react-aria/link": "^3.2.0", "@react-aria/separator": "^3.1.3", "@react-aria/textfield": "^3.5.0", "@vanilla-extract/recipes": "^0.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d051722b1..019688825 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ specifiers: '@babel/preset-typescript': ^7.16.0 '@dessert-box/react': ^0.2.0 '@react-aria/button': ^3.3.4 + '@react-aria/link': ^3.2.0 '@react-aria/separator': ^3.1.3 '@react-aria/textfield': ^3.5.0 '@react-types/button': ^3.4.1 @@ -58,6 +59,7 @@ specifiers: dependencies: '@dessert-box/react': 0.2.0_react@17.0.2 '@react-aria/button': 3.3.4_react@17.0.2 + '@react-aria/link': 3.2.0_react@17.0.2 '@react-aria/separator': 3.1.3_react@17.0.2 '@react-aria/textfield': 3.5.0_react@17.0.2 '@vanilla-extract/recipes': 0.2.3_@vanilla-extract+css@1.6.8 @@ -2234,6 +2236,20 @@ packages: react: 17.0.2 dev: false + /@react-aria/link/3.2.0_react@17.0.2: + resolution: {integrity: sha512-UTAA1x+8n1t/SPAbP6LRnwru0nn/VySOwWp3tl3SJ8WLqtDQf5YTJL7shjkWf3HxVkGUv/pT4XvOPX49Yg7YtQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 + dependencies: + '@babel/runtime': 7.16.3 + '@react-aria/focus': 3.5.0_react@17.0.2 + '@react-aria/interactions': 3.7.0_react@17.0.2 + '@react-aria/utils': 3.11.0_react@17.0.2 + '@react-types/link': 3.2.0_react@17.0.2 + '@react-types/shared': 3.10.1_react@17.0.2 + react: 17.0.2 + dev: false + /@react-aria/separator/3.1.3_react@17.0.2: resolution: {integrity: sha512-Vl5UjLvt7NojRZOmKunXzttDqrjZp9i3oIKmwk5ydppchfzvriKsPeFinbWzcRMzIaHOljQ8Gj8yqgGjJtuvuQ==} peerDependencies: @@ -2328,6 +2344,16 @@ packages: react: 17.0.2 dev: false + /@react-types/link/3.2.0_react@17.0.2: + resolution: {integrity: sha512-0oSFoU2EenKCyhyJE+BN33lHVnOIN/+Gl4zj2ilJToY+obzOtzjSDFgiCJ0izVp9l9BQnkzc247RrVKhTLZ8Rw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 + dependencies: + '@react-aria/interactions': 3.7.0_react@17.0.2 + '@react-types/shared': 3.10.1_react@17.0.2 + react: 17.0.2 + dev: false + /@react-types/shared/3.10.1_react@17.0.2: resolution: {integrity: sha512-U3dLJtstvOiZ8XLrWdNv9WXuruoDyfIfSXguTs9N0naDdO+M0MIbt/1Hg7Toe43ueAe56GM14IFL+S0/jhv8ow==} peerDependencies: diff --git a/src/Link/Link.css.ts b/src/Link/Link.css.ts new file mode 100644 index 000000000..f7a3aa4e6 --- /dev/null +++ b/src/Link/Link.css.ts @@ -0,0 +1,9 @@ +import { bentoSprinkles } from "../internal/sprinkles.css"; + +export const link = bentoSprinkles({ + cursor: { + default: "pointer", + disabled: "notAllowed", + }, + outline: "none", +}); diff --git a/src/Link/createLink.tsx b/src/Link/createLink.tsx new file mode 100644 index 000000000..eb97fa517 --- /dev/null +++ b/src/Link/createLink.tsx @@ -0,0 +1,68 @@ +import { AnchorHTMLAttributes, useRef } from "react"; +import { useLinkComponent } from "../util/link"; +import { Label, LabelProps, LocalizedString } from ".."; +import { BentoSprinkles, Box } from "../internal"; +import { useLink } from "@react-aria/link"; +import * as resetStyles from "../reset.css"; +import { link } from "./Link.css"; +import { extendedHitAreaRecipe } from "../util/extendedHitArea.css"; + +export type LinkProps = { + href: string; + label: LocalizedString; + isDisabled?: boolean; + active?: boolean; +} & Omit, "color">; + +type LinkConfig = { + labelSize: LabelProps["size"]; + labelWeight: BentoSprinkles["fontWeight"]; + labelDecoration: BentoSprinkles["textDecoration"]; +}; + +export function createLink( + config: LinkConfig = { + labelSize: "large", + labelWeight: "semibold", + labelDecoration: { + default: "none", + active: "none", + hover: "underline", + focus: "underline", + disabled: "none", + }, + } +) { + return function Link({ href, isDisabled, label, active = false, ...props }: LinkProps) { + const LinkComponent = useLinkComponent(); + const ref = useRef(null); + const { + linkProps: { color: _discard, ...linkProps }, + } = useLink( + { + isDisabled, + // NOTE(gabro): using anything other than 'a' so that we set the correct accessibility props + elementType: "span", + }, + ref + ); + + return ( + + + + ); + }; +} diff --git a/src/Typography/Label/Label.tsx b/src/Typography/Label/Label.tsx index ecf9a9047..463498e31 100644 --- a/src/Typography/Label/Label.tsx +++ b/src/Typography/Label/Label.tsx @@ -7,7 +7,7 @@ type Size = "small" | "medium" | "large"; type Align = "left" | "center" | "right"; type Color = "default" | "secondary" | "disabled"; -type Props = { +export type LabelProps = { children: LocalizedString; size: Size; color?: Color; @@ -25,7 +25,7 @@ export function Label({ color = "default", uppercase = false, ...boxProps -}: Props) { +}: LabelProps) { return ( {children} diff --git a/src/index.ts b/src/index.ts index 2d9d07dce..51a60c2df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export * from "./Field/createFormFields"; export * from "./IconButton/IconButton"; export * from "./Icons"; export * from "./Layout/createLayoutComponents"; +export * from "./Link/createLink"; export * from "./Placeholder/Placeholder"; export * from "./Toast/createToast"; export * from "./Toast/useToast"; diff --git a/src/reset.css.ts b/src/reset.css.ts index d44b3ab9e..e6ca688d7 100644 --- a/src/reset.css.ts +++ b/src/reset.css.ts @@ -43,7 +43,13 @@ const label = style({ cursor: "inherit", }); +const a = style({ + textDecoration: "none", + color: "inherit", +}); + export const element: Partial> = { + a, button, div, input, diff --git a/src/util/atoms.ts b/src/util/atoms.ts index c7e5041f9..6a8204071 100644 --- a/src/util/atoms.ts +++ b/src/util/atoms.ts @@ -32,7 +32,7 @@ export const unconditionalProperties = { } as const; export const responsiveProperties = { - display: ["flex", "none", "block"], + display: ["flex", "none", "block", "grid", "inline-block"], flexDirection: ["row", "column"], alignItems: { flexStart: "flex-start", @@ -86,4 +86,5 @@ export const statusProperties = { outline: { ...vars.outlineColor, none: "none" }, stroke: color, fill: color, + textDecoration: ["none", "underline"], } as const; diff --git a/src/util/conditions.ts b/src/util/conditions.ts index 54e306e4c..de125fc2b 100644 --- a/src/util/conditions.ts +++ b/src/util/conditions.ts @@ -1,7 +1,7 @@ export const statusConditions = { default: {}, - hover: { selector: "&:hover:not(:disabled)" }, - focus: { selector: "&:focus:not(:disabled)" }, - active: { selector: "&:active:not(:disabled)" }, + hover: { selector: "&:hover:not(:disabled):not([disabled])" }, + focus: { selector: "&:focus:not(:disabled):not([disabled])" }, + active: { selector: "&:active:not(:disabled):not([disabled])" }, disabled: { selector: "&:disabled, &[disabled], :disabled &, [disabled] &" }, } as const; diff --git a/src/util/link.tsx b/src/util/link.tsx new file mode 100644 index 000000000..d2b24248c --- /dev/null +++ b/src/util/link.tsx @@ -0,0 +1,27 @@ +import { + AnchorHTMLAttributes, + ComponentType, + createContext, + forwardRef, + ForwardRefRenderFunction, + useContext, +} from "react"; +import { Box } from "../internal"; + +export type LinkComponentProps = { + href: string; +} & Omit, "color">; + +export const makeLinkComponent = ( + render: ForwardRefRenderFunction +) => forwardRef(render); + +export type LinkComponent = ComponentType; + +const DefaultLinkComponent = makeLinkComponent((props, ref) => ); + +export const LinkComponentContext = createContext(DefaultLinkComponent); + +export const useLinkComponent = () => { + return useContext(LinkComponentContext); +}; diff --git a/stories/Components/Link.stories.tsx b/stories/Components/Link.stories.tsx new file mode 100644 index 000000000..8becaf76b --- /dev/null +++ b/stories/Components/Link.stories.tsx @@ -0,0 +1,21 @@ +import { createComponentStories, formatMessage, textArgType } from "../util"; +import { Link } from "../"; + +const { defaultExport, createStory } = createComponentStories({ + component: Link, + args: { + label: formatMessage("Button"), + href: "https://google.com", + target: "blank", + }, + argTypes: { + label: textArgType, + }, +}); + +export default defaultExport; + +export const Default = createStory({}); +export const Disabled = createStory({ + isDisabled: true, +}); diff --git a/stories/index.tsx b/stories/index.tsx index a519b2afe..f2dcb1784 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -9,6 +9,7 @@ import { createToast, createActions, createCard, + createLink, } from "../src"; import { sprinkles } from "./sprinkles.css"; @@ -21,3 +22,4 @@ export const Banner = createBanner({}); export const { Toast, ToastProvider } = createToast(Button, {}); export const Actions = createActions(Button); export const Card = createCard<"4" | "8" | "16">({}); +export const Link = createLink(); From 607bc0e880efe6f55b76438ef8091659bc740311 Mon Sep 17 00:00:00 2001 From: Nemobot Date: Wed, 9 Feb 2022 16:01:26 +0100 Subject: [PATCH 2/2] implement Breadcrumb --- package.json | 1 + pnpm-lock.yaml | 104 +++++++++++++++++++++- src/Breadcrumb/createBreadcrumb.tsx | 92 +++++++++++++++++++ src/Icons/IconChevronRight.tsx | 10 +++ src/Icons/index.ts | 1 + src/index.ts | 1 + stories/Components/Breadcrumb.stories.tsx | 37 ++++++++ stories/index.tsx | 10 ++- 8 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 src/Breadcrumb/createBreadcrumb.tsx create mode 100644 src/Icons/IconChevronRight.tsx create mode 100644 stories/Components/Breadcrumb.stories.tsx diff --git a/package.json b/package.json index de6481eb6..98bb83b89 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 019688825..8c5abd4fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ specifiers: '@babel/preset-react': ^7.16.0 '@babel/preset-typescript': ^7.16.0 '@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 @@ -58,6 +59,7 @@ specifiers: dependencies: '@dessert-box/react': 0.2.0_react@17.0.2 + '@react-aria/breadcrumbs': 3.1.5_react@17.0.2 '@react-aria/button': 3.3.4_react@17.0.2 '@react-aria/link': 3.2.0_react@17.0.2 '@react-aria/separator': 3.1.3_react@17.0.2 @@ -1779,6 +1781,40 @@ packages: - supports-color dev: true + /@formatjs/ecma402-abstract/1.11.3: + resolution: {integrity: sha512-kP/Buv5vVFMAYLHNvvUzr0lwRTU0u2WTy44Tqwku1X3C3lJ5dKqDCYVqA8wL+Y19Bq+MwHgxqd5FZJRCIsLRyQ==} + dependencies: + '@formatjs/intl-localematcher': 0.2.24 + tslib: 2.3.1 + dev: false + + /@formatjs/fast-memoize/1.2.1: + resolution: {integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==} + dependencies: + tslib: 2.3.1 + dev: false + + /@formatjs/icu-messageformat-parser/2.0.18: + resolution: {integrity: sha512-vquIzsAJJmZ5jWVH8dEgUKcbG4yu3KqtyPet+q35SW5reLOvblkfeCXTRW2TpIwNXzdVqsJBwjbTiRiSU9JxwQ==} + dependencies: + '@formatjs/ecma402-abstract': 1.11.3 + '@formatjs/icu-skeleton-parser': 1.3.5 + tslib: 2.3.1 + dev: false + + /@formatjs/icu-skeleton-parser/1.3.5: + resolution: {integrity: sha512-Nhyo2/6kG7ZfgeEfo02sxviOuBcvtzH6SYUharj3DLCDJH3A/4OxkKcmx/2PWGX4bc6iSieh+FA94CsKDxnZBQ==} + dependencies: + '@formatjs/ecma402-abstract': 1.11.3 + tslib: 2.3.1 + dev: false + + /@formatjs/intl-localematcher/0.2.24: + resolution: {integrity: sha512-K/HRGo6EMnCbhpth/y3u4rW4aXkmQNqRe1L2G+Y5jNr3v0gYhvaucV8WixNju/INAMbPBlbsRBRo/nfjnoOnxQ==} + dependencies: + tslib: 2.3.1 + dev: false + /@gar/promisify/1.1.2: resolution: {integrity: sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==} dev: true @@ -1798,6 +1834,25 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@internationalized/date/3.0.0-alpha.1: + resolution: {integrity: sha512-fxciU4AQ/4XBYfse/mT9h1nsyNkmQkxwQtTmQVu6b4Tp2u95Y3m5BNgWgV2m3vLiiKZ82NtHJXAIGoqiK53w4g==} + dependencies: + '@babel/runtime': 7.16.3 + dev: false + + /@internationalized/message/3.0.3: + resolution: {integrity: sha512-TpNLP6FgzD9kukdNOhcxYhULf1mcE7Du+eMZe5voSP/yWlAl9GsJqPVY2knsR5Ld1oQELhxU61griqn6uhGsnA==} + dependencies: + '@babel/runtime': 7.16.3 + intl-messageformat: 9.11.4 + dev: false + + /@internationalized/number/3.0.3: + resolution: {integrity: sha512-ewFoVvsxSyd9QZnknvOWPjirYqdMQhXTeDhJg3hM6C/FeZt0banpGH1nZ0SGMZXHz8NK9uAa2KVIq+jqAIOg4w==} + dependencies: + '@babel/runtime': 7.16.3 + dev: false + /@istanbuljs/load-nyc-config/1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -2186,6 +2241,21 @@ packages: resolution: {integrity: sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==} dev: true + /@react-aria/breadcrumbs/3.1.5_react@17.0.2: + resolution: {integrity: sha512-0ruIP6gP4hkGyX/b3g8MeuaP7ZX9M4mvauPHvuqGHNpUAZdESMj4jHo5ERImaTUJTObC2Vid2674OyzYFITSUA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 + dependencies: + '@babel/runtime': 7.16.3 + '@react-aria/i18n': 3.3.4_react@17.0.2 + '@react-aria/interactions': 3.7.0_react@17.0.2 + '@react-aria/link': 3.2.0_react@17.0.2 + '@react-aria/utils': 3.11.0_react@17.0.2 + '@react-types/breadcrumbs': 3.2.1_react@17.0.2 + '@react-types/shared': 3.10.1_react@17.0.2 + react: 17.0.2 + dev: false + /@react-aria/button/3.3.4_react@17.0.2: resolution: {integrity: sha512-vebTcf9YpwaKCvsca2VWhn6eYPa15OJtMENwaGop72UrL35Oa7xDgU0RG22RAjRjt8HRVlAfLpHkJQW6GBGU3g==} peerDependencies: @@ -2213,6 +2283,21 @@ packages: react: 17.0.2 dev: false + /@react-aria/i18n/3.3.4_react@17.0.2: + resolution: {integrity: sha512-1DV3I82UfL2dT8WBI/88TwtokO80B7ISSyuz6rO/6n7q76A/nC2AtVINbrGYrcKsCcxCEoEMxW5RVJ39fcLijA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 + dependencies: + '@babel/runtime': 7.16.3 + '@internationalized/date': 3.0.0-alpha.1 + '@internationalized/message': 3.0.3 + '@internationalized/number': 3.0.3 + '@react-aria/ssr': 3.1.0_react@17.0.2 + '@react-aria/utils': 3.11.0_react@17.0.2 + '@react-types/shared': 3.10.1_react@17.0.2 + react: 17.0.2 + dev: false + /@react-aria/interactions/3.7.0_react@17.0.2: resolution: {integrity: sha512-Xomchjb9bqvh3ocil+QCEYFSxsTy8PHEz43mNP6z2yuu3UqTpl2FsWfyKgF/Yy0WKVkyV2dO2uz758KJTCLZhw==} peerDependencies: @@ -2318,6 +2403,15 @@ packages: react: 17.0.2 dev: false + /@react-types/breadcrumbs/3.2.1_react@17.0.2: + resolution: {integrity: sha512-njXfiYTlACKAz5xVp34tXb7gtm6avzgzrkYT70r3HHk8g7cBUS7iJPiSIgCRxUGwIpesIYeZY3a1Nvqzvohgmg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 + dependencies: + '@react-types/shared': 3.10.1_react@17.0.2 + react: 17.0.2 + dev: false + /@react-types/button/3.4.1_react@17.0.2: resolution: {integrity: sha512-B54M84LxdEppwjXNlkBEJyMfe9fd+bvFV7R6+NJvupGrZm/LuFNYjFcHk7yjMKWTdWm6DbpIuQz54n5qTW7Vlg==} peerDependencies: @@ -9977,6 +10071,15 @@ packages: resolution: {integrity: sha512-2Vkz8z46Dv401zTWudDGwO7KiGHNDkMv417T5ItcNYfmvHR/1qCTVBO9vwH8zZmQ0WkA/1ARwpysR9bsnop4NQ==} dev: true + /intl-messageformat/9.11.4: + resolution: {integrity: sha512-77TSkNubIy/hsapz6LQpyR6OADcxhWdhSaboPb5flMaALCVkPvAIxr48AlPqaMl4r1anNcvR9rpLWVdwUY1IKg==} + dependencies: + '@formatjs/ecma402-abstract': 1.11.3 + '@formatjs/fast-memoize': 1.2.1 + '@formatjs/icu-messageformat-parser': 2.0.18 + tslib: 2.3.1 + dev: false + /invariant/2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -15503,7 +15606,6 @@ packages: /tslib/2.3.1: resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} - dev: true /tsup/5.11.11_typescript@4.5.2: resolution: {integrity: sha512-rgbTu+KhAI9PdGUS07rKohDXbRLTstBGJaxl75q7RZYRGF+n+kN8L4RlXY5pqYb9Hsq0gEB6nS39v7nSvVBS+g==} diff --git a/src/Breadcrumb/createBreadcrumb.tsx b/src/Breadcrumb/createBreadcrumb.tsx new file mode 100644 index 000000000..0ca97e71f --- /dev/null +++ b/src/Breadcrumb/createBreadcrumb.tsx @@ -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 & { isCurrent: boolean }; + +type BreadcrumbConfig = { + separator: FunctionComponent; + separatorSize: IconProps["size"]; + space: BentoSprinkles["gap"]; +}; + +export function createBreadcrumb( + Link: FunctionComponent, + config: BreadcrumbConfig = { + separator: IconChevronRight, + separatorSize: "8", + space: "16", + } +) { + const BreadcrumbItem = createBreadcrumbItem(Link); + const Separator = config.separator; + return function Breadcrumb(props: BreadcrumbProps) { + const children = ( + + + {props.items.map((item, idx) => { + const isCurrent = idx === props.items.length - 1; + return ( + + + {!isCurrent && ( + + )} + + ); + })} + + + ); + const { navProps } = useBreadcrumbs({ children }); + return ( + + {children} + + ); + }; +} + +function createBreadcrumbItem(Link: FunctionComponent) { + return function BreadcrumbItem({ isCurrent, label, href = "" }: BreadcrumbItemProps) { + const ref = useRef(null); + const { itemProps } = useBreadcrumbItem( + { children: label, isCurrent, elementType: "div" }, + ref + ); + + return ( + + {isCurrent ? ( + + ) : ( + + )} + + ); + }; +} diff --git a/src/Icons/IconChevronRight.tsx b/src/Icons/IconChevronRight.tsx new file mode 100644 index 000000000..c77bd3db4 --- /dev/null +++ b/src/Icons/IconChevronRight.tsx @@ -0,0 +1,10 @@ +import { IconProps } from "./IconProps"; +import { svgIconProps } from "./svgIconProps"; + +export function IconChevronRight(props: IconProps) { + return ( + + + + ); +} diff --git a/src/Icons/index.ts b/src/Icons/index.ts index b706720cb..fce2b9446 100644 --- a/src/Icons/index.ts +++ b/src/Icons/index.ts @@ -1,4 +1,5 @@ export * from "./IconCheckCircleSolid"; +export * from "./IconChevronRight"; export * from "./IconClose"; export * from "./IconInformative"; export * from "./IconNegative"; diff --git a/src/index.ts b/src/index.ts index 51a60c2df..bbfe4e792 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from "./Actions/createActions"; export * from "./Banner/createBanner"; export * from "./Box/createBentoBox"; +export * from "./Breadcrumb/createBreadcrumb"; export * from "./Button/createButton"; export * from "./Card/createCard"; export * from "./Divider/Divider"; diff --git a/stories/Components/Breadcrumb.stories.tsx b/stories/Components/Breadcrumb.stories.tsx new file mode 100644 index 000000000..b71d2cf16 --- /dev/null +++ b/stories/Components/Breadcrumb.stories.tsx @@ -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"), + }, + ], +}); diff --git a/stories/index.tsx b/stories/index.tsx index f2dcb1784..9b936ba2b 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -1,15 +1,16 @@ import "./fonts.css"; import "../src/reset.css"; import { + createBanner, createBentoBox, - createLayoutComponents, - createFormFields, + createBreadcrumb, createButton, - createBanner, - createToast, createActions, createCard, + createFormFields, + createLayoutComponents, createLink, + createToast, } from "../src"; import { sprinkles } from "./sprinkles.css"; @@ -23,3 +24,4 @@ export const { Toast, ToastProvider } = createToast(Button, {}); export const Actions = createActions(Button); export const Card = createCard<"4" | "8" | "16">({}); export const Link = createLink(); +export const Breadcrumb = createBreadcrumb(Link);