@misoto22/design 0.7.0 → 0.8.0
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/AGENTS.md +87 -0
- package/CLAUDE.md +1 -0
- package/README.md +50 -5
- package/bin/misoto22-design.mjs +59 -13
- package/dist/agent/Accordion.md +24 -1
- package/dist/agent/Alert.md +23 -1
- package/dist/agent/AppShell.md +30 -2
- package/dist/agent/ArchitectureFigure.md +25 -2
- package/dist/agent/AreaChart.md +27 -2
- package/dist/agent/Article.md +26 -3
- package/dist/agent/AspectRatio.md +57 -0
- package/dist/agent/Avatar.md +25 -5
- package/dist/agent/Badge.md +22 -1
- package/dist/agent/BarChart.md +29 -2
- package/dist/agent/BarList.md +27 -1
- package/dist/agent/BigNumber.md +26 -2
- package/dist/agent/BoxPlot.md +24 -1
- package/dist/agent/Breadcrumb.md +24 -2
- package/dist/agent/BulletChart.md +23 -1
- package/dist/agent/Button.md +25 -1
- package/dist/agent/Calendar.md +37 -3
- package/dist/agent/Card.md +24 -2
- package/dist/agent/Checkbox.md +23 -2
- package/dist/agent/Code.md +50 -0
- package/dist/agent/CodeBlock.md +74 -0
- package/dist/agent/Collapsible.md +22 -1
- package/dist/agent/Combobox.md +29 -3
- package/dist/agent/Command.md +25 -2
- package/dist/agent/ComposedChart.md +26 -2
- package/dist/agent/ContextMenu.md +37 -4
- package/dist/agent/DataflowFigure.md +22 -1
- package/dist/agent/DatePicker.md +34 -6
- package/dist/agent/DescriptionList.md +68 -0
- package/dist/agent/Diagram.md +27 -3
- package/dist/agent/DiagramCanvas.md +24 -1
- package/dist/agent/DiagramExportMenu.md +29 -3
- package/dist/agent/DiagramInspector.md +23 -1
- package/dist/agent/DiagramLegend.md +22 -1
- package/dist/agent/DiagramMinimap.md +24 -4
- package/dist/agent/DiagramToolbar.md +23 -1
- package/dist/agent/Dialog.md +26 -3
- package/dist/agent/DropdownMenu.md +45 -6
- package/dist/agent/EmptyState.md +26 -1
- package/dist/agent/ErrorState.md +27 -2
- package/dist/agent/Facet.md +25 -1
- package/dist/agent/Field.md +45 -4
- package/dist/agent/FigureBand.md +24 -1
- package/dist/agent/FloatingIconButton.md +23 -1
- package/dist/agent/FunnelChart.md +23 -1
- package/dist/agent/Heading.md +64 -0
- package/dist/agent/Heatmap.md +25 -1
- package/dist/agent/Histogram.md +26 -2
- package/dist/agent/Input.md +22 -1
- package/dist/agent/Kbd.md +19 -1
- package/dist/agent/LifecycleFigure.md +22 -1
- package/dist/agent/LineChart.md +26 -2
- package/dist/agent/LinkArrow.md +20 -1
- package/dist/agent/Markdown.md +68 -0
- package/dist/agent/NativeSelect.md +23 -2
- package/dist/agent/NavItem.md +21 -1
- package/dist/agent/Pagination.md +28 -2
- package/dist/agent/PieChart.md +26 -2
- package/dist/agent/Popover.md +23 -1
- package/dist/agent/Progress.md +27 -3
- package/dist/agent/RadarChart.md +26 -2
- package/dist/agent/RadialChart.md +26 -3
- package/dist/agent/RadioGroup.md +23 -2
- package/dist/agent/SankeyChart.md +25 -2
- package/dist/agent/ScatterChart.md +27 -2
- package/dist/agent/ScrollArea.md +24 -3
- package/dist/agent/SearchableMenu.md +23 -1
- package/dist/agent/Select.md +29 -3
- package/dist/agent/Separator.md +28 -4
- package/dist/agent/SequenceFigure.md +23 -1
- package/dist/agent/Sheet.md +25 -2
- package/dist/agent/Skeleton.md +24 -2
- package/dist/agent/Slider.md +27 -4
- package/dist/agent/Sparkline.md +23 -1
- package/dist/agent/Spinner.md +24 -2
- package/dist/agent/StatusDot.md +20 -1
- package/dist/agent/StatusPill.md +28 -2
- package/dist/agent/Steps.md +24 -1
- package/dist/agent/Switch.md +21 -1
- package/dist/agent/Table.md +26 -3
- package/dist/agent/Tabs.md +24 -2
- package/dist/agent/Tag.md +40 -9
- package/dist/agent/Text.md +65 -0
- package/dist/agent/Textarea.md +20 -1
- package/dist/agent/Timestamp.md +64 -0
- package/dist/agent/Toast.md +26 -2
- package/dist/agent/ToggleGroup.md +24 -2
- package/dist/agent/Toolbar.md +71 -0
- package/dist/agent/Tooltip.md +23 -1
- package/dist/agent/TreemapChart.md +24 -1
- package/dist/agent/WaterfallChart.md +24 -1
- package/dist/agent/WorkflowFigure.md +24 -2
- package/dist/agent/catalog.json +5602 -745
- package/dist/agent/index.md +13 -4
- package/dist/article.css +16 -4
- package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
- package/dist/charts/AreaChart/AreaChart.js +12 -13
- package/dist/charts/AreaChart/AreaChart.js.map +1 -1
- package/dist/charts/BarChart/BarChart.d.ts +14 -3
- package/dist/charts/BarChart/BarChart.js +20 -12
- package/dist/charts/BarChart/BarChart.js.map +1 -1
- package/dist/charts/BarList/BarList.d.ts +9 -1
- package/dist/charts/BarList/BarList.js +9 -1
- package/dist/charts/BarList/BarList.js.map +1 -1
- package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
- package/dist/charts/BigNumber/BigNumber.js +25 -5
- package/dist/charts/BigNumber/BigNumber.js.map +1 -1
- package/dist/charts/BoxPlot/BoxPlot.js +4 -2
- package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
- package/dist/charts/BulletChart/BulletChart.js +28 -7
- package/dist/charts/BulletChart/BulletChart.js.map +1 -1
- package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
- package/dist/charts/ComposedChart/ComposedChart.js +10 -10
- package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
- package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
- package/dist/charts/FunnelChart/FunnelChart.js +6 -16
- package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
- package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
- package/dist/charts/Heatmap/Heatmap.js +20 -11
- package/dist/charts/Heatmap/Heatmap.js.map +1 -1
- package/dist/charts/Histogram/Histogram.d.ts +24 -1
- package/dist/charts/Histogram/Histogram.js +43 -11
- package/dist/charts/Histogram/Histogram.js.map +1 -1
- package/dist/charts/LineChart/LineChart.d.ts +9 -2
- package/dist/charts/LineChart/LineChart.js +11 -12
- package/dist/charts/LineChart/LineChart.js.map +1 -1
- package/dist/charts/PieChart/PieChart.d.ts +15 -3
- package/dist/charts/PieChart/PieChart.js +14 -6
- package/dist/charts/PieChart/PieChart.js.map +1 -1
- package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
- package/dist/charts/RadarChart/RadarChart.js +11 -9
- package/dist/charts/RadarChart/RadarChart.js.map +1 -1
- package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
- package/dist/charts/RadialChart/RadialChart.js +28 -6
- package/dist/charts/RadialChart/RadialChart.js.map +1 -1
- package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
- package/dist/charts/SankeyChart/SankeyChart.js +14 -6
- package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
- package/dist/charts/ScatterChart/ScatterChart.js +21 -15
- package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
- package/dist/charts/Sparkline/Sparkline.js +5 -3
- package/dist/charts/Sparkline/Sparkline.js.map +1 -1
- package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
- package/dist/charts/TreemapChart/TreemapChart.js +20 -11
- package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
- package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
- package/dist/charts/lib/export.d.ts +1 -0
- package/dist/charts/lib/figure.d.ts +21 -1
- package/dist/charts/lib/figure.js +5 -2
- package/dist/charts/lib/figure.js.map +1 -1
- package/dist/charts/lib/scale.d.ts +23 -0
- package/dist/charts/lib/scale.js +13 -0
- package/dist/charts/lib/scale.js.map +1 -0
- package/dist/charts/lib/selection.d.ts +20 -0
- package/dist/charts/lib/selection.js +18 -0
- package/dist/charts/lib/selection.js.map +1 -0
- package/dist/charts/lib/toolbar.d.ts +1 -0
- package/dist/components/Accordion/Accordion.js +8 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/AppShell/AppShell.d.ts +6 -0
- package/dist/components/AppShell/AppShell.js +34 -4
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Article/Article.d.ts +11 -4
- package/dist/components/Article/Article.js +13 -0
- package/dist/components/Article/Article.js.map +1 -1
- package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
- package/dist/components/AspectRatio/AspectRatio.js +33 -0
- package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
- package/dist/components/Avatar/Avatar.d.ts +12 -3
- package/dist/components/Avatar/Avatar.js +6 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/Button/Button.js +14 -0
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.d.ts +29 -3
- package/dist/components/Calendar/Calendar.js +60 -12
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.d.ts +6 -0
- package/dist/components/Card/Card.js +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +5 -2
- package/dist/components/Checkbox/Checkbox.js +12 -2
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Code/Code.d.ts +30 -0
- package/dist/components/Code/Code.js +21 -0
- package/dist/components/Code/Code.js.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
- package/dist/components/CodeBlock/CodeBlock.js +123 -0
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
- package/dist/components/Collapsible/Collapsible.js +10 -2
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts +15 -1
- package/dist/components/Combobox/Combobox.js +25 -5
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.d.ts +4 -2
- package/dist/components/Command/Command.js +13 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
- package/dist/components/ContextMenu/ContextMenu.js +32 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.d.ts +36 -4
- package/dist/components/DatePicker/DatePicker.js +55 -12
- package/dist/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
- package/dist/components/DescriptionList/DescriptionList.js +35 -0
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
- package/dist/components/Diagram/Diagram.d.ts +8 -3
- package/dist/components/Diagram/Diagram.js +82 -10
- package/dist/components/Diagram/Diagram.js.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +18 -3
- package/dist/components/Dialog/Dialog.js +20 -2
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
- package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.d.ts +25 -1
- package/dist/components/EmptyState/EmptyState.js +3 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.d.ts +22 -1
- package/dist/components/ErrorState/ErrorState.js +11 -2
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/Field/Field.d.ts +70 -3
- package/dist/components/Field/Field.js +53 -12
- package/dist/components/Field/Field.js.map +1 -1
- package/dist/components/Field/field-control.d.ts +31 -0
- package/dist/components/Field/field-control.js +12 -0
- package/dist/components/Field/field-control.js.map +1 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
- package/dist/components/Heading/Heading.d.ts +62 -0
- package/dist/components/Heading/Heading.js +44 -0
- package/dist/components/Heading/Heading.js.map +1 -0
- package/dist/components/Markdown/Markdown.d.ts +139 -0
- package/dist/components/Markdown/Markdown.js +135 -0
- package/dist/components/Markdown/Markdown.js.map +1 -0
- package/dist/components/Markdown/inline.d.ts +29 -0
- package/dist/components/Markdown/inline.js +74 -0
- package/dist/components/Markdown/inline.js.map +1 -0
- package/dist/components/Markdown/nodes.d.ts +65 -0
- package/dist/components/Markdown/nodes.js +1 -0
- package/dist/components/Markdown/nodes.js.map +1 -0
- package/dist/components/Markdown/parse.d.ts +24 -0
- package/dist/components/Markdown/parse.js +110 -0
- package/dist/components/Markdown/parse.js.map +1 -0
- package/dist/components/Markdown/slug.d.ts +15 -0
- package/dist/components/Markdown/slug.js +7 -0
- package/dist/components/Markdown/slug.js.map +1 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
- package/dist/components/NativeSelect/NativeSelect.js +2 -3
- package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
- package/dist/components/Pagination/Pagination.d.ts +24 -1
- package/dist/components/Pagination/Pagination.js +6 -3
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Progress/Progress.d.ts +17 -5
- package/dist/components/Progress/Progress.js +18 -6
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
- package/dist/components/RadioGroup/RadioGroup.js +11 -1
- package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
- package/dist/components/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
- package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
- package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
- package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
- package/dist/components/Select/Select.d.ts +18 -2
- package/dist/components/Select/Select.js +20 -3
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Separator/Separator.d.ts +21 -3
- package/dist/components/Separator/Separator.js +9 -1
- package/dist/components/Separator/Separator.js.map +1 -1
- package/dist/components/Sheet/Sheet.d.ts +3 -0
- package/dist/components/Sheet/Sheet.js +11 -2
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Skeleton/Skeleton.d.ts +8 -1
- package/dist/components/Skeleton/Skeleton.js +1 -1
- package/dist/components/Skeleton/Skeleton.js.map +1 -1
- package/dist/components/Slider/Slider.d.ts +21 -3
- package/dist/components/Slider/Slider.js +54 -27
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts +8 -0
- package/dist/components/Spinner/Spinner.js +3 -2
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/StatusPill/StatusPill.d.ts +5 -0
- package/dist/components/StatusPill/StatusPill.js +6 -0
- package/dist/components/StatusPill/StatusPill.js.map +1 -1
- package/dist/components/Table/Table.d.ts +6 -0
- package/dist/components/Table/Table.js +3 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tag/Tag.d.ts +68 -9
- package/dist/components/Tag/Tag.js +50 -10
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Text/Text.d.ts +62 -0
- package/dist/components/Text/Text.js +30 -0
- package/dist/components/Text/Text.js.map +1 -0
- package/dist/components/Timestamp/Timestamp.d.ts +59 -0
- package/dist/components/Timestamp/Timestamp.js +56 -0
- package/dist/components/Timestamp/Timestamp.js.map +1 -0
- package/dist/components/Toast/Toast.d.ts +9 -1
- package/dist/components/Toast/Toast.js +27 -4
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
- package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Toolbar/Toolbar.d.ts +55 -0
- package/dist/components/Toolbar/Toolbar.js +43 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
- package/dist/diagrams/index.d.ts +1 -0
- package/dist/diagrams/index.js +10 -0
- package/dist/diagrams/index.js.map +1 -1
- package/dist/diagrams/lib/dev.d.ts +61 -0
- package/dist/diagrams/lib/dev.js +96 -0
- package/dist/diagrams/lib/dev.js.map +1 -0
- package/dist/diagrams/lib/frame.d.ts +48 -7
- package/dist/diagrams/lib/frame.js +17 -2
- package/dist/diagrams/lib/frame.js.map +1 -1
- package/dist/diagrams/lib/wires.js.map +1 -1
- package/dist/diagrams/spec.d.ts +64 -12
- package/dist/index.d.ts +16 -4
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/keyframes.css +57 -3
- package/dist/lib/overlay-container.d.ts +14 -4
- package/dist/lib/overlay-container.js.map +1 -1
- package/dist/lib/warn.d.ts +73 -0
- package/dist/lib/warn.js +38 -0
- package/dist/lib/warn.js.map +1 -0
- package/dist/semantic.css +17 -6
- package/dist/styles.css +1 -1
- package/dist/tokens.css +20 -2
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +39 -36
- package/dist/tokens.json +39 -36
- package/llms.txt +35 -0
- package/package.json +5 -2
- package/skills/misoto22-design/evals/evals.json +84 -0
- package/skills/misoto22-design/rules/a11y.md +55 -3
- package/skills/misoto22-design/rules/forms.md +5 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ContextMenu/ContextMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as ContextMenuPrimitive from '@radix-ui/react-context-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport type
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ContextMenu/ContextMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as ContextMenuPrimitive from '@radix-ui/react-context-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport { isValidElement, useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/**\n * True for a component type — `icon={Copy}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (icon === undefined || icon === null || icon === false) return null\n if (!isIconComponent(icon)) {\n return (\n <span aria-hidden className=\"grid size-4 shrink-0 place-items-center [&_svg]:size-4\">\n {icon}\n </span>\n )\n }\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} className=\"shrink-0\" aria-hidden />\n}\n\n/** Radix ContextMenu root and trigger, as typed passthroughs. */\nexport const ContextMenu = ContextMenuPrimitive.Root\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger\n\n/**\n * The menu a right-click opens.\n *\n * Never the only way to reach an action. A context menu is opened by a\n * secondary pointer button or a keyboard's own menu key, and a meaningful share\n * of readers have neither — a touch user, someone on a trackpad they have not\n * configured, anyone driving the page by keyboard alone. Whatever is in here\n * belongs somewhere reachable too: a row's overflow button, a toolbar.\n *\n * @example\n * <ContextMenu>\n * <ContextMenuTrigger asChild><Card>Right-click me</Card></ContextMenuTrigger>\n * <ContextMenuContent>\n * <ContextMenuItem icon={Copy}>Copy</ContextMenuItem>\n * </ContextMenuContent>\n * </ContextMenu>\n */\nexport function ContextMenuContent({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Content>) {\n const container = useOverlayContainer()\n\n return (\n <ContextMenuPrimitive.Portal container={container ?? undefined}>\n <ContextMenuPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur',\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n className,\n )}\n {...rest}\n />\n </ContextMenuPrimitive.Portal>\n )\n}\n\nexport interface ContextMenuItemProps\n extends ComponentProps<typeof ContextMenuPrimitive.Item> {\n /**\n * Optional leading icon. Either spelling — `icon={Copy}` passes the component\n * and this sizes it, `icon={<Copy size={16} />}` passes the element and this\n * places it.\n */\n icon?: LucideIcon | ReactNode\n /** Paints the row as destructive. Use for delete, revoke, disconnect. */\n destructive?: boolean\n}\n\n/** A row. Highlight follows `data-highlighted`, which covers hover and keyboard. */\nexport function ContextMenuItem({\n icon,\n destructive = false,\n className,\n children,\n ...rest\n}: ContextMenuItemProps) {\n return (\n <ContextMenuPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n destructive ? 'text-(--danger)' : 'text-(--ink-2) data-[highlighted]:text-(--ink)',\n className,\n )}\n {...rest}\n >\n {iconNode(icon)}\n {children}\n </ContextMenuPrimitive.Item>\n )\n}\n\n/** Hairline divider between groups. */\nexport function ContextMenuSeparator({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n return (\n <ContextMenuPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...rest} />\n )\n}\n\n/**\n * Mono eyebrow heading, on its own.\n *\n * Visual only: Radix renders it as a bare `<div>` with no role. Reach for\n * `ContextMenuGroup` when the eyebrow is a HEADING over rows; this is right for\n * a line that heads nothing.\n */\nexport function ContextMenuLabel({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Label>) {\n return (\n <ContextMenuPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...rest}\n />\n )\n}\n\nexport interface ContextMenuGroupProps\n extends ComponentProps<typeof ContextMenuPrimitive.Group> {\n /** The eyebrow over the rows, and the group's accessible name. */\n label?: ReactNode\n}\n\n/**\n * A named section of a menu.\n *\n * The eyebrow alone was a picture of a heading: Radix's `MenuLabel` carries no\n * role and no `aria-labelledby` wiring, so a sighted reader saw sections and a\n * screen-reader user got one undifferentiated list. This renders the group,\n * renders the label inside it, and points the one at the other.\n */\nexport function ContextMenuGroup({ label, children, ...rest }: ContextMenuGroupProps) {\n const id = useId()\n\n return (\n <ContextMenuPrimitive.Group\n aria-labelledby={label === undefined ? undefined : id}\n {...rest}\n >\n {label !== undefined && <ContextMenuLabel id={id}>{label}</ContextMenuLabel>}\n {children}\n </ContextMenuPrimitive.Group>\n )\n}\n\nexport default ContextMenu\n"],"mappings":";AA8BM,cA0EF,YA1EE;AA5BN,YAAY,0BAA0B;AAEtC,SAAS,gBAAgB,aAAkD;AAC3E,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAYpC,SAAS,gBAAgB,MAAkD;AACzE,MAAI,eAAe,IAAI,EAAG,QAAO;AACjC,MAAI,OAAO,SAAS,WAAY,QAAO;AACvC,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,SAAO,CAAC,MAAM,QAAQ,IAAI,KAAK,EAAE,OAAO,YAAY,SAAS,EAAE,UAAU;AAC3E;AAGA,SAAS,SAAS,MAAyC;AACzD,MAAI,SAAS,UAAa,SAAS,QAAQ,SAAS,MAAO,QAAO;AAClE,MAAI,CAAC,gBAAgB,IAAI,GAAG;AAC1B,WACE,oBAAC,UAAK,eAAW,MAAC,WAAU,0DACzB,gBACH;AAAA,EAEJ;AACA,QAAM,OAAO;AACb,SAAO,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,WAAU,YAAW,eAAW,MAAC;AAC5E;AAGO,MAAM,cAAc,qBAAqB;AACzC,MAAM,qBAAqB,qBAAqB;AAmBhD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAwD;AACtD,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,qBAAqB,QAArB,EAA4B,WAAW,aAAa,QACnD;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAeO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,oBAAoB;AAAA,QAClC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBAAS,IAAI;AAAA,QACb;AAAA;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA,GAAG;AACL,GAA0D;AACxD,SACE,oBAAC,qBAAqB,WAArB,EAA+B,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MAAM;AAEjG;AASO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAgBO,SAAS,iBAAiB,EAAE,OAAO,UAAU,GAAG,KAAK,GAA0B;AACpF,QAAM,KAAK,MAAM;AAEjB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,mBAAiB,UAAU,SAAY,SAAY;AAAA,MAClD,GAAG;AAAA,MAEH;AAAA,kBAAU,UAAa,oBAAC,oBAAiB,IAAS,iBAAM;AAAA,QACxD;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
|
|
@@ -24,11 +24,23 @@ interface DatePickerProps {
|
|
|
24
24
|
value?: Date;
|
|
25
25
|
defaultValue?: Date;
|
|
26
26
|
onValueChange?: (value: Date | undefined) => void;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Names the control. Required — the trigger's text is a value, not a label.
|
|
29
|
+
*
|
|
30
|
+
* Announced together with the printed date rather than instead of it, so
|
|
31
|
+
* `format` reaches a screen reader as well as the screen. Inside a `Field`
|
|
32
|
+
* with a label, that label names the trigger and this one is not repeated.
|
|
33
|
+
*/
|
|
28
34
|
label: string;
|
|
29
35
|
placeholder?: string;
|
|
30
36
|
disabled?: boolean;
|
|
31
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Days the reader may not choose.
|
|
39
|
+
*
|
|
40
|
+
* Reaches the calendar AND the shortcut rail: a preset landing on a blocked
|
|
41
|
+
* day is drawn unavailable and refuses the click, rather than committing a
|
|
42
|
+
* value the grid beside it would not accept.
|
|
43
|
+
*/
|
|
32
44
|
disabledDates?: ComponentProps<typeof Calendar>['disabled'];
|
|
33
45
|
/** How the chosen date is printed on the trigger. */
|
|
34
46
|
format?: (date: Date) => string;
|
|
@@ -38,6 +50,12 @@ interface DatePickerProps {
|
|
|
38
50
|
*/
|
|
39
51
|
presets?: boolean | DatePreset<Date>[];
|
|
40
52
|
className?: string;
|
|
53
|
+
/** The TRIGGER's id — the element a label points at. A `Field` sets it. */
|
|
54
|
+
id?: string;
|
|
55
|
+
/** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */
|
|
56
|
+
'aria-describedby'?: string;
|
|
57
|
+
/** Announced on the trigger. A `Field` sets it from `error`. */
|
|
58
|
+
'aria-invalid'?: boolean | 'true' | 'false';
|
|
41
59
|
}
|
|
42
60
|
/**
|
|
43
61
|
* A date, chosen from a calendar.
|
|
@@ -55,14 +73,22 @@ interface DatePickerProps {
|
|
|
55
73
|
* @example
|
|
56
74
|
* <Field label="Publish on"><DatePicker label="Publish on" /></Field>
|
|
57
75
|
*/
|
|
58
|
-
declare function DatePicker({ value, defaultValue, onValueChange, label, placeholder, disabled, disabledDates, format, presets, className, }: DatePickerProps): react.JSX.Element;
|
|
76
|
+
declare function DatePicker({ value, defaultValue, onValueChange, label, placeholder, disabled, disabledDates, format, presets, className, id, 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, }: DatePickerProps): react.JSX.Element;
|
|
59
77
|
interface DateRangePickerProps {
|
|
60
78
|
value?: DateRange;
|
|
61
79
|
defaultValue?: DateRange;
|
|
62
80
|
onValueChange?: (value: DateRange | undefined) => void;
|
|
81
|
+
/** Names the control. Announced together with the printed range, not instead of it. */
|
|
63
82
|
label: string;
|
|
64
83
|
placeholder?: string;
|
|
65
84
|
disabled?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Days the reader may not choose.
|
|
87
|
+
*
|
|
88
|
+
* Reaches the shortcut rail as well as the grid, at the ENDS of each preset
|
|
89
|
+
* range — a shortcut whose interior straddles a blocked day is still offered,
|
|
90
|
+
* the way the grid still lets a reader drag a range across one.
|
|
91
|
+
*/
|
|
66
92
|
disabledDates?: ComponentProps<typeof Calendar>['disabled'];
|
|
67
93
|
/** How many months are shown side by side. Falls back to one under `sm`. */
|
|
68
94
|
months?: number;
|
|
@@ -77,6 +103,12 @@ interface DateRangePickerProps {
|
|
|
77
103
|
*/
|
|
78
104
|
presets?: boolean | DatePreset<DateRange>[];
|
|
79
105
|
className?: string;
|
|
106
|
+
/** The TRIGGER's id — the element a label points at. A `Field` sets it. */
|
|
107
|
+
id?: string;
|
|
108
|
+
/** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */
|
|
109
|
+
'aria-describedby'?: string;
|
|
110
|
+
/** Announced on the trigger. A `Field` sets it from `error`. */
|
|
111
|
+
'aria-invalid'?: boolean | 'true' | 'false';
|
|
80
112
|
}
|
|
81
113
|
/**
|
|
82
114
|
* A span of dates — a stay, a reporting period, a filter.
|
|
@@ -93,6 +125,6 @@ interface DateRangePickerProps {
|
|
|
93
125
|
* @example
|
|
94
126
|
* <DateRangePicker label="Reporting period" />
|
|
95
127
|
*/
|
|
96
|
-
declare function DateRangePicker({ value, defaultValue, onValueChange, label, placeholder, disabled, disabledDates, months, format, presets, className, }: DateRangePickerProps): react.JSX.Element;
|
|
128
|
+
declare function DateRangePicker({ value, defaultValue, onValueChange, label, placeholder, disabled, disabledDates, months, format, presets, className, id, 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, }: DateRangePickerProps): react.JSX.Element;
|
|
97
129
|
|
|
98
130
|
export { DATE_PRESETS, DatePicker, type DatePickerProps, type DatePreset, DateRangePicker, type DateRangePickerProps, RANGE_PRESETS, DatePicker as default };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { CalendarDays } from "lucide-react";
|
|
4
|
-
import { useRef, useState } from "react";
|
|
4
|
+
import { useId, useRef, useState } from "react";
|
|
5
|
+
import { dateMatchModifiers } from "react-day-picker";
|
|
5
6
|
import { cn } from "../../lib/cn.js";
|
|
6
7
|
import { Calendar } from "../Calendar/Calendar.js";
|
|
8
|
+
import { useFieldControl } from "../Field/field-control.js";
|
|
7
9
|
import { Popover, PopoverContent, PopoverTrigger } from "../Popover/Popover.js";
|
|
8
10
|
const daysAgo = (n) => {
|
|
9
11
|
const to = /* @__PURE__ */ new Date();
|
|
@@ -61,7 +63,8 @@ const DATE_PRESETS = [
|
|
|
61
63
|
function PresetRail({
|
|
62
64
|
presets,
|
|
63
65
|
onPick,
|
|
64
|
-
label
|
|
66
|
+
label,
|
|
67
|
+
blocked
|
|
65
68
|
}) {
|
|
66
69
|
return /* @__PURE__ */ jsx(
|
|
67
70
|
"div",
|
|
@@ -73,8 +76,13 @@ function PresetRail({
|
|
|
73
76
|
"button",
|
|
74
77
|
{
|
|
75
78
|
type: "button",
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
disabled: blocked?.(preset.value()) ?? false,
|
|
80
|
+
onClick: () => {
|
|
81
|
+
const next = preset.value();
|
|
82
|
+
if (blocked?.(next)) return;
|
|
83
|
+
onPick(next);
|
|
84
|
+
},
|
|
85
|
+
className: "rounded-(--radius-sm) px-2.5 py-1.5 text-start text-[13px] text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
|
|
78
86
|
children: preset.label
|
|
79
87
|
},
|
|
80
88
|
preset.label
|
|
@@ -94,32 +102,53 @@ function DatePicker({
|
|
|
94
102
|
disabledDates,
|
|
95
103
|
format = formatDate,
|
|
96
104
|
presets,
|
|
97
|
-
className
|
|
105
|
+
className,
|
|
106
|
+
id,
|
|
107
|
+
"aria-describedby": describedBy,
|
|
108
|
+
"aria-invalid": ariaInvalid
|
|
98
109
|
}) {
|
|
99
110
|
const rail = presets === true ? DATE_PRESETS : presets || void 0;
|
|
100
111
|
const [open, setOpen] = useState(false);
|
|
101
112
|
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
102
113
|
const current = value ?? uncontrolled;
|
|
114
|
+
const field = useFieldControl();
|
|
115
|
+
const generated = useId();
|
|
116
|
+
const triggerId = id ?? generated;
|
|
117
|
+
const valueId = `${triggerId}-value`;
|
|
118
|
+
const nameId = field?.labelId ?? `${triggerId}-name`;
|
|
103
119
|
const choose = (next) => {
|
|
104
120
|
if (value === void 0) setUncontrolled(next);
|
|
105
121
|
onValueChange?.(next);
|
|
106
122
|
if (next) setOpen(false);
|
|
107
123
|
};
|
|
124
|
+
const refuses = (date) => disabledDates !== void 0 && dateMatchModifiers(date, disabledDates);
|
|
108
125
|
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
109
126
|
/* @__PURE__ */ jsxs(
|
|
110
127
|
PopoverTrigger,
|
|
111
128
|
{
|
|
112
|
-
|
|
129
|
+
id: triggerId,
|
|
130
|
+
"aria-labelledby": `${nameId} ${valueId}`,
|
|
131
|
+
"aria-describedby": describedBy,
|
|
132
|
+
"aria-invalid": ariaInvalid || void 0,
|
|
113
133
|
disabled,
|
|
114
134
|
className: cn(TRIGGER, current ? "text-(--ink)" : "text-(--ink-3-aa)", className),
|
|
115
135
|
children: [
|
|
116
|
-
|
|
136
|
+
field?.labelId == null && /* @__PURE__ */ jsx("span", { id: nameId, className: "sr-only", children: label }),
|
|
137
|
+
/* @__PURE__ */ jsx("span", { id: valueId, className: "truncate", children: current ? format(current) : placeholder }),
|
|
117
138
|
/* @__PURE__ */ jsx(CalendarDays, { size: 14, strokeWidth: 1.5, "aria-hidden": true, className: "shrink-0 text-(--ink-3-aa)" })
|
|
118
139
|
]
|
|
119
140
|
}
|
|
120
141
|
),
|
|
121
142
|
/* @__PURE__ */ jsx(PopoverContent, { label, align: "start", className: "w-auto overflow-hidden p-0", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row", children: [
|
|
122
|
-
rail && /* @__PURE__ */ jsx(
|
|
143
|
+
rail && /* @__PURE__ */ jsx(
|
|
144
|
+
PresetRail,
|
|
145
|
+
{
|
|
146
|
+
presets: rail,
|
|
147
|
+
onPick: choose,
|
|
148
|
+
label: `${label} shortcuts`,
|
|
149
|
+
blocked: refuses
|
|
150
|
+
}
|
|
151
|
+
),
|
|
123
152
|
/* @__PURE__ */ jsx(
|
|
124
153
|
Calendar,
|
|
125
154
|
{
|
|
@@ -144,12 +173,21 @@ function DateRangePicker({
|
|
|
144
173
|
months = 2,
|
|
145
174
|
format = formatDate,
|
|
146
175
|
presets = true,
|
|
147
|
-
className
|
|
176
|
+
className,
|
|
177
|
+
id,
|
|
178
|
+
"aria-describedby": describedBy,
|
|
179
|
+
"aria-invalid": ariaInvalid
|
|
148
180
|
}) {
|
|
149
181
|
const rail = presets === true ? RANGE_PRESETS : presets || void 0;
|
|
150
182
|
const [open, setOpen] = useState(false);
|
|
151
183
|
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
152
184
|
const current = value ?? uncontrolled;
|
|
185
|
+
const field = useFieldControl();
|
|
186
|
+
const generated = useId();
|
|
187
|
+
const triggerId = id ?? generated;
|
|
188
|
+
const valueId = `${triggerId}-value`;
|
|
189
|
+
const nameId = field?.labelId ?? `${triggerId}-name`;
|
|
190
|
+
const refuses = (range) => disabledDates !== void 0 && [range.from, range.to].some((date) => date != null && dateMatchModifiers(date, disabledDates));
|
|
153
191
|
const clicks = useRef(0);
|
|
154
192
|
const choose = (next) => {
|
|
155
193
|
clicks.current += 1;
|
|
@@ -167,11 +205,15 @@ function DateRangePicker({
|
|
|
167
205
|
/* @__PURE__ */ jsxs(
|
|
168
206
|
PopoverTrigger,
|
|
169
207
|
{
|
|
170
|
-
|
|
208
|
+
id: triggerId,
|
|
209
|
+
"aria-labelledby": `${nameId} ${valueId}`,
|
|
210
|
+
"aria-describedby": describedBy,
|
|
211
|
+
"aria-invalid": ariaInvalid || void 0,
|
|
171
212
|
disabled,
|
|
172
213
|
className: cn(TRIGGER, current?.from ? "text-(--ink)" : "text-(--ink-3-aa)", className),
|
|
173
214
|
children: [
|
|
174
|
-
/* @__PURE__ */ jsx("span", { className: "
|
|
215
|
+
field?.labelId == null && /* @__PURE__ */ jsx("span", { id: nameId, className: "sr-only", children: label }),
|
|
216
|
+
/* @__PURE__ */ jsx("span", { id: valueId, className: "truncate", children: printed }),
|
|
175
217
|
/* @__PURE__ */ jsx(CalendarDays, { size: 14, strokeWidth: 1.5, "aria-hidden": true, className: "shrink-0 text-(--ink-3-aa)" })
|
|
176
218
|
]
|
|
177
219
|
}
|
|
@@ -186,7 +228,8 @@ function DateRangePicker({
|
|
|
186
228
|
onValueChange?.(next);
|
|
187
229
|
setOpen(false);
|
|
188
230
|
},
|
|
189
|
-
label: `${label} shortcuts
|
|
231
|
+
label: `${label} shortcuts`,
|
|
232
|
+
blocked: refuses
|
|
190
233
|
}
|
|
191
234
|
),
|
|
192
235
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["'use client'\n\nimport { CalendarDays } from 'lucide-react'\nimport { useRef, useState, type ComponentProps } from 'react'\nimport type { DateRange } from 'react-day-picker'\nimport { cn } from '../../lib/cn'\nimport { Calendar } from '../Calendar/Calendar'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport type { DateRange }\n\n/** A named shortcut in the rail beside the calendar. */\nexport interface DatePreset<T> {\n label: string\n /** Computed when clicked, not when rendered — \"today\" must mean today. */\n value: () => T\n}\n\n/** `n` days back from today, inclusive of today. */\nconst daysAgo = (n: number): DateRange => {\n const to = new Date()\n const from = new Date()\n from.setDate(from.getDate() - (n - 1))\n return { from, to }\n}\n\n/**\n * The shortcuts a range picker is asked for on nearly every screen it appears\n * on, so they ship rather than being rebuilt per dashboard.\n *\n * Computed on click: a preset list built at render time freezes \"today\" at\n * whenever the page loaded, which is wrong for anything left open overnight.\n */\nexport const RANGE_PRESETS: DatePreset<DateRange>[] = [\n { label: 'Last 7 days', value: () => daysAgo(7) },\n { label: 'Last 30 days', value: () => daysAgo(30) },\n { label: 'Last 90 days', value: () => daysAgo(90) },\n { label: 'Last 12 months', value: () => daysAgo(365) },\n {\n label: 'Month to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), to.getMonth(), 1), to }\n },\n },\n {\n label: 'Year to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), 0, 1), to }\n },\n },\n]\n\n/** The single-date equivalent. */\nexport const DATE_PRESETS: DatePreset<Date>[] = [\n { label: 'Today', value: () => new Date() },\n {\n label: 'Tomorrow',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 1)\n return date\n },\n },\n {\n label: 'In a week',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 7)\n return date\n },\n },\n {\n label: 'In a month',\n value: () => {\n const date = new Date()\n date.setMonth(date.getMonth() + 1)\n return date\n },\n },\n]\n\n/**\n * The rail of shortcuts.\n *\n * A `<nav>`-less list of plain buttons rather than a menu: they set the same\n * value the grid beside them sets, so they are part of one control and should\n * Tab in the same pass rather than opening something.\n */\nfunction PresetRail<T>({\n presets,\n onPick,\n label,\n}: {\n presets: DatePreset<T>[]\n onPick: (value: T) => void\n label: string\n}) {\n return (\n <div\n role=\"group\"\n aria-label={label}\n className=\"flex shrink-0 flex-col gap-0.5 border-b border-(--rule) p-2 sm:border-b-0 sm:border-e max-sm:flex-row max-sm:flex-wrap\"\n >\n {presets.map((preset) => (\n <button\n key={preset.label}\n type=\"button\"\n onClick={() => onPick(preset.value())}\n className=\"rounded-(--radius-sm) px-2.5 py-1.5 text-start text-[13px] text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n {preset.label}\n </button>\n ))}\n </div>\n )\n}\n\nconst TRIGGER =\n 'flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\n/**\n * The visitor's own locale, not a fixed `dd/mm/yyyy`.\n *\n * `03/04` is March the fourth in one country and the third of April in the\n * next, and there is no format that is unambiguous to everyone. `Intl` already\n * knows which one this reader expects.\n */\nconst formatDate = (date: Date) =>\n new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)\n\nexport interface DatePickerProps {\n value?: Date\n defaultValue?: Date\n onValueChange?: (value: Date | undefined) => void\n /** Names the control. Required — the trigger's text is a value, not a label. */\n label: string\n placeholder?: string\n disabled?: boolean\n /** Days the reader may not choose. Passed straight to the calendar. */\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How the chosen date is printed on the trigger. */\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid. Pass `true` for the built-in set, an array\n * for your own, or leave it off for none.\n */\n presets?: boolean | DatePreset<Date>[]\n className?: string\n}\n\n/**\n * A date, chosen from a calendar.\n *\n * A trigger and a `Calendar` in a `Popover` — not a new component so much as\n * the composition people otherwise assemble slightly differently on every\n * screen.\n *\n * It is deliberately NOT a text input with a calendar attached. A typed date\n * needs parsing, and parsing needs a format, and a format is a locale argument\n * nobody wins. When typing genuinely matters — a birth date, a long way back —\n * the calendar's month and year are dropdowns, which is the same journey\n * without the ambiguity.\n *\n * @example\n * <Field label=\"Publish on\"><DatePicker label=\"Publish on\" /></Field>\n */\nexport function DatePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a date',\n disabled = false,\n disabledDates,\n format = formatDate,\n presets,\n className,\n}: DatePickerProps) {\n const rail = presets === true ? DATE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<Date | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n const choose = (next: Date | undefined) => {\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (next) setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n aria-label={label}\n disabled={disabled}\n className={cn(TRIGGER, current ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n {current ? format(current) : placeholder}\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && <PresetRail presets={rail} onPick={choose} label={`${label} shortcuts`} />}\n <Calendar\n mode=\"single\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport interface DateRangePickerProps {\n value?: DateRange\n defaultValue?: DateRange\n onValueChange?: (value: DateRange | undefined) => void\n label: string\n placeholder?: string\n disabled?: boolean\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How many months are shown side by side. Falls back to one under `sm`. */\n months?: number\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid — Last 30 days and its neighbours. `true`\n * for the built-in set, an array for your own.\n *\n * On by default here and off on the single picker, because \"last 30 days\" is\n * most of what a range picker is ever asked for, while a single date is\n * usually a specific one.\n */\n presets?: boolean | DatePreset<DateRange>[]\n className?: string\n}\n\n/**\n * A span of dates — a stay, a reporting period, a filter.\n *\n * Two months side by side, because a range that crosses a month boundary is the\n * common case, and paging back and forth to see both ends is what makes a range\n * picker tiring. They stack under `sm`, where two would not fit — the calendar's\n * own `months` class already carries that, so there is nothing to override.\n *\n * The panel stays open until both ends are chosen: a range is not a value until\n * it has a second date, and closing on the first one would mean re-opening to\n * finish.\n *\n * @example\n * <DateRangePicker label=\"Reporting period\" />\n */\nexport function DateRangePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a range',\n disabled = false,\n disabledDates,\n months = 2,\n format = formatDate,\n presets = true,\n className,\n}: DateRangePickerProps) {\n const rail = presets === true ? RANGE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<DateRange | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n /**\n * How many days have been clicked since the panel opened.\n *\n * Counted, rather than inferred from the range being \"complete\", because the\n * first click returns `{ from: day, to: day }` — a complete range by any\n * reading of the value. Closing on that meant every attempt at a range\n * produced a one-day one and shut the panel, which is the opposite of what a\n * range picker is for.\n */\n const clicks = useRef(0)\n\n const choose = (next: DateRange | undefined) => {\n clicks.current += 1\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (clicks.current >= 2 && next?.from && next.to) setOpen(false)\n }\n\n const setPanel = (next: boolean) => {\n if (next) clicks.current = 0\n setOpen(next)\n }\n\n const complete = current?.from && current.to && current.from.getTime() !== current.to.getTime()\n const printed = current?.from\n ? complete\n ? `${format(current.from)} – ${format(current.to!)}`\n : `${format(current.from)} – …`\n : placeholder\n\n return (\n <Popover open={open} onOpenChange={setPanel}>\n <PopoverTrigger\n aria-label={label}\n disabled={disabled}\n className={cn(TRIGGER, current?.from ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n <span className=\"truncate\">{printed}</span>\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && (\n <PresetRail\n presets={rail}\n onPick={(next) => {\n // A preset is a complete range in one gesture, so the panel\n // closes — unlike a first click on the grid, which is half an\n // answer.\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n setOpen(false)\n }}\n label={`${label} shortcuts`}\n />\n )}\n <Calendar\n mode=\"range\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n numberOfMonths={months}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default DatePicker\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["'use client'\n\nimport { CalendarDays } from 'lucide-react'\nimport { useId, useRef, useState, type ComponentProps } from 'react'\nimport { dateMatchModifiers, type DateRange } from 'react-day-picker'\nimport { cn } from '../../lib/cn'\nimport { Calendar } from '../Calendar/Calendar'\nimport { useFieldControl } from '../Field/field-control'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport type { DateRange }\n\n/** A named shortcut in the rail beside the calendar. */\nexport interface DatePreset<T> {\n label: string\n /** Computed when clicked, not when rendered — \"today\" must mean today. */\n value: () => T\n}\n\n/** `n` days back from today, inclusive of today. */\nconst daysAgo = (n: number): DateRange => {\n const to = new Date()\n const from = new Date()\n from.setDate(from.getDate() - (n - 1))\n return { from, to }\n}\n\n/**\n * The shortcuts a range picker is asked for on nearly every screen it appears\n * on, so they ship rather than being rebuilt per dashboard.\n *\n * Computed on click: a preset list built at render time freezes \"today\" at\n * whenever the page loaded, which is wrong for anything left open overnight.\n */\nexport const RANGE_PRESETS: DatePreset<DateRange>[] = [\n { label: 'Last 7 days', value: () => daysAgo(7) },\n { label: 'Last 30 days', value: () => daysAgo(30) },\n { label: 'Last 90 days', value: () => daysAgo(90) },\n { label: 'Last 12 months', value: () => daysAgo(365) },\n {\n label: 'Month to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), to.getMonth(), 1), to }\n },\n },\n {\n label: 'Year to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), 0, 1), to }\n },\n },\n]\n\n/** The single-date equivalent. */\nexport const DATE_PRESETS: DatePreset<Date>[] = [\n { label: 'Today', value: () => new Date() },\n {\n label: 'Tomorrow',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 1)\n return date\n },\n },\n {\n label: 'In a week',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 7)\n return date\n },\n },\n {\n label: 'In a month',\n value: () => {\n const date = new Date()\n date.setMonth(date.getMonth() + 1)\n return date\n },\n },\n]\n\n/**\n * The rail of shortcuts.\n *\n * A `<nav>`-less list of plain buttons rather than a menu: they set the same\n * value the grid beside them sets, so they are part of one control and should\n * Tab in the same pass rather than opening something.\n *\n * Setting the same value means being refused for the same reasons: a shortcut\n * that commits a date the grid beside it will not accept hands the caller back\n * a value the control itself calls invalid. `blocked` is asked at render, so the\n * button is visibly unavailable rather than silently inert, and again on click,\n * because the value is computed then — \"today\" has to mean today.\n */\nfunction PresetRail<T>({\n presets,\n onPick,\n label,\n blocked,\n}: {\n presets: DatePreset<T>[]\n onPick: (value: T) => void\n label: string\n blocked?: (value: T) => boolean\n}) {\n return (\n <div\n role=\"group\"\n aria-label={label}\n className=\"flex shrink-0 flex-col gap-0.5 border-b border-(--rule) p-2 sm:border-b-0 sm:border-e max-sm:flex-row max-sm:flex-wrap\"\n >\n {presets.map((preset) => (\n <button\n key={preset.label}\n type=\"button\"\n disabled={blocked?.(preset.value()) ?? false}\n onClick={() => {\n const next = preset.value()\n if (blocked?.(next)) return\n onPick(next)\n }}\n className=\"rounded-(--radius-sm) px-2.5 py-1.5 text-start text-[13px] text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none\"\n >\n {preset.label}\n </button>\n ))}\n </div>\n )\n}\n\nconst TRIGGER =\n 'flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\n/**\n * The visitor's own locale, not a fixed `dd/mm/yyyy`.\n *\n * `03/04` is March the fourth in one country and the third of April in the\n * next, and there is no format that is unambiguous to everyone. `Intl` already\n * knows which one this reader expects.\n */\nconst formatDate = (date: Date) =>\n new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)\n\nexport interface DatePickerProps {\n value?: Date\n defaultValue?: Date\n onValueChange?: (value: Date | undefined) => void\n /**\n * Names the control. Required — the trigger's text is a value, not a label.\n *\n * Announced together with the printed date rather than instead of it, so\n * `format` reaches a screen reader as well as the screen. Inside a `Field`\n * with a label, that label names the trigger and this one is not repeated.\n */\n label: string\n placeholder?: string\n disabled?: boolean\n /**\n * Days the reader may not choose.\n *\n * Reaches the calendar AND the shortcut rail: a preset landing on a blocked\n * day is drawn unavailable and refuses the click, rather than committing a\n * value the grid beside it would not accept.\n */\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How the chosen date is printed on the trigger. */\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid. Pass `true` for the built-in set, an array\n * for your own, or leave it off for none.\n */\n presets?: boolean | DatePreset<Date>[]\n className?: string\n /** The TRIGGER's id — the element a label points at. A `Field` sets it. */\n id?: string\n /** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */\n 'aria-describedby'?: string\n /** Announced on the trigger. A `Field` sets it from `error`. */\n 'aria-invalid'?: boolean | 'true' | 'false'\n}\n\n/**\n * A date, chosen from a calendar.\n *\n * A trigger and a `Calendar` in a `Popover` — not a new component so much as\n * the composition people otherwise assemble slightly differently on every\n * screen.\n *\n * It is deliberately NOT a text input with a calendar attached. A typed date\n * needs parsing, and parsing needs a format, and a format is a locale argument\n * nobody wins. When typing genuinely matters — a birth date, a long way back —\n * the calendar's month and year are dropdowns, which is the same journey\n * without the ambiguity.\n *\n * @example\n * <Field label=\"Publish on\"><DatePicker label=\"Publish on\" /></Field>\n */\nexport function DatePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a date',\n disabled = false,\n disabledDates,\n format = formatDate,\n presets,\n className,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n}: DatePickerProps) {\n const rail = presets === true ? DATE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<Date | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n const field = useFieldControl()\n const generated = useId()\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const choose = (next: Date | undefined) => {\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (next) setOpen(false)\n }\n\n const refuses = (date: Date) =>\n disabledDates !== undefined && dateMatchModifiers(date, disabledDates)\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n id={triggerId}\n // The label and the printed date, in that order. `aria-label` outranked\n // the trigger's own text, so `format` reached the screen and nothing\n // else — the chosen date was invisible to a screen reader.\n aria-labelledby={`${nameId} ${valueId}`}\n aria-describedby={describedBy}\n aria-invalid={ariaInvalid || undefined}\n disabled={disabled}\n className={cn(TRIGGER, current ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n <span id={valueId} className=\"truncate\">\n {current ? format(current) : placeholder}\n </span>\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && (\n <PresetRail\n presets={rail}\n onPick={choose}\n label={`${label} shortcuts`}\n blocked={refuses}\n />\n )}\n <Calendar\n mode=\"single\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport interface DateRangePickerProps {\n value?: DateRange\n defaultValue?: DateRange\n onValueChange?: (value: DateRange | undefined) => void\n /** Names the control. Announced together with the printed range, not instead of it. */\n label: string\n placeholder?: string\n disabled?: boolean\n /**\n * Days the reader may not choose.\n *\n * Reaches the shortcut rail as well as the grid, at the ENDS of each preset\n * range — a shortcut whose interior straddles a blocked day is still offered,\n * the way the grid still lets a reader drag a range across one.\n */\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How many months are shown side by side. Falls back to one under `sm`. */\n months?: number\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid — Last 30 days and its neighbours. `true`\n * for the built-in set, an array for your own.\n *\n * On by default here and off on the single picker, because \"last 30 days\" is\n * most of what a range picker is ever asked for, while a single date is\n * usually a specific one.\n */\n presets?: boolean | DatePreset<DateRange>[]\n className?: string\n /** The TRIGGER's id — the element a label points at. A `Field` sets it. */\n id?: string\n /** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */\n 'aria-describedby'?: string\n /** Announced on the trigger. A `Field` sets it from `error`. */\n 'aria-invalid'?: boolean | 'true' | 'false'\n}\n\n/**\n * A span of dates — a stay, a reporting period, a filter.\n *\n * Two months side by side, because a range that crosses a month boundary is the\n * common case, and paging back and forth to see both ends is what makes a range\n * picker tiring. They stack under `sm`, where two would not fit — the calendar's\n * own `months` class already carries that, so there is nothing to override.\n *\n * The panel stays open until both ends are chosen: a range is not a value until\n * it has a second date, and closing on the first one would mean re-opening to\n * finish.\n *\n * @example\n * <DateRangePicker label=\"Reporting period\" />\n */\nexport function DateRangePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a range',\n disabled = false,\n disabledDates,\n months = 2,\n format = formatDate,\n presets = true,\n className,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n}: DateRangePickerProps) {\n const rail = presets === true ? RANGE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<DateRange | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n const field = useFieldControl()\n const generated = useId()\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const refuses = (range: DateRange) =>\n disabledDates !== undefined &&\n [range.from, range.to].some((date) => date != null && dateMatchModifiers(date, disabledDates))\n\n /**\n * How many days have been clicked since the panel opened.\n *\n * Counted, rather than inferred from the range being \"complete\", because the\n * first click returns `{ from: day, to: day }` — a complete range by any\n * reading of the value. Closing on that meant every attempt at a range\n * produced a one-day one and shut the panel, which is the opposite of what a\n * range picker is for.\n */\n const clicks = useRef(0)\n\n const choose = (next: DateRange | undefined) => {\n clicks.current += 1\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (clicks.current >= 2 && next?.from && next.to) setOpen(false)\n }\n\n const setPanel = (next: boolean) => {\n if (next) clicks.current = 0\n setOpen(next)\n }\n\n const complete = current?.from && current.to && current.from.getTime() !== current.to.getTime()\n const printed = current?.from\n ? complete\n ? `${format(current.from)} – ${format(current.to!)}`\n : `${format(current.from)} – …`\n : placeholder\n\n return (\n <Popover open={open} onOpenChange={setPanel}>\n <PopoverTrigger\n id={triggerId}\n // The label and the printed range together — see DatePicker above.\n aria-labelledby={`${nameId} ${valueId}`}\n aria-describedby={describedBy}\n aria-invalid={ariaInvalid || undefined}\n disabled={disabled}\n className={cn(TRIGGER, current?.from ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n <span id={valueId} className=\"truncate\">\n {printed}\n </span>\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && (\n <PresetRail\n presets={rail}\n onPick={(next) => {\n // A preset is a complete range in one gesture, so the panel\n // closes — unlike a first click on the grid, which is half an\n // answer.\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n setOpen(false)\n }}\n label={`${label} shortcuts`}\n blocked={refuses}\n />\n )}\n <Calendar\n mode=\"range\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n numberOfMonths={months}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default DatePicker\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface DescriptionListItem {
|
|
5
|
+
/**
|
|
6
|
+
* A stable key. Optional: the index is used when it is absent, which is
|
|
7
|
+
* correct for the fixed field list a record page renders and wrong for a list
|
|
8
|
+
* whose rows are added, removed or reordered — pass it there.
|
|
9
|
+
*/
|
|
10
|
+
id?: string;
|
|
11
|
+
/** The label. Rendered as a `<dt>`. */
|
|
12
|
+
term: ReactNode;
|
|
13
|
+
/** The value. Rendered as a `<dd>`, so it can be a `Badge` or a link. */
|
|
14
|
+
description: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Where the value sits relative to its label.
|
|
18
|
+
*
|
|
19
|
+
* `row` is the record-page shape: label in a fixed column at the inline start,
|
|
20
|
+
* value beside it, so a reader scans the labels down one edge. It collapses to
|
|
21
|
+
* one column under the `sm` breakpoint, because a 12rem label column on a phone
|
|
22
|
+
* leaves the value about eight characters wide.
|
|
23
|
+
*
|
|
24
|
+
* `stacked` puts the value under its label at every width — the right answer
|
|
25
|
+
* inside a card or a sidebar that is narrow by design rather than by viewport.
|
|
26
|
+
*/
|
|
27
|
+
type DescriptionListLayout = 'row' | 'stacked';
|
|
28
|
+
interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {
|
|
29
|
+
/** The pairs, in the order they should read. An empty array renders nothing. */
|
|
30
|
+
items: DescriptionListItem[];
|
|
31
|
+
/** How the value sits relative to its label. See {@link DescriptionListLayout}. */
|
|
32
|
+
layout?: DescriptionListLayout;
|
|
33
|
+
/**
|
|
34
|
+
* Draws a hairline under every pair but the last. Off for a list inside a
|
|
35
|
+
* card that already has an edge, on for a list that is the page's structure.
|
|
36
|
+
*/
|
|
37
|
+
divided?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A record's fields: label, value, label, value.
|
|
41
|
+
*
|
|
42
|
+
* The single most repeated shape in any detail page, and the one most often
|
|
43
|
+
* hand-built out of a `<div>` grid — which loses the only thing the markup was
|
|
44
|
+
* carrying. A `<dl>` tells a screen reader that "Owner" names the thing beside
|
|
45
|
+
* it; a grid of divs tells it there are two columns of unrelated text, and the
|
|
46
|
+
* reader has to infer the pairing from reading order alone.
|
|
47
|
+
*
|
|
48
|
+
* Reach for `Table` instead when there are several records. This is one record
|
|
49
|
+
* seen from the front; a table is many records seen from above.
|
|
50
|
+
*
|
|
51
|
+
* Each pair is wrapped in a `<div>` — which the HTML specification allows
|
|
52
|
+
* inside a `<dl>` precisely so a pair can be laid out as a unit — so the
|
|
53
|
+
* hairline runs the full width of the row rather than stopping in the column
|
|
54
|
+
* gap.
|
|
55
|
+
*
|
|
56
|
+
* An empty `items` renders `null` rather than an empty bordered box. A record
|
|
57
|
+
* with no fields is a state the page above should be handling with an
|
|
58
|
+
* `EmptyState`, and a hairline around nothing looks like a component that
|
|
59
|
+
* failed to load.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* <DescriptionList
|
|
63
|
+
* items={[
|
|
64
|
+
* { term: 'Owner', description: 'Henry Chen' },
|
|
65
|
+
* { term: 'Status', description: <Badge tone="success">Deployed</Badge> },
|
|
66
|
+
* ]}
|
|
67
|
+
* />
|
|
68
|
+
* @example
|
|
69
|
+
* <DescriptionList layout="stacked" divided={false} items={fields} />
|
|
70
|
+
*/
|
|
71
|
+
declare function DescriptionList({ items, layout, divided, className, ...rest }: DescriptionListProps): react.JSX.Element | null;
|
|
72
|
+
|
|
73
|
+
export { DescriptionList, type DescriptionListItem, type DescriptionListLayout, type DescriptionListProps, DescriptionList as default };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
const LAYOUT = {
|
|
4
|
+
row: "grid gap-x-6 gap-y-1 sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)]",
|
|
5
|
+
stacked: "grid gap-y-1"
|
|
6
|
+
};
|
|
7
|
+
function DescriptionList({
|
|
8
|
+
items,
|
|
9
|
+
layout = "row",
|
|
10
|
+
divided = true,
|
|
11
|
+
className,
|
|
12
|
+
...rest
|
|
13
|
+
}) {
|
|
14
|
+
if (items.length === 0) return null;
|
|
15
|
+
return /* @__PURE__ */ jsx("dl", { className: cn("m-0", !divided && "flex flex-col gap-3", className), ...rest, children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
className: cn(
|
|
19
|
+
LAYOUT[layout],
|
|
20
|
+
divided && "border-b border-(--rule) py-3 first:pt-0 last:border-b-0 last:pb-0"
|
|
21
|
+
),
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ jsx("dt", { className: "text-sm text-(--ink-3-aa)", children: item.term }),
|
|
24
|
+
/* @__PURE__ */ jsx("dd", { className: "m-0 text-sm text-(--ink-2)", children: item.description })
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
item.id ?? index
|
|
28
|
+
)) });
|
|
29
|
+
}
|
|
30
|
+
var DescriptionList_default = DescriptionList;
|
|
31
|
+
export {
|
|
32
|
+
DescriptionList,
|
|
33
|
+
DescriptionList_default as default
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=DescriptionList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/DescriptionList/DescriptionList.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface DescriptionListItem {\n /**\n * A stable key. Optional: the index is used when it is absent, which is\n * correct for the fixed field list a record page renders and wrong for a list\n * whose rows are added, removed or reordered — pass it there.\n */\n id?: string\n /** The label. Rendered as a `<dt>`. */\n term: ReactNode\n /** The value. Rendered as a `<dd>`, so it can be a `Badge` or a link. */\n description: ReactNode\n}\n\n/**\n * Where the value sits relative to its label.\n *\n * `row` is the record-page shape: label in a fixed column at the inline start,\n * value beside it, so a reader scans the labels down one edge. It collapses to\n * one column under the `sm` breakpoint, because a 12rem label column on a phone\n * leaves the value about eight characters wide.\n *\n * `stacked` puts the value under its label at every width — the right answer\n * inside a card or a sidebar that is narrow by design rather than by viewport.\n */\nexport type DescriptionListLayout = 'row' | 'stacked'\n\nexport interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {\n /** The pairs, in the order they should read. An empty array renders nothing. */\n items: DescriptionListItem[]\n /** How the value sits relative to its label. See {@link DescriptionListLayout}. */\n layout?: DescriptionListLayout\n /**\n * Draws a hairline under every pair but the last. Off for a list inside a\n * card that already has an edge, on for a list that is the page's structure.\n */\n divided?: boolean\n}\n\nconst LAYOUT: Record<DescriptionListLayout, string> = {\n row: 'grid gap-x-6 gap-y-1 sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)]',\n stacked: 'grid gap-y-1',\n}\n\n/**\n * A record's fields: label, value, label, value.\n *\n * The single most repeated shape in any detail page, and the one most often\n * hand-built out of a `<div>` grid — which loses the only thing the markup was\n * carrying. A `<dl>` tells a screen reader that \"Owner\" names the thing beside\n * it; a grid of divs tells it there are two columns of unrelated text, and the\n * reader has to infer the pairing from reading order alone.\n *\n * Reach for `Table` instead when there are several records. This is one record\n * seen from the front; a table is many records seen from above.\n *\n * Each pair is wrapped in a `<div>` — which the HTML specification allows\n * inside a `<dl>` precisely so a pair can be laid out as a unit — so the\n * hairline runs the full width of the row rather than stopping in the column\n * gap.\n *\n * An empty `items` renders `null` rather than an empty bordered box. A record\n * with no fields is a state the page above should be handling with an\n * `EmptyState`, and a hairline around nothing looks like a component that\n * failed to load.\n *\n * @example\n * <DescriptionList\n * items={[\n * { term: 'Owner', description: 'Henry Chen' },\n * { term: 'Status', description: <Badge tone=\"success\">Deployed</Badge> },\n * ]}\n * />\n * @example\n * <DescriptionList layout=\"stacked\" divided={false} items={fields} />\n */\nexport function DescriptionList({\n items,\n layout = 'row',\n divided = true,\n className,\n ...rest\n}: DescriptionListProps) {\n if (items.length === 0) return null\n\n return (\n <dl className={cn('m-0', !divided && 'flex flex-col gap-3', className)} {...rest}>\n {items.map((item, index) => (\n <div\n key={item.id ?? index}\n className={cn(\n LAYOUT[layout],\n divided && 'border-b border-(--rule) py-3 first:pt-0 last:border-b-0 last:pb-0',\n )}\n >\n <dt className=\"text-sm text-(--ink-3-aa)\">{item.term}</dt>\n <dd className=\"m-0 text-sm text-(--ink-2)\">{item.description}</dd>\n </div>\n ))}\n </dl>\n )\n}\n\nexport default DescriptionList\n"],"mappings":"AA0FQ,SAOE,KAPF;AAzFR,SAAS,UAAU;AAwCnB,MAAM,SAAgD;AAAA,EACpD,KAAK;AAAA,EACL,SAAS;AACX;AAkCO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,EACT,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACE,oBAAC,QAAG,WAAW,GAAG,OAAO,CAAC,WAAW,uBAAuB,SAAS,GAAI,GAAG,MACzE,gBAAM,IAAI,CAAC,MAAM,UAChB;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW;AAAA,QACT,OAAO,MAAM;AAAA,QACb,WAAW;AAAA,MACb;AAAA,MAEA;AAAA,4BAAC,QAAG,WAAU,6BAA6B,eAAK,MAAK;AAAA,QACrD,oBAAC,QAAG,WAAU,8BAA8B,eAAK,aAAY;AAAA;AAAA;AAAA,IAPxD,KAAK,MAAM;AAAA,EAQlB,CACD,GACH;AAEJ;AAEA,IAAO,0BAAQ;","names":[]}
|
|
@@ -24,7 +24,12 @@ interface DiagramSpec {
|
|
|
24
24
|
/** How the top-level nodes are laid out. Defaults to a row. */
|
|
25
25
|
direction?: 'row' | 'column';
|
|
26
26
|
nodes: DiagramNode[];
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Arrows between adjacent siblings, by id and in the order the rank runs.
|
|
29
|
+
* Each edge is spent the first time a pair matches it, at whatever depth
|
|
30
|
+
* that pair sits — and an edge that matches nothing says so in development
|
|
31
|
+
* rather than drawing nothing and reporting nothing.
|
|
32
|
+
*/
|
|
28
33
|
edges?: DiagramEdge[];
|
|
29
34
|
/** The figure's caption. */
|
|
30
35
|
caption?: string;
|
|
@@ -68,8 +73,8 @@ interface DiagramProps {
|
|
|
68
73
|
* edges: [{ from: 'edge', to: 'app', label: 'HTTPS' }],
|
|
69
74
|
* nodes: [
|
|
70
75
|
* { id: 'edge', label: 'Edge', note: 'CDN' },
|
|
71
|
-
* { id: 'app', label: 'Application',
|
|
72
|
-
* { label: 'Router' },
|
|
76
|
+
* { id: 'app', label: 'Application', children: [
|
|
77
|
+
* { label: 'Router', accent: true },
|
|
73
78
|
* { label: 'Handlers' },
|
|
74
79
|
* ] },
|
|
75
80
|
* ],
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../lib/cn.js";
|
|
3
|
+
import { DEV, warn } from "../../lib/warn.js";
|
|
3
4
|
function Diagram({ spec, className }) {
|
|
4
5
|
const { nodes, edges = [], direction = "row", caption, label } = spec;
|
|
6
|
+
const { leads, drawn } = resolveLeads(nodes, edges);
|
|
7
|
+
if (DEV) warnSpec(nodes, edges, drawn);
|
|
5
8
|
return /* @__PURE__ */ jsxs("figure", { className: cn("m-0", className), role: "group", "aria-label": label ?? caption ?? void 0, children: [
|
|
6
|
-
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim", children: /* @__PURE__ */ jsx(NodeList, { nodes,
|
|
9
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim", children: /* @__PURE__ */ jsx(NodeList, { nodes, leads, direction, depth: 0 }) }),
|
|
7
10
|
caption && // A <div>, not a <figcaption>. A long-form stylesheet dropped around
|
|
8
11
|
// this will style `figcaption` and `p` unlayered, and an unlayered rule
|
|
9
12
|
// beats a utility whatever the specificity — inside an article a real
|
|
@@ -13,9 +16,30 @@ function Diagram({ spec, className }) {
|
|
|
13
16
|
/* @__PURE__ */ jsx("div", { className: "mt-3 mono-meta leading-[1.6] text-(--ink-3-aa)", children: caption })
|
|
14
17
|
] });
|
|
15
18
|
}
|
|
19
|
+
function resolveLeads(nodes, edges) {
|
|
20
|
+
const leads = /* @__PURE__ */ new Map();
|
|
21
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
22
|
+
const ranks = [nodes];
|
|
23
|
+
while (ranks.length > 0) {
|
|
24
|
+
const rank = ranks.shift();
|
|
25
|
+
for (let index = 1; index < rank.length; index += 1) {
|
|
26
|
+
const from = rank[index - 1];
|
|
27
|
+
const to = rank[index];
|
|
28
|
+
if (!from.id || !to.id) continue;
|
|
29
|
+
const edge = edges.find(
|
|
30
|
+
(candidate) => !drawn.has(candidate) && candidate.from === from.id && candidate.to === to.id
|
|
31
|
+
);
|
|
32
|
+
if (!edge) continue;
|
|
33
|
+
drawn.add(edge);
|
|
34
|
+
leads.set(to, edge);
|
|
35
|
+
}
|
|
36
|
+
for (const node of rank) if (node.children?.length) ranks.push(node.children);
|
|
37
|
+
}
|
|
38
|
+
return { leads, drawn };
|
|
39
|
+
}
|
|
16
40
|
function NodeList({
|
|
17
41
|
nodes,
|
|
18
|
-
|
|
42
|
+
leads,
|
|
19
43
|
direction,
|
|
20
44
|
depth
|
|
21
45
|
}) {
|
|
@@ -24,21 +48,17 @@ function NodeList({
|
|
|
24
48
|
NodeFrame,
|
|
25
49
|
{
|
|
26
50
|
node,
|
|
27
|
-
|
|
51
|
+
leads,
|
|
28
52
|
depth,
|
|
29
|
-
lead:
|
|
53
|
+
lead: leads.get(node),
|
|
30
54
|
row
|
|
31
55
|
},
|
|
32
56
|
node.id ?? `${node.label}-${index}`
|
|
33
57
|
)) });
|
|
34
58
|
}
|
|
35
|
-
function findEdge(edges, from, to) {
|
|
36
|
-
if (!from.id || !to.id) return void 0;
|
|
37
|
-
return edges.find((edge) => edge.from === from.id && edge.to === to.id);
|
|
38
|
-
}
|
|
39
59
|
function NodeFrame({
|
|
40
60
|
node,
|
|
41
|
-
|
|
61
|
+
leads,
|
|
42
62
|
depth,
|
|
43
63
|
lead,
|
|
44
64
|
row
|
|
@@ -64,7 +84,7 @@ function NodeFrame({
|
|
|
64
84
|
NodeList,
|
|
65
85
|
{
|
|
66
86
|
nodes: children,
|
|
67
|
-
|
|
87
|
+
leads,
|
|
68
88
|
direction: node.direction ?? "row",
|
|
69
89
|
depth: depth + 1
|
|
70
90
|
}
|
|
@@ -131,6 +151,58 @@ function EdgeMark({ label, row }) {
|
|
|
131
151
|
}
|
|
132
152
|
);
|
|
133
153
|
}
|
|
154
|
+
function warnSpec(nodes, edges, drawn) {
|
|
155
|
+
const ids = /* @__PURE__ */ new Set();
|
|
156
|
+
warnNodes(nodes, ids);
|
|
157
|
+
for (const edge of edges) {
|
|
158
|
+
if (drawn.has(edge)) continue;
|
|
159
|
+
const pair = `${edge.from} \u2192 ${edge.to}`;
|
|
160
|
+
const unknown = !ids.has(edge.from) || !ids.has(edge.to);
|
|
161
|
+
warn({
|
|
162
|
+
code: unknown ? "DIAGRAM_EDGE_UNKNOWN_NODE" : "DIAGRAM_EDGE_NOT_ADJACENT",
|
|
163
|
+
problem: unknown ? `The edge ${pair} names an id no node in this spec carries, so no arrow is drawn and nothing else changes.` : `The edge ${pair} names two nodes that are never consecutive siblings, so no arrow is drawn anywhere.`,
|
|
164
|
+
field: `spec.edges: ${pair}`,
|
|
165
|
+
fix: unknown ? "Give the node the id the edge names, or correct the edge to an id the spec already has." : "Write the edge from a node to the node immediately after it in the same rank, in that order. Nesting is containment here; an edge is a step between siblings.",
|
|
166
|
+
component: "Diagram"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function warnNodes(nodes, ids) {
|
|
171
|
+
for (const node of nodes) {
|
|
172
|
+
if (node.id) {
|
|
173
|
+
if (ids.has(node.id)) {
|
|
174
|
+
warn({
|
|
175
|
+
code: "DIAGRAM_DUPLICATE_ID",
|
|
176
|
+
problem: `Two nodes carry the id "${node.id}", so an edge naming it lands on whichever comes first and silently means the other.`,
|
|
177
|
+
field: `spec.nodes: id "${node.id}"`,
|
|
178
|
+
fix: "Make every id unique across the whole spec, or drop it from the node no edge names.",
|
|
179
|
+
component: "Diagram"
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
ids.add(node.id);
|
|
183
|
+
}
|
|
184
|
+
const isGroup = (node.children?.length ?? 0) > 0;
|
|
185
|
+
if (isGroup && node.accent) {
|
|
186
|
+
warn({
|
|
187
|
+
code: "DIAGRAM_ACCENT_ON_CONTAINER",
|
|
188
|
+
problem: `"${node.label}" has children, so it is drawn as a band \u2014 and a band has no fill for accent to take. It type-checks and paints nothing.`,
|
|
189
|
+
field: `spec.nodes: accent on "${node.label}"`,
|
|
190
|
+
fix: "Move accent to the leaf the diagram is actually about, or drop the children so this node becomes that leaf.",
|
|
191
|
+
component: "Diagram"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (!isGroup && node.direction) {
|
|
195
|
+
warn({
|
|
196
|
+
code: "DIAGRAM_DIRECTION_ON_LEAF",
|
|
197
|
+
problem: `"${node.label}" has no children, so there is nothing here to lay out \u2014 direction is read only from a node that has some.`,
|
|
198
|
+
field: `spec.nodes: direction on "${node.label}"`,
|
|
199
|
+
fix: "Set direction on the parent whose axis you meant, or on spec.direction for the top rank.",
|
|
200
|
+
component: "Diagram"
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (node.children) warnNodes(node.children, ids);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
134
206
|
var Diagram_default = Diagram;
|
|
135
207
|
export {
|
|
136
208
|
Diagram,
|