diff --git a/.changeset/legal-pigs-tan.md b/.changeset/legal-pigs-tan.md new file mode 100644 index 000000000000..36781344fc61 --- /dev/null +++ b/.changeset/legal-pigs-tan.md @@ -0,0 +1,19 @@ +--- +'@braid-design-system/docs-ui': minor +--- + +**TitleLink:** Added new component for rendering linkable headings with an optional copy-to-clipboard interaction. Should be wrapped in your required typographic component. + +**EXAMPLE:** +```jsx + + Getting started + +``` + +With copy-to-clipboard: +```jsx + + Appearance + +``` diff --git a/.changeset/red-drinks-float.md b/.changeset/red-drinks-float.md new file mode 100644 index 000000000000..5f34abb35b96 --- /dev/null +++ b/.changeset/red-drinks-float.md @@ -0,0 +1,16 @@ +--- +'braid-design-system': minor +--- + +--- +updated: + - vars +--- + +**vars:** Exposed `vars.transition`. Transition CSS variables are available in stylesheets and runtime styles. + +**EXAMPLE USAGE:** +```ts +export const myStyle = style({ + transition: vars.transition.fast, +}); \ No newline at end of file diff --git a/.changeset/slick-ways-exist.md b/.changeset/slick-ways-exist.md new file mode 100644 index 000000000000..501a59346918 --- /dev/null +++ b/.changeset/slick-ways-exist.md @@ -0,0 +1,10 @@ +--- +'@braid-design-system/docs-ui': minor +--- + +**CategoryHeading:** Added new component for rendering category-style navigation headings. + +**EXAMPLE USAGE:** +```jsx +Layout +``` diff --git a/.changeset/some-states-battle.md b/.changeset/some-states-battle.md new file mode 100644 index 000000000000..b14ee671b423 --- /dev/null +++ b/.changeset/some-states-battle.md @@ -0,0 +1,28 @@ +--- +'braid-design-system': minor +--- + +--- +updated: + - Badge +--- + +**Badge:** Added aria-hidden and aria-label props. + +`aria-hidden` allows a badge to be hidden from assistive technology + +`aria-label` allows visible badge text to be overridden with a more descriptive label for screen readers + + +**EXAMPLE USAGE:** +```jsx + + Deprecated + +``` + +```jsx + + 2 + +``` \ No newline at end of file diff --git a/packages/braid-design-system/src/css.ts b/packages/braid-design-system/src/css.ts index 8f966ee7e1da..e508e272af34 100644 --- a/packages/braid-design-system/src/css.ts +++ b/packages/braid-design-system/src/css.ts @@ -24,6 +24,7 @@ const { borderRadius, borderWidth, shadow, + transition, } = internalVars; const vars = { @@ -38,6 +39,7 @@ const vars = { borderRadius, borderWidth, shadow, + transition, }; function atoms(props: Omit) { diff --git a/packages/braid-design-system/src/lib/components/Badge/Badge.tsx b/packages/braid-design-system/src/lib/components/Badge/Badge.tsx index 241d8727e40f..8ce41cfb71aa 100644 --- a/packages/braid-design-system/src/lib/components/Badge/Badge.tsx +++ b/packages/braid-design-system/src/lib/components/Badge/Badge.tsx @@ -39,6 +39,8 @@ export interface BadgeProps { data?: DataAttributeMap; tabIndex?: BoxProps['tabIndex']; 'aria-describedby'?: string; + 'aria-hidden'?: boolean; + 'aria-label'?: string; } const lightModeBackgroundForTone = { @@ -74,6 +76,8 @@ export const Badge = forwardRef( data, tabIndex, 'aria-describedby': ariaDescribedBy, + 'aria-hidden': ariaHidden, + 'aria-label': ariaLabel, ...restProps }, ref, @@ -112,6 +116,8 @@ export const Badge = forwardRef( ref={ref} tabIndex={tabIndex} aria-describedby={ariaDescribedBy} + aria-hidden={ariaHidden} + aria-label={ariaLabel} title={ title ?? (!ariaDescribedBy ? stringifyChildren(children) : undefined) diff --git a/packages/braid-design-system/src/lib/css/vars.docs.tsx b/packages/braid-design-system/src/lib/css/vars.docs.tsx index 878193339d75..d7b395b0ac1a 100644 --- a/packages/braid-design-system/src/lib/css/vars.docs.tsx +++ b/packages/braid-design-system/src/lib/css/vars.docs.tsx @@ -32,7 +32,7 @@ const Row = ({ hideCanvas?: boolean; darkCanvas?: boolean; }) => ( - + vars{group ? `.${group}` : null}. @@ -49,16 +49,22 @@ const Row = ({ ); -const ContentWidthValue = ({ var: varName }: { var: string }) => { - const [size, setSize] = useState(0); +const CssVarValue = ({ + var: varName, + property, +}: { + var: string; + property: 'width' | 'transition'; +}) => { + const [value, setValue] = useState(''); const { themeKey } = useThemeSettings(); const ref = useRef(null); useEffect(() => { if (ref.current && themeKey) { - setSize(ref.current.offsetWidth); + setValue(getComputedStyle(ref.current)[property]); } - }, [themeKey]); + }, [themeKey, property]); return ( <> @@ -68,9 +74,9 @@ const ContentWidthValue = ({ var: varName }: { var: string }) => { pointerEvents="none" opacity={0} ref={ref} - style={{ width: varName }} + style={{ [property]: varName } as any} /> - {size > 0 ? `${size}px` : ' '} + {value || '\u00a0'} ); }; @@ -106,7 +112,7 @@ const varDocs: Record = { {index > 0 ? : null} - + @@ -283,6 +289,27 @@ const varDocs: Record = { ))} ), + transition: ( + + {Object.entries(vars.transition).map( + ([transitionName, transitionVar], index) => ( + + {index > 0 ? : null} + + + + + + + ), + )} + + ), } as const; const docs: CssDoc = { diff --git a/packages/docs-ui/src/components/CategoryHeading/CategoryHeading.tsx b/packages/docs-ui/src/components/CategoryHeading/CategoryHeading.tsx new file mode 100644 index 000000000000..147637248ae7 --- /dev/null +++ b/packages/docs-ui/src/components/CategoryHeading/CategoryHeading.tsx @@ -0,0 +1,16 @@ +import { Box, Text } from 'braid-design-system'; +import type { ComponentProps, ReactNode } from 'react'; + +type CategoryHeadingProps = { + children: ReactNode; + component?: ComponentProps['component']; +}; + +export const CategoryHeading = ({ + children, + component, +}: CategoryHeadingProps) => ( + + {children} + +); diff --git a/packages/docs-ui/src/components/SideNavigation/SideNavigationSection.tsx b/packages/docs-ui/src/components/SideNavigation/SideNavigationSection.tsx index ca10bb399488..dac282a04423 100644 --- a/packages/docs-ui/src/components/SideNavigation/SideNavigationSection.tsx +++ b/packages/docs-ui/src/components/SideNavigation/SideNavigationSection.tsx @@ -1,4 +1,6 @@ -import { Box, HiddenVisually, Stack, Text } from 'braid-design-system'; +import { Box, HiddenVisually, Stack } from 'braid-design-system'; + +import { CategoryHeading } from '../CategoryHeading/CategoryHeading'; import { SideNavigationItem } from './SideNavigationItem'; @@ -11,9 +13,7 @@ interface SideNavigationSection { } const Title = ({ children }: { children: string }) => ( - - {children} - + {children} ); const ItemList = ({ items }: { items: SideNavigationItem[] }) => ( diff --git a/packages/docs-ui/src/components/TitleLink/TitleLink.css.ts b/packages/docs-ui/src/components/TitleLink/TitleLink.css.ts new file mode 100644 index 000000000000..dc68b097e96e --- /dev/null +++ b/packages/docs-ui/src/components/TitleLink/TitleLink.css.ts @@ -0,0 +1,29 @@ +import { style } from '@vanilla-extract/css'; +import { atoms, vars } from 'braid-design-system/css'; + +export const titleLink = style([ + atoms({ + display: 'flex', + borderRadius: 'small', + gap: 'xsmall', + alignItems: 'center', + }), + { + maxWidth: 'fit-content', + scrollMarginBlockStart: vars.space.small, + outlineOffset: vars.space.xsmall, + }, +]); + +export const isCopying = style({}); + +export const showIcon = style({ + selectors: { + [`${titleLink}:hover &, ${titleLink}:focus-visible &, ${isCopying}&`]: { + opacity: 1, + }, + [`${titleLink}:hover &`]: { + transition: vars.transition.fast, + }, + }, +}); diff --git a/packages/docs-ui/src/components/TitleLink/TitleLink.tsx b/packages/docs-ui/src/components/TitleLink/TitleLink.tsx new file mode 100644 index 000000000000..0f535fd8f5b0 --- /dev/null +++ b/packages/docs-ui/src/components/TitleLink/TitleLink.tsx @@ -0,0 +1,97 @@ +import { + Box, + IconLink, + Link, + TooltipRenderer, + Text, + IconPositive, +} from 'braid-design-system'; +import type { MouseEventHandler, ReactNode, RefCallback } from 'react'; + +import { useCopy } from '../../utils/useCopy'; + +import * as styles from './TitleLink.css'; + +const slugify = (string: string) => + string + .replace(/[\s?]/g, '-') + .replace('--', '-') + .replace(/-$/, '') + .toLowerCase(); + +type TriggerProps = { + ref: RefCallback; + tabIndex: 0; + 'aria-describedby': string; +}; + +const TitleLinkAnchor = ({ + slug, + onClick, + children, + triggerProps, + copying, +}: { + slug: string; + onClick?: MouseEventHandler; + children: ReactNode; + triggerProps?: TriggerProps; + copying?: boolean; +}) => ( + + + {children} + + {copying ? : } + + + +); + +type TitleLink = { copyable?: boolean } & ( + | { children: string } + | { children: ReactNode; label: string } +); + +export const TitleLink = ({ copyable = false, ...restProps }: TitleLink) => { + const label = 'label' in restProps ? restProps.label : restProps.children; + const slug = slugify(label); + const { copying, onCopyClick } = useCopy(); + + const handleClick: MouseEventHandler = (event) => { + if (event.metaKey) { + return; + } + event.preventDefault(); + const anchor = event.currentTarget as HTMLAnchorElement; + onCopyClick(anchor.href); + }; + + if (!copyable) { + return {restProps.children}; + } + + return ( + Copy to clipboard}> + {({ triggerProps }) => ( + + {restProps.children} + + )} + + ); +}; diff --git a/packages/docs-ui/src/index.ts b/packages/docs-ui/src/index.ts index 4b13a6b97cc1..9a7bcda64168 100644 --- a/packages/docs-ui/src/index.ts +++ b/packages/docs-ui/src/index.ts @@ -6,3 +6,5 @@ export { KeyboardShortcut, KeyboardIcon, } from './components/KeyboardShortcut/KeyboardShortcut'; +export { TitleLink } from './components/TitleLink/TitleLink'; +export { CategoryHeading } from './components/CategoryHeading/CategoryHeading'; diff --git a/packages/docs-ui/src/utils/useCopy.ts b/packages/docs-ui/src/utils/useCopy.ts new file mode 100644 index 000000000000..572a105963a9 --- /dev/null +++ b/packages/docs-ui/src/utils/useCopy.ts @@ -0,0 +1,26 @@ +import { useEffect, useState } from 'react'; + +export const useCopy = () => { + const [copying, setCopying] = useState(false); + + useEffect(() => { + if (copying) { + const timer = setTimeout(() => { + setCopying(false); + }, 2000); + return () => clearTimeout(timer); + } + }, [copying]); + + const onCopyClick = async (content: string) => { + if (!copying) { + setCopying(true); + await navigator.clipboard.writeText(content); + } + }; + + return { + copying, + onCopyClick, + }; +}; diff --git a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap index f1bd60932e8f..6ebc96d2ccf4 100644 --- a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap +++ b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap @@ -160,6 +160,8 @@ exports[`Badge 1`] = ` exportType: component, props: { aria-describedby?: string + aria-hidden?: boolean + aria-label?: string bleedY?: boolean children: | (string | number)[] diff --git a/site/src/App/CategoryHeading/CategoryHeading.tsx b/site/src/App/CategoryHeading/CategoryHeading.tsx deleted file mode 100644 index 683337d85904..000000000000 --- a/site/src/App/CategoryHeading/CategoryHeading.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Box, Text } from 'braid-design-system'; -import type { ComponentProps } from 'react'; - -export const CategoryHeading = ({ - children, - component, -}: { - children: React.ReactNode; - component?: ComponentProps['component']; -}) => ( - - - {children} - - -); diff --git a/site/src/App/DocNavigation/DocDetails.tsx b/site/src/App/DocNavigation/DocDetails.tsx index 19ffb03c7a83..e7f9077a1a80 100644 --- a/site/src/App/DocNavigation/DocDetails.tsx +++ b/site/src/App/DocNavigation/DocDetails.tsx @@ -1,4 +1,4 @@ -import { LinkableHeading } from '@braid-design-system/docs-ui'; +import { CategoryHeading, TitleLink } from '@braid-design-system/docs-ui'; import { Box, Stack, @@ -6,6 +6,7 @@ import { TextLink, Secondary, Text, + Heading, } from 'braid-src/lib/components'; import { PlayroomStateProvider } from 'braid-src/lib/playroom/playroomState'; import { useContext, useMemo } from 'react'; @@ -196,7 +197,9 @@ export const DocDetails = () => { {'accessibility' in docs && docs.accessibility ? ( - Accessibility + + Accessibility + {docs.accessibility} ) : null} @@ -206,46 +209,55 @@ export const DocDetails = () => { .filter(([, docSectionChildren]) => docSectionChildren.some(hasContent), ) - .map(([sectionKey, docSectionChildren]) => ( - - - {getSectionHeading(sectionKey)} - - - {docSectionChildren.map( - (example: { label?: string }, index: number) => ( - - ), - )} - {sectionKey === 'bestPractices' && - 'alternatives' in docs && - docs.alternatives.length > 0 ? ( - - - Alternatives - - - {docs.alternatives.map((alt) => ( - - - {alt.name} - {' '} - — {alt.description} - - ))} - - - ) : null} + .map(([sectionKey, docSectionChildren]) => { + const heading = getSectionHeading(sectionKey); + return ( + + + + {heading} + + + + + {docSectionChildren.map( + (example: { label?: string }, index: number) => ( + + ), + )} + {sectionKey === 'bestPractices' && + 'alternatives' in docs && + docs.alternatives.length > 0 ? ( + + + + Alternatives + + + + + {docs.alternatives.map((alt) => ( + + + {alt.name} + {' '} + — {alt.description} + + ))} + + + ) : null} + - - ))} + ); + })} {(docs.additional || []).map((example, index) => ( { !hasBestPractices && docs.alternatives.length > 0 ? ( - Alternatives + + Alternatives + {docs.alternatives.map((alt) => ( diff --git a/site/src/App/DocNavigation/DocProps.tsx b/site/src/App/DocNavigation/DocProps.tsx index 50ed19327994..7d4a4b1fc33d 100644 --- a/site/src/App/DocNavigation/DocProps.tsx +++ b/site/src/App/DocNavigation/DocProps.tsx @@ -1,4 +1,4 @@ -import { LinkableHeading } from '@braid-design-system/docs-ui'; +import { TitleLink } from '@braid-design-system/docs-ui'; import type { NormalisedPropType, ExportDoc, @@ -13,6 +13,7 @@ import { TooltipRenderer, TextLink, IconInfo, + Heading, } from 'braid-src/lib/components'; import partition from 'lodash.partition'; import { Fragment, useContext, useMemo } from 'react'; @@ -199,7 +200,10 @@ export const DocProps = () => { {Array.isArray(propsToDocument) ? ( propsToDocument.map((c) => ( - {c} + + {c} + + )) @@ -208,9 +212,9 @@ export const DocProps = () => { )} - - Further References - + + Further References + View Source diff --git a/site/src/App/DocNavigation/DocReleases.tsx b/site/src/App/DocNavigation/DocReleases.tsx index 75d7559fa70e..19d0b9374986 100644 --- a/site/src/App/DocNavigation/DocReleases.tsx +++ b/site/src/App/DocNavigation/DocReleases.tsx @@ -1,4 +1,4 @@ -import { LinkableHeading } from '@braid-design-system/docs-ui'; +import { TitleLink } from '@braid-design-system/docs-ui'; import { Box, Stack, @@ -64,7 +64,10 @@ export const DocReleases = () => { ) : null} - {`v${version}`} + + {`v${version}`} + + {historyItem.time ? ( ( - - {section.label ? ( - Deprecated - ) : undefined - } - > - {section.label} - - ) : null} - {section.description ?? null} - {section.code || section.Example ? ( - - - - ) : null} - -); +}) => { + const { deprecated } = section; + return ( + + {deprecated ? ( + + Deprecated + + ) : undefined} + + {section.label ? ( + + + {section.label} + {deprecated ? ( + , deprecated + ) : null} + + + ) : null} + {section.description ?? null} + {section.code || section.Example ? ( + + + + ) : null} + + + ); +}; diff --git a/site/src/App/JumpToModal/SearchResults.tsx b/site/src/App/JumpToModal/SearchResults.tsx index cbef4c05ea70..05fcbdf3eb61 100644 --- a/site/src/App/JumpToModal/SearchResults.tsx +++ b/site/src/App/JumpToModal/SearchResults.tsx @@ -1,4 +1,7 @@ -import { KeyboardShortcut } from '@braid-design-system/docs-ui'; +import { + KeyboardShortcut, + CategoryHeading, +} from '@braid-design-system/docs-ui'; import { Box, Stack, @@ -8,8 +11,6 @@ import { Spread, } from 'braid-src/lib/components'; -import { CategoryHeading } from '../CategoryHeading/CategoryHeading'; - import type { GroupedResults, SearchItem } from './getSearchItems'; interface SearchResultsProps {