@rtkelly-labs/design-system 0.0.0-bootstrap.0 → 0.16.0-dev.0.37dedac
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +347 -0
- package/LICENSE +21 -0
- package/README.md +317 -2
- package/dist/components/AlertDialog.js +95 -0
- package/dist/components/AlertDialog.js.map +1 -0
- package/dist/components/AppShell.js +261 -0
- package/dist/components/AppShell.js.map +1 -0
- package/dist/components/AsciiDivider.js +8 -0
- package/dist/components/AsciiDivider.js.map +1 -0
- package/dist/components/AsciiGauge.js +130 -0
- package/dist/components/AsciiGauge.js.map +1 -0
- package/dist/components/Avatar.js +87 -0
- package/dist/components/Avatar.js.map +1 -0
- package/dist/components/Badge.js +25 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/BarChart.js +288 -0
- package/dist/components/BarChart.js.map +1 -0
- package/dist/components/BracketText.js +23 -0
- package/dist/components/BracketText.js.map +1 -0
- package/dist/components/BulletChart.js +50 -0
- package/dist/components/BulletChart.js.map +1 -0
- package/dist/components/Button.js +130 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.js +181 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/ChartTooltip.js +54 -0
- package/dist/components/ChartTooltip.js.map +1 -0
- package/dist/components/Checkbox.js +55 -0
- package/dist/components/Checkbox.js.map +1 -0
- package/dist/components/ColophonCard.js +122 -0
- package/dist/components/ColophonCard.js.map +1 -0
- package/dist/components/DataTable.js +299 -0
- package/dist/components/DataTable.js.map +1 -0
- package/dist/components/Divider.js +42 -0
- package/dist/components/Divider.js.map +1 -0
- package/dist/components/Drawer.js +81 -0
- package/dist/components/Drawer.js.map +1 -0
- package/dist/components/EmptyState.js +39 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ErrorSummary.js +122 -0
- package/dist/components/ErrorSummary.js.map +1 -0
- package/dist/components/Fieldset.js +37 -0
- package/dist/components/Fieldset.js.map +1 -0
- package/dist/components/Glyph.js +60 -0
- package/dist/components/Glyph.js.map +1 -0
- package/dist/components/Input.js +111 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/LinkProvider.js +76 -0
- package/dist/components/LinkProvider.js.map +1 -0
- package/dist/components/Menu.js +144 -0
- package/dist/components/Menu.js.map +1 -0
- package/dist/components/MetadataGrid.js +184 -0
- package/dist/components/MetadataGrid.js.map +1 -0
- package/dist/components/MobileNav.js +120 -0
- package/dist/components/MobileNav.js.map +1 -0
- package/dist/components/Modal.js +79 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/NerdIcon.js +177 -0
- package/dist/components/NerdIcon.js.map +1 -0
- package/dist/components/NoteBlock.js +84 -0
- package/dist/components/NoteBlock.js.map +1 -0
- package/dist/components/PageHeader.js +34 -0
- package/dist/components/PageHeader.js.map +1 -0
- package/dist/components/PageTitle.js +21 -0
- package/dist/components/PageTitle.js.map +1 -0
- package/dist/components/Pagination.js +96 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/Popover.js +87 -0
- package/dist/components/Popover.js.map +1 -0
- package/dist/components/Progress.js +83 -0
- package/dist/components/Progress.js.map +1 -0
- package/dist/components/RadioGroup.js +74 -0
- package/dist/components/RadioGroup.js.map +1 -0
- package/dist/components/ReportDocument.js +75 -0
- package/dist/components/ReportDocument.js.map +1 -0
- package/dist/components/SectionContainer.js +18 -0
- package/dist/components/SectionContainer.js.map +1 -0
- package/dist/components/Select.js +199 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/SiteFooter.js +29 -0
- package/dist/components/SiteFooter.js.map +1 -0
- package/dist/components/SiteHeader.js +59 -0
- package/dist/components/SiteHeader.js.map +1 -0
- package/dist/components/SiteNav.js +141 -0
- package/dist/components/SiteNav.js.map +1 -0
- package/dist/components/Skeleton.js +51 -0
- package/dist/components/Skeleton.js.map +1 -0
- package/dist/components/SocialIcon.js +77 -0
- package/dist/components/SocialIcon.js.map +1 -0
- package/dist/components/Sparkline.js +104 -0
- package/dist/components/Sparkline.js.map +1 -0
- package/dist/components/Spinner.js +65 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/StatCard.js +53 -0
- package/dist/components/StatCard.js.map +1 -0
- package/dist/components/StatusPage.js +99 -0
- package/dist/components/StatusPage.js.map +1 -0
- package/dist/components/Swatch.js +55 -0
- package/dist/components/Swatch.js.map +1 -0
- package/dist/components/Switch.js +53 -0
- package/dist/components/Switch.js.map +1 -0
- package/dist/components/TLDR.js +53 -0
- package/dist/components/TLDR.js.map +1 -0
- package/dist/components/Table.js +155 -0
- package/dist/components/Table.js.map +1 -0
- package/dist/components/Tabs.js +325 -0
- package/dist/components/Tabs.js.map +1 -0
- package/dist/components/Tag.js +19 -0
- package/dist/components/Tag.js.map +1 -0
- package/dist/components/TelemetryGauge.js +112 -0
- package/dist/components/TelemetryGauge.js.map +1 -0
- package/dist/components/ThemeProvider.js +87 -0
- package/dist/components/ThemeProvider.js.map +1 -0
- package/dist/components/TickerTape.js +196 -0
- package/dist/components/TickerTape.js.map +1 -0
- package/dist/components/Toast.js +200 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/Tooltip.js +69 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/components/TrackCard.js +206 -0
- package/dist/components/TrackCard.js.map +1 -0
- package/dist/components/blog/BlogPost.js +116 -0
- package/dist/components/blog/BlogPost.js.map +1 -0
- package/dist/components/blog/LoremIpsumPost.js +62 -0
- package/dist/components/blog/LoremIpsumPost.js.map +1 -0
- package/dist/components/blog/author.js +25 -0
- package/dist/components/blog/author.js.map +1 -0
- package/dist/components/booleanControl.js +50 -0
- package/dist/components/booleanControl.js.map +1 -0
- package/dist/components/dialogPopupRef.js +28 -0
- package/dist/components/dialogPopupRef.js.map +1 -0
- package/dist/components/dialogSurface.js +75 -0
- package/dist/components/dialogSurface.js.map +1 -0
- package/dist/components/docs/AnchorHeading.js +80 -0
- package/dist/components/docs/AnchorHeading.js.map +1 -0
- package/dist/components/docs/Breadcrumbs.js +26 -0
- package/dist/components/docs/Breadcrumbs.js.map +1 -0
- package/dist/components/docs/CodeBlock.js +63 -0
- package/dist/components/docs/CodeBlock.js.map +1 -0
- package/dist/components/docs/CodeTabs.js +71 -0
- package/dist/components/docs/CodeTabs.js.map +1 -0
- package/dist/components/docs/DocPager.js +28 -0
- package/dist/components/docs/DocPager.js.map +1 -0
- package/dist/components/docs/DocsHeader.js +124 -0
- package/dist/components/docs/DocsHeader.js.map +1 -0
- package/dist/components/docs/DocsLayout.js +61 -0
- package/dist/components/docs/DocsLayout.js.map +1 -0
- package/dist/components/docs/DocsLinkProvider.js +25 -0
- package/dist/components/docs/DocsLinkProvider.js.map +1 -0
- package/dist/components/docs/DocsSidebar.js +147 -0
- package/dist/components/docs/DocsSidebar.js.map +1 -0
- package/dist/components/docs/Prose.js +56 -0
- package/dist/components/docs/Prose.js.map +1 -0
- package/dist/components/docs/TableOfContents.js +71 -0
- package/dist/components/docs/TableOfContents.js.map +1 -0
- package/dist/components/docs/codeTabsStore.js +75 -0
- package/dist/components/docs/codeTabsStore.js.map +1 -0
- package/dist/components/docs/figures/ActivityGrid.js +116 -0
- package/dist/components/docs/figures/ActivityGrid.js.map +1 -0
- package/dist/components/docs/figures/BeforeAfter.js +64 -0
- package/dist/components/docs/figures/BeforeAfter.js.map +1 -0
- package/dist/components/docs/figures/ChangeSummary.js +74 -0
- package/dist/components/docs/figures/ChangeSummary.js.map +1 -0
- package/dist/components/docs/figures/Decision.js +34 -0
- package/dist/components/docs/figures/Decision.js.map +1 -0
- package/dist/components/docs/figures/DrawingSheet.js +37 -0
- package/dist/components/docs/figures/DrawingSheet.js.map +1 -0
- package/dist/components/docs/figures/FigureFrame.js +226 -0
- package/dist/components/docs/figures/FigureFrame.js.map +1 -0
- package/dist/components/docs/figures/FileChangeTree.js +134 -0
- package/dist/components/docs/figures/FileChangeTree.js.map +1 -0
- package/dist/components/docs/figures/FlowDiagram.js +82 -0
- package/dist/components/docs/figures/FlowDiagram.js.map +1 -0
- package/dist/components/docs/figures/GanttChart.js +79 -0
- package/dist/components/docs/figures/GanttChart.js.map +1 -0
- package/dist/components/docs/figures/IndexedSection.js +45 -0
- package/dist/components/docs/figures/IndexedSection.js.map +1 -0
- package/dist/components/docs/figures/MetricStrip.js +23 -0
- package/dist/components/docs/figures/MetricStrip.js.map +1 -0
- package/dist/components/docs/figures/Steps.js +58 -0
- package/dist/components/docs/figures/Steps.js.map +1 -0
- package/dist/components/docs/figures/SwatchCompare.js +42 -0
- package/dist/components/docs/figures/SwatchCompare.js.map +1 -0
- package/dist/components/docs/figures/TermGrid.js +22 -0
- package/dist/components/docs/figures/TermGrid.js.map +1 -0
- package/dist/components/docs/figures/Terminal.js +37 -0
- package/dist/components/docs/figures/Terminal.js.map +1 -0
- package/dist/components/docs/figures/Timeline.js +61 -0
- package/dist/components/docs/figures/Timeline.js.map +1 -0
- package/dist/components/docs/figures/TokenTable.js +42 -0
- package/dist/components/docs/figures/TokenTable.js.map +1 -0
- package/dist/components/docs/figures/TreeDiagram.js +76 -0
- package/dist/components/docs/figures/TreeDiagram.js.map +1 -0
- package/dist/components/docs/figures/UptimeStrip.js +68 -0
- package/dist/components/docs/figures/UptimeStrip.js.map +1 -0
- package/dist/components/docs/figures/index.js +24 -0
- package/dist/components/docs/figures/index.js.map +1 -0
- package/dist/components/docs/index.js +15 -0
- package/dist/components/docs/index.js.map +1 -0
- package/dist/components/docs/mdxComponents.js +108 -0
- package/dist/components/docs/mdxComponents.js.map +1 -0
- package/dist/components/experiments/DesignSandbox.js +206 -0
- package/dist/components/experiments/DesignSandbox.js.map +1 -0
- package/dist/components/experiments/ExperimentsView.js +138 -0
- package/dist/components/experiments/ExperimentsView.js.map +1 -0
- package/dist/components/fieldFrame.js +200 -0
- package/dist/components/fieldFrame.js.map +1 -0
- package/dist/components/floatingSurface.js +35 -0
- package/dist/components/floatingSurface.js.map +1 -0
- package/dist/components/marketing/CTASection.js +47 -0
- package/dist/components/marketing/CTASection.js.map +1 -0
- package/dist/components/marketing/FeatureGrid.js +49 -0
- package/dist/components/marketing/FeatureGrid.js.map +1 -0
- package/dist/components/marketing/Hero.js +44 -0
- package/dist/components/marketing/Hero.js.map +1 -0
- package/dist/components/marketing/PricingGrid.js +65 -0
- package/dist/components/marketing/PricingGrid.js.map +1 -0
- package/dist/components/marketing/SectionGrid.js +51 -0
- package/dist/components/marketing/SectionGrid.js.map +1 -0
- package/dist/components/paginationRange.js +28 -0
- package/dist/components/paginationRange.js.map +1 -0
- package/dist/components/portalTheme.js +14 -0
- package/dist/components/portalTheme.js.map +1 -0
- package/dist/components/saas/SaasLandingPage.js +201 -0
- package/dist/components/saas/SaasLandingPage.js.map +1 -0
- package/dist/components/siteNavContext.js +24 -0
- package/dist/components/siteNavContext.js.map +1 -0
- package/dist/components/slides/Slide.js +84 -0
- package/dist/components/slides/Slide.js.map +1 -0
- package/dist/components/slides/SlideDeck.js +228 -0
- package/dist/components/slides/SlideDeck.js.map +1 -0
- package/dist/components/themeInitScript.js +16 -0
- package/dist/components/themeInitScript.js.map +1 -0
- package/dist/components/themeScope.js +9 -0
- package/dist/components/themeScope.js.map +1 -0
- package/dist/components/toastLifetime.js +21 -0
- package/dist/components/toastLifetime.js.map +1 -0
- package/dist/fonts/DSSymbols-OFL.txt +98 -0
- package/dist/fonts/DSSymbols-Regular.woff2 +0 -0
- package/dist/fonts/SymbolsNerdFontMono-Regular.woff2 +0 -0
- package/dist/hooks/useActiveHeading.js +59 -0
- package/dist/hooks/useActiveHeading.js.map +1 -0
- package/dist/hooks/useCopyToClipboard.js +58 -0
- package/dist/hooks/useCopyToClipboard.js.map +1 -0
- package/dist/index.d.ts +6394 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/accentClasses.js +87 -0
- package/dist/lib/accentClasses.js.map +1 -0
- package/dist/lib/recipe.js +33 -0
- package/dist/lib/recipe.js.map +1 -0
- package/dist/lib/slug.js +39 -0
- package/dist/lib/slug.js.map +1 -0
- package/dist/lib/theme.js +76 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/tokenRules.js +51 -0
- package/dist/lib/tokenRules.js.map +1 -0
- package/dist/prose.css +766 -0
- package/dist/styles.css +203 -0
- package/dist/theme/ansi.js +77 -0
- package/dist/theme/ansi.js.map +1 -0
- package/dist/theme/contrast.js +342 -0
- package/dist/theme/contrast.js.map +1 -0
- package/dist/theme/levels.js +234 -0
- package/dist/theme/levels.js.map +1 -0
- package/dist/theme/media.js +115 -0
- package/dist/theme/media.js.map +1 -0
- package/dist/theme/recommended.js +105 -0
- package/dist/theme/recommended.js.map +1 -0
- package/dist/theme.css +538 -0
- package/dist/tokens.js +1 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +209 -6
- package/skills/design-system-usage/SKILL.md +75 -0
- package/terminal/rtk-midnight.alacritty.toml +33 -0
- package/terminal/rtk-midnight.ghostty.conf +24 -0
- package/terminal/rtk-midnight.itermcolors +167 -0
- package/terminal/rtk-midnight.windows-terminal.json +23 -0
- package/terminal/rtk-sketch.alacritty.toml +33 -0
- package/terminal/rtk-sketch.ghostty.conf +24 -0
- package/terminal/rtk-sketch.itermcolors +167 -0
- package/terminal/rtk-sketch.windows-terminal.json +23 -0
- package/tokens/palette.midnight.tokens.json +477 -0
- package/tokens/palette.sketch.tokens.json +477 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/SiteNav.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, useMemo } from 'react';\nimport type { AnchorHTMLAttributes, HTMLAttributes, MouseEvent, ReactNode, Ref } from 'react';\nimport { Menu as BaseMenu } from '@base-ui/react/menu';\nimport { ChevronDown } from 'lucide-react';\nimport { cn, recipe } from '../lib/recipe';\nimport { SiteLink } from './LinkProvider';\nimport { Menu } from './Menu';\nimport { floatingParts } from './floatingSurface';\nimport { SiteNavContext, useSiteNavContext } from './siteNavContext';\n\n/**\n * Which way a `SiteNav` lays its items out: a row, for a header, or a column,\n * for a drawer, a footer or a sidebar. A plain string union, so no library's\n * type reaches the published `.d.ts`.\n */\nexport type SiteNavOrientation = 'horizontal' | 'vertical';\n\nexport interface SiteNavProps\n extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'className' | 'aria-label'> {\n /**\n * The navigation landmark's accessible name — `Primary`, `Footer`,\n * `Resources`. Required: a page with two `<nav>`s and no names gives a\n * screen reader two landmarks called \"navigation\" and no way to tell them\n * apart. It lands on the `<nav>` itself.\n */\n label: string;\n /** `SiteNavItem`s, in order. The component supplies none of its own. */\n children: ReactNode;\n /** A row (`horizontal`, the default) or a column (`vertical`). */\n orientation?: SiteNavOrientation;\n /** Merged into the `<nav>`'s classes. For placement, not colour. */\n className?: string;\n}\n\n// The two layouts, and the item each one wears.\n//\n// A row's current item is marked by an accent edge under it; a column's by an\n// accent edge beside it and the page ground behind it — the tab and sidebar\n// selection devices `check:contrast` already audits, so the current item is\n// told apart by a shape as well as a colour. Both are keyed off\n// `aria-current`, so the styling and the announcement read one attribute.\n//\n// A column's items are `min-h-11`, 44px at the `web` Medium: in a drawer they\n// are touch targets.\nconst siteNav = recipe({\n slots: {\n root: 'font-mono',\n list: 'm-0 flex list-none p-0',\n link: 'border-transparent font-bold uppercase text-content-secondary no-underline '\n + 'hover:text-accent-primary aria-[current=page]:border-accent-primary '\n + 'aria-[current=page]:text-accent-primary',\n trigger: 'inline-flex cursor-pointer items-center gap-1 border-transparent bg-transparent '\n + 'font-mono font-bold uppercase text-content-secondary hover:text-accent-primary '\n + 'data-[popup-open]:text-accent-primary',\n },\n variants: {\n orientation: {\n horizontal: {\n list: 'flex-wrap items-center gap-x-6 gap-y-2',\n link: 'inline-block border-b-2 py-1 text-sm tracking-wide',\n trigger: 'border-b-2 py-1 text-sm tracking-wide',\n },\n vertical: {\n list: 'flex-col gap-1',\n link: 'block min-h-11 border-l-4 px-3 py-3 text-sm aria-[current=page]:bg-surface-base',\n trigger: 'min-h-11 w-full border-l-4 px-3 py-3 text-left text-sm',\n },\n },\n },\n defaultVariants: { orientation: 'horizontal' },\n});\n\n/**\n * A site's primary navigation: a named `<nav>` landmark around a list of\n * {@link SiteNavItem}s.\n *\n * It owns the landmark, the list semantics and the two layouts, and nothing\n * else. It holds no items of its own and no default ones — every link comes\n * from the caller — and it does not decide which item is current: that is\n * each item's `current`, or the `isCurrent` a {@link LinkProvider} was given\n * from the consumer's router. Nothing here reads `window.location`.\n *\n * In a {@link SiteHeader} it is the desktop navigation; the same items passed\n * to a {@link MobileNav} become the narrow-viewport one. In a\n * {@link SiteFooter} it is a column (`orientation=\"vertical\"`) with a name of\n * its own.\n *\n * @example\n * ```tsx\n * <SiteNav label=\"Primary\">\n * <SiteNavItem href=\"/writing\">Writing</SiteNavItem>\n * <SiteNavItem href=\"/projects\">Projects</SiteNavItem>\n * </SiteNav>\n * ```\n */\nexport const SiteNav = forwardRef<HTMLElement, SiteNavProps>(function SiteNav(\n { label, children, orientation = 'horizontal', className, ...props },\n ref,\n) {\n const parent = useSiteNavContext();\n const styles = siteNav({ orientation });\n const onNavigate = parent.onNavigate;\n const context = useMemo(\n () => ({ orientation, inMenu: false, onNavigate }),\n [orientation, onNavigate],\n );\n\n return (\n <nav\n ref={ref}\n data-slot=\"site-nav\"\n data-orientation={orientation}\n {...props}\n aria-label={label}\n className={styles.root({ class: className })}\n >\n <ul data-slot=\"site-nav-list\" className={styles.list()}>\n <SiteNavContext.Provider value={context}>{children}</SiteNavContext.Provider>\n </ul>\n </nav>\n );\n});\n\n/** A link item — `href` and a label. */\nexport interface SiteNavLinkItemProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'children' | 'className'> {\n /** Where it goes. Rendered through the `LinkProvider`'s link component. */\n href: string;\n /** The visible label, which is also the link's accessible name. */\n children: ReactNode;\n /**\n * Marks this item as the current page. Leave it unset to let the\n * `LinkProvider`'s `isCurrent` decide from the consumer's route.\n */\n current?: boolean;\n /** Merged into the link's classes. */\n className?: string;\n label?: never;\n defaultOpen?: never;\n}\n\n/** A group item — a `label` and nested link items, opened as a `Menu`. */\nexport interface SiteNavGroupItemProps {\n /**\n * The group's visible label. It names the trigger, and so the menu: Base UI\n * labels the `role=\"menu\"` popup by its trigger.\n */\n label: string;\n /** Link `SiteNavItem`s. One level: a group inside a group is not supported. */\n children: ReactNode;\n /** Open on first render — for a story or a walkthrough. */\n defaultOpen?: boolean;\n /** Merged into the trigger's classes. */\n className?: string;\n href?: never;\n current?: never;\n}\n\nexport type SiteNavItemProps = SiteNavLinkItemProps | SiteNavGroupItemProps;\n\n/**\n * One entry in a {@link SiteNav} or a {@link MobileNav}.\n *\n * Two shapes, chosen by the props rather than by a second component: an item\n * with an `href` is a link, and an item with a `label` and child items is a\n * group whose children open in a {@link Menu}. The group is `Menu`, not a\n * disclosure written here, so its keyboard model — arrow keys, typeahead,\n * Escape back to the trigger — is the one every other menu in the package has.\n *\n * The current link carries `aria-current=\"page\"` on the anchor itself, and\n * its styling is keyed off that attribute rather than a second flag.\n *\n * Forwards its ref to the element it renders: the anchor for a link, the\n * trigger button for a group.\n */\nexport const SiteNavItem = forwardRef<HTMLAnchorElement | HTMLButtonElement, SiteNavItemProps>(\n function SiteNavItem(props, ref) {\n const context = useSiteNavContext();\n const styles = siteNav({ orientation: context.orientation });\n\n if (typeof props.href !== 'string') {\n const { label, children, defaultOpen, className } = props as SiteNavGroupItemProps;\n return (\n <li data-slot=\"site-nav-item\">\n <Menu\n defaultOpen={defaultOpen}\n trigger={\n <button\n ref={ref as Ref<HTMLButtonElement>}\n type=\"button\"\n data-slot=\"site-nav-group\"\n className={styles.trigger({ class: className })}\n >\n {label}\n <ChevronDown size={14} strokeWidth={3} aria-hidden=\"true\" />\n </button>\n }\n >\n <SiteNavContext.Provider value={{ ...context, inMenu: true }}>\n {children}\n </SiteNavContext.Provider>\n </Menu>\n </li>\n );\n }\n\n const { href, children, current, className, onClick, ...rest } = props as SiteNavLinkItemProps;\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n onClick?.(event);\n // A consumer that cancels the click has kept the reader on this page,\n // so the drawer they navigated from stays open.\n if (!event.defaultPrevented) context.onNavigate?.();\n };\n\n if (context.inMenu) {\n return (\n <MenuLink\n ref={ref as Ref<HTMLAnchorElement>}\n {...rest}\n href={href}\n current={current}\n className={className}\n onClick={handleClick}\n >\n {children}\n </MenuLink>\n );\n }\n\n return (\n <li data-slot=\"site-nav-item\">\n <SiteLink\n ref={ref as Ref<HTMLAnchorElement>}\n {...rest}\n href={href}\n current={current}\n onClick={handleClick}\n data-slot=\"site-nav-link\"\n className={styles.link({ class: className })}\n >\n {children}\n </SiteLink>\n </li>\n );\n },\n);\n\n// A link inside a group's menu: Base UI's menu link item, which keeps the\n// arrow keys and typeahead of the menu, rendered through `SiteLink` so it goes\n// through the consumer's router and carries `aria-current` like any other item.\n// It closes the menu when followed — the page it opened over is going away.\ntype MenuLinkProps = Omit<SiteNavLinkItemProps, 'onClick'> & {\n onClick: (event: MouseEvent<HTMLAnchorElement>) => void;\n};\n\nconst MenuLink = forwardRef<HTMLAnchorElement, MenuLinkProps>(function MenuLink(\n { href, current, className, children, ...rest },\n ref,\n) {\n const parts = floatingParts();\n return (\n <BaseMenu.LinkItem\n ref={ref}\n closeOnClick\n data-slot=\"site-nav-menu-link\"\n className={cn(\n parts.item(),\n 'no-underline aria-[current=page]:font-bold aria-[current=page]:underline',\n className,\n )}\n render={(renderProps) => <SiteLink {...renderProps} href={href} current={current} />}\n {...rest}\n >\n {children}\n </BaseMenu.LinkItem>\n );\n});\n"],"mappings":";;;AAEA,SAAS,YAAY,eAAe;AAEpC,SAAS,QAAQ,gBAAgB;AACjC,SAAS,mBAAmB;AAC5B,SAAS,IAAI,cAAc;AAC3B,SAAS,gBAAgB;AACzB,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB,yBAAyB;AA6G1C,cAsEM,YAtEN;AAzER,IAAM,UAAU,OAAO;AAAA,EACrB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IAGN,SAAS;AAAA,EAGX;AAAA,EACA,UAAU;AAAA,IACR,aAAa;AAAA,MACX,YAAY;AAAA,QACV,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,aAAa,aAAa;AAC/C,CAAC;AAyBM,IAAM,UAAU,WAAsC,SAASA,SACpE,EAAE,OAAO,UAAU,cAAc,cAAc,WAAW,GAAG,MAAM,GACnE,KACA;AACA,QAAM,SAAS,kBAAkB;AACjC,QAAM,SAAS,QAAQ,EAAE,YAAY,CAAC;AACtC,QAAM,aAAa,OAAO;AAC1B,QAAM,UAAU;AAAA,IACd,OAAO,EAAE,aAAa,QAAQ,OAAO,WAAW;AAAA,IAChD,CAAC,aAAa,UAAU;AAAA,EAC1B;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,oBAAkB;AAAA,MACjB,GAAG;AAAA,MACJ,cAAY;AAAA,MACZ,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,MAE3C,8BAAC,QAAG,aAAU,iBAAgB,WAAW,OAAO,KAAK,GACnD,8BAAC,eAAe,UAAf,EAAwB,OAAO,SAAU,UAAS,GACrD;AAAA;AAAA,EACF;AAEJ,CAAC;AAsDM,IAAM,cAAc;AAAA,EACzB,SAASC,aAAY,OAAO,KAAK;AAC/B,UAAM,UAAU,kBAAkB;AAClC,UAAM,SAAS,QAAQ,EAAE,aAAa,QAAQ,YAAY,CAAC;AAE3D,QAAI,OAAO,MAAM,SAAS,UAAU;AAClC,YAAM,EAAE,OAAO,UAAAC,WAAU,aAAa,WAAAC,WAAU,IAAI;AACpD,aACE,oBAAC,QAAG,aAAU,iBACZ;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,SACE;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA,MAAK;AAAA,cACL,aAAU;AAAA,cACV,WAAW,OAAO,QAAQ,EAAE,OAAOA,WAAU,CAAC;AAAA,cAE7C;AAAA;AAAA,gBACD,oBAAC,eAAY,MAAM,IAAI,aAAa,GAAG,eAAY,QAAO;AAAA;AAAA;AAAA,UAC5D;AAAA,UAGF,8BAAC,eAAe,UAAf,EAAwB,OAAO,EAAE,GAAG,SAAS,QAAQ,KAAK,GACxD,UAAAD,WACH;AAAA;AAAA,MACF,GACF;AAAA,IAEJ;AAEA,UAAM,EAAE,MAAM,UAAU,SAAS,WAAW,SAAS,GAAG,KAAK,IAAI;AACjE,UAAM,cAAc,CAAC,UAAyC;AAC5D,gBAAU,KAAK;AAGf,UAAI,CAAC,MAAM,iBAAkB,SAAQ,aAAa;AAAA,IACpD;AAEA,QAAI,QAAQ,QAAQ;AAClB,aACE;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACC,GAAG;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS;AAAA,UAER;AAAA;AAAA,MACH;AAAA,IAEJ;AAEA,WACE,oBAAC,QAAG,aAAU,iBACZ;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACC,GAAG;AAAA,QACJ;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,aAAU;AAAA,QACV,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,QAE1C;AAAA;AAAA,IACH,GACF;AAAA,EAEJ;AACF;AAUA,IAAM,WAAW,WAA6C,SAASE,UACrE,EAAE,MAAM,SAAS,WAAW,UAAU,GAAG,KAAK,GAC9C,KACA;AACA,QAAM,QAAQ,cAAc;AAC5B,SACE;AAAA,IAAC,SAAS;AAAA,IAAT;AAAA,MACC;AAAA,MACA,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW;AAAA,QACT,MAAM,KAAK;AAAA,QACX;AAAA,QACA;AAAA,MACF;AAAA,MACA,QAAQ,CAAC,gBAAgB,oBAAC,YAAU,GAAG,aAAa,MAAY,SAAkB;AAAA,MACjF,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;","names":["SiteNav","SiteNavItem","children","className","MenuLink"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/components/Skeleton.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { recipe } from "../lib/recipe.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
var skeleton = recipe({
|
|
6
|
+
slots: {
|
|
7
|
+
/*
|
|
8
|
+
* `motion-reduce:animate-none` — the rule the issue names, and the one case
|
|
9
|
+
* in this package where the preference removes the animation rather than
|
|
10
|
+
* slowing it. Nothing is lost: a skeleton says "content of this shape is
|
|
11
|
+
* coming" by its geometry, and the pulse only ever said "still coming".
|
|
12
|
+
* `Spinner` slows instead of stopping, because there the motion *is* the
|
|
13
|
+
* message.
|
|
14
|
+
*/
|
|
15
|
+
root: "block animate-ds-pulse border border-edge-subtle bg-surface-sunken motion-reduce:animate-none"
|
|
16
|
+
},
|
|
17
|
+
variants: {
|
|
18
|
+
/**
|
|
19
|
+
* The shape of the content, not a size. Each is a placeholder for a thing
|
|
20
|
+
* the system already renders, which is why there is no `width` prop: a
|
|
21
|
+
* caller sizes it with `className`, in the layout the real content will
|
|
22
|
+
* land in.
|
|
23
|
+
*/
|
|
24
|
+
shape: {
|
|
25
|
+
text: { root: "h-4 w-full" },
|
|
26
|
+
heading: { root: "h-7 w-2/3" },
|
|
27
|
+
block: { root: "h-24 w-full" },
|
|
28
|
+
avatar: { root: "size-12" }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
shape: "text"
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
var Skeleton = forwardRef(function Skeleton2({ shape = "text", className, ...props }, ref) {
|
|
36
|
+
const styles = skeleton({ shape });
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
ref,
|
|
41
|
+
"aria-hidden": "true",
|
|
42
|
+
"data-slot": "skeleton",
|
|
43
|
+
className: styles.root({ class: className }),
|
|
44
|
+
...props
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
export {
|
|
49
|
+
Skeleton
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=Skeleton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Skeleton.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type { HTMLAttributes } from 'react';\nimport { recipe } from '../lib/recipe';\n\n/**\n * A sunken box with a hard edge, in the shape of the thing that is coming.\n *\n * `bg-surface-sunken` inside `border-edge-subtle`, and no radius: a skeleton\n * that does not look like this system's boxes is a grey pill borrowed from\n * another system, which is the drift this component exists to stop.\n *\n * The pulse is opacity only. A shimmer sweeping a gradient across the box is\n * the canonical vestibular trigger, and it also needs a second surface colour\n * to sweep *with* — two reasons not to have one.\n */\nconst skeleton = recipe({\n slots: {\n /*\n * `motion-reduce:animate-none` — the rule the issue names, and the one case\n * in this package where the preference removes the animation rather than\n * slowing it. Nothing is lost: a skeleton says \"content of this shape is\n * coming\" by its geometry, and the pulse only ever said \"still coming\".\n * `Spinner` slows instead of stopping, because there the motion *is* the\n * message.\n */\n root: 'block animate-ds-pulse border border-edge-subtle bg-surface-sunken motion-reduce:animate-none',\n },\n variants: {\n /**\n * The shape of the content, not a size. Each is a placeholder for a thing\n * the system already renders, which is why there is no `width` prop: a\n * caller sizes it with `className`, in the layout the real content will\n * land in.\n */\n shape: {\n text: { root: 'h-4 w-full' },\n heading: { root: 'h-7 w-2/3' },\n block: { root: 'h-24 w-full' },\n avatar: { root: 'size-12' },\n },\n },\n defaultVariants: {\n shape: 'text',\n },\n});\n\nexport interface SkeletonProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {\n /**\n * Which piece of content this stands in for — a line of prose, a heading, a\n * block of media, an avatar. It sets the default height and width only;\n * `className` overrides either, and is how a skeleton is fitted to the\n * layout it is holding open.\n */\n shape?: 'text' | 'heading' | 'block' | 'avatar';\n /**\n * Merged onto the box. Sizing belongs here — `w-40`, `h-64` — because the\n * measurement that matters is the one the real content will have.\n */\n className?: string;\n}\n\n/**\n * A placeholder in the shape of the content that has not arrived.\n *\n * One skeleton is one piece of content: a line, a heading, a thumbnail. A\n * loading card is several of them in the layout the real card will use, which\n * is the point — the page does not move when the data lands, because the\n * skeleton already held the space. That is also why there is no `lines` prop:\n * a stack of three with the last one short is composition the caller can see,\n * and a prop that renders a paragraph for you cannot match a layout it has\n * never been shown.\n *\n * Reach for it when the shape of what is coming is known and the wait is long\n * enough to leave a hole. For a short wait in a small space, `Spinner` is\n * honest and cheaper; for a wait whose fraction is known, `Progress`.\n *\n * ## It is hidden from assistive technology\n *\n * `aria-hidden=\"true\"` by default. A skeleton is a drawing of absent content,\n * and a screen reader reading out a scaffold of empty boxes is worse than\n * silence. What a screen reader user needs is the *state*, once, from the\n * region that is loading: put `aria-busy=\"true\"` on the container and a\n * `Spinner`'s label inside it, and let the skeletons be decoration. The\n * attribute spreads like any other prop, so a caller who has a reason can set\n * it back.\n *\n * ## Reduced motion\n *\n * The pulse stops entirely under `prefers-reduced-motion`, leaving the sunken\n * box. See the note on the recipe for why this component stops and `Spinner`\n * only slows.\n */\nexport const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(function Skeleton(\n { shape = 'text', className, ...props },\n ref,\n) {\n const styles = skeleton({ shape });\n\n return (\n <div\n ref={ref}\n aria-hidden=\"true\"\n data-slot=\"skeleton\"\n className={styles.root({ class: className })}\n {...props}\n />\n );\n});\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,SAAS,cAAc;AAiGnB;AApFJ,IAAM,WAAW,OAAO;AAAA,EACtB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASL,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOR,OAAO;AAAA,MACL,MAAM,EAAE,MAAM,aAAa;AAAA,MAC3B,SAAS,EAAE,MAAM,YAAY;AAAA,MAC7B,OAAO,EAAE,MAAM,cAAc;AAAA,MAC7B,QAAQ,EAAE,MAAM,UAAU;AAAA,IAC5B;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA,IACf,OAAO;AAAA,EACT;AACF,CAAC;AAgDM,IAAM,WAAW,WAA0C,SAASA,UACzE,EAAE,QAAQ,QAAQ,WAAW,GAAG,MAAM,GACtC,KACA;AACA,QAAM,SAAS,SAAS,EAAE,MAAM,CAAC;AAEjC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,eAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,MAC1C,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;","names":["Skeleton"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/components/SocialIcon.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { accentTextClass } from "../lib/accentClasses.js";
|
|
4
|
+
import { cn } from "../lib/recipe.js";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var LABELS = {
|
|
7
|
+
github: "GitHub",
|
|
8
|
+
linkedin: "LinkedIn",
|
|
9
|
+
bluesky: "Bluesky",
|
|
10
|
+
x: "X",
|
|
11
|
+
mail: "Email"
|
|
12
|
+
};
|
|
13
|
+
var PATHS = {
|
|
14
|
+
github: "M12 2C6.477 2 2 6.477 2 12c0 4.419 2.865 8.167 6.839 9.49.5.092.682-.217.682-.482 0-.237-.009-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.091-.647.35-1.087.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.682-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.564 9.564 0 0 1 12 6.844a9.58 9.58 0 0 1 2.504.337c1.909-1.294 2.748-1.025 2.748-1.025.546 1.377.202 2.394.1 2.647.64.698 1.028 1.591 1.028 2.682 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.744 0 .267.18.578.688.48A10.003 10.003 0 0 0 22 12c0-5.523-4.477-10-10-10Z",
|
|
15
|
+
linkedin: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.35V8.999h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.602 0 4.267 2.37 4.267 5.455v6.287ZM5.337 7.433a2.062 2.062 0 1 1 0-4.124 2.062 2.062 0 0 1 0 4.124ZM7.119 20.452H3.555V8.999h3.564v11.453ZM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.225 0Z",
|
|
16
|
+
bluesky: "M12 10.8C10.5 7.9 6.42 2.5 2.62 2.5 1.1 2.5.5 3.4.5 4.6c0 1.1.6 6.4 1 7.4.7 1.7 3.1 2.3 5.8 1.9-4.4.7-8.1 2.4-5.6 5.3 2.6 3 6.9-.7 10.3-3.6 3.4 2.9 7.7 6.6 10.3 3.6 2.5-2.9-1.2-4.6-5.6-5.3 2.7.4 5.1-.2 5.8-1.9.4-1 1-6.3 1-7.4 0-1.2-.6-2.1-2.12-2.1-3.8 0-7.88 5.4-9.38 8.3Z",
|
|
17
|
+
x: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.657l-5.214-6.817-5.965 6.817H1.681l7.73-8.835L1.255 2.25h6.826l4.713 6.231 5.45-6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77Z",
|
|
18
|
+
mail: "M2.5 4.5h19a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-13a1 1 0 0 1 1-1Zm0 1.8v.35l9.5 6.1 9.5-6.1V6.3L12.54 12.4a1 1 0 0 1-1.08 0L2.5 6.3Zm19 11.4V8.8l-8.54 5.48a1.8 1.8 0 0 1-1.92 0L2.5 8.8v8.9h19Z"
|
|
19
|
+
};
|
|
20
|
+
var SocialIcon = forwardRef(
|
|
21
|
+
function SocialIcon2({ name, accent = "primary", label, href, target, rel, className, ...svgProps }, ref) {
|
|
22
|
+
const accessibleName = label ?? svgProps["aria-label"] ?? LABELS[name];
|
|
23
|
+
const svg = /* @__PURE__ */ jsx(
|
|
24
|
+
"svg",
|
|
25
|
+
{
|
|
26
|
+
...svgProps,
|
|
27
|
+
ref: href ? void 0 : ref,
|
|
28
|
+
"data-slot": "social-icon",
|
|
29
|
+
viewBox: "0 0 24 24",
|
|
30
|
+
fill: "currentColor",
|
|
31
|
+
"aria-hidden": href ? true : void 0,
|
|
32
|
+
"aria-label": href ? void 0 : accessibleName,
|
|
33
|
+
role: href ? void 0 : "img",
|
|
34
|
+
className: cn("inline-block size-5 shrink-0", accentTextClass(accent), className),
|
|
35
|
+
children: /* @__PURE__ */ jsx("path", { d: PATHS[name] })
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
if (!href) return svg;
|
|
39
|
+
return /* @__PURE__ */ jsx(
|
|
40
|
+
"a",
|
|
41
|
+
{
|
|
42
|
+
ref,
|
|
43
|
+
href,
|
|
44
|
+
target,
|
|
45
|
+
rel: rel ?? (target === "_blank" ? "noopener noreferrer" : void 0),
|
|
46
|
+
"aria-label": accessibleName,
|
|
47
|
+
className: "inline-flex items-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-primary",
|
|
48
|
+
children: svg
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
SocialIcon.displayName = "SocialIcon";
|
|
54
|
+
function GitHubIcon(props) {
|
|
55
|
+
return /* @__PURE__ */ jsx(SocialIcon, { name: "github", ...props });
|
|
56
|
+
}
|
|
57
|
+
function LinkedInIcon(props) {
|
|
58
|
+
return /* @__PURE__ */ jsx(SocialIcon, { name: "linkedin", ...props });
|
|
59
|
+
}
|
|
60
|
+
function BlueskyIcon(props) {
|
|
61
|
+
return /* @__PURE__ */ jsx(SocialIcon, { name: "bluesky", ...props });
|
|
62
|
+
}
|
|
63
|
+
function XIcon(props) {
|
|
64
|
+
return /* @__PURE__ */ jsx(SocialIcon, { name: "x", ...props });
|
|
65
|
+
}
|
|
66
|
+
function MailIcon(props) {
|
|
67
|
+
return /* @__PURE__ */ jsx(SocialIcon, { name: "mail", ...props });
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
BlueskyIcon,
|
|
71
|
+
GitHubIcon,
|
|
72
|
+
LinkedInIcon,
|
|
73
|
+
MailIcon,
|
|
74
|
+
SocialIcon,
|
|
75
|
+
XIcon
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=SocialIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/SocialIcon.tsx"],"sourcesContent":["import { forwardRef, type AnchorHTMLAttributes, type Ref, type SVGProps } from 'react';\nimport type { AccentToken } from '../lib/theme';\nimport { accentTextClass } from '../lib/accentClasses';\nimport { cn } from '../lib/recipe';\n\n/** Brand names supported by {@link SocialIcon}. */\nexport type SocialIconName = 'github' | 'linkedin' | 'bluesky' | 'x' | 'mail';\n\nconst LABELS: Record<SocialIconName, string> = {\n github: 'GitHub',\n linkedin: 'LinkedIn',\n bluesky: 'Bluesky',\n x: 'X',\n mail: 'Email',\n};\n\n/**\n * Paths are kept here rather than pulled from a second icon package. Social\n * marks are rendering primitives, and the design system owns their accessible\n * name, current-colour treatment, and link behaviour.\n */\nconst PATHS: Record<SocialIconName, string> = {\n github:\n 'M12 2C6.477 2 2 6.477 2 12c0 4.419 2.865 8.167 6.839 9.49.5.092.682-.217.682-.482 0-.237-.009-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.091-.647.35-1.087.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.682-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.564 9.564 0 0 1 12 6.844a9.58 9.58 0 0 1 2.504.337c1.909-1.294 2.748-1.025 2.748-1.025.546 1.377.202 2.394.1 2.647.64.698 1.028 1.591 1.028 2.682 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.744 0 .267.18.578.688.48A10.003 10.003 0 0 0 22 12c0-5.523-4.477-10-10-10Z',\n linkedin:\n 'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.35V8.999h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.602 0 4.267 2.37 4.267 5.455v6.287ZM5.337 7.433a2.062 2.062 0 1 1 0-4.124 2.062 2.062 0 0 1 0 4.124ZM7.119 20.452H3.555V8.999h3.564v11.453ZM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.225 0Z',\n bluesky:\n 'M12 10.8C10.5 7.9 6.42 2.5 2.62 2.5 1.1 2.5.5 3.4.5 4.6c0 1.1.6 6.4 1 7.4.7 1.7 3.1 2.3 5.8 1.9-4.4.7-8.1 2.4-5.6 5.3 2.6 3 6.9-.7 10.3-3.6 3.4 2.9 7.7 6.6 10.3 3.6 2.5-2.9-1.2-4.6-5.6-5.3 2.7.4 5.1-.2 5.8-1.9.4-1 1-6.3 1-7.4 0-1.2-.6-2.1-2.12-2.1-3.8 0-7.88 5.4-9.38 8.3Z',\n x: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.657l-5.214-6.817-5.965 6.817H1.681l7.73-8.835L1.255 2.25h6.826l4.713 6.231 5.45-6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77Z',\n mail: 'M2.5 4.5h19a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-13a1 1 0 0 1 1-1Zm0 1.8v.35l9.5 6.1 9.5-6.1V6.3L12.54 12.4a1 1 0 0 1-1.08 0L2.5 6.3Zm19 11.4V8.8l-8.54 5.48a1.8 1.8 0 0 1-1.92 0L2.5 8.8v8.9h19Z',\n};\n\nexport interface SocialIconProps extends Omit<SVGProps<SVGSVGElement>, 'color'> {\n /** Brand mark to render. */\n name: SocialIconName;\n /** Semantic role used for the current-colour mark. */\n accent?: AccentToken;\n /** Accessible name. Defaults to the brand name. */\n label?: string;\n /** Optional destination. When set, the icon is wrapped in an anchor. */\n href?: string;\n /** Link target. `_blank` automatically receives a safe `rel` value. */\n target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];\n /** Explicit link relationship, overriding the `_blank` default. */\n rel?: string;\n}\n\n/**\n * Accessible brand SVG for estate social and contact links.\n *\n * Without `href` this is a labelled image. With `href` it becomes a labelled\n * link and the SVG is hidden from assistive technology to avoid two names.\n * All marks use semantic accent roles and `currentColor`, so both theme levels\n * resolve through the design-system token ladder.\n */\nexport const SocialIcon = forwardRef<SVGSVGElement | HTMLAnchorElement, SocialIconProps>(\n function SocialIcon(\n { name, accent = 'primary', label, href, target, rel, className, ...svgProps },\n ref,\n ) {\n const accessibleName = label ?? svgProps['aria-label'] ?? LABELS[name];\n const svg = (\n <svg\n {...svgProps}\n // The ref follows the rendered root only: inside an anchor the SVG is\n // decoration, and a callback ref fired on both would run element\n // setup/cleanup logic against the nested mark first.\n ref={href ? undefined : (ref as Ref<SVGSVGElement>)}\n data-slot=\"social-icon\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden={href ? true : undefined}\n aria-label={href ? undefined : accessibleName}\n role={href ? undefined : 'img'}\n className={cn('inline-block size-5 shrink-0', accentTextClass(accent), className)}\n >\n <path d={PATHS[name]} />\n </svg>\n );\n\n if (!href) return svg;\n\n return (\n <a\n ref={ref as Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined)}\n aria-label={accessibleName}\n className=\"inline-flex items-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-primary\"\n >\n {svg}\n </a>\n );\n },\n);\n\nSocialIcon.displayName = 'SocialIcon';\n\ntype NamedSocialIconProps = Omit<SocialIconProps, 'name'>;\n\n/** GitHub brand mark. */\nexport function GitHubIcon(props: NamedSocialIconProps) {\n return <SocialIcon name=\"github\" {...props} />;\n}\n\n/** LinkedIn brand mark. */\nexport function LinkedInIcon(props: NamedSocialIconProps) {\n return <SocialIcon name=\"linkedin\" {...props} />;\n}\n\n/** Bluesky brand mark. */\nexport function BlueskyIcon(props: NamedSocialIconProps) {\n return <SocialIcon name=\"bluesky\" {...props} />;\n}\n\n/** X brand mark. */\nexport function XIcon(props: NamedSocialIconProps) {\n return <SocialIcon name=\"x\" {...props} />;\n}\n\n/** Email envelope mark. */\nexport function MailIcon(props: NamedSocialIconProps) {\n return <SocialIcon name=\"mail\" {...props} />;\n}\n"],"mappings":";AAAA,SAAS,kBAAsE;AAE/E,SAAS,uBAAuB;AAChC,SAAS,UAAU;AAyEX;AApER,IAAM,SAAyC;AAAA,EAC7C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,GAAG;AAAA,EACH,MAAM;AACR;AAOA,IAAM,QAAwC;AAAA,EAC5C,QACE;AAAA,EACF,UACE;AAAA,EACF,SACE;AAAA,EACF,GAAG;AAAA,EACH,MAAM;AACR;AAyBO,IAAM,aAAa;AAAA,EACxB,SAASA,YACP,EAAE,MAAM,SAAS,WAAW,OAAO,MAAM,QAAQ,KAAK,WAAW,GAAG,SAAS,GAC7E,KACA;AACA,UAAM,iBAAiB,SAAS,SAAS,YAAY,KAAK,OAAO,IAAI;AACrE,UAAM,MACJ;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QAIJ,KAAK,OAAO,SAAa;AAAA,QACzB,aAAU;AAAA,QACV,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,eAAa,OAAO,OAAO;AAAA,QAC3B,cAAY,OAAO,SAAY;AAAA,QAC/B,MAAM,OAAO,SAAY;AAAA,QACzB,WAAW,GAAG,gCAAgC,gBAAgB,MAAM,GAAG,SAAS;AAAA,QAEhF,8BAAC,UAAK,GAAG,MAAM,IAAI,GAAG;AAAA;AAAA,IACxB;AAGF,QAAI,CAAC,KAAM,QAAO;AAElB,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,QAAQ,WAAW,WAAW,wBAAwB;AAAA,QAC3D,cAAY;AAAA,QACZ,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,EAEJ;AACF;AAEA,WAAW,cAAc;AAKlB,SAAS,WAAW,OAA6B;AACtD,SAAO,oBAAC,cAAW,MAAK,UAAU,GAAG,OAAO;AAC9C;AAGO,SAAS,aAAa,OAA6B;AACxD,SAAO,oBAAC,cAAW,MAAK,YAAY,GAAG,OAAO;AAChD;AAGO,SAAS,YAAY,OAA6B;AACvD,SAAO,oBAAC,cAAW,MAAK,WAAW,GAAG,OAAO;AAC/C;AAGO,SAAS,MAAM,OAA6B;AACjD,SAAO,oBAAC,cAAW,MAAK,KAAK,GAAG,OAAO;AACzC;AAGO,SAAS,SAAS,OAA6B;AACpD,SAAO,oBAAC,cAAW,MAAK,QAAQ,GAAG,OAAO;AAC5C;","names":["SocialIcon"]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/Sparkline.tsx
|
|
4
|
+
import { AreaClosed, LinePath } from "@visx/shape";
|
|
5
|
+
import { scaleLinear } from "@visx/scale";
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
import { cn } from "../lib/recipe.js";
|
|
8
|
+
import { accentVar } from "../lib/theme.js";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var Sparkline = forwardRef(function Sparkline2({
|
|
11
|
+
data,
|
|
12
|
+
width = 120,
|
|
13
|
+
height = 32,
|
|
14
|
+
accent = "primary",
|
|
15
|
+
showArea = true,
|
|
16
|
+
ariaLabel,
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}, ref) {
|
|
20
|
+
const strokeColor = accentVar(accent);
|
|
21
|
+
if (!data || data.length === 0) {
|
|
22
|
+
return /* @__PURE__ */ jsx(
|
|
23
|
+
"svg",
|
|
24
|
+
{
|
|
25
|
+
ref,
|
|
26
|
+
"data-slot": "sparkline",
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
30
|
+
role: "img",
|
|
31
|
+
"aria-label": ariaLabel ?? "Empty sparkline",
|
|
32
|
+
className: cn("inline-block overflow-visible", className),
|
|
33
|
+
...props,
|
|
34
|
+
children: /* @__PURE__ */ jsx(
|
|
35
|
+
"line",
|
|
36
|
+
{
|
|
37
|
+
x1: 0,
|
|
38
|
+
y1: height / 2,
|
|
39
|
+
x2: width,
|
|
40
|
+
y2: height / 2,
|
|
41
|
+
stroke: "var(--ds-border-subtle)",
|
|
42
|
+
strokeWidth: 1,
|
|
43
|
+
strokeDasharray: "2 2"
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const minVal = Math.min(...data);
|
|
50
|
+
const maxVal = Math.max(...data);
|
|
51
|
+
const padding = 2;
|
|
52
|
+
const xScale = scaleLinear({
|
|
53
|
+
domain: [0, Math.max(1, data.length - 1)],
|
|
54
|
+
range: [padding, width - padding]
|
|
55
|
+
});
|
|
56
|
+
const yScale = scaleLinear({
|
|
57
|
+
domain: minVal === maxVal ? [minVal - 1, maxVal + 1] : [minVal, maxVal],
|
|
58
|
+
range: [height - padding, padding]
|
|
59
|
+
});
|
|
60
|
+
return /* @__PURE__ */ jsxs(
|
|
61
|
+
"svg",
|
|
62
|
+
{
|
|
63
|
+
ref,
|
|
64
|
+
"data-slot": "sparkline",
|
|
65
|
+
width,
|
|
66
|
+
height,
|
|
67
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
68
|
+
role: "img",
|
|
69
|
+
"aria-label": ariaLabel ?? `Trend from ${data[0]} to ${data[data.length - 1]}`,
|
|
70
|
+
className: cn("inline-block overflow-visible", className),
|
|
71
|
+
...props,
|
|
72
|
+
children: [
|
|
73
|
+
showArea && data.length > 1 && /* @__PURE__ */ jsx(
|
|
74
|
+
AreaClosed,
|
|
75
|
+
{
|
|
76
|
+
data,
|
|
77
|
+
x: (_, i) => xScale(i) ?? 0,
|
|
78
|
+
y: (d) => yScale(d) ?? 0,
|
|
79
|
+
yScale,
|
|
80
|
+
fill: strokeColor,
|
|
81
|
+
fillOpacity: 0.12
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
LinePath,
|
|
86
|
+
{
|
|
87
|
+
data,
|
|
88
|
+
x: (_, i) => xScale(i) ?? 0,
|
|
89
|
+
y: (d) => yScale(d) ?? 0,
|
|
90
|
+
stroke: strokeColor,
|
|
91
|
+
strokeWidth: 2,
|
|
92
|
+
strokeLinecap: "square",
|
|
93
|
+
strokeLinejoin: "miter"
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
Sparkline.displayName = "Sparkline";
|
|
101
|
+
export {
|
|
102
|
+
Sparkline
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=Sparkline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Sparkline.tsx"],"sourcesContent":["'use client';\n\nimport { AreaClosed, LinePath } from '@visx/shape';\nimport { scaleLinear } from '@visx/scale';\nimport { forwardRef, type SVGProps } from 'react';\nimport { cn } from '../lib/recipe';\nimport { type AccentToken, accentVar } from '../lib/theme';\n\nexport interface SparklineProps extends Omit<SVGProps<SVGSVGElement>, 'data'> {\n /** Ordered series of numerical values to plot across the trend line. */\n data: number[];\n /** Display width in pixels or coordinate units (default 120). */\n width?: number;\n /** Display height in pixels or coordinate units (default 32). */\n height?: number;\n /** Role accent controlling line stroke and area wash (default 'primary'). */\n accent?: AccentToken;\n /** Whether to render a semi-transparent area fill beneath the trend line (default true). */\n showArea?: boolean;\n /** Screen reader accessibility label. */\n ariaLabel?: string;\n}\n\n/**\n * Compact brutalist metric trend indicator.\n *\n * Renders an inline, unopinionated linear SVG trend line with an optional area wash.\n * Engineered for data density inside StatCards, table cells, and summary badges\n * without axis decoration or DOM layout overhead.\n */\nexport const Sparkline = forwardRef<SVGSVGElement, SparklineProps>(function Sparkline(\n {\n data,\n width = 120,\n height = 32,\n accent = 'primary',\n showArea = true,\n ariaLabel,\n className,\n ...props\n },\n ref,\n) {\n const strokeColor = accentVar(accent);\n\n if (!data || data.length === 0) {\n return (\n <svg\n ref={ref}\n data-slot=\"sparkline\"\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label={ariaLabel ?? 'Empty sparkline'}\n className={cn('inline-block overflow-visible', className)}\n {...props}\n >\n <line\n x1={0}\n y1={height / 2}\n x2={width}\n y2={height / 2}\n stroke=\"var(--ds-border-subtle)\"\n strokeWidth={1}\n strokeDasharray=\"2 2\"\n />\n </svg>\n );\n }\n\n const minVal = Math.min(...data);\n const maxVal = Math.max(...data);\n const padding = 2;\n\n const xScale = scaleLinear<number>({\n domain: [0, Math.max(1, data.length - 1)],\n range: [padding, width - padding],\n });\n\n const yScale = scaleLinear<number>({\n domain: minVal === maxVal ? [minVal - 1, maxVal + 1] : [minVal, maxVal],\n range: [height - padding, padding],\n });\n\n return (\n <svg\n ref={ref}\n data-slot=\"sparkline\"\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label={ariaLabel ?? `Trend from ${data[0]} to ${data[data.length - 1]}`}\n className={cn('inline-block overflow-visible', className)}\n {...props}\n >\n {showArea && data.length > 1 && (\n <AreaClosed\n data={data}\n x={(_, i) => xScale(i) ?? 0}\n y={(d) => yScale(d) ?? 0}\n yScale={yScale}\n fill={strokeColor}\n fillOpacity={0.12}\n />\n )}\n\n <LinePath\n data={data}\n x={(_, i) => xScale(i) ?? 0}\n y={(d) => yScale(d) ?? 0}\n stroke={strokeColor}\n strokeWidth={2}\n strokeLinecap=\"square\"\n strokeLinejoin=\"miter\"\n />\n </svg>\n );\n});\n\nSparkline.displayName = 'Sparkline';\n"],"mappings":";;;AAEA,SAAS,YAAY,gBAAgB;AACrC,SAAS,mBAAmB;AAC5B,SAAS,kBAAiC;AAC1C,SAAS,UAAU;AACnB,SAA2B,iBAAiB;AAoDpC,cA4BJ,YA5BI;AA5BD,IAAM,YAAY,WAA0C,SAASA,WAC1E;AAAA,EACE;AAAA,EACA,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,cAAc,UAAU,MAAM;AAEpC,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA,SAAS,OAAO,KAAK,IAAI,MAAM;AAAA,QAC/B,MAAK;AAAA,QACL,cAAY,aAAa;AAAA,QACzB,WAAW,GAAG,iCAAiC,SAAS;AAAA,QACvD,GAAG;AAAA,QAEJ;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,IAAI,SAAS;AAAA,YACb,IAAI;AAAA,YACJ,IAAI,SAAS;AAAA,YACb,QAAO;AAAA,YACP,aAAa;AAAA,YACb,iBAAgB;AAAA;AAAA,QAClB;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,QAAM,SAAS,KAAK,IAAI,GAAG,IAAI;AAC/B,QAAM,SAAS,KAAK,IAAI,GAAG,IAAI;AAC/B,QAAM,UAAU;AAEhB,QAAM,SAAS,YAAoB;AAAA,IACjC,QAAQ,CAAC,GAAG,KAAK,IAAI,GAAG,KAAK,SAAS,CAAC,CAAC;AAAA,IACxC,OAAO,CAAC,SAAS,QAAQ,OAAO;AAAA,EAClC,CAAC;AAED,QAAM,SAAS,YAAoB;AAAA,IACjC,QAAQ,WAAW,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,MAAM;AAAA,IACtE,OAAO,CAAC,SAAS,SAAS,OAAO;AAAA,EACnC,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,SAAS,OAAO,KAAK,IAAI,MAAM;AAAA,MAC/B,MAAK;AAAA,MACL,cAAY,aAAa,cAAc,KAAK,CAAC,CAAC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC;AAAA,MAC1E,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA,MAEH;AAAA,oBAAY,KAAK,SAAS,KACzB;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,KAAK;AAAA,YAC1B,GAAG,CAAC,MAAM,OAAO,CAAC,KAAK;AAAA,YACvB;AAAA,YACA,MAAM;AAAA,YACN,aAAa;AAAA;AAAA,QACf;AAAA,QAGF;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,KAAK;AAAA,YAC1B,GAAG,CAAC,MAAM,OAAO,CAAC,KAAK;AAAA,YACvB,QAAQ;AAAA,YACR,aAAa;AAAA,YACb,eAAc;AAAA,YACd,gBAAe;AAAA;AAAA,QACjB;AAAA;AAAA;AAAA,EACF;AAEJ,CAAC;AAED,UAAU,cAAc;","names":["Sparkline"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/components/Spinner.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { recipe } from "../lib/recipe.js";
|
|
4
|
+
import { accentTextClass } from "../lib/accentClasses.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
var spinner = recipe({
|
|
7
|
+
slots: {
|
|
8
|
+
root: "inline-flex items-center justify-center align-middle",
|
|
9
|
+
/*
|
|
10
|
+
* `motion-reduce:animate-ds-spin-slow`, not `motion-reduce:animate-none`.
|
|
11
|
+
*
|
|
12
|
+
* A spinner that has stopped is indistinguishable from a page that has
|
|
13
|
+
* hung, and `prefers-reduced-motion` asks for less vestibular load, not for
|
|
14
|
+
* less information. A rotation this small translates nothing across the
|
|
15
|
+
* viewport; what the preference gets is a third of the angular speed.
|
|
16
|
+
* `Skeleton` is the component where the preference removes the motion
|
|
17
|
+
* outright, because there the motion carries nothing.
|
|
18
|
+
*/
|
|
19
|
+
mark: "block animate-ds-spin border-edge-subtle border-t-current motion-reduce:animate-ds-spin-slow"
|
|
20
|
+
},
|
|
21
|
+
variants: {
|
|
22
|
+
/** Stroke scales with the box: a 4px border on a 12px square is a blob. */
|
|
23
|
+
size: {
|
|
24
|
+
sm: { mark: "size-3 border-2" },
|
|
25
|
+
md: { mark: "size-5 border-2" },
|
|
26
|
+
lg: { mark: "size-8 border-4" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
size: "md"
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
var Spinner = forwardRef(function Spinner2({ label = "Loading", size = "md", accent = "primary", className, ...props }, ref) {
|
|
34
|
+
const styles = spinner({ size });
|
|
35
|
+
return /* @__PURE__ */ jsxs(
|
|
36
|
+
"span",
|
|
37
|
+
{
|
|
38
|
+
ref,
|
|
39
|
+
role: "status",
|
|
40
|
+
"data-slot": "spinner",
|
|
41
|
+
className: styles.root({ class: className }),
|
|
42
|
+
...props,
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
"span",
|
|
46
|
+
{
|
|
47
|
+
"aria-hidden": "true",
|
|
48
|
+
"data-slot": "spinner-mark",
|
|
49
|
+
className: styles.mark({
|
|
50
|
+
// `current` is for a spinner on a fill (a pending Button): `edge-subtle`
|
|
51
|
+
// is as light as the leading edge there and the mark reads as one flat
|
|
52
|
+
// ring, so the resting edge is the text colour at low alpha instead.
|
|
53
|
+
class: accent === "current" ? "border-current/30 border-t-current" : accentTextClass(accent)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
export {
|
|
63
|
+
Spinner
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=Spinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Spinner.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type { HTMLAttributes } from 'react';\nimport { recipe } from '../lib/recipe';\nimport { accentTextClass } from '../lib/accentClasses';\nimport type { AccentToken } from '../lib/theme';\n\n/**\n * A square that turns. No radius, no arc, no gradient — the mark is the border\n * of a box with one edge picked out in the accent, which is the same treatment\n * `Card` and `Button` give an edge and the only rotation in the package.\n *\n * `border-t-current` rather than a second accent map: the accent is already on\n * the element as a colour, so the leading edge inherits it and the eight roles\n * stay one table in `accentClasses`. `border-edge-subtle` is the rest of the\n * box, so the mark reads as a ring at rest rather than as three missing sides.\n */\nconst spinner = recipe({\n slots: {\n root: 'inline-flex items-center justify-center align-middle',\n /*\n * `motion-reduce:animate-ds-spin-slow`, not `motion-reduce:animate-none`.\n *\n * A spinner that has stopped is indistinguishable from a page that has\n * hung, and `prefers-reduced-motion` asks for less vestibular load, not for\n * less information. A rotation this small translates nothing across the\n * viewport; what the preference gets is a third of the angular speed.\n * `Skeleton` is the component where the preference removes the motion\n * outright, because there the motion carries nothing.\n */\n mark: 'block animate-ds-spin border-edge-subtle border-t-current motion-reduce:animate-ds-spin-slow',\n },\n variants: {\n /** Stroke scales with the box: a 4px border on a 12px square is a blob. */\n size: {\n sm: { mark: 'size-3 border-2' },\n md: { mark: 'size-5 border-2' },\n lg: { mark: 'size-8 border-4' },\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\nexport interface SpinnerProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'className'> {\n /**\n * What is being waited for, as a screen reader hears it — \"Loading results\",\n * not \"Loading\". It is rendered for assistive technology only, so it may be\n * longer than anything that would fit beside the mark, and it is the whole\n * reason this component exists rather than a rotating `div`.\n */\n label?: string;\n /**\n * `sm` sits inside a control's line box, `md` beside a sentence, `lg` alone\n * in an empty panel. Anything larger is a `Skeleton`: past a point the honest\n * thing to show is the shape of the content, not that work is happening.\n */\n size?: 'sm' | 'md' | 'lg';\n /**\n * Semantic colour for the leading edge. Accepts an `Emphasis`\n * (`primary`…`quiet`) or an `Intent` (`info`/`success`/`warning`/`danger`).\n *\n * `current` takes the ink of whatever the spinner sits in, for a spinner\n * inside a control: on a filled `Button` every accent is either the fill\n * itself or a clash with it, and the button's own text colour is the one\n * already measured against that fill.\n */\n accent?: AccentToken | 'current';\n /**\n * Merged onto the wrapper, which is what positions the spinner. The mark\n * itself is sized by `size`.\n */\n className?: string;\n}\n\n/**\n * Indeterminate activity in a small space — a control that is working, a panel\n * that is fetching, a form mid-submit.\n *\n * Reach for it when the wait is short and its length is unknown. When the shape\n * of what is arriving is known, `Skeleton` says more; when the fraction done is\n * known, `Progress` says more still. A spinner is the weakest of the three\n * claims: *something is happening*.\n *\n * ## It announces itself\n *\n * The wrapper is `role=\"status\"` and carries a visually hidden `label`, so the\n * wait is spoken rather than merely drawn. A spinner announced as nothing is a\n * spinner a screen reader user sits through in silence, and `role=\"status\"` is\n * a polite live region — it interrupts nothing, and it is read when the region\n * appears. That is why the label lives in the DOM as text rather than as an\n * `aria-label` on an empty element: text in a live region is announced on\n * insertion, and an attribute on an empty node is not reliably.\n *\n * The mark itself is `aria-hidden`. It is the same fact twice otherwise, once\n * as a name and once as an unlabelled box.\n */\nexport const Spinner = forwardRef<HTMLSpanElement, SpinnerProps>(function Spinner(\n { label = 'Loading', size = 'md', accent = 'primary', className, ...props },\n ref,\n) {\n const styles = spinner({ size });\n\n return (\n <span\n ref={ref}\n // `role=\"status\"` rather than `role=\"progressbar\"`: nothing here has a\n // value, and a progressbar with no `aria-valuenow` is a control that\n // reports an unknown position rather than a region that reports activity.\n role=\"status\"\n data-slot=\"spinner\"\n className={styles.root({ class: className })}\n {...props}\n >\n <span\n aria-hidden=\"true\"\n data-slot=\"spinner-mark\"\n // The accent is a *text* colour so the leading edge can be\n // `border-t-current`. One map, eight roles, no second table to drift.\n // Through the recipe's `class` slot rather than appended, so a caller\n // who reaches the mark through `className` still wins the conflict.\n className={styles.mark({\n // `current` is for a spinner on a fill (a pending Button): `edge-subtle`\n // is as light as the leading edge there and the mark reads as one flat\n // ring, so the resting edge is the text colour at low alpha instead.\n class: accent === 'current' ? 'border-current/30 border-t-current' : accentTextClass(accent),\n })}\n />\n <span className=\"sr-only\">{label}</span>\n </span>\n );\n});\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,SAAS,cAAc;AACvB,SAAS,uBAAuB;AAqG5B,SAUE,KAVF;AAxFJ,IAAM,UAAU,OAAO;AAAA,EACrB,OAAO;AAAA,IACL,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWN,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA;AAAA,IAER,MAAM;AAAA,MACJ,IAAI,EAAE,MAAM,kBAAkB;AAAA,MAC9B,IAAI,EAAE,MAAM,kBAAkB;AAAA,MAC9B,IAAI,EAAE,MAAM,kBAAkB;AAAA,IAChC;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA,IACf,MAAM;AAAA,EACR;AACF,CAAC;AAuDM,IAAM,UAAU,WAA0C,SAASA,SACxE,EAAE,QAAQ,WAAW,OAAO,MAAM,SAAS,WAAW,WAAW,GAAG,MAAM,GAC1E,KACA;AACA,QAAM,SAAS,QAAQ,EAAE,KAAK,CAAC;AAE/B,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MAIA,MAAK;AAAA,MACL,aAAU;AAAA,MACV,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,MAC1C,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,aAAU;AAAA,YAKV,WAAW,OAAO,KAAK;AAAA;AAAA;AAAA;AAAA,cAIrB,OAAO,WAAW,YAAY,uCAAuC,gBAAgB,MAAM;AAAA,YAC7F,CAAC;AAAA;AAAA,QACH;AAAA,QACA,oBAAC,UAAK,WAAU,WAAW,iBAAM;AAAA;AAAA;AAAA,EACnC;AAEJ,CAAC;","names":["Spinner"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/components/StatCard.tsx
|
|
2
|
+
import { accentHoverEdgeClass, accentTextClass } from "../lib/accentClasses.js";
|
|
3
|
+
import { cn } from "../lib/recipe.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var CHANGE_CLASS = {
|
|
6
|
+
positive: "text-intent-success",
|
|
7
|
+
negative: "text-intent-danger",
|
|
8
|
+
neutral: "text-content-muted"
|
|
9
|
+
};
|
|
10
|
+
function StatCard({
|
|
11
|
+
title,
|
|
12
|
+
value,
|
|
13
|
+
change,
|
|
14
|
+
changeType = "positive",
|
|
15
|
+
subtitle,
|
|
16
|
+
icon: Icon,
|
|
17
|
+
accent = "primary",
|
|
18
|
+
className
|
|
19
|
+
}) {
|
|
20
|
+
return /* @__PURE__ */ jsxs(
|
|
21
|
+
"div",
|
|
22
|
+
{
|
|
23
|
+
"data-slot": "stat-card",
|
|
24
|
+
className: cn(
|
|
25
|
+
"bg-surface-raised border-2 border-edge-strong p-6 transition-all duration-200",
|
|
26
|
+
accentHoverEdgeClass(accent),
|
|
27
|
+
className
|
|
28
|
+
),
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "statcard-header", className: "flex justify-between items-start mb-2", children: [
|
|
31
|
+
/* @__PURE__ */ jsxs("span", { "data-slot": "statcard-label", className: "font-mono text-xs font-bold uppercase tracking-wider text-content-muted", children: [
|
|
32
|
+
"[ ",
|
|
33
|
+
title,
|
|
34
|
+
" ]"
|
|
35
|
+
] }),
|
|
36
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: cn("h-6 w-6", accentTextClass(accent)) })
|
|
37
|
+
] }),
|
|
38
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "statcard-value-row", className: "flex items-baseline gap-3 my-1", children: [
|
|
39
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "statcard-value", className: "font-display text-4xl font-extrabold text-content-primary tracking-tight", children: value }),
|
|
40
|
+
change && /* @__PURE__ */ jsx("span", { "data-slot": "statcard-change", className: cn("font-mono text-xs font-bold", CHANGE_CLASS[changeType]), children: change })
|
|
41
|
+
] }),
|
|
42
|
+
subtitle && /* @__PURE__ */ jsxs("p", { "data-slot": "statcard-detail", className: "font-mono text-xs text-content-muted mt-2", children: [
|
|
43
|
+
"> ",
|
|
44
|
+
subtitle
|
|
45
|
+
] })
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
StatCard
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=StatCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/StatCard.tsx"],"sourcesContent":["import type { ElementType } from 'react';\nimport { accentHoverEdgeClass, accentTextClass } from '../lib/accentClasses';\nimport { cn } from '../lib/recipe';\nimport type { AccentToken } from '../lib/theme';\n\n/**\n * Accepted accents. Widened from the four palette names to the full role set;\n * the old names still resolve to the same roles, so existing call sites keep\n * rendering identically.\n */\nexport type StatCardAccent = AccentToken;\n\nexport interface StatCardProps {\n title: string;\n value: string | number;\n change?: string;\n changeType?: 'positive' | 'negative' | 'neutral';\n subtitle?: string;\n icon?: ElementType<{ className?: string }>;\n accent?: StatCardAccent;\n className?: string;\n}\n\n/**\n * A change is meaning, not decoration, so it reads from the intent roles rather\n * than the card's accent — a card accented `danger` still shows a rise in the\n * success colour.\n */\nconst CHANGE_CLASS: Record<NonNullable<StatCardProps['changeType']>, string> = {\n positive: 'text-intent-success',\n negative: 'text-intent-danger',\n neutral: 'text-content-muted',\n};\n\nexport function StatCard({\n title,\n value,\n change,\n changeType = 'positive',\n subtitle,\n icon: Icon,\n accent = 'primary',\n className,\n}: StatCardProps) {\n return (\n <div data-slot=\"stat-card\"\n className={cn(\n 'bg-surface-raised border-2 border-edge-strong p-6 transition-all duration-200',\n accentHoverEdgeClass(accent),\n className,\n )}\n >\n <div data-slot=\"statcard-header\" className=\"flex justify-between items-start mb-2\">\n <span data-slot=\"statcard-label\" className=\"font-mono text-xs font-bold uppercase tracking-wider text-content-muted\">\n [ {title} ]\n </span>\n {Icon && <Icon className={cn('h-6 w-6', accentTextClass(accent))} />}\n </div>\n <div data-slot=\"statcard-value-row\" className=\"flex items-baseline gap-3 my-1\">\n <span data-slot=\"statcard-value\" className=\"font-display text-4xl font-extrabold text-content-primary tracking-tight\">\n {value}\n </span>\n {change && (\n <span data-slot=\"statcard-change\" className={cn('font-mono text-xs font-bold', CHANGE_CLASS[changeType])}>\n {change}\n </span>\n )}\n </div>\n {subtitle && (\n <p data-slot=\"statcard-detail\" className=\"font-mono text-xs text-content-muted mt-2\">\n > {subtitle}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";AACA,SAAS,sBAAsB,uBAAuB;AACtD,SAAS,UAAU;AAmDX,SAGS,KAHT;AAzBR,IAAM,eAAyE;AAAA,EAC7E,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AACX;AAEO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA,MAAM;AAAA,EACN,SAAS;AAAA,EACT;AACF,GAAkB;AAChB,SACE;AAAA,IAAC;AAAA;AAAA,MAAI,aAAU;AAAA,MACb,WAAW;AAAA,QACT;AAAA,QACA,qBAAqB,MAAM;AAAA,QAC3B;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,SAAI,aAAU,mBAAkB,WAAU,yCACzC;AAAA,+BAAC,UAAK,aAAU,kBAAiB,WAAU,2EAA0E;AAAA;AAAA,YAChH;AAAA,YAAM;AAAA,aACX;AAAA,UACC,QAAQ,oBAAC,QAAK,WAAW,GAAG,WAAW,gBAAgB,MAAM,CAAC,GAAG;AAAA,WACpE;AAAA,QACA,qBAAC,SAAI,aAAU,sBAAqB,WAAU,kCAC5C;AAAA,8BAAC,UAAK,aAAU,kBAAiB,WAAU,4EACxC,iBACH;AAAA,UACC,UACC,oBAAC,UAAK,aAAU,mBAAkB,WAAW,GAAG,+BAA+B,aAAa,UAAU,CAAC,GACpG,kBACH;AAAA,WAEJ;AAAA,QACC,YACC,qBAAC,OAAE,aAAU,mBAAkB,WAAU,6CAA4C;AAAA;AAAA,UAC7E;AAAA,WACR;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/components/StatusPage.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { recipe } from "../lib/recipe.js";
|
|
4
|
+
import { Button } from "./Button.js";
|
|
5
|
+
import { EmptyState } from "./EmptyState.js";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
var statusPage = recipe({
|
|
8
|
+
slots: {
|
|
9
|
+
root: "flex w-full justify-center px-4 py-16 md:py-24",
|
|
10
|
+
state: "w-full max-w-2xl [&_[data-slot=empty-state-title]]:text-2xl md:[&_[data-slot=empty-state-title]]:text-3xl",
|
|
11
|
+
/**
|
|
12
|
+
* The status code: large, and in the muted role like `EmptyState`'s own
|
|
13
|
+
* glyph, because the title carries the status in words and this only
|
|
14
|
+
* repeats it. Large text is gated at 3:1, which muted clears on every Level.
|
|
15
|
+
*/
|
|
16
|
+
code: "font-display text-6xl font-black leading-none tracking-tight md:text-8xl"
|
|
17
|
+
},
|
|
18
|
+
variants: {
|
|
19
|
+
standalone: {
|
|
20
|
+
true: { root: "min-h-dvh items-center bg-surface-base text-content-primary" },
|
|
21
|
+
false: {}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: { standalone: false }
|
|
25
|
+
});
|
|
26
|
+
var StatusPage = forwardRef(function StatusPage2({ title, code, description, action, children, standalone = false, className, ...props }, ref) {
|
|
27
|
+
const styles = statusPage({ standalone });
|
|
28
|
+
const rootProps = {
|
|
29
|
+
"data-slot": "status-page",
|
|
30
|
+
...props,
|
|
31
|
+
className: styles.root({ class: className })
|
|
32
|
+
};
|
|
33
|
+
const state = /* @__PURE__ */ jsx(
|
|
34
|
+
EmptyState,
|
|
35
|
+
{
|
|
36
|
+
className: styles.state(),
|
|
37
|
+
headingLevel: 1,
|
|
38
|
+
icon: code ? /* @__PURE__ */ jsx("p", { "data-slot": "status-page-code", className: styles.code(), children: code }) : void 0,
|
|
39
|
+
title,
|
|
40
|
+
description,
|
|
41
|
+
action,
|
|
42
|
+
children
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
return standalone ? /* @__PURE__ */ jsx("main", { ref, ...rootProps, children: state }) : /* @__PURE__ */ jsx("div", { ref, ...rootProps, children: state });
|
|
46
|
+
});
|
|
47
|
+
function homeAction(homeHref, homeLabel) {
|
|
48
|
+
return /* @__PURE__ */ jsx(Button, { href: homeHref, variant: "primary", bracketed: true, children: homeLabel });
|
|
49
|
+
}
|
|
50
|
+
var NotFoundPage = forwardRef(function NotFoundPage2({
|
|
51
|
+
title = "Page not found",
|
|
52
|
+
code = "404",
|
|
53
|
+
description = "Nothing lives at this address. It may have moved, or the link that brought you here may be wrong.",
|
|
54
|
+
homeHref = "/",
|
|
55
|
+
homeLabel = "GO HOME",
|
|
56
|
+
action,
|
|
57
|
+
...props
|
|
58
|
+
}, ref) {
|
|
59
|
+
return /* @__PURE__ */ jsx(
|
|
60
|
+
StatusPage,
|
|
61
|
+
{
|
|
62
|
+
ref,
|
|
63
|
+
title,
|
|
64
|
+
code,
|
|
65
|
+
description,
|
|
66
|
+
action: action ?? homeAction(homeHref, homeLabel),
|
|
67
|
+
...props
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
var ServerErrorPage = forwardRef(function ServerErrorPage2({
|
|
72
|
+
title = "Something went wrong",
|
|
73
|
+
code = "500",
|
|
74
|
+
description = "The server hit an error it could not recover from. It is not something you did \u2014 try again in a moment.",
|
|
75
|
+
homeHref = "/",
|
|
76
|
+
homeLabel = "GO HOME",
|
|
77
|
+
action,
|
|
78
|
+
standalone = true,
|
|
79
|
+
...props
|
|
80
|
+
}, ref) {
|
|
81
|
+
return /* @__PURE__ */ jsx(
|
|
82
|
+
StatusPage,
|
|
83
|
+
{
|
|
84
|
+
ref,
|
|
85
|
+
title,
|
|
86
|
+
code,
|
|
87
|
+
description,
|
|
88
|
+
action: action ?? homeAction(homeHref, homeLabel),
|
|
89
|
+
standalone,
|
|
90
|
+
...props
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
export {
|
|
95
|
+
NotFoundPage,
|
|
96
|
+
ServerErrorPage,
|
|
97
|
+
StatusPage
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=StatusPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/StatusPage.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactNode, Ref } from 'react';\nimport { recipe } from '../lib/recipe';\nimport { Button } from './Button';\nimport { EmptyState } from './EmptyState';\n\n/**\n * The page frame around an `EmptyState`, and nothing more.\n *\n * `standalone` is the one real variant: inside the site chrome the page is a\n * region of the consumer's `<main>`; outside it — a 500 served because the\n * shell itself failed, a maintenance page from the edge — it has to be the\n * `<main>` and fill the viewport on its own ground.\n *\n * The title is resized through its `data-slot` rather than a second recipe,\n * because it is `EmptyState`'s title: one element, sized for a page here.\n */\nconst statusPage = recipe({\n slots: {\n root: 'flex w-full justify-center px-4 py-16 md:py-24',\n state:\n 'w-full max-w-2xl '\n + '[&_[data-slot=empty-state-title]]:text-2xl md:[&_[data-slot=empty-state-title]]:text-3xl',\n /**\n * The status code: large, and in the muted role like `EmptyState`'s own\n * glyph, because the title carries the status in words and this only\n * repeats it. Large text is gated at 3:1, which muted clears on every Level.\n */\n code: 'font-display text-6xl font-black leading-none tracking-tight md:text-8xl',\n },\n variants: {\n standalone: {\n true: { root: 'min-h-dvh items-center bg-surface-base text-content-primary' },\n false: {},\n },\n },\n defaultVariants: { standalone: false },\n});\n\nexport interface StatusPageProps\n extends Omit<HTMLAttributes<HTMLElement>, 'title' | 'children' | 'className'> {\n /**\n * The page's `<h1>` — the status, in words: \"Page not found\", \"Down for\n * maintenance\". This is what a screen reader and a search result read, so it\n * must say the state on its own, without the `code` beside it.\n */\n title: string;\n /**\n * A short code shown large above the title — `404`, `503`. Rendered as text,\n * so it is read out before the heading. A decorative glyph can go here too;\n * give that node `aria-hidden` so it is not read as a word.\n */\n code?: ReactNode;\n /** What happened, and whether it is the reader's to fix. */\n description?: ReactNode;\n /**\n * The route back to somewhere useful — normally a `Button` with an `href`.\n * Links rather than handlers, so the way out works when the page is served\n * without its JavaScript.\n */\n action?: ReactNode;\n /** Anything else: a reference id to quote to support, a status-page link. */\n children?: ReactNode;\n /**\n * Render as the page's `<main>`, filling the viewport on `bg-surface-base`,\n * for a page served without the site chrome. Off by default, when the page is\n * placed inside the consumer's own `<main>` between `SiteHeader` and\n * `SiteFooter` and renders a plain `<div>`.\n */\n standalone?: boolean;\n /** Merged into the root's classes. */\n className?: string;\n}\n\n/**\n * A whole-page system state — not found, server error, maintenance, offline,\n * unauthorized — composed from `EmptyState`.\n *\n * It adds three things to the empty state and no more: the title becomes the\n * page's single `<h1>`, an optional status `code` sits above it, and the frame\n * either sits inside the site chrome or, with `standalone`, stands in for it.\n * `NotFoundPage` and `ServerErrorPage` are this with their copy filled in;\n * maintenance, offline and unauthorized pages are written with it directly,\n * because their copy is different on every product.\n *\n * @example\n * ```tsx\n * <StatusPage\n * standalone\n * code=\"503\"\n * title=\"Down for maintenance\"\n * description=\"Back by 09:00 UTC.\"\n * action={<Button href=\"https://status.example.com\">STATUS PAGE</Button>}\n * />\n * ```\n */\nexport const StatusPage = forwardRef<HTMLElement, StatusPageProps>(function StatusPage(\n { title, code, description, action, children, standalone = false, className, ...props },\n ref,\n) {\n const styles = statusPage({ standalone });\n const rootProps = {\n 'data-slot': 'status-page',\n ...props,\n className: styles.root({ class: className }),\n };\n const state = (\n <EmptyState\n className={styles.state()}\n headingLevel={1}\n icon={\n code ? (\n <p data-slot=\"status-page-code\" className={styles.code()}>\n {code}\n </p>\n ) : undefined\n }\n title={title}\n description={description}\n action={action}\n >\n {children}\n </EmptyState>\n );\n\n // Two elements, not an `as` prop: a `<main>` is a landmark with rules (one\n // per page), and the choice is the one thing `standalone` means.\n return standalone ? (\n <main ref={ref} {...rootProps}>\n {state}\n </main>\n ) : (\n <div ref={ref as Ref<HTMLDivElement>} {...rootProps}>\n {state}\n </div>\n );\n});\n\n/** Shared by the two presets: every `StatusPage` prop, with the copy optional. */\ninterface StatusPagePresetProps extends Omit<StatusPageProps, 'title'> {\n /** Where the default action goes. The site root, `/`, by default. */\n homeHref?: string;\n /** The default action's label. `GO HOME` by default. */\n homeLabel?: string;\n}\n\nfunction homeAction(homeHref: string, homeLabel: string) {\n return (\n <Button href={homeHref} variant=\"primary\" bracketed>\n {homeLabel}\n </Button>\n );\n}\n\nexport interface NotFoundPageProps extends StatusPagePresetProps {\n /** The page's `<h1>`. \"Page not found\" by default; `code` is `404`. */\n title?: string;\n}\n\n/**\n * The 404 page: `StatusPage` with the copy every site writes identically.\n *\n * Code `404`, the title \"Page not found\", a sentence that does not blame the\n * reader, and a link home. Every one of them is a prop, so a site that wants\n * search in the page or a different way out passes `action` or `children`\n * rather than rebuilding the page.\n *\n * @example\n * ```tsx\n * <main id=\"main-content\"><NotFoundPage /></main>\n * ```\n */\nexport const NotFoundPage = forwardRef<HTMLElement, NotFoundPageProps>(function NotFoundPage(\n {\n title = 'Page not found',\n code = '404',\n description = 'Nothing lives at this address. It may have moved, or the link that brought you here may be wrong.',\n homeHref = '/',\n homeLabel = 'GO HOME',\n action,\n ...props\n },\n ref,\n) {\n return (\n <StatusPage\n ref={ref}\n title={title}\n code={code}\n description={description}\n action={action ?? homeAction(homeHref, homeLabel)}\n {...props}\n />\n );\n});\n\nexport interface ServerErrorPageProps extends StatusPagePresetProps {\n /** The page's `<h1>`. \"Something went wrong\" by default; `code` is `500`. */\n title?: string;\n /**\n * `true` by default, unlike `StatusPage` and `NotFoundPage`. A 500 is usually\n * served because the application failed, and the shell that would frame it\n * may be what failed. Pass `standalone={false}` for an error the shell\n * survived, inside the consumer's own `<main>`.\n */\n standalone?: boolean;\n}\n\n/**\n * The 500 page: `StatusPage` with the copy every site writes identically.\n *\n * Code `500`, the title \"Something went wrong\", a sentence saying it is not\n * the reader's fault, and a link home. It is standalone by default, since the\n * page is usually served because the application failed and the shell that\n * would have framed it is what may not render. Pass `standalone={false}` to\n * place it inside the site chrome. A request or incident id to quote to\n * support belongs in `children`.\n *\n * @example\n * ```tsx\n * <ServerErrorPage>\n * <p>Reference: {requestId}</p>\n * </ServerErrorPage>\n * ```\n */\nexport const ServerErrorPage = forwardRef<HTMLElement, ServerErrorPageProps>(function ServerErrorPage(\n {\n title = 'Something went wrong',\n code = '500',\n description = 'The server hit an error it could not recover from. It is not something you did — try again in a moment.',\n homeHref = '/',\n homeLabel = 'GO HOME',\n action,\n standalone = true,\n ...props\n },\n ref,\n) {\n return (\n <StatusPage\n ref={ref}\n title={title}\n code={code}\n description={description}\n action={action ?? homeAction(homeHref, homeLabel)}\n standalone={standalone}\n {...props}\n />\n );\n});\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,kBAAkB;AA4GjB;AA/FV,IAAM,aAAa,OAAO;AAAA,EACxB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,OACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOF,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA,IACR,YAAY;AAAA,MACV,MAAM,EAAE,MAAM,8DAA8D;AAAA,MAC5E,OAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,YAAY,MAAM;AACvC,CAAC;AA2DM,IAAM,aAAa,WAAyC,SAASA,YAC1E,EAAE,OAAO,MAAM,aAAa,QAAQ,UAAU,aAAa,OAAO,WAAW,GAAG,MAAM,GACtF,KACA;AACA,QAAM,SAAS,WAAW,EAAE,WAAW,CAAC;AACxC,QAAM,YAAY;AAAA,IAChB,aAAa;AAAA,IACb,GAAG;AAAA,IACH,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,EAC7C;AACA,QAAM,QACJ;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,OAAO,MAAM;AAAA,MACxB,cAAc;AAAA,MACd,MACE,OACE,oBAAC,OAAE,aAAU,oBAAmB,WAAW,OAAO,KAAK,GACpD,gBACH,IACE;AAAA,MAEN;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAKF,SAAO,aACL,oBAAC,UAAK,KAAW,GAAG,WACjB,iBACH,IAEA,oBAAC,SAAI,KAAkC,GAAG,WACvC,iBACH;AAEJ,CAAC;AAUD,SAAS,WAAW,UAAkB,WAAmB;AACvD,SACE,oBAAC,UAAO,MAAM,UAAU,SAAQ,WAAU,WAAS,MAChD,qBACH;AAEJ;AAoBO,IAAM,eAAe,WAA2C,SAASC,cAC9E;AAAA,EACE,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,WAAW,UAAU,SAAS;AAAA,MAC/C,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AA+BM,IAAM,kBAAkB,WAA8C,SAASC,iBACpF;AAAA,EACE,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AAAA,EACA,aAAa;AAAA,EACb,GAAG;AACL,GACA,KACA;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,WAAW,UAAU,SAAS;AAAA,MAChD;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;","names":["StatusPage","NotFoundPage","ServerErrorPage"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/components/Swatch.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { cn } from "../lib/recipe.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var Swatch = forwardRef(function Swatch2({ value, name, detail, size = "md", className, ...props }, ref) {
|
|
6
|
+
return /* @__PURE__ */ jsxs(
|
|
7
|
+
"div",
|
|
8
|
+
{
|
|
9
|
+
ref,
|
|
10
|
+
"data-slot": "swatch",
|
|
11
|
+
className: cn("flex items-center gap-3 font-mono", className),
|
|
12
|
+
...props,
|
|
13
|
+
children: [
|
|
14
|
+
/* @__PURE__ */ jsx(
|
|
15
|
+
"span",
|
|
16
|
+
{
|
|
17
|
+
"data-slot": "swatch-well",
|
|
18
|
+
"aria-hidden": "true",
|
|
19
|
+
className: cn(
|
|
20
|
+
"flex-none border-2 border-edge-strong",
|
|
21
|
+
size === "sm" ? "h-6 w-6" : "h-10 w-10"
|
|
22
|
+
),
|
|
23
|
+
style: { backgroundColor: value }
|
|
24
|
+
}
|
|
25
|
+
),
|
|
26
|
+
/* @__PURE__ */ jsxs("span", { "data-slot": "swatch-label", className: "flex min-w-0 flex-col", children: [
|
|
27
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-xs text-content-secondary", children: name }),
|
|
28
|
+
detail && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-content-muted", children: detail })
|
|
29
|
+
] })
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
var SwatchGroup = forwardRef(function SwatchGroup2({ title, entries, size, className, ...props }, ref) {
|
|
35
|
+
return /* @__PURE__ */ jsxs("section", { ref, "data-slot": "swatch-group", className: cn("mb-8", className), ...props, children: [
|
|
36
|
+
/* @__PURE__ */ jsxs(
|
|
37
|
+
"h3",
|
|
38
|
+
{
|
|
39
|
+
"data-slot": "swatch-group-title",
|
|
40
|
+
className: "mb-3 font-display text-xs font-extrabold uppercase tracking-widest text-accent-secondary",
|
|
41
|
+
children: [
|
|
42
|
+
"[ ",
|
|
43
|
+
title,
|
|
44
|
+
" ]"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
),
|
|
48
|
+
/* @__PURE__ */ jsx("div", { "data-slot": "swatch-group-items", className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: Object.entries(entries).map(([name, value]) => /* @__PURE__ */ jsx(Swatch, { name, value, size }, name)) })
|
|
49
|
+
] });
|
|
50
|
+
});
|
|
51
|
+
export {
|
|
52
|
+
Swatch,
|
|
53
|
+
SwatchGroup
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=Swatch.js.map
|