@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/Menu.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactElement, ReactNode } from 'react';\nimport { Menu as BaseMenu } from '@base-ui/react/menu';\nimport { Check } from 'lucide-react';\nimport { cn, recipe } from '../lib/recipe';\nimport { usePortalThemeAttribute } from './portalTheme';\nimport { floatingSurface, floatingParts } from './floatingSurface';\nimport type { OverlayAlign, OverlaySide } from './Tooltip';\n\nexport interface MenuProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'className'> {\n /**\n * The control that opens it — one element that forwards its ref, such as\n * `Button`. Its accessible name becomes the menu's: Base UI labels the\n * `role=\"menu\"` popup by the trigger, so name the trigger for what the\n * menu holds.\n */\n trigger: ReactElement;\n /** `MenuItem`, `MenuRadioGroup` and `MenuSeparator`, in order. */\n children: ReactNode;\n /** The preferred side. `bottom` by default; flips when there is no room. */\n side?: OverlaySide;\n /** Alignment along the trigger's edge. `start` by default. */\n align?: OverlayAlign;\n /** Controlled open state. Leave unset and the menu manages its own. */\n open?: boolean;\n /** Open on first render, uncontrolled — for a story or a walkthrough. */\n defaultOpen?: boolean;\n /** Called with the next open state, whatever caused the change. */\n onOpenChange?: (open: boolean) => void;\n /** Merged into the popup's classes. */\n className?: string;\n}\n\n// A list: a minimum width so short labels do not make a sliver, and the\n// available height as a cap, so a long menu scrolls inside itself.\nconst menu = recipe({\n extend: floatingSurface,\n slots: { popup: 'min-w-48 max-h-(--available-height) overflow-y-auto py-1' },\n});\n\n/**\n * A list of actions or choices, opened from a button.\n *\n * The *Context actions* capability: rename, duplicate, delete, or a choice\n * between a few settings, behind one control rather than spread across a\n * toolbar. Built on `@base-ui/react/menu`, which owns the keyboard model —\n * Enter, Space or ArrowDown on the trigger opens it on the first item,\n * ArrowUp on the last; the arrow keys move and wrap; Home and End jump;\n * typing a letter jumps to the item that starts with it; Enter or a click\n * selects; Escape or a press outside closes and returns focus to the\n * trigger. Tab closes it too, rather than stepping through the items: a menu\n * is one stop in the tab order.\n *\n * Escape closes only the topmost surface — a menu opened inside a `Modal`\n * closes and leaves the modal open, the stack #162 established.\n *\n * @example\n * ```tsx\n * <Menu trigger={<Button size=\"sm\">ACTIONS</Button>}>\n * <MenuItem onClick={rename}>Rename</MenuItem>\n * <MenuItem disabled>Move</MenuItem>\n * <MenuSeparator />\n * <MenuItem intent=\"danger\" onClick={remove}>Delete</MenuItem>\n * </Menu>\n * ```\n */\nexport const Menu = forwardRef<HTMLDivElement, MenuProps>(function Menu(\n {\n trigger,\n children,\n side = 'bottom',\n align = 'start',\n open,\n defaultOpen,\n onOpenChange,\n className,\n ...props\n },\n ref,\n) {\n const styles = menu();\n\n const portalTheme = usePortalThemeAttribute();\n return (\n <BaseMenu.Root\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange ? (next) => onOpenChange(next) : undefined}\n >\n <BaseMenu.Trigger render={trigger} />\n <BaseMenu.Portal>\n <BaseMenu.Positioner\n {...portalTheme}\n side={side}\n align={align}\n sideOffset={6}\n collisionPadding={8}\n data-slot=\"menu-positioner\"\n className={styles.positioner()}\n >\n <BaseMenu.Popup\n ref={ref}\n data-slot=\"menu\"\n className={styles.popup({ class: className })}\n {...props}\n >\n {children}\n </BaseMenu.Popup>\n </BaseMenu.Positioner>\n </BaseMenu.Portal>\n </BaseMenu.Root>\n );\n});\n\nexport interface MenuItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {\n /** The label. Its first letter is what typeahead matches. */\n children: ReactNode;\n /**\n * `danger` for the destructive action — the `intent.danger` ink, and the\n * danger fill when highlighted. Put it last, after a `MenuSeparator`.\n */\n intent?: 'danger';\n /**\n * Shown muted and cannot be activated. It stays in the arrow-key sequence,\n * announced as unavailable, rather than vanishing from it.\n */\n disabled?: boolean;\n /**\n * Close the menu when the item is chosen. On by default; turn it off for an\n * item that changes something the user will want to change again at once.\n */\n closeOnClick?: boolean;\n /** Merged into the item's classes. */\n className?: string;\n}\n\n/**\n * One action in a {@link Menu}.\n *\n * Handle it with `onClick`, which fires for a pointer press and for Enter or\n * Space alike — Base UI routes the keyboard through the same handler.\n */\nexport const MenuItem = forwardRef<HTMLDivElement, MenuItemProps>(function MenuItem(\n { children, intent, disabled = false, closeOnClick = true, className, ...props },\n ref,\n) {\n const parts = floatingParts();\n\n // A disabled item stays in Base UI's roving list, as the WAI-ARIA menu\n // pattern recommends: the arrow keys still land on it, so a keyboard or\n // screen-reader user learns the action exists and is unavailable, rather\n // than finding a gap. Base UI marks it `aria-disabled` and never fires it.\n return (\n <BaseMenu.Item\n ref={ref}\n disabled={disabled}\n closeOnClick={closeOnClick}\n data-slot=\"menu-item\"\n className={cn(\n parts.item(),\n intent === 'danger' && parts.danger(),\n disabled && parts.disabled(),\n className,\n )}\n {...props}\n >\n {children}\n </BaseMenu.Item>\n );\n});\n\nexport interface MenuRadioGroupProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'defaultValue' | 'onChange' | 'className'> {\n /**\n * The group's visible heading, and its accessible name — Base UI wires the\n * label's id to the group's `aria-labelledby`, so the name lands on the\n * `role=\"group\"` element rather than on a wrapper.\n */\n label?: string;\n /** The chosen item's `value`. Controlled; pair with `onValueChange`. */\n value?: string;\n /** The initially chosen value, uncontrolled. */\n defaultValue?: string;\n /** Called with the chosen item's `value`. */\n onValueChange?: (value: string) => void;\n /** `MenuRadioItem`s. */\n children: ReactNode;\n /** Merged into the group's classes. */\n className?: string;\n}\n\n/**\n * A choose-one set inside a {@link Menu} — a setting rather than an action.\n * The chosen item carries `aria-checked` and a check mark; the mark, not the\n * colour, is what says which one is chosen.\n */\nexport const MenuRadioGroup = forwardRef<HTMLDivElement, MenuRadioGroupProps>(\n function MenuRadioGroup(\n { label, value, defaultValue, onValueChange, children, className, ...props },\n ref,\n ) {\n const parts = floatingParts();\n\n return (\n <BaseMenu.RadioGroup\n ref={ref}\n value={value}\n defaultValue={defaultValue}\n onValueChange={onValueChange ? (next: string) => onValueChange(next) : undefined}\n data-slot=\"menu-radio-group\"\n className={className}\n {...props}\n >\n {label ? (\n <BaseMenu.GroupLabel data-slot=\"menu-group-label\" className={parts.groupLabel()}>\n {label}\n </BaseMenu.GroupLabel>\n ) : null}\n {children}\n </BaseMenu.RadioGroup>\n );\n },\n);\n\nexport interface MenuRadioItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {\n /** What choosing this item sets the group's value to. */\n value: string;\n /** The label. Its first letter is what typeahead matches. */\n children: ReactNode;\n /**\n * Close the menu once a value is chosen. Off by default, as Base UI's is —\n * a setting is often adjusted and checked in one visit. A picker whose\n * choice is the whole point, like a theme, turns it on.\n */\n closeOnClick?: boolean;\n /** Merged into the item's classes. */\n className?: string;\n}\n\n/** One choice in a {@link MenuRadioGroup}. */\nexport const MenuRadioItem = forwardRef<HTMLDivElement, MenuRadioItemProps>(\n function MenuRadioItem({ value, children, closeOnClick = false, className, ...props }, ref) {\n const parts = floatingParts();\n\n return (\n <BaseMenu.RadioItem\n ref={ref}\n value={value}\n closeOnClick={closeOnClick}\n data-slot=\"menu-radio-item\"\n className={cn(parts.item(), className)}\n {...props}\n >\n {/* The slot is always there so labels align whether or not they are\n chosen; only the mark inside it mounts and unmounts. */}\n <span aria-hidden=\"true\" className={parts.indicator()}>\n <BaseMenu.RadioItemIndicator data-slot=\"menu-radio-indicator\">\n <Check size={14} strokeWidth={3} />\n </BaseMenu.RadioItemIndicator>\n </span>\n {children}\n </BaseMenu.RadioItem>\n );\n },\n);\n\nexport interface MenuSeparatorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {\n /** Merged into the rule's classes. */\n className?: string;\n}\n\n/**\n * A rule between groups of items — before the destructive action, or between\n * actions and settings. Announced as a separator; never a keyboard stop.\n */\nexport const MenuSeparator = forwardRef<HTMLDivElement, MenuSeparatorProps>(\n function MenuSeparator({ className, ...props }, ref) {\n const parts = floatingParts();\n return (\n <BaseMenu.Separator\n ref={ref}\n data-slot=\"menu-separator\"\n className={cn(parts.separator(), className)}\n {...props}\n />\n );\n },\n);\n"],"mappings":"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","names":["Menu","MenuItem","MenuRadioGroup","MenuRadioItem","MenuSeparator"]}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// src/components/MetadataGrid.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { cn } from "../lib/recipe.js";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var MetadataGrid = forwardRef(
|
|
6
|
+
function MetadataGrid2({
|
|
7
|
+
published,
|
|
8
|
+
readTime,
|
|
9
|
+
series,
|
|
10
|
+
tags,
|
|
11
|
+
items,
|
|
12
|
+
className,
|
|
13
|
+
...props
|
|
14
|
+
}, ref) {
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
ref,
|
|
19
|
+
"data-slot": "metadata-grid",
|
|
20
|
+
className: cn("@container", className),
|
|
21
|
+
...props,
|
|
22
|
+
children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 @md:grid-cols-4 border-2 border-edge-strong bg-surface-base font-mono text-xs text-content-primary divide-y-2 @md:divide-y-0 @md:divide-x-2 divide-edge-strong [&>*:nth-child(even)]:border-l-2 [&>*:nth-child(even)]:border-edge-strong @md:[&>*:nth-child(even)]:border-l-0 shadow-hard-sm", children: items ? items.map((item, idx) => /* @__PURE__ */ jsxs(
|
|
23
|
+
"div",
|
|
24
|
+
{
|
|
25
|
+
"data-slot": "metadata-grid-cell",
|
|
26
|
+
className: "p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors",
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ jsxs(
|
|
29
|
+
"span",
|
|
30
|
+
{
|
|
31
|
+
"data-slot": "metadata-grid-label",
|
|
32
|
+
className: "text-[10px] font-black uppercase text-content-muted tracking-wide mb-1",
|
|
33
|
+
children: [
|
|
34
|
+
"[\xA0",
|
|
35
|
+
item.label,
|
|
36
|
+
"\xA0]"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
),
|
|
40
|
+
item.href ? /* @__PURE__ */ jsx(
|
|
41
|
+
"a",
|
|
42
|
+
{
|
|
43
|
+
href: item.href,
|
|
44
|
+
className: "text-xs font-bold text-accent-secondary hover:underline truncate",
|
|
45
|
+
children: item.value
|
|
46
|
+
}
|
|
47
|
+
) : /* @__PURE__ */ jsx("span", { className: "text-xs font-bold text-content-primary tracking-wide", children: item.value })
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
`grid-custom-${idx}`
|
|
51
|
+
)) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
52
|
+
/* @__PURE__ */ jsxs(
|
|
53
|
+
"div",
|
|
54
|
+
{
|
|
55
|
+
"data-slot": "metadata-grid-cell",
|
|
56
|
+
className: "p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors",
|
|
57
|
+
children: [
|
|
58
|
+
/* @__PURE__ */ jsx(
|
|
59
|
+
"span",
|
|
60
|
+
{
|
|
61
|
+
"data-slot": "metadata-grid-label",
|
|
62
|
+
className: "text-[10px] font-black uppercase text-content-muted tracking-wide mb-1",
|
|
63
|
+
children: "[\xA0PUBLISHED\xA0]"
|
|
64
|
+
}
|
|
65
|
+
),
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
"data-slot": "metadata-grid-value",
|
|
70
|
+
className: "text-xs font-bold text-content-primary tracking-wide",
|
|
71
|
+
children: published ?? "\u2014"
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
/* @__PURE__ */ jsxs(
|
|
78
|
+
"div",
|
|
79
|
+
{
|
|
80
|
+
"data-slot": "metadata-grid-cell",
|
|
81
|
+
className: "p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors",
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
"span",
|
|
85
|
+
{
|
|
86
|
+
"data-slot": "metadata-grid-label",
|
|
87
|
+
className: "text-[10px] font-black uppercase text-content-muted tracking-wide mb-1",
|
|
88
|
+
children: "[\xA0READ TIME\xA0]"
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
/* @__PURE__ */ jsx(
|
|
92
|
+
"span",
|
|
93
|
+
{
|
|
94
|
+
"data-slot": "metadata-grid-value",
|
|
95
|
+
className: "text-xs font-bold text-accent-primary tracking-wide",
|
|
96
|
+
children: readTime ?? "\u2014"
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ jsxs(
|
|
103
|
+
"div",
|
|
104
|
+
{
|
|
105
|
+
"data-slot": "metadata-grid-cell",
|
|
106
|
+
className: "p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors",
|
|
107
|
+
children: [
|
|
108
|
+
/* @__PURE__ */ jsx(
|
|
109
|
+
"span",
|
|
110
|
+
{
|
|
111
|
+
"data-slot": "metadata-grid-label",
|
|
112
|
+
className: "text-[10px] font-black uppercase text-content-muted tracking-wide mb-1",
|
|
113
|
+
children: "[\xA0SERIES TRACK\xA0]"
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
series ? series.href ? /* @__PURE__ */ jsxs(
|
|
117
|
+
"a",
|
|
118
|
+
{
|
|
119
|
+
href: series.href,
|
|
120
|
+
className: "text-xs font-bold text-accent-secondary hover:underline break-words",
|
|
121
|
+
title: series.name,
|
|
122
|
+
children: [
|
|
123
|
+
series.name,
|
|
124
|
+
series.part !== void 0 && ` (${series.part}/${series.totalParts ?? "?"})`
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
) : /* @__PURE__ */ jsxs(
|
|
128
|
+
"span",
|
|
129
|
+
{
|
|
130
|
+
className: "text-xs font-bold text-accent-secondary break-words",
|
|
131
|
+
title: series.name,
|
|
132
|
+
children: [
|
|
133
|
+
series.name,
|
|
134
|
+
series.part !== void 0 && ` (${series.part}/${series.totalParts ?? "?"})`
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
) : /* @__PURE__ */ jsx("span", { className: "text-xs font-bold text-content-muted", children: "STANDALONE" })
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
/* @__PURE__ */ jsxs(
|
|
142
|
+
"div",
|
|
143
|
+
{
|
|
144
|
+
"data-slot": "metadata-grid-cell",
|
|
145
|
+
className: "p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors",
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
"span",
|
|
149
|
+
{
|
|
150
|
+
"data-slot": "metadata-grid-label",
|
|
151
|
+
className: "text-[10px] font-black uppercase text-content-muted tracking-wide mb-1",
|
|
152
|
+
children: "[\xA0TOPICS\xA0]"
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
tags && tags.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1.5 mt-0.5", children: [
|
|
156
|
+
tags.slice(0, 3).map((tag) => /* @__PURE__ */ jsxs(
|
|
157
|
+
"span",
|
|
158
|
+
{
|
|
159
|
+
className: "px-1.5 py-0.5 text-[10px] font-bold bg-surface-sunken text-content-secondary border border-edge-subtle hover:border-accent-primary hover:text-accent-primary transition-colors",
|
|
160
|
+
children: [
|
|
161
|
+
"#",
|
|
162
|
+
tag.toUpperCase()
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
tag
|
|
166
|
+
)),
|
|
167
|
+
tags.length > 3 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-bold text-content-muted self-center", children: [
|
|
168
|
+
"+",
|
|
169
|
+
tags.length - 3
|
|
170
|
+
] })
|
|
171
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "text-xs font-bold text-content-muted", children: "\u2014" })
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
] }) })
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
MetadataGrid.displayName = "MetadataGrid";
|
|
181
|
+
export {
|
|
182
|
+
MetadataGrid
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=MetadataGrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/MetadataGrid.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cn } from '../lib/recipe';\n\nexport interface MetadataGridSeries {\n /** Series title or name. */\n name: string;\n /** Current part index (e.g. 2). */\n part?: number;\n /** Total parts in the series (e.g. 5). */\n totalParts?: number;\n /** Optional destination link for the series catalog. */\n href?: string;\n}\n\nexport interface MetadataGridCustomItem {\n /** Column category label (e.g. \"STATUS\", \"AUTHOR\"). */\n label: string;\n /** Primary cell content. */\n value: ReactNode;\n /** Optional link destination for the value. */\n href?: string;\n}\n\nexport interface MetadataGridProps extends HTMLAttributes<HTMLDivElement> {\n /** Publication date string (e.g. \"2026-09-14\" or \"September 14, 2026\"). */\n published?: string;\n /** Estimated reading time or word count (e.g. \"12 MIN READ\" or \"~3,400 WORDS\"). */\n readTime?: string;\n /** Optional series context (e.g. { name: \"DISTRIBUTED // PART 02\", part: 2, totalParts: 5 }). */\n series?: MetadataGridSeries;\n /** List of topic or technology tags. */\n tags?: string[];\n /** Arbitrary custom cells to override or augment standard editorial cells. */\n items?: MetadataGridCustomItem[];\n}\n\n/**\n * High-density 4-column brutalist metadata grid.\n *\n * Formats frontmatter, series hierarchy, and article taxonomy into a structured\n * key/value matrix. Features hard cell borders, monospace category headers, and\n * responsive 2-to-4 column reflow.\n *\n * @example\n * ```tsx\n * <MetadataGrid\n * published=\"SEPTEMBER 16, 2026\"\n * readTime=\"14 MIN READ\"\n * series={{ name: \"DISTRIBUTED SYSTEMS\", part: 2, totalParts: 4 }}\n * tags={[\"rust\", \"consensus\", \"raft\"]}\n * />\n * ```\n */\nexport const MetadataGrid = forwardRef<HTMLDivElement, MetadataGridProps>(\n function MetadataGrid(\n {\n published,\n readTime,\n series,\n tags,\n items,\n className,\n ...props\n },\n ref,\n ) {\n return (\n <div\n ref={ref}\n data-slot=\"metadata-grid\"\n className={cn('@container', className)}\n {...props}\n >\n <div className=\"grid grid-cols-2 @md:grid-cols-4 border-2 border-edge-strong bg-surface-base font-mono text-xs text-content-primary divide-y-2 @md:divide-y-0 @md:divide-x-2 divide-edge-strong [&>*:nth-child(even)]:border-l-2 [&>*:nth-child(even)]:border-edge-strong @md:[&>*:nth-child(even)]:border-l-0 shadow-hard-sm\">\n {items ? (\n items.map((item, idx) => (\n <div\n key={`grid-custom-${idx}`}\n data-slot=\"metadata-grid-cell\"\n className=\"p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors\"\n >\n <span\n data-slot=\"metadata-grid-label\"\n className=\"text-[10px] font-black uppercase text-content-muted tracking-wide mb-1\"\n >\n [ {item.label} ]\n </span>\n {item.href ? (\n <a\n href={item.href}\n className=\"text-xs font-bold text-accent-secondary hover:underline truncate\"\n >\n {item.value}\n </a>\n ) : (\n <span className=\"text-xs font-bold text-content-primary tracking-wide\">\n {item.value}\n </span>\n )}\n </div>\n ))\n ) : (\n <>\n {/* 1. PUBLISHED */}\n <div\n data-slot=\"metadata-grid-cell\"\n className=\"p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors\"\n >\n <span\n data-slot=\"metadata-grid-label\"\n className=\"text-[10px] font-black uppercase text-content-muted tracking-wide mb-1\"\n >\n [ PUBLISHED ]\n </span>\n <span\n data-slot=\"metadata-grid-value\"\n className=\"text-xs font-bold text-content-primary tracking-wide\"\n >\n {published ?? '—'}\n </span>\n </div>\n\n {/* 2. READ TIME */}\n <div\n data-slot=\"metadata-grid-cell\"\n className=\"p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors\"\n >\n <span\n data-slot=\"metadata-grid-label\"\n className=\"text-[10px] font-black uppercase text-content-muted tracking-wide mb-1\"\n >\n [ READ TIME ]\n </span>\n <span\n data-slot=\"metadata-grid-value\"\n className=\"text-xs font-bold text-accent-primary tracking-wide\"\n >\n {readTime ?? '—'}\n </span>\n </div>\n\n {/* 3. SERIES */}\n <div\n data-slot=\"metadata-grid-cell\"\n className=\"p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors\"\n >\n <span\n data-slot=\"metadata-grid-label\"\n className=\"text-[10px] font-black uppercase text-content-muted tracking-wide mb-1\"\n >\n [ SERIES TRACK ]\n </span>\n {series ? (\n series.href ? (\n <a\n href={series.href}\n className=\"text-xs font-bold text-accent-secondary hover:underline break-words\"\n title={series.name}\n >\n {series.name}\n {series.part !== undefined &&\n ` (${series.part}/${series.totalParts ?? '?'})`}\n </a>\n ) : (\n <span\n className=\"text-xs font-bold text-accent-secondary break-words\"\n title={series.name}\n >\n {series.name}\n {series.part !== undefined &&\n ` (${series.part}/${series.totalParts ?? '?'})`}\n </span>\n )\n ) : (\n <span className=\"text-xs font-bold text-content-muted\">\n STANDALONE\n </span>\n )}\n </div>\n\n {/* 4. TAGS */}\n <div\n data-slot=\"metadata-grid-cell\"\n className=\"p-3.5 flex flex-col justify-between hover:bg-surface-raised transition-colors\"\n >\n <span\n data-slot=\"metadata-grid-label\"\n className=\"text-[10px] font-black uppercase text-content-muted tracking-wide mb-1\"\n >\n [ TOPICS ]\n </span>\n {tags && tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1.5 mt-0.5\">\n {tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"px-1.5 py-0.5 text-[10px] font-bold bg-surface-sunken text-content-secondary border border-edge-subtle hover:border-accent-primary hover:text-accent-primary transition-colors\"\n >\n #{tag.toUpperCase()}\n </span>\n ))}\n {tags.length > 3 && (\n <span className=\"text-[10px] font-bold text-content-muted self-center\">\n +{tags.length - 3}\n </span>\n )}\n </div>\n ) : (\n <span className=\"text-xs font-bold text-content-muted\">—</span>\n )}\n </div>\n </>\n )}\n </div>\n </div>\n );\n },\n);\n\nMetadataGrid.displayName = 'MetadataGrid';\n"],"mappings":"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","names":["MetadataGrid"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/MobileNav.tsx
|
|
4
|
+
import {
|
|
5
|
+
forwardRef,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
useSyncExternalStore
|
|
13
|
+
} from "react";
|
|
14
|
+
import { Menu as MenuIcon } from "lucide-react";
|
|
15
|
+
import { cn } from "../lib/recipe.js";
|
|
16
|
+
import { Button } from "./Button.js";
|
|
17
|
+
import { Drawer } from "./Drawer.js";
|
|
18
|
+
import { SiteNav } from "./SiteNav.js";
|
|
19
|
+
import { COLLAPSE_QUERY, SiteHeaderCollapseContext, SiteNavContext } from "./siteNavContext.js";
|
|
20
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
function canMatchMedia() {
|
|
22
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function";
|
|
23
|
+
}
|
|
24
|
+
function useMatches(query) {
|
|
25
|
+
const subscribe = useCallback(
|
|
26
|
+
(onChange) => {
|
|
27
|
+
if (!query || !canMatchMedia()) return () => {
|
|
28
|
+
};
|
|
29
|
+
const list = window.matchMedia(query);
|
|
30
|
+
list.addEventListener("change", onChange);
|
|
31
|
+
return () => list.removeEventListener("change", onChange);
|
|
32
|
+
},
|
|
33
|
+
[query]
|
|
34
|
+
);
|
|
35
|
+
return useSyncExternalStore(
|
|
36
|
+
subscribe,
|
|
37
|
+
() => Boolean(query) && canMatchMedia() && window.matchMedia(query).matches,
|
|
38
|
+
() => false
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
var MobileNav = forwardRef(function MobileNav2({
|
|
42
|
+
label,
|
|
43
|
+
children,
|
|
44
|
+
title = "Menu",
|
|
45
|
+
triggerLabel = "MENU",
|
|
46
|
+
open,
|
|
47
|
+
defaultOpen = false,
|
|
48
|
+
onOpenChange,
|
|
49
|
+
footer,
|
|
50
|
+
className,
|
|
51
|
+
...props
|
|
52
|
+
}, ref) {
|
|
53
|
+
const [uncontrolled, setUncontrolled] = useState(defaultOpen);
|
|
54
|
+
const isOpen = open ?? uncontrolled;
|
|
55
|
+
const collapseAt = useContext(SiteHeaderCollapseContext);
|
|
56
|
+
const collapseQuery = collapseAt ? COLLAPSE_QUERY[collapseAt] : void 0;
|
|
57
|
+
const collapsed = useMatches(collapseQuery);
|
|
58
|
+
const drawerOpen = isOpen && !collapsed;
|
|
59
|
+
const onChange = useRef(onOpenChange);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
onChange.current = onOpenChange;
|
|
62
|
+
});
|
|
63
|
+
const controlled = open !== void 0;
|
|
64
|
+
const setOpen = useCallback(
|
|
65
|
+
(next) => {
|
|
66
|
+
if (!controlled) setUncontrolled(next);
|
|
67
|
+
onChange.current?.(next);
|
|
68
|
+
},
|
|
69
|
+
[controlled]
|
|
70
|
+
);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!isOpen || !collapseQuery || !canMatchMedia()) return void 0;
|
|
73
|
+
const list = window.matchMedia(collapseQuery);
|
|
74
|
+
const onWiden = () => {
|
|
75
|
+
if (list.matches) setOpen(false);
|
|
76
|
+
};
|
|
77
|
+
list.addEventListener("change", onWiden);
|
|
78
|
+
return () => list.removeEventListener("change", onWiden);
|
|
79
|
+
}, [isOpen, collapseQuery, setOpen]);
|
|
80
|
+
const close = useCallback(() => setOpen(false), [setOpen]);
|
|
81
|
+
const context = useMemo(
|
|
82
|
+
() => ({ orientation: "vertical", inMenu: false, onNavigate: close }),
|
|
83
|
+
[close]
|
|
84
|
+
);
|
|
85
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
86
|
+
/* @__PURE__ */ jsxs(
|
|
87
|
+
Button,
|
|
88
|
+
{
|
|
89
|
+
type: "button",
|
|
90
|
+
size: "sm",
|
|
91
|
+
"data-slot": "mobile-nav-trigger",
|
|
92
|
+
"aria-haspopup": "dialog",
|
|
93
|
+
"aria-expanded": drawerOpen,
|
|
94
|
+
onClick: () => setOpen(true),
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ jsx(MenuIcon, { size: 16, strokeWidth: 3, "aria-hidden": "true" }),
|
|
97
|
+
triggerLabel
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
Drawer,
|
|
103
|
+
{
|
|
104
|
+
ref,
|
|
105
|
+
isOpen: drawerOpen,
|
|
106
|
+
onClose: close,
|
|
107
|
+
placement: "left",
|
|
108
|
+
title,
|
|
109
|
+
footer,
|
|
110
|
+
...props,
|
|
111
|
+
className: cn(className),
|
|
112
|
+
children: /* @__PURE__ */ jsx(SiteNavContext.Provider, { value: context, children: /* @__PURE__ */ jsx(SiteNav, { label, orientation: "vertical", children }) })
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
] });
|
|
116
|
+
});
|
|
117
|
+
export {
|
|
118
|
+
MobileNav
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=MobileNav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/MobileNav.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport type { HTMLAttributes, ReactNode } from 'react';\nimport { Menu as MenuIcon } from 'lucide-react';\nimport { cn } from '../lib/recipe';\nimport { Button } from './Button';\nimport { Drawer } from './Drawer';\nimport { SiteNav } from './SiteNav';\nimport { COLLAPSE_QUERY, SiteHeaderCollapseContext, SiteNavContext } from './siteNavContext';\n\nfunction canMatchMedia(): boolean {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function';\n}\n\n// Whether the viewport is at or past `query`; false with no query, on the\n// server, and where there is no `matchMedia`.\nfunction useMatches(query: string | undefined): boolean {\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (!query || !canMatchMedia()) return () => {};\n const list = window.matchMedia(query);\n list.addEventListener('change', onChange);\n return () => list.removeEventListener('change', onChange);\n },\n [query],\n );\n return useSyncExternalStore(\n subscribe,\n () => Boolean(query) && canMatchMedia() && window.matchMedia(query as string).matches,\n () => false,\n );\n}\n\nexport interface MobileNavProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title' | 'className'> {\n /**\n * The `<nav>` landmark's accessible name inside the drawer. Give it the same\n * name as the desktop `SiteNav` it stands in for: at any one width only one\n * of the two is rendered, and it is the same navigation.\n */\n label: string;\n /** `SiteNavItem`s — normally the very same ones the desktop `SiteNav` gets. */\n children: ReactNode;\n /** The drawer's title, and so its accessible name. `Menu` by default. */\n title?: string;\n /** The trigger button's visible label, and so its accessible name. `MENU` by default. */\n triggerLabel?: string;\n /** Controlled open state. Leave unset and the component manages its own. */\n open?: boolean;\n /** Open on first render, uncontrolled — for a story or a walkthrough. */\n defaultOpen?: boolean;\n /** Called with the next open state, whatever caused the change. */\n onOpenChange?: (open: boolean) => void;\n /** Replaces the drawer's default close button — a theme control, a CTA. */\n footer?: ReactNode;\n /** Merged into the drawer panel's classes. */\n className?: string;\n}\n\n/**\n * The narrow-viewport navigation: a trigger button that opens the site's nav\n * in a {@link Drawer} from the left.\n *\n * It is `Drawer`, not a second off-canvas implementation. Focus moves into the\n * panel on open and back to the trigger on close; Escape, the backdrop and the\n * close control dismiss it; it sits on the dialog stack, so a group's `Menu`\n * opened inside it closes first on Escape; the page behind is scroll-locked\n * and hidden from assistive technology. All of that is inherited, and none of\n * it is written here.\n *\n * What is its own is small: the trigger, which says whether it is expanded;\n * the vertical `SiteNav` inside the panel; and closing the drawer when an item\n * is followed, because under a client-side router following a link does not\n * unload the page the drawer is open over.\n *\n * {@link SiteHeader} shows it below its `collapseAt` breakpoint and hides it\n * above, where the desktop `SiteNav` takes over. Like `SiteNav` it carries no\n * items and no current-page logic of its own.\n *\n * Forwards its ref, and any other props, to the drawer panel.\n *\n * @example\n * ```tsx\n * <MobileNav label=\"Primary\">\n * <SiteNavItem href=\"/writing\">Writing</SiteNavItem>\n * <SiteNavItem href=\"/projects\">Projects</SiteNavItem>\n * </MobileNav>\n * ```\n */\nexport const MobileNav = forwardRef<HTMLDivElement, MobileNavProps>(function MobileNav(\n {\n label,\n children,\n title = 'Menu',\n triggerLabel = 'MENU',\n open,\n defaultOpen = false,\n onOpenChange,\n footer,\n className,\n ...props\n },\n ref,\n) {\n const [uncontrolled, setUncontrolled] = useState(defaultOpen);\n const isOpen = open ?? uncontrolled;\n // Inside a `SiteHeader`, past its `collapseAt` the trigger is hidden by CSS\n // but the portalled drawer is not, so the drawer is kept shut there and an\n // open one is closed as the viewport widens past it.\n const collapseAt = useContext(SiteHeaderCollapseContext);\n const collapseQuery = collapseAt ? COLLAPSE_QUERY[collapseAt] : undefined;\n const collapsed = useMatches(collapseQuery);\n const drawerOpen = isOpen && !collapsed;\n\n // The latest callback, read at call time, so `setOpen` keeps one identity\n // and the effect below does not re-report a close on every render.\n const onChange = useRef(onOpenChange);\n useEffect(() => {\n onChange.current = onOpenChange;\n });\n const controlled = open !== undefined;\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!controlled) setUncontrolled(next);\n onChange.current?.(next);\n },\n [controlled],\n );\n\n // Closed from the media query's own change event rather than from a render,\n // so a widening reports one close to the owner.\n useEffect(() => {\n if (!isOpen || !collapseQuery || !canMatchMedia()) return undefined;\n const list = window.matchMedia(collapseQuery);\n const onWiden = () => {\n if (list.matches) setOpen(false);\n };\n list.addEventListener('change', onWiden);\n return () => list.removeEventListener('change', onWiden);\n }, [isOpen, collapseQuery, setOpen]);\n\n const close = useCallback(() => setOpen(false), [setOpen]);\n const context = useMemo(\n () => ({ orientation: 'vertical' as const, inMenu: false, onNavigate: close }),\n [close],\n );\n\n return (\n <>\n <Button\n type=\"button\"\n size=\"sm\"\n data-slot=\"mobile-nav-trigger\"\n aria-haspopup=\"dialog\"\n aria-expanded={drawerOpen}\n onClick={() => setOpen(true)}\n >\n <MenuIcon size={16} strokeWidth={3} aria-hidden=\"true\" />\n {triggerLabel}\n </Button>\n <Drawer\n ref={ref}\n isOpen={drawerOpen}\n onClose={close}\n placement=\"left\"\n title={title}\n footer={footer}\n {...props}\n className={cn(className)}\n >\n <SiteNavContext.Provider value={context}>\n <SiteNav label={label} orientation=\"vertical\">\n {children}\n </SiteNav>\n </SiteNavContext.Provider>\n </Drawer>\n </>\n );\n});\n"],"mappings":"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","names":["MobileNav"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/Modal.tsx
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
6
|
+
import { cn } from "../lib/recipe.js";
|
|
7
|
+
import { usePortalThemeAttribute } from "./portalTheme.js";
|
|
8
|
+
import { dialogSurface } from "./dialogSurface.js";
|
|
9
|
+
import { usePopupRef } from "./dialogPopupRef.js";
|
|
10
|
+
import { Button } from "./Button.js";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var Modal = forwardRef(function Modal2({
|
|
13
|
+
isOpen,
|
|
14
|
+
onClose,
|
|
15
|
+
title,
|
|
16
|
+
children,
|
|
17
|
+
footer,
|
|
18
|
+
closeOnBackdropClick = true,
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}, ref) {
|
|
22
|
+
const slots = dialogSurface();
|
|
23
|
+
const [popup, attachPopup] = usePopupRef(ref);
|
|
24
|
+
const portalTheme = usePortalThemeAttribute();
|
|
25
|
+
return /* @__PURE__ */ jsx(
|
|
26
|
+
Dialog.Root,
|
|
27
|
+
{
|
|
28
|
+
open: isOpen,
|
|
29
|
+
onOpenChange: (open) => {
|
|
30
|
+
if (!open) onClose();
|
|
31
|
+
},
|
|
32
|
+
disablePointerDismissal: !closeOnBackdropClick,
|
|
33
|
+
children: /* @__PURE__ */ jsxs(Dialog.Portal, { ...portalTheme, children: [
|
|
34
|
+
/* @__PURE__ */ jsx(Dialog.Backdrop, { "data-slot": "modal-backdrop", className: slots.backdrop() }),
|
|
35
|
+
/* @__PURE__ */ jsx(Dialog.Viewport, { "data-slot": "modal-viewport", className: slots.viewport(), children: /* @__PURE__ */ jsxs(
|
|
36
|
+
Dialog.Popup,
|
|
37
|
+
{
|
|
38
|
+
ref: attachPopup,
|
|
39
|
+
initialFocus: () => popup.current,
|
|
40
|
+
"data-slot": "modal",
|
|
41
|
+
className: cn(slots.popup(), className),
|
|
42
|
+
...props,
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "modal-header", className: slots.header(), children: [
|
|
45
|
+
/* @__PURE__ */ jsx(
|
|
46
|
+
Dialog.Title,
|
|
47
|
+
{
|
|
48
|
+
"data-slot": "modal-title",
|
|
49
|
+
className: slots.title(),
|
|
50
|
+
render: /* @__PURE__ */ jsxs("h3", { children: [
|
|
51
|
+
"[ ",
|
|
52
|
+
title,
|
|
53
|
+
" ]"
|
|
54
|
+
] })
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
Dialog.Close,
|
|
59
|
+
{
|
|
60
|
+
"data-slot": "modal-close",
|
|
61
|
+
className: slots.close(),
|
|
62
|
+
"aria-label": "Close dialog",
|
|
63
|
+
children: "\xD7"
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
] }),
|
|
67
|
+
/* @__PURE__ */ jsx("div", { "data-slot": "modal-body", className: slots.body(), children }),
|
|
68
|
+
/* @__PURE__ */ jsx("div", { "data-slot": "modal-footer", className: slots.footer(), children: footer || /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", bracketed: true, size: "sm", children: "CLOSE" }) })
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
) })
|
|
72
|
+
] })
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
export {
|
|
77
|
+
Modal
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=Modal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Modal.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactNode } from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { cn } from '../lib/recipe';\nimport { usePortalThemeAttribute } from './portalTheme';\nimport { dialogSurface } from './dialogSurface';\nimport { usePopupRef } from './dialogPopupRef';\nimport { Button } from './Button';\n\nexport interface ModalProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children' | 'className'> {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n children: ReactNode;\n footer?: ReactNode;\n /**\n * Close when the backdrop is clicked. On for the usual case; turn it off for\n * a dialog holding unsaved input, where a stray click should not discard it.\n */\n closeOnBackdropClick?: boolean;\n className?: string;\n}\n\n/**\n * A modal dialog, on Base UI's `dialog`.\n *\n * \"Modal\" is a behavioural claim, not a visual one: while it is open, the rest\n * of the page is inert. This component used to make that claim and hand-roll\n * every part of it — a capture-phase `document` listener for Escape, a\n * first/last focus trap, `body.style.overflow`, and focus return through a ref\n * captured at open time. All of it is deleted. What replaced it is not a\n * smaller version of the same code; it is the primitive that owns the problem.\n *\n * Three defects went with the deletion, each of which the hand-rolled version\n * had and none of which was cheap to fix in place:\n *\n * 1. **No dialog stack.** Two open dialogs both listened on `document` in\n * the capture phase, so Escape closed both. Base UI keeps a stack, and\n * Escape now closes the topmost surface only — the invariant every\n * overlay added after this one inherits.\n * 2. **The background was never hidden from assistive technology.** Nothing\n * set `inert` or `aria-hidden`, so a screen reader's virtual cursor\n * walked the page behind the dialog even though Tab could not. Base UI\n * marks every sibling of the portal `aria-hidden` while the dialog is\n * open — `aria-hidden`, not `inert`, which is worth knowing when reading\n * the DOM: the outside content stays in the tab order's way only as far\n * as the focus guards allow, and the guards are what stop it.\n * 3. **The focus trap read the DOM once, by first and last.** Content that\n * changed while open — a list that filtered, a button that became\n * disabled — left the boundary pointing at a node that was no longer\n * focusable.\n *\n * ## What is deliberately kept\n *\n * `ModalProps` is unchanged, so no consumer moves. `isOpen`/`onClose` stay the\n * API rather than becoming Base UI's `open`/`onOpenChange`: this is a\n * controlled dialog in both consumers, and a compound `Dialog.Root`/`Trigger`\n * composition would be a breaking change bought for nothing they need.\n *\n * Focus goes to the **popup**, not to the first tabbable element inside it.\n * Base UI's default would land on the close button, which a screen reader\n * announces as \"Close dialog, button\" — the dialog's own title never read.\n * Pointing `initialFocus` at the popup restores what the hand-rolled\n * `tabIndex={-1}` container was for.\n */\nexport const Modal = forwardRef<HTMLDivElement, ModalProps>(function Modal(\n {\n isOpen,\n onClose,\n title,\n children,\n footer,\n closeOnBackdropClick = true,\n className,\n ...props\n },\n ref,\n) {\n const slots = dialogSurface();\n const [popup, attachPopup] = usePopupRef(ref);\n\n const portalTheme = usePortalThemeAttribute();\n return (\n <Dialog.Root\n open={isOpen}\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n // Base UI's own name for \"a click outside does not dismiss this\". The\n // prop is inverted because the published API asks the question the other\n // way round, and the published API is the one that stays.\n disablePointerDismissal={!closeOnBackdropClick}\n >\n <Dialog.Portal {...portalTheme}>\n <Dialog.Backdrop data-slot=\"modal-backdrop\" className={slots.backdrop()} />\n <Dialog.Viewport data-slot=\"modal-viewport\" className={slots.viewport()}>\n <Dialog.Popup\n ref={attachPopup}\n // Base UI's default is the first tabbable element, which here is\n // the close button. Pointing at the popup itself is what the\n // hand-rolled `tabIndex={-1}` container did, and it is why the\n // dialog's own title is what gets announced.\n // The function form, not the ref form: Base UI reads a `RefObject`\n // early enough that the popup is sometimes still `null`, and it\n // then silently falls back to the first tabbable element. Reading\n // `.current` at focus time is the same intent without the race.\n initialFocus={() => popup.current}\n data-slot=\"modal\"\n // `class`, not an appended string: a caller's `max-w-3xl` would\n // otherwise emit alongside `max-w-lg` and leave CSS source order to\n // pick one. A dialog is sized by its caller often enough that this\n // is the prop most likely to be reached for.\n className={cn(slots.popup(), className)}\n {...props}\n >\n <div data-slot=\"modal-header\" className={slots.header()}>\n {/*\n * The heading text lives on the render element rather than on\n * `Title`: Base UI keeps the element's own children, and the\n * a11y lint rule can only see content it can read literally.\n */}\n <Dialog.Title\n data-slot=\"modal-title\"\n className={slots.title()}\n render={<h3>[ {title} ]</h3>}\n />\n <Dialog.Close\n data-slot=\"modal-close\"\n className={slots.close()}\n aria-label=\"Close dialog\"\n >\n ×\n </Dialog.Close>\n </div>\n\n <div data-slot=\"modal-body\" className={slots.body()}>\n {children}\n </div>\n\n <div data-slot=\"modal-footer\" className={slots.footer()}>\n {footer || (\n <Button onClick={onClose} variant=\"tertiary\" bracketed size=\"sm\">\n CLOSE\n </Button>\n )}\n </div>\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n </Dialog.Root>\n );\n});\n"],"mappings":";;;AAEA,SAAS,kBAAkB;AAE3B,SAAS,cAAc;AACvB,SAAS,UAAU;AACnB,SAAS,+BAA+B;AACxC,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AAwFf,cA8BgB,YA9BhB;AA7BD,IAAM,QAAQ,WAAuC,SAASA,OACnE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,uBAAuB;AAAA,EACvB;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,QAAQ,cAAc;AAC5B,QAAM,CAAC,OAAO,WAAW,IAAI,YAAY,GAAG;AAE5C,QAAM,cAAc,wBAAwB;AAC5C,SACE;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,MAAM;AAAA,MACN,cAAc,CAAC,SAAS;AACtB,YAAI,CAAC,KAAM,SAAQ;AAAA,MACrB;AAAA,MAIA,yBAAyB,CAAC;AAAA,MAE1B,+BAAC,OAAO,QAAP,EAAe,GAAG,aACjB;AAAA,4BAAC,OAAO,UAAP,EAAgB,aAAU,kBAAiB,WAAW,MAAM,SAAS,GAAG;AAAA,QACzE,oBAAC,OAAO,UAAP,EAAgB,aAAU,kBAAiB,WAAW,MAAM,SAAS,GACpE;AAAA,UAAC,OAAO;AAAA,UAAP;AAAA,YACC,KAAK;AAAA,YASL,cAAc,MAAM,MAAM;AAAA,YAC1B,aAAU;AAAA,YAKV,WAAW,GAAG,MAAM,MAAM,GAAG,SAAS;AAAA,YACrC,GAAG;AAAA,YAEJ;AAAA,mCAAC,SAAI,aAAU,gBAAe,WAAW,MAAM,OAAO,GAMpD;AAAA;AAAA,kBAAC,OAAO;AAAA,kBAAP;AAAA,oBACC,aAAU;AAAA,oBACV,WAAW,MAAM,MAAM;AAAA,oBACvB,QAAQ,qBAAC,QAAG;AAAA;AAAA,sBAAG;AAAA,sBAAM;AAAA,uBAAE;AAAA;AAAA,gBACzB;AAAA,gBACA;AAAA,kBAAC,OAAO;AAAA,kBAAP;AAAA,oBACC,aAAU;AAAA,oBACV,WAAW,MAAM,MAAM;AAAA,oBACvB,cAAW;AAAA,oBACZ;AAAA;AAAA,gBAED;AAAA,iBACF;AAAA,cAEA,oBAAC,SAAI,aAAU,cAAa,WAAW,MAAM,KAAK,GAC/C,UACH;AAAA,cAEA,oBAAC,SAAI,aAAU,gBAAe,WAAW,MAAM,OAAO,GACnD,oBACC,oBAAC,UAAO,SAAS,SAAS,SAAQ,YAAW,WAAS,MAAC,MAAK,MAAK,mBAEjE,GAEJ;AAAA;AAAA;AAAA,QACF,GACF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["Modal"]}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/components/NerdIcon.tsx
|
|
2
|
+
import { recipe } from "../lib/recipe.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var NERD_GLYPHS = {
|
|
5
|
+
// Navigation & Sorting
|
|
6
|
+
"sort": "\uF0DC",
|
|
7
|
+
//
|
|
8
|
+
"sort-asc": "\uF0DE",
|
|
9
|
+
//
|
|
10
|
+
"sort-desc": "\uF0DD",
|
|
11
|
+
//
|
|
12
|
+
"chevron-right": "\uF054",
|
|
13
|
+
//
|
|
14
|
+
"chevron-left": "\uF053",
|
|
15
|
+
//
|
|
16
|
+
"chevron-up": "\uF077",
|
|
17
|
+
//
|
|
18
|
+
"chevron-down": "\uF078",
|
|
19
|
+
//
|
|
20
|
+
"arrow-right": "\uF061",
|
|
21
|
+
//
|
|
22
|
+
"arrow-left": "\uF060",
|
|
23
|
+
//
|
|
24
|
+
"arrow-up": "\uF062",
|
|
25
|
+
//
|
|
26
|
+
"arrow-down": "\uF063",
|
|
27
|
+
//
|
|
28
|
+
"expand": "\uF065",
|
|
29
|
+
//
|
|
30
|
+
"compress": "\uF066",
|
|
31
|
+
//
|
|
32
|
+
// Git & DevOps
|
|
33
|
+
"git-branch": "\uF126",
|
|
34
|
+
//
|
|
35
|
+
"git-commit": "\uF417",
|
|
36
|
+
//
|
|
37
|
+
"git-merge": "\uF419",
|
|
38
|
+
//
|
|
39
|
+
"git-pull-request": "\uF407",
|
|
40
|
+
//
|
|
41
|
+
"docker": "\uF308",
|
|
42
|
+
//
|
|
43
|
+
"kubernetes": "\u{F10FE}",
|
|
44
|
+
"aws": "\u{F0E0F}",
|
|
45
|
+
"linux": "\uF17C",
|
|
46
|
+
//
|
|
47
|
+
"apple": "\uF179",
|
|
48
|
+
//
|
|
49
|
+
"windows": "\uF17A",
|
|
50
|
+
//
|
|
51
|
+
"database": "\uF1C0",
|
|
52
|
+
//
|
|
53
|
+
"server": "\uF233",
|
|
54
|
+
//
|
|
55
|
+
"terminal": "\uF120",
|
|
56
|
+
//
|
|
57
|
+
"code": "\uF121",
|
|
58
|
+
//
|
|
59
|
+
"bug": "\uF188",
|
|
60
|
+
//
|
|
61
|
+
"cpu": "\uF4BC",
|
|
62
|
+
"ram": "\uEFC5",
|
|
63
|
+
// System & Chrome Actions
|
|
64
|
+
"search": "\uF002",
|
|
65
|
+
//
|
|
66
|
+
"settings": "\uF013",
|
|
67
|
+
//
|
|
68
|
+
"check": "\uF00C",
|
|
69
|
+
//
|
|
70
|
+
"close": "\uF00D",
|
|
71
|
+
//
|
|
72
|
+
"alert": "\uF071",
|
|
73
|
+
//
|
|
74
|
+
"info": "\uF05A",
|
|
75
|
+
//
|
|
76
|
+
"help": "\uF059",
|
|
77
|
+
//
|
|
78
|
+
"refresh": "\uF021",
|
|
79
|
+
//
|
|
80
|
+
"sync": "\uF46A",
|
|
81
|
+
//
|
|
82
|
+
"copy": "\uF0C5",
|
|
83
|
+
//
|
|
84
|
+
"external-link": "\uF08E",
|
|
85
|
+
//
|
|
86
|
+
"trash": "\uF1F8",
|
|
87
|
+
//
|
|
88
|
+
"edit": "\uF044",
|
|
89
|
+
//
|
|
90
|
+
"plus": "\uF067",
|
|
91
|
+
//
|
|
92
|
+
"minus": "\uF068",
|
|
93
|
+
//
|
|
94
|
+
"filter": "\uF0B0",
|
|
95
|
+
//
|
|
96
|
+
"folder": "\uF07B",
|
|
97
|
+
//
|
|
98
|
+
"folder-open": "\uF07C",
|
|
99
|
+
// 📂
|
|
100
|
+
"file": "\uF15B",
|
|
101
|
+
//
|
|
102
|
+
"file-code": "\uF1C9",
|
|
103
|
+
//
|
|
104
|
+
"lock": "\uF023",
|
|
105
|
+
//
|
|
106
|
+
"unlock": "\uF09C",
|
|
107
|
+
//
|
|
108
|
+
"link": "\uF0C1",
|
|
109
|
+
//
|
|
110
|
+
"play": "\uF04B",
|
|
111
|
+
//
|
|
112
|
+
"pause": "\uF04C",
|
|
113
|
+
//
|
|
114
|
+
"stop": "\uF04D"
|
|
115
|
+
//
|
|
116
|
+
};
|
|
117
|
+
var iconStyles = recipe({
|
|
118
|
+
slots: {
|
|
119
|
+
root: "inline-flex items-center font-mono select-none",
|
|
120
|
+
symbol: "font-mono text-center inline-block leading-none"
|
|
121
|
+
},
|
|
122
|
+
variants: {
|
|
123
|
+
accent: {
|
|
124
|
+
primary: { symbol: "text-accent-primary" },
|
|
125
|
+
secondary: { symbol: "text-accent-secondary" },
|
|
126
|
+
tertiary: { symbol: "text-accent-tertiary" },
|
|
127
|
+
success: { symbol: "text-intent-success" },
|
|
128
|
+
warning: { symbol: "text-intent-warning" },
|
|
129
|
+
danger: { symbol: "text-intent-danger" },
|
|
130
|
+
info: { symbol: "text-intent-info" },
|
|
131
|
+
muted: { symbol: "text-content-muted" }
|
|
132
|
+
},
|
|
133
|
+
size: {
|
|
134
|
+
xs: { symbol: "text-[10px]" },
|
|
135
|
+
sm: { symbol: "text-xs" },
|
|
136
|
+
md: { symbol: "text-sm" },
|
|
137
|
+
lg: { symbol: "text-base" },
|
|
138
|
+
xl: { symbol: "text-lg" }
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
defaultVariants: {
|
|
142
|
+
size: "md"
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
function NerdIcon({
|
|
146
|
+
name,
|
|
147
|
+
accent,
|
|
148
|
+
size,
|
|
149
|
+
bracketed = false,
|
|
150
|
+
label,
|
|
151
|
+
className,
|
|
152
|
+
...props
|
|
153
|
+
}) {
|
|
154
|
+
const glyph = NERD_GLYPHS[name] || "?";
|
|
155
|
+
const styles = iconStyles({ accent, size });
|
|
156
|
+
return /* @__PURE__ */ jsxs(
|
|
157
|
+
"span",
|
|
158
|
+
{
|
|
159
|
+
"data-slot": "nerd-icon",
|
|
160
|
+
className: styles.root({ class: className }),
|
|
161
|
+
role: label ? "img" : void 0,
|
|
162
|
+
"aria-label": label,
|
|
163
|
+
"aria-hidden": !label,
|
|
164
|
+
...props,
|
|
165
|
+
children: [
|
|
166
|
+
bracketed && /* @__PURE__ */ jsx("span", { className: "text-content-muted font-bold mr-0.5", children: "[" }),
|
|
167
|
+
/* @__PURE__ */ jsx("span", { className: styles.symbol(), children: glyph }),
|
|
168
|
+
bracketed && /* @__PURE__ */ jsx("span", { className: "text-content-muted font-bold ml-0.5", children: "]" })
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
NERD_GLYPHS,
|
|
175
|
+
NerdIcon
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=NerdIcon.js.map
|