@misoto22/design 0.6.1 → 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 +62 -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 +71 -0
- 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 +48 -0
- 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 +70 -0
- package/dist/agent/DiagramExportMenu.md +64 -0
- package/dist/agent/DiagramInspector.md +72 -0
- package/dist/agent/DiagramLegend.md +55 -0
- package/dist/agent/DiagramMinimap.md +68 -0
- package/dist/agent/DiagramToolbar.md +69 -0
- 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 +49 -0
- 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 +50 -0
- 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 +50 -0
- package/dist/agent/catalog.json +6086 -1009
- package/dist/agent/index.md +27 -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/index.d.ts +2 -1
- package/dist/charts/lib/export.d.ts +34 -43
- package/dist/charts/lib/export.js +16 -158
- package/dist/charts/lib/export.js.map +1 -1
- 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 +55 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +175 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.d.ts +28 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +148 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +78 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +229 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +106 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +123 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.d.ts +70 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +94 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.d.ts +42 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.js +35 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.js.map +1 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +77 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +106 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.d.ts +48 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.js +53 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.js.map +1 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.d.ts +31 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +234 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.d.ts +38 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +261 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +36 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +259 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -0
- package/dist/diagrams/index.d.ts +17 -0
- package/dist/diagrams/index.js +29 -0
- package/dist/diagrams/index.js.map +1 -0
- 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 +138 -0
- package/dist/diagrams/lib/frame.js +131 -0
- package/dist/diagrams/lib/frame.js.map +1 -0
- package/dist/diagrams/lib/geometry.d.ts +114 -0
- package/dist/diagrams/lib/geometry.js +113 -0
- package/dist/diagrams/lib/geometry.js.map +1 -0
- package/dist/diagrams/lib/legend.d.ts +32 -0
- package/dist/diagrams/lib/legend.js +84 -0
- package/dist/diagrams/lib/legend.js.map +1 -0
- package/dist/diagrams/lib/marks.d.ts +207 -0
- package/dist/diagrams/lib/marks.js +480 -0
- package/dist/diagrams/lib/marks.js.map +1 -0
- package/dist/diagrams/lib/route.d.ts +85 -0
- package/dist/diagrams/lib/route.js +178 -0
- package/dist/diagrams/lib/route.js.map +1 -0
- package/dist/diagrams/lib/wires.d.ts +59 -0
- package/dist/diagrams/lib/wires.js +90 -0
- package/dist/diagrams/lib/wires.js.map +1 -0
- package/dist/diagrams/spec.d.ts +405 -0
- package/dist/diagrams/spec.js +1 -0
- package/dist/diagrams/spec.js.map +1 -0
- 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/svg-export.d.ts +138 -0
- package/dist/lib/svg-export.js +198 -0
- package/dist/lib/svg-export.js.map +1 -0
- 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 +51 -6
- package/dist/styles.css +1 -1
- package/dist/tokens.css +20 -2
- package/dist/tokens.d.ts +17 -1
- package/dist/tokens.js +121 -36
- package/dist/tokens.json +121 -36
- package/llms.txt +35 -0
- package/package.json +9 -2
- package/skills/misoto22-design/SKILL.md +21 -7
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/AspectRatio/AspectRatio.tsx"],"sourcesContent":["import type { CSSProperties, HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface AspectRatioProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Width over height. A number (`16 / 9`) or the CSS form (`'16 / 9'`).\n *\n * It is a style rather than a class because Tailwind can only generate what\n * it can read verbatim in the source, and this value arrives at runtime.\n */\n ratio?: number | string\n children?: ReactNode\n}\n\n/**\n * A box that keeps its shape whatever is inside it.\n *\n * The one layout primitive that is genuinely hard to hand-roll. The `padding-\n * top: 56.25%` trick everyone reaches for is a percentage of the WIDTH, which\n * is why it works at all and also why it silently breaks the moment the box is\n * a flex or grid child — and it takes the element's own padding with it. The\n * modern `aspect-ratio` property does the same job in one line, and only holds\n * if nothing inside forces a height, which is what the absolute positioning\n * below guarantees.\n *\n * So: the box declares the ratio, and every direct child is stretched to fill\n * it and taken out of flow. That means a child with no intrinsic size at all —\n * an empty `<div>`, a map that measures its container, a skeleton — still gets\n * the full box, and an `<img>` or `<video>` is cropped to cover it rather than\n * letterboxed. Content that must not be cropped should set `object-contain` on\n * itself.\n *\n * Reach for this when the height must be known before the content loads: a\n * media grid that would otherwise reflow every time an image arrives is the\n * usual case, and that reflow is the layout shift a Core Web Vitals score is\n * measuring.\n *\n * @example\n * <AspectRatio ratio={16 / 9}><img src={cover} alt=\"\" /></AspectRatio>\n * @example\n * <AspectRatio ratio=\"1 / 1\" className=\"rounded-(--radius) border border-(--rule-2)\">\n * <Skeleton />\n * </AspectRatio>\n */\nexport function AspectRatio({\n ratio = 16 / 9,\n className,\n style,\n children,\n ...rest\n}: AspectRatioProps) {\n const box: CSSProperties = { aspectRatio: String(ratio), ...style }\n\n return (\n <div\n style={box}\n className={cn(\n 'relative w-full overflow-hidden',\n // Every direct child fills the box, so nothing inside can push the\n // height around and break the ratio the box just declared.\n '[&>*]:absolute [&>*]:inset-0 [&>*]:size-full',\n '[&>img]:object-cover [&>video]:object-cover',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\nexport default AspectRatio\n"],"mappings":"AAsDI;AArDJ,SAAS,UAAU;AA2CZ,SAAS,YAAY;AAAA,EAC1B,QAAQ,KAAK;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,MAAqB,EAAE,aAAa,OAAO,KAAK,GAAG,GAAG,MAAM;AAElE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,MACP,WAAW;AAAA,QACT;AAAA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
|
|
@@ -5,9 +5,13 @@ import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
|
5
5
|
interface AvatarProps extends Omit<ComponentProps<typeof AvatarPrimitive.Root>, 'children'> {
|
|
6
6
|
src?: string;
|
|
7
7
|
/**
|
|
8
|
-
* Describes the person, not the picture.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Describes the person, not the picture. It names the circle itself rather
|
|
9
|
+
* than the image inside it, because the image is the optional half and most
|
|
10
|
+
* rows of a real list render as initials.
|
|
11
|
+
*
|
|
12
|
+
* Empty string is correct and deliberate when the name is already printed
|
|
13
|
+
* beside the avatar: the circle then leaves the accessibility tree entirely,
|
|
14
|
+
* rather than repeating a name a screen reader has just read.
|
|
11
15
|
*/
|
|
12
16
|
alt: string;
|
|
13
17
|
/** Shown while the image loads and if it never does. Usually initials. */
|
|
@@ -21,6 +25,11 @@ interface AvatarProps extends Omit<ComponentProps<typeof AvatarPrimitive.Root>,
|
|
|
21
25
|
* or is still loading — a hand-rolled `onError` swap flashes the initials on
|
|
22
26
|
* every render before the cache answers.
|
|
23
27
|
*
|
|
28
|
+
* The ROOT carries the accessible name, not the image. `alt` on the image
|
|
29
|
+
* reaches the DOM only when `src` does, and the initials are aria-hidden, so an
|
|
30
|
+
* avatar with no photograph announced nothing at all however carefully `alt`
|
|
31
|
+
* was written — and a user list where photographs are optional is full of them.
|
|
32
|
+
*
|
|
24
33
|
* @example
|
|
25
34
|
* <Avatar src={user.photo} alt={user.name} fallback="HC" />
|
|
26
35
|
*/
|
|
@@ -8,9 +8,12 @@ const SIZE = {
|
|
|
8
8
|
lg: "size-12 text-[13px]"
|
|
9
9
|
};
|
|
10
10
|
function Avatar({ src, alt, fallback, size = "md", className, ...rest }) {
|
|
11
|
+
const named = alt.trim() !== "";
|
|
11
12
|
return /* @__PURE__ */ jsxs(
|
|
12
13
|
AvatarPrimitive.Root,
|
|
13
14
|
{
|
|
15
|
+
role: named ? "img" : void 0,
|
|
16
|
+
"aria-label": named ? alt : void 0,
|
|
14
17
|
className: cn(
|
|
15
18
|
"relative inline-flex shrink-0 overflow-hidden rounded-full border border-(--rule) bg-(--stone)",
|
|
16
19
|
SIZE[size],
|
|
@@ -18,7 +21,9 @@ function Avatar({ src, alt, fallback, size = "md", className, ...rest }) {
|
|
|
18
21
|
),
|
|
19
22
|
...rest,
|
|
20
23
|
children: [
|
|
21
|
-
src &&
|
|
24
|
+
src && // Empty, deliberately: the root above is the one element that names
|
|
25
|
+
// this person, and a second name here is the same person read twice.
|
|
26
|
+
/* @__PURE__ */ jsx(AvatarPrimitive.Image, { src, alt: "", className: "size-full object-cover" }),
|
|
22
27
|
/* @__PURE__ */ jsx(
|
|
23
28
|
AvatarPrimitive.Fallback,
|
|
24
29
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["'use client'\n\nimport * as AvatarPrimitive from '@radix-ui/react-avatar'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface AvatarProps extends Omit<ComponentProps<typeof AvatarPrimitive.Root>, 'children'> {\n src?: string\n /**\n * Describes the person, not the picture.
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["'use client'\n\nimport * as AvatarPrimitive from '@radix-ui/react-avatar'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface AvatarProps extends Omit<ComponentProps<typeof AvatarPrimitive.Root>, 'children'> {\n src?: string\n /**\n * Describes the person, not the picture. It names the circle itself rather\n * than the image inside it, because the image is the optional half and most\n * rows of a real list render as initials.\n *\n * Empty string is correct and deliberate when the name is already printed\n * beside the avatar: the circle then leaves the accessibility tree entirely,\n * rather than repeating a name a screen reader has just read.\n */\n alt: string\n /** Shown while the image loads and if it never does. Usually initials. */\n fallback: string\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst SIZE = {\n sm: 'size-7 text-[10px]',\n md: 'size-9 text-[11px]',\n lg: 'size-12 text-[13px]',\n} as const\n\n/**\n * A person, as a circle.\n *\n * Wraps Radix so the fallback appears only after the image has actually failed\n * or is still loading — a hand-rolled `onError` swap flashes the initials on\n * every render before the cache answers.\n *\n * The ROOT carries the accessible name, not the image. `alt` on the image\n * reaches the DOM only when `src` does, and the initials are aria-hidden, so an\n * avatar with no photograph announced nothing at all however carefully `alt`\n * was written — and a user list where photographs are optional is full of them.\n *\n * @example\n * <Avatar src={user.photo} alt={user.name} fallback=\"HC\" />\n */\nexport function Avatar({ src, alt, fallback, size = 'md', className, ...rest }: AvatarProps) {\n // An empty alt takes no role at all rather than an unnamed one: `role=\"img\"`\n // with nothing to say is a violation, and the empty string exists precisely\n // to say there is nothing to add.\n const named = alt.trim() !== ''\n\n return (\n <AvatarPrimitive.Root\n role={named ? 'img' : undefined}\n aria-label={named ? alt : undefined}\n className={cn(\n 'relative inline-flex shrink-0 overflow-hidden rounded-full border border-(--rule) bg-(--stone)',\n SIZE[size],\n className,\n )}\n {...rest}\n >\n {src && (\n // Empty, deliberately: the root above is the one element that names\n // this person, and a second name here is the same person read twice.\n <AvatarPrimitive.Image src={src} alt=\"\" className=\"size-full object-cover\" />\n )}\n <AvatarPrimitive.Fallback\n // Nothing to announce: the root carries the identity whether or not the\n // photograph ever arrives. Initials read aloud are noise.\n aria-hidden=\"true\"\n className=\"grid size-full place-items-center font-mono uppercase tracking-wider text-(--ink-3-aa)\"\n >\n {fallback}\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n )\n}\n\nexport default Avatar\n"],"mappings":";AAmDI,SAaI,KAbJ;AAjDJ,YAAY,qBAAqB;AAEjC,SAAS,UAAU;AAmBnB,MAAM,OAAO;AAAA,EACX,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAiBO,SAAS,OAAO,EAAE,KAAK,KAAK,UAAU,OAAO,MAAM,WAAW,GAAG,KAAK,GAAgB;AAI3F,QAAM,QAAQ,IAAI,KAAK,MAAM;AAE7B,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,MAAM,QAAQ,QAAQ;AAAA,MACtB,cAAY,QAAQ,MAAM;AAAA,MAC1B,WAAW;AAAA,QACT;AAAA,QACA,KAAK,IAAI;AAAA,QACT;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA;AAAA,QAGC,oBAAC,gBAAgB,OAAhB,EAAsB,KAAU,KAAI,IAAG,WAAU,0BAAyB;AAAA,QAE7E;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YAGC,eAAY;AAAA,YACZ,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,iBAAQ;","names":[]}
|
|
@@ -23,6 +23,12 @@ interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {
|
|
|
23
23
|
* Separators live in `<li aria-hidden>` so the trail is read as its items and
|
|
24
24
|
* not as "home slash work slash".
|
|
25
25
|
*
|
|
26
|
+
* A middle crumb with no `href` is a defect that is invisible twice over: it
|
|
27
|
+
* renders as plain text in the same `--ink-3-aa` as the links beside it, and it
|
|
28
|
+
* carries no `aria-current` either — so it reads as the page the reader is on
|
|
29
|
+
* when it is not, and neither the browser nor a review says a word. The console
|
|
30
|
+
* does, in development.
|
|
31
|
+
*
|
|
26
32
|
* @example
|
|
27
33
|
* <Breadcrumb items={[{ label: 'Components', href: '/components' }, { label: 'Button' }]} />
|
|
28
34
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment } from "react";
|
|
3
3
|
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { DEV, warn } from "../../lib/warn.js";
|
|
4
5
|
function Breadcrumb({
|
|
5
6
|
items,
|
|
6
7
|
label = "Breadcrumb",
|
|
@@ -8,6 +9,7 @@ function Breadcrumb({
|
|
|
8
9
|
className,
|
|
9
10
|
...rest
|
|
10
11
|
}) {
|
|
12
|
+
if (DEV) warnUnlinked(items);
|
|
11
13
|
return /* @__PURE__ */ jsx("nav", { "aria-label": label, className: cn("mono-meta text-(--ink-3-aa)", className), ...rest, children: /* @__PURE__ */ jsx("ol", { className: "m-0 flex list-none flex-wrap items-center gap-2 p-0", children: items.map((item, index) => {
|
|
12
14
|
const last = index === items.length - 1;
|
|
13
15
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -23,6 +25,19 @@ function Breadcrumb({
|
|
|
23
25
|
] }, index);
|
|
24
26
|
}) }) });
|
|
25
27
|
}
|
|
28
|
+
function warnUnlinked(items) {
|
|
29
|
+
items.forEach((item, index) => {
|
|
30
|
+
if (item.href || index === items.length - 1) return;
|
|
31
|
+
const named = typeof item.label === "string" ? `"${item.label}"` : `at ${index}`;
|
|
32
|
+
warn({
|
|
33
|
+
code: "BREADCRUMB_CRUMB_NOT_LINKED",
|
|
34
|
+
problem: `The crumb ${named} has no href, so it renders as plain text in the same colour as the links beside it and takes no aria-current. It reads as the current page, and it is not.`,
|
|
35
|
+
field: `items[${index}]`,
|
|
36
|
+
fix: "Give every crumb but the last an href, or drop the crumb if there is nothing to link to.",
|
|
37
|
+
component: "Breadcrumb"
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
26
41
|
var Breadcrumb_default = Breadcrumb;
|
|
27
42
|
export {
|
|
28
43
|
Breadcrumb,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import { Fragment } from 'react'\nimport type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface Crumb {\n label: ReactNode\n /** Omit on the last crumb — the page you are already on is not a link. */\n href?: string\n}\n\nexport interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {\n items: Crumb[]\n /** Names this trail when a page has more than one. */\n label?: string\n /** Rendered between crumbs. A slash by default; it is decorative either way. */\n separator?: ReactNode\n}\n\n/**\n * Where you are, as a path.\n *\n * The last crumb is plain text with `aria-current=\"page\"` rather than a link to\n * itself — a self-link is the most common breadcrumb bug, and it makes a screen\n * reader offer a navigation that goes nowhere.\n *\n * Separators live in `<li aria-hidden>` so the trail is read as its items and\n * not as \"home slash work slash\".\n *\n * @example\n * <Breadcrumb items={[{ label: 'Components', href: '/components' }, { label: 'Button' }]} />\n */\nexport function Breadcrumb({\n items,\n label = 'Breadcrumb',\n separator = '/',\n className,\n ...rest\n}: BreadcrumbProps) {\n return (\n <nav aria-label={label} className={cn('mono-meta text-(--ink-3-aa)', className)} {...rest}>\n <ol className=\"m-0 flex list-none flex-wrap items-center gap-2 p-0\">\n {items.map((item, index) => {\n const last = index === items.length - 1\n return (\n <Fragment key={index}>\n <li className=\"min-w-0\">\n {item.href && !last ? (\n <a\n href={item.href}\n className=\"text-(--ink-3-aa) underline decoration-transparent underline-offset-4 transition-colors duration-(--duration-fast) hover:text-(--ink) hover:decoration-(--rule-2)\"\n >\n {item.label}\n </a>\n ) : (\n <span aria-current={last ? 'page' : undefined} className={last ? 'text-(--ink)' : undefined}>\n {item.label}\n </span>\n )}\n </li>\n {!last && (\n <li aria-hidden=\"true\" className=\"select-none text-(--rule-2)\">\n {separator}\n </li>\n )}\n </Fragment>\n )\n })}\n </ol>\n </nav>\n )\n}\n\nexport default Breadcrumb\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import { Fragment } from 'react'\nimport type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { DEV, warn } from '../../lib/warn'\n\nexport interface Crumb {\n label: ReactNode\n /** Omit on the last crumb — the page you are already on is not a link. */\n href?: string\n}\n\nexport interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {\n items: Crumb[]\n /** Names this trail when a page has more than one. */\n label?: string\n /** Rendered between crumbs. A slash by default; it is decorative either way. */\n separator?: ReactNode\n}\n\n/**\n * Where you are, as a path.\n *\n * The last crumb is plain text with `aria-current=\"page\"` rather than a link to\n * itself — a self-link is the most common breadcrumb bug, and it makes a screen\n * reader offer a navigation that goes nowhere.\n *\n * Separators live in `<li aria-hidden>` so the trail is read as its items and\n * not as \"home slash work slash\".\n *\n * A middle crumb with no `href` is a defect that is invisible twice over: it\n * renders as plain text in the same `--ink-3-aa` as the links beside it, and it\n * carries no `aria-current` either — so it reads as the page the reader is on\n * when it is not, and neither the browser nor a review says a word. The console\n * does, in development.\n *\n * @example\n * <Breadcrumb items={[{ label: 'Components', href: '/components' }, { label: 'Button' }]} />\n */\nexport function Breadcrumb({\n items,\n label = 'Breadcrumb',\n separator = '/',\n className,\n ...rest\n}: BreadcrumbProps) {\n if (DEV) warnUnlinked(items)\n\n return (\n <nav aria-label={label} className={cn('mono-meta text-(--ink-3-aa)', className)} {...rest}>\n <ol className=\"m-0 flex list-none flex-wrap items-center gap-2 p-0\">\n {items.map((item, index) => {\n const last = index === items.length - 1\n return (\n <Fragment key={index}>\n <li className=\"min-w-0\">\n {item.href && !last ? (\n <a\n href={item.href}\n className=\"text-(--ink-3-aa) underline decoration-transparent underline-offset-4 transition-colors duration-(--duration-fast) hover:text-(--ink) hover:decoration-(--rule-2)\"\n >\n {item.label}\n </a>\n ) : (\n <span aria-current={last ? 'page' : undefined} className={last ? 'text-(--ink)' : undefined}>\n {item.label}\n </span>\n )}\n </li>\n {!last && (\n <li aria-hidden=\"true\" className=\"select-none text-(--rule-2)\">\n {separator}\n </li>\n )}\n </Fragment>\n )\n })}\n </ol>\n </nav>\n )\n}\n\n/**\n * Dev only: every crumb but the last owes the reader a destination.\n *\n * The label is a ReactNode, so a crumb whose label is not a string is named by\n * its position instead — the message has to identify the crumb well enough to\n * repair the call without opening anything.\n */\nfunction warnUnlinked(items: Crumb[]): void {\n items.forEach((item, index) => {\n if (item.href || index === items.length - 1) return\n const named = typeof item.label === 'string' ? `\"${item.label}\"` : `at ${index}`\n\n warn({\n code: 'BREADCRUMB_CRUMB_NOT_LINKED',\n problem: `The crumb ${named} has no href, so it renders as plain text in the same colour as the links beside it and takes no aria-current. It reads as the current page, and it is not.`,\n field: `items[${index}]`,\n fix: 'Give every crumb but the last an href, or drop the crumb if there is nothing to link to.',\n component: 'Breadcrumb',\n })\n })\n}\n\nexport default Breadcrumb\n"],"mappings":"AAqDY,SAGM,KAHN;AArDZ,SAAS,gBAAgB;AAEzB,SAAS,UAAU;AACnB,SAAS,KAAK,YAAY;AAmCnB,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,MAAI,IAAK,cAAa,KAAK;AAE3B,SACE,oBAAC,SAAI,cAAY,OAAO,WAAW,GAAG,+BAA+B,SAAS,GAAI,GAAG,MACnF,8BAAC,QAAG,WAAU,uDACX,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,UAAM,OAAO,UAAU,MAAM,SAAS;AACtC,WACE,qBAAC,YACC;AAAA,0BAAC,QAAG,WAAU,WACX,eAAK,QAAQ,CAAC,OACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,KAAK;AAAA,UACX,WAAU;AAAA,UAET,eAAK;AAAA;AAAA,MACR,IAEA,oBAAC,UAAK,gBAAc,OAAO,SAAS,QAAW,WAAW,OAAO,iBAAiB,QAC/E,eAAK,OACR,GAEJ;AAAA,MACC,CAAC,QACA,oBAAC,QAAG,eAAY,QAAO,WAAU,+BAC9B,qBACH;AAAA,SAlBW,KAoBf;AAAA,EAEJ,CAAC,GACH,GACF;AAEJ;AASA,SAAS,aAAa,OAAsB;AAC1C,QAAM,QAAQ,CAAC,MAAM,UAAU;AAC7B,QAAI,KAAK,QAAQ,UAAU,MAAM,SAAS,EAAG;AAC7C,UAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,IAAI,KAAK,KAAK,MAAM,MAAM,KAAK;AAE9E,SAAK;AAAA,MACH,MAAM;AAAA,MACN,SAAS,aAAa,KAAK;AAAA,MAC3B,OAAO,SAAS,KAAK;AAAA,MACrB,KAAK;AAAA,MACL,WAAW;AAAA,IACb,CAAC;AAAA,EACH,CAAC;AACH;AAEA,IAAO,qBAAQ;","names":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
3
|
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { DEV, warn } from "../../lib/warn.js";
|
|
4
5
|
import { Spinner } from "../Spinner/Spinner.js";
|
|
5
6
|
const BASE = "inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius) font-sans transition-[opacity,color,border-color,background-color] duration-(--duration-fast) ease-(--ease-out-expo) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none aria-disabled:opacity-(--disabled-opacity) aria-disabled:pointer-events-none";
|
|
6
7
|
const VARIANT = {
|
|
@@ -37,6 +38,19 @@ function Button(props) {
|
|
|
37
38
|
className
|
|
38
39
|
} = props;
|
|
39
40
|
const cls = cn(BASE, VARIANT[variant], iconOnly ? ICON_SIZE[size] : SIZE[size], className);
|
|
41
|
+
if (DEV && iconOnly) {
|
|
42
|
+
const named = typeof props["aria-label"] === "string" && props["aria-label"].trim() !== "";
|
|
43
|
+
const described = typeof props["aria-labelledby"] === "string";
|
|
44
|
+
if (!named && !described) {
|
|
45
|
+
warn({
|
|
46
|
+
code: "BUTTON_ICON_ONLY_UNNAMED",
|
|
47
|
+
problem: 'An iconOnly Button has no text, so without an accessible name a screen reader announces it as "button" and nothing else.',
|
|
48
|
+
field: "aria-label",
|
|
49
|
+
fix: 'Add aria-label with the action the button performs, e.g. aria-label="Delete invoice".',
|
|
50
|
+
component: "Button"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
40
54
|
const spinnerTone = variant === "primary" || variant === "danger" ? "current" : "default";
|
|
41
55
|
const decoration = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
56
|
loading && /* @__PURE__ */ jsx(Spinner, { size: SPINNER_SIZE[size], tone: spinnerTone, label: null }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { Spinner } from '../Spinner/Spinner'\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * The base every variant shares.\n *\n * It reads `--radius`, the CONTROL step, which is the same corner an `Input`,\n * a `Select` trigger and a `Textarea` draw. It used to be a pill, and a pill\n * beside an 8px field is two different ideas of what a control is — the one\n * inconsistency in the system that every reader noticed and none of the\n * component pages could explain. Everything on the control line now agrees,\n * and the capsule is kept for the things that genuinely are one: a badge, a\n * status pill, a segmented strip, a progress track.\n *\n * It does not move on hover. An earlier base lifted a pixel and asked for a\n * shadow the White Reset does not have — `--shadow` resolves to `none`, so that\n * hover state was invisible on every button in the system while still costing a\n * transition. Opacity carries the filled hover; the border carries the outlined\n * one.\n *\n * The transition names its properties rather than using `all`, so a layout\n * change (a width that grows when the label becomes \"Saving…\") is instant\n * instead of sliding.\n */\nconst BASE =\n 'inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius) font-sans transition-[opacity,color,border-color,background-color] duration-(--duration-fast) ease-(--ease-out-expo) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none aria-disabled:opacity-(--disabled-opacity) aria-disabled:pointer-events-none'\n\nconst VARIANT: Record<ButtonVariant, string> = {\n primary: 'bg-(--accent) text-(--accent-foreground) border border-(--accent) hover:opacity-85',\n secondary: 'border border-(--rule-2) text-(--ink) bg-transparent hover:border-(--ink)',\n ghost: 'border border-transparent text-(--ink-2) hover:text-(--ink) hover:bg-(--stone)',\n /* The one place chroma is allowed on a control, and only because the action\n is destructive — that is a state, not a brand colour. */\n danger: 'bg-(--danger) text-(--paper) border border-(--danger) hover:opacity-85',\n}\n\n/**\n * `min-h` rather than `h`, so a button that wraps to two lines grows instead of\n * clipping.\n *\n * The heights come from the density tokens rather than from a literal, so a\n * subtree marked `data-density=\"compact\"` shrinks its controls without a single\n * call site being told. At the default density `md` is 44px — the pointer\n * target WCAG 2.5.5 asks for.\n */\nconst SIZE: Record<ButtonSize, string> = {\n sm: 'min-h-(--control-h-sm) px-(--control-px-sm) py-(--control-py-sm) text-[13px]',\n md: 'min-h-(--control-h-md) px-(--control-px-md) py-(--control-py-md) text-sm',\n lg: 'min-h-(--control-h-lg) px-(--control-px-lg) py-(--control-py-lg) text-[15px]',\n}\n\n/** Square, because an icon has no label to give the box its width. */\nconst ICON_SIZE: Record<ButtonSize, string> = {\n sm: 'size-(--control-h-sm) p-0',\n md: 'size-(--control-h-md) p-0',\n lg: 'size-(--control-h-lg) p-0',\n}\n\nconst SPINNER_SIZE: Record<ButtonSize, 'sm' | 'md'> = { sm: 'sm', md: 'sm', lg: 'md' }\n\ninterface CommonProps {\n children?: ReactNode\n /**\n * Which action this is. `primary` is the one thing the screen wants you to\n * do, so there is at most one per view; `danger` is reserved for destructive\n * actions and is the only place chroma is allowed on a control.\n */\n variant?: ButtonVariant\n /** 36 / 44 / 48px tall. `md` meets the pointer-target floor on its own. */\n size?: ButtonSize\n /** Optional mono keycap glyph rendered after the label (e.g. \"P\"). */\n keycap?: string\n /**\n * Swaps the leading content for a spinner and blocks interaction. The label\n * stays put — a button that empties out while it works loses its width, and\n * the page reflows under the pointer that just clicked it.\n */\n loading?: boolean\n /**\n * Square control with no label. REQUIRES `aria-label`: an icon-only button\n * with no accessible name is invisible to a screen reader, and this is the\n * single most common way a design system ships an unusable control.\n */\n iconOnly?: boolean\n /**\n * Render the child element instead of a `<button>`, keeping these styles.\n * Use it to hand the styling to a router's `Link` — `asChild` is what keeps\n * this package free of any one router.\n *\n * The decoration slots (`keycap`, `loading`) are not injected into a slotted\n * child; compose them inside it yourself.\n *\n * @example\n * <Button asChild><Link href=\"/work\">Work</Link></Button>\n */\n asChild?: boolean\n className?: string\n}\n\ntype ButtonAsLink = CommonProps &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> & { href: string }\n\ntype ButtonAsButton = CommonProps &\n Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children'> & { href?: undefined }\n\nexport type ButtonProps = ButtonAsLink | ButtonAsButton\n\nfunction Keycap({ glyph }: { glyph: string }) {\n return (\n <span className=\"rounded-(--radius-sm) border border-current px-2 py-0.5 font-mono text-[11px] opacity-55\">\n {glyph}\n </span>\n )\n}\n\n/**\n * The system's action.\n *\n * Renders a `<button>`, or an `<a>` when given `href`, or whatever you hand it\n * via `asChild`. No router is imported, so the package stays framework-agnostic\n * and a Next or React Router app wires its own `Link` at the call site.\n *\n * Server-component friendly: there is no client boundary here, so it renders in\n * a static page as well as an interactive one.\n *\n * @example\n * <Button variant=\"secondary\" keycap=\"P\">View projects</Button>\n * @example\n * <Button loading disabled>Saving…</Button>\n * @example\n * <Button iconOnly aria-label=\"Close\"><X size={16} /></Button>\n */\nexport function Button(props: ButtonProps) {\n const {\n children,\n variant = 'primary',\n size = 'md',\n keycap,\n loading = false,\n iconOnly = false,\n asChild = false,\n className,\n } = props\n\n const cls = cn(BASE, VARIANT[variant], iconOnly ? ICON_SIZE[size] : SIZE[size], className)\n\n // A filled ground needs a spinner in the inherited colour; an outlined one\n // reads better against the page's own rule scale.\n const spinnerTone = variant === 'primary' || variant === 'danger' ? 'current' : 'default'\n const decoration = (\n <>\n {loading && <Spinner size={SPINNER_SIZE[size]} tone={spinnerTone} label={null} />}\n {children}\n {keycap && <Keycap glyph={keycap} />}\n </>\n )\n\n if (asChild) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <Slot className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n if (props.href !== undefined) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, href, ...rest } = props\n return (\n <a\n href={href}\n className={cls}\n // A link cannot be `disabled`; `aria-disabled` plus the pointer-events\n // rule in BASE is the accessible equivalent.\n aria-disabled={loading || undefined}\n {...rest}\n >\n {decoration}\n </a>\n )\n }\n\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <button\n type={rest.type ?? 'button'}\n aria-busy={loading || undefined}\n disabled={rest.disabled || loading}\n className={cls}\n {...rest}\n >\n {decoration}\n </button>\n )\n}\n\nexport default Button\n"],"mappings":"AAkHI,SAyCA,UAzCA,KAyCA,YAzCA;AAlHJ,SAAS,YAAY;AAErB,SAAS,UAAU;AACnB,SAAS,eAAe;AA0BxB,MAAM,OACJ;AAEF,MAAM,UAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA;AAAA;AAAA,EAGP,QAAQ;AACV;AAWA,MAAM,OAAmC;AAAA,EACvC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,MAAM,YAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,eAAgD,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK;AAiDrF,SAAS,OAAO,EAAE,MAAM,GAAsB;AAC5C,SACE,oBAAC,UAAK,WAAU,4FACb,iBACH;AAEJ;AAmBO,SAAS,OAAO,OAAoB;AACzC,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,EACF,IAAI;AAEJ,QAAM,MAAM,GAAG,MAAM,QAAQ,OAAO,GAAG,WAAW,UAAU,IAAI,IAAI,KAAK,IAAI,GAAG,SAAS;AAIzF,QAAM,cAAc,YAAY,aAAa,YAAY,WAAW,YAAY;AAChF,QAAM,aACJ,iCACG;AAAA,eAAW,oBAAC,WAAQ,MAAM,aAAa,IAAI,GAAG,MAAM,aAAa,OAAO,MAAM;AAAA,IAC9E;AAAA,IACA,UAAU,oBAAC,UAAO,OAAO,QAAQ;AAAA,KACpC;AAGF,MAAI,SAAS;AACX,UAAM,EAAE,UAAUA,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,GAAGC,MAAK,IAAI;AAC7H,WACE,oBAAC,QAAK,WAAW,KAAM,GAAGA,OACvB,UACH;AAAA,EAEJ;AAEA,MAAI,MAAM,SAAS,QAAW;AAC5B,UAAM,EAAE,UAAUR,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,MAAM,GAAGC,MAAK,IAAI;AACnI,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,QAGX,iBAAe,WAAW;AAAA,QACzB,GAAGA;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,QAAM,EAAE,UAAU,IAAI,SAAS,IAAI,MAAM,IAAI,QAAQ,IAAI,SAAS,IAAI,UAAU,IAAI,SAAS,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI;AAC7H,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK,QAAQ;AAAA,MACnB,aAAW,WAAW;AAAA,MACtB,UAAU,KAAK,YAAY;AAAA,MAC3B,WAAW;AAAA,MACV,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,iBAAQ;","names":["_c","_v","_s","_k","_l","_i","_a","_cn","rest"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { DEV, warn } from '../../lib/warn'\nimport { Spinner } from '../Spinner/Spinner'\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * The base every variant shares.\n *\n * It reads `--radius`, the CONTROL step, which is the same corner an `Input`,\n * a `Select` trigger and a `Textarea` draw. It used to be a pill, and a pill\n * beside an 8px field is two different ideas of what a control is — the one\n * inconsistency in the system that every reader noticed and none of the\n * component pages could explain. Everything on the control line now agrees,\n * and the capsule is kept for the things that genuinely are one: a badge, a\n * status pill, a segmented strip, a progress track.\n *\n * It does not move on hover. An earlier base lifted a pixel and asked for a\n * shadow the White Reset does not have — `--shadow` resolves to `none`, so that\n * hover state was invisible on every button in the system while still costing a\n * transition. Opacity carries the filled hover; the border carries the outlined\n * one.\n *\n * The transition names its properties rather than using `all`, so a layout\n * change (a width that grows when the label becomes \"Saving…\") is instant\n * instead of sliding.\n */\nconst BASE =\n 'inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius) font-sans transition-[opacity,color,border-color,background-color] duration-(--duration-fast) ease-(--ease-out-expo) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none aria-disabled:opacity-(--disabled-opacity) aria-disabled:pointer-events-none'\n\nconst VARIANT: Record<ButtonVariant, string> = {\n primary: 'bg-(--accent) text-(--accent-foreground) border border-(--accent) hover:opacity-85',\n secondary: 'border border-(--rule-2) text-(--ink) bg-transparent hover:border-(--ink)',\n ghost: 'border border-transparent text-(--ink-2) hover:text-(--ink) hover:bg-(--stone)',\n /* The one place chroma is allowed on a control, and only because the action\n is destructive — that is a state, not a brand colour. */\n danger: 'bg-(--danger) text-(--paper) border border-(--danger) hover:opacity-85',\n}\n\n/**\n * `min-h` rather than `h`, so a button that wraps to two lines grows instead of\n * clipping.\n *\n * The heights come from the density tokens rather than from a literal, so a\n * subtree marked `data-density=\"compact\"` shrinks its controls without a single\n * call site being told. At the default density `md` is 44px — the pointer\n * target WCAG 2.5.5 asks for.\n */\nconst SIZE: Record<ButtonSize, string> = {\n sm: 'min-h-(--control-h-sm) px-(--control-px-sm) py-(--control-py-sm) text-[13px]',\n md: 'min-h-(--control-h-md) px-(--control-px-md) py-(--control-py-md) text-sm',\n lg: 'min-h-(--control-h-lg) px-(--control-px-lg) py-(--control-py-lg) text-[15px]',\n}\n\n/** Square, because an icon has no label to give the box its width. */\nconst ICON_SIZE: Record<ButtonSize, string> = {\n sm: 'size-(--control-h-sm) p-0',\n md: 'size-(--control-h-md) p-0',\n lg: 'size-(--control-h-lg) p-0',\n}\n\nconst SPINNER_SIZE: Record<ButtonSize, 'sm' | 'md'> = { sm: 'sm', md: 'sm', lg: 'md' }\n\ninterface CommonProps {\n children?: ReactNode\n /**\n * Which action this is. `primary` is the one thing the screen wants you to\n * do, so there is at most one per view; `danger` is reserved for destructive\n * actions and is the only place chroma is allowed on a control.\n */\n variant?: ButtonVariant\n /** 36 / 44 / 48px tall. `md` meets the pointer-target floor on its own. */\n size?: ButtonSize\n /** Optional mono keycap glyph rendered after the label (e.g. \"P\"). */\n keycap?: string\n /**\n * Swaps the leading content for a spinner and blocks interaction. The label\n * stays put — a button that empties out while it works loses its width, and\n * the page reflows under the pointer that just clicked it.\n */\n loading?: boolean\n /**\n * Square control with no label. REQUIRES `aria-label`: an icon-only button\n * with no accessible name is invisible to a screen reader, and this is the\n * single most common way a design system ships an unusable control.\n */\n iconOnly?: boolean\n /**\n * Render the child element instead of a `<button>`, keeping these styles.\n * Use it to hand the styling to a router's `Link` — `asChild` is what keeps\n * this package free of any one router.\n *\n * The decoration slots (`keycap`, `loading`) are not injected into a slotted\n * child; compose them inside it yourself.\n *\n * @example\n * <Button asChild><Link href=\"/work\">Work</Link></Button>\n */\n asChild?: boolean\n className?: string\n}\n\ntype ButtonAsLink = CommonProps &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> & { href: string }\n\ntype ButtonAsButton = CommonProps &\n Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children'> & { href?: undefined }\n\nexport type ButtonProps = ButtonAsLink | ButtonAsButton\n\nfunction Keycap({ glyph }: { glyph: string }) {\n return (\n <span className=\"rounded-(--radius-sm) border border-current px-2 py-0.5 font-mono text-[11px] opacity-55\">\n {glyph}\n </span>\n )\n}\n\n/**\n * The system's action.\n *\n * Renders a `<button>`, or an `<a>` when given `href`, or whatever you hand it\n * via `asChild`. No router is imported, so the package stays framework-agnostic\n * and a Next or React Router app wires its own `Link` at the call site.\n *\n * Server-component friendly: there is no client boundary here, so it renders in\n * a static page as well as an interactive one.\n *\n * @example\n * <Button variant=\"secondary\" keycap=\"P\">View projects</Button>\n * @example\n * <Button loading disabled>Saving…</Button>\n * @example\n * <Button iconOnly aria-label=\"Close\"><X size={16} /></Button>\n */\nexport function Button(props: ButtonProps) {\n const {\n children,\n variant = 'primary',\n size = 'md',\n keycap,\n loading = false,\n iconOnly = false,\n asChild = false,\n className,\n } = props\n\n const cls = cn(BASE, VARIANT[variant], iconOnly ? ICON_SIZE[size] : SIZE[size], className)\n\n if (DEV && iconOnly) {\n // The control renders correctly and is announced as \"button\" and nothing\n // else. Nothing about the rendered page says so, which is why this is the\n // single most common way a design system ships an unusable control.\n const named =\n typeof props['aria-label'] === 'string' && props['aria-label'].trim() !== ''\n const described = typeof props['aria-labelledby'] === 'string'\n if (!named && !described) {\n warn({\n code: 'BUTTON_ICON_ONLY_UNNAMED',\n problem:\n 'An iconOnly Button has no text, so without an accessible name a screen reader announces it as \"button\" and nothing else.',\n field: 'aria-label',\n fix: 'Add aria-label with the action the button performs, e.g. aria-label=\"Delete invoice\".',\n component: 'Button',\n })\n }\n }\n\n // A filled ground needs a spinner in the inherited colour; an outlined one\n // reads better against the page's own rule scale.\n const spinnerTone = variant === 'primary' || variant === 'danger' ? 'current' : 'default'\n const decoration = (\n <>\n {loading && <Spinner size={SPINNER_SIZE[size]} tone={spinnerTone} label={null} />}\n {children}\n {keycap && <Keycap glyph={keycap} />}\n </>\n )\n\n if (asChild) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <Slot className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n if (props.href !== undefined) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, href, ...rest } = props\n return (\n <a\n href={href}\n className={cls}\n // A link cannot be `disabled`; `aria-disabled` plus the pointer-events\n // rule in BASE is the accessible equivalent.\n aria-disabled={loading || undefined}\n {...rest}\n >\n {decoration}\n </a>\n )\n }\n\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <button\n type={rest.type ?? 'button'}\n aria-busy={loading || undefined}\n disabled={rest.disabled || loading}\n className={cls}\n {...rest}\n >\n {decoration}\n </button>\n )\n}\n\nexport default Button\n"],"mappings":"AAmHI,SA4DA,UA5DA,KA4DA,YA5DA;AAnHJ,SAAS,YAAY;AAErB,SAAS,UAAU;AACnB,SAAS,KAAK,YAAY;AAC1B,SAAS,eAAe;AA0BxB,MAAM,OACJ;AAEF,MAAM,UAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA;AAAA;AAAA,EAGP,QAAQ;AACV;AAWA,MAAM,OAAmC;AAAA,EACvC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,MAAM,YAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,eAAgD,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK;AAiDrF,SAAS,OAAO,EAAE,MAAM,GAAsB;AAC5C,SACE,oBAAC,UAAK,WAAU,4FACb,iBACH;AAEJ;AAmBO,SAAS,OAAO,OAAoB;AACzC,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,EACF,IAAI;AAEJ,QAAM,MAAM,GAAG,MAAM,QAAQ,OAAO,GAAG,WAAW,UAAU,IAAI,IAAI,KAAK,IAAI,GAAG,SAAS;AAEzF,MAAI,OAAO,UAAU;AAInB,UAAM,QACJ,OAAO,MAAM,YAAY,MAAM,YAAY,MAAM,YAAY,EAAE,KAAK,MAAM;AAC5E,UAAM,YAAY,OAAO,MAAM,iBAAiB,MAAM;AACtD,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,WAAK;AAAA,QACH,MAAM;AAAA,QACN,SACE;AAAA,QACF,OAAO;AAAA,QACP,KAAK;AAAA,QACL,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAIA,QAAM,cAAc,YAAY,aAAa,YAAY,WAAW,YAAY;AAChF,QAAM,aACJ,iCACG;AAAA,eAAW,oBAAC,WAAQ,MAAM,aAAa,IAAI,GAAG,MAAM,aAAa,OAAO,MAAM;AAAA,IAC9E;AAAA,IACA,UAAU,oBAAC,UAAO,OAAO,QAAQ;AAAA,KACpC;AAGF,MAAI,SAAS;AACX,UAAM,EAAE,UAAUA,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,GAAGC,MAAK,IAAI;AAC7H,WACE,oBAAC,QAAK,WAAW,KAAM,GAAGA,OACvB,UACH;AAAA,EAEJ;AAEA,MAAI,MAAM,SAAS,QAAW;AAC5B,UAAM,EAAE,UAAUR,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,MAAM,GAAGC,MAAK,IAAI;AACnI,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,QAGX,iBAAe,WAAW;AAAA,QACzB,GAAGA;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,QAAM,EAAE,UAAU,IAAI,SAAS,IAAI,MAAM,IAAI,QAAQ,IAAI,SAAS,IAAI,UAAU,IAAI,SAAS,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI;AAC7H,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK,QAAQ;AAAA,MACnB,aAAW,WAAW;AAAA,MACtB,UAAU,KAAK,YAAY;AAAA,MAC3B,WAAW;AAAA,MACV,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,iBAAQ;","names":["_c","_v","_s","_k","_l","_i","_a","_cn","rest"]}
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { DayPickerProps } from 'react-day-picker';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The strings the month-and-year picker says, none of which react-day-picker
|
|
6
|
+
* knows about — its own `labels` prop covers its own chrome, and this panel is
|
|
7
|
+
* ours. English by default, and exposed for the same reason `AppShell` exposes
|
|
8
|
+
* `openLabel`: every one of these controls is an icon or a bare number, so the
|
|
9
|
+
* string IS the control as far as a screen reader is concerned.
|
|
10
|
+
*
|
|
11
|
+
* The month names already follow `locale`, which is what made the chrome around
|
|
12
|
+
* them read as an oversight rather than as a policy.
|
|
13
|
+
*/
|
|
14
|
+
interface CalendarLabels {
|
|
15
|
+
/** Names the panel showing twelve months. */
|
|
16
|
+
monthPanelLabel?: string;
|
|
17
|
+
/** Names the panel showing a page of years. */
|
|
18
|
+
yearPanelLabel?: string;
|
|
19
|
+
/** The two chevrons that browse a year at a time, inside the month panel. */
|
|
20
|
+
previousYearLabel?: string;
|
|
21
|
+
nextYearLabel?: string;
|
|
22
|
+
/** The two chevrons that page the year grid, twenty-four at a time. */
|
|
23
|
+
earlierYearsLabel?: string;
|
|
24
|
+
laterYearsLabel?: string;
|
|
25
|
+
}
|
|
26
|
+
type CalendarProps = DayPickerProps & CalendarLabels;
|
|
5
27
|
/**
|
|
6
28
|
* A month, as a grid of days.
|
|
7
29
|
*
|
|
@@ -17,12 +39,16 @@ type CalendarProps = DayPickerProps;
|
|
|
17
39
|
* library's OWN class is kept on each slot, because passing a class REPLACES it
|
|
18
40
|
* and every `.rdp-*` selector downstream depends on those.
|
|
19
41
|
*
|
|
42
|
+
* The month names follow `locale`. The picker's own chrome — the two panels and
|
|
43
|
+
* the four chevrons that browse them — follows `CalendarLabels`, because
|
|
44
|
+
* `locale` cannot supply strings this package invented.
|
|
45
|
+
*
|
|
20
46
|
* @example
|
|
21
47
|
* <Calendar mode="single" selected={date} onSelect={setDate} />
|
|
22
48
|
* @example
|
|
23
49
|
* // A birth date: widen the years, since the default span is deliberately short.
|
|
24
50
|
* <Calendar mode="single" startMonth={new Date(1920, 0)} endMonth={new Date()} />
|
|
25
51
|
*/
|
|
26
|
-
declare function Calendar({ className, classNames, components, ...props }: CalendarProps): react.JSX.Element;
|
|
52
|
+
declare function Calendar({ className, classNames, components, monthPanelLabel, yearPanelLabel, previousYearLabel, nextYearLabel, earlierYearsLabel, laterYearsLabel, ...props }: CalendarProps): react.JSX.Element;
|
|
27
53
|
|
|
28
|
-
export { Calendar, type CalendarProps, Calendar as default };
|
|
54
|
+
export { Calendar, type CalendarLabels, type CalendarProps, Calendar as default };
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState
|
|
11
|
+
} from "react";
|
|
5
12
|
import {
|
|
6
13
|
DayPicker,
|
|
7
14
|
useDayPicker
|
|
@@ -9,12 +16,21 @@ import {
|
|
|
9
16
|
import { cn } from "../../lib/cn.js";
|
|
10
17
|
const DEFAULT_SPAN = 10;
|
|
11
18
|
const YEAR_PAGE = 24;
|
|
19
|
+
const DEFAULT_LABELS = {
|
|
20
|
+
monthPanelLabel: "Month and year",
|
|
21
|
+
yearPanelLabel: "Year",
|
|
22
|
+
previousYearLabel: "Previous year",
|
|
23
|
+
nextYearLabel: "Next year",
|
|
24
|
+
earlierYearsLabel: "Earlier years",
|
|
25
|
+
laterYearsLabel: "Later years"
|
|
26
|
+
};
|
|
12
27
|
const PickerContext = createContext({
|
|
13
28
|
open: null,
|
|
14
29
|
setOpen: () => {
|
|
15
30
|
},
|
|
16
31
|
startMonth: /* @__PURE__ */ new Date(),
|
|
17
|
-
endMonth: /* @__PURE__ */ new Date()
|
|
32
|
+
endMonth: /* @__PURE__ */ new Date(),
|
|
33
|
+
labels: DEFAULT_LABELS
|
|
18
34
|
});
|
|
19
35
|
function useLabels() {
|
|
20
36
|
const { dayPickerProps } = useDayPicker();
|
|
@@ -38,7 +54,7 @@ function MonthYearPanel({
|
|
|
38
54
|
year: browsing,
|
|
39
55
|
month
|
|
40
56
|
}) {
|
|
41
|
-
const { setOpen, startMonth, endMonth } = useContext(PickerContext);
|
|
57
|
+
const { setOpen, startMonth, endMonth, labels: names } = useContext(PickerContext);
|
|
42
58
|
const { goToMonth } = useDayPicker();
|
|
43
59
|
const labels = useLabels();
|
|
44
60
|
const year = browsing ?? month.getFullYear();
|
|
@@ -64,13 +80,13 @@ function MonthYearPanel({
|
|
|
64
80
|
const back = start - YEAR_PAGE >= firstYear;
|
|
65
81
|
const forward = start + YEAR_PAGE <= lastYear;
|
|
66
82
|
const move = (delta) => setOpen({ index, view: "years", anchor: start + delta * YEAR_PAGE });
|
|
67
|
-
return /* @__PURE__ */ jsxs(Panel, { label:
|
|
83
|
+
return /* @__PURE__ */ jsxs(Panel, { label: names.yearPanelLabel, children: [
|
|
68
84
|
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-1", children: [
|
|
69
85
|
/* @__PURE__ */ jsx(
|
|
70
86
|
"button",
|
|
71
87
|
{
|
|
72
88
|
type: "button",
|
|
73
|
-
"aria-label":
|
|
89
|
+
"aria-label": names.earlierYearsLabel,
|
|
74
90
|
disabled: !back,
|
|
75
91
|
onClick: () => move(-1),
|
|
76
92
|
className: STEP,
|
|
@@ -86,7 +102,7 @@ function MonthYearPanel({
|
|
|
86
102
|
"button",
|
|
87
103
|
{
|
|
88
104
|
type: "button",
|
|
89
|
-
"aria-label":
|
|
105
|
+
"aria-label": names.laterYearsLabel,
|
|
90
106
|
disabled: !forward,
|
|
91
107
|
onClick: () => move(1),
|
|
92
108
|
className: STEP,
|
|
@@ -107,13 +123,13 @@ function MonthYearPanel({
|
|
|
107
123
|
)) })
|
|
108
124
|
] }, "years");
|
|
109
125
|
}
|
|
110
|
-
return /* @__PURE__ */ jsxs(Panel, { label:
|
|
126
|
+
return /* @__PURE__ */ jsxs(Panel, { label: names.monthPanelLabel, children: [
|
|
111
127
|
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-1", children: [
|
|
112
128
|
/* @__PURE__ */ jsx(
|
|
113
129
|
"button",
|
|
114
130
|
{
|
|
115
131
|
type: "button",
|
|
116
|
-
"aria-label":
|
|
132
|
+
"aria-label": names.previousYearLabel,
|
|
117
133
|
disabled: year <= firstYear,
|
|
118
134
|
onClick: () => setOpen({ index, view: "months", year: year - 1 }),
|
|
119
135
|
className: STEP,
|
|
@@ -133,7 +149,7 @@ function MonthYearPanel({
|
|
|
133
149
|
"button",
|
|
134
150
|
{
|
|
135
151
|
type: "button",
|
|
136
|
-
"aria-label":
|
|
152
|
+
"aria-label": names.nextYearLabel,
|
|
137
153
|
disabled: year >= lastYear,
|
|
138
154
|
onClick: () => setOpen({ index, view: "months", year: year + 1 }),
|
|
139
155
|
className: STEP,
|
|
@@ -162,12 +178,25 @@ function Panel({ label, children }) {
|
|
|
162
178
|
const target = box.current?.querySelector("[aria-current]") ?? grid?.querySelector("button:not(:disabled)");
|
|
163
179
|
target?.focus();
|
|
164
180
|
}, []);
|
|
181
|
+
const stops = () => Array.from(box.current?.querySelectorAll("button:not([disabled])") ?? []);
|
|
182
|
+
const wrap = (event) => {
|
|
183
|
+
if (event.key !== "Tab") return;
|
|
184
|
+
const buttons = stops();
|
|
185
|
+
const first = buttons[0];
|
|
186
|
+
const last = buttons[buttons.length - 1];
|
|
187
|
+
if (!first || !last) return;
|
|
188
|
+
const [edge, opposite] = event.shiftKey ? [first, last] : [last, first];
|
|
189
|
+
if (document.activeElement !== edge) return;
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
opposite.focus();
|
|
192
|
+
};
|
|
165
193
|
return /* @__PURE__ */ jsx(
|
|
166
194
|
"div",
|
|
167
195
|
{
|
|
168
196
|
ref: box,
|
|
169
|
-
role: "
|
|
197
|
+
role: "dialog",
|
|
170
198
|
"aria-label": label,
|
|
199
|
+
onKeyDown: wrap,
|
|
171
200
|
className: "absolute inset-x-0 bottom-0 top-12 z-2 flex flex-col justify-center bg-(--paper)",
|
|
172
201
|
children
|
|
173
202
|
}
|
|
@@ -229,14 +258,33 @@ function CalendarCaption({ calendarMonth, displayIndex, className, ...rest }) {
|
|
|
229
258
|
}
|
|
230
259
|
);
|
|
231
260
|
}
|
|
232
|
-
function Calendar({
|
|
261
|
+
function Calendar({
|
|
262
|
+
className,
|
|
263
|
+
classNames,
|
|
264
|
+
components,
|
|
265
|
+
monthPanelLabel,
|
|
266
|
+
yearPanelLabel,
|
|
267
|
+
previousYearLabel,
|
|
268
|
+
nextYearLabel,
|
|
269
|
+
earlierYearsLabel,
|
|
270
|
+
laterYearsLabel,
|
|
271
|
+
...props
|
|
272
|
+
}) {
|
|
233
273
|
const now = /* @__PURE__ */ new Date();
|
|
234
274
|
const [open, setOpen] = useState(null);
|
|
275
|
+
const labels = {
|
|
276
|
+
monthPanelLabel: monthPanelLabel ?? DEFAULT_LABELS.monthPanelLabel,
|
|
277
|
+
yearPanelLabel: yearPanelLabel ?? DEFAULT_LABELS.yearPanelLabel,
|
|
278
|
+
previousYearLabel: previousYearLabel ?? DEFAULT_LABELS.previousYearLabel,
|
|
279
|
+
nextYearLabel: nextYearLabel ?? DEFAULT_LABELS.nextYearLabel,
|
|
280
|
+
earlierYearsLabel: earlierYearsLabel ?? DEFAULT_LABELS.earlierYearsLabel,
|
|
281
|
+
laterYearsLabel: laterYearsLabel ?? DEFAULT_LABELS.laterYearsLabel
|
|
282
|
+
};
|
|
235
283
|
const startMonth = props.startMonth ?? new Date(now.getFullYear() - DEFAULT_SPAN, 0);
|
|
236
284
|
const endMonth = props.endMonth ?? new Date(now.getFullYear() + DEFAULT_SPAN, 11);
|
|
237
285
|
const isRange = props.mode === "range";
|
|
238
286
|
const FILL = "[&>button]:bg-(--accent) [&>button]:text-(--accent-foreground) [&>button:hover]:bg-(--accent) [&>button:hover]:text-(--accent-foreground)";
|
|
239
|
-
return /* @__PURE__ */ jsx(PickerContext, { value: { open, setOpen, startMonth, endMonth }, children: /* @__PURE__ */ jsx(
|
|
287
|
+
return /* @__PURE__ */ jsx(PickerContext, { value: { open, setOpen, startMonth, endMonth, labels }, children: /* @__PURE__ */ jsx(
|
|
240
288
|
DayPicker,
|
|
241
289
|
{
|
|
242
290
|
showOutsideDays: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["'use client'\n\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react'\nimport { createContext, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport {\n DayPicker,\n useDayPicker,\n type DayPickerProps,\n type MonthCaptionProps,\n} from 'react-day-picker'\nimport { cn } from '../../lib/cn'\n\nexport type CalendarProps = DayPickerProps\n\n/** Ten years either side of now — see the note on `startMonth` below. */\nconst DEFAULT_SPAN = 10\n\n/** How many years one page of the year grid holds: four columns, six rows. */\nconst YEAR_PAGE = 24\n\ntype View = 'months' | 'years'\n\ninterface PickerOpen {\n /** Which displayed month has its picker open. */\n index: number\n view: View\n /** The first year of the page the year grid is showing, when it is showing. */\n anchor?: number\n /**\n * The year the MONTH grid is showing, when it is not the displayed month's.\n *\n * The picker browses; it does not navigate. Stepping the year used to call\n * `goToMonth` and close the panel, so `‹` on \"2026\" left the reader looking\n * at a different month with the picker gone — one click that did two things,\n * neither of them the one asked for. Now the arrows move this, the grid\n * re-renders under them, and the calendar moves only when a month is chosen.\n */\n year?: number\n}\n\ninterface PickerState {\n open: PickerOpen | null\n setOpen: (next: PickerOpen | null) => void\n startMonth: Date\n endMonth: Date\n}\n\nconst PickerContext = createContext<PickerState>({\n open: null,\n setOpen: () => {},\n startMonth: new Date(),\n endMonth: new Date(),\n})\n\n/** `Intl` beats the library's formatter here: it takes a plain BCP 47 tag. */\nfunction useLabels() {\n const { dayPickerProps } = useDayPicker()\n const code = dayPickerProps.locale?.code ?? 'en-US'\n return useMemo(\n () => ({\n caption: new Intl.DateTimeFormat(code, { month: 'long', year: 'numeric' }),\n month: new Intl.DateTimeFormat(code, { month: 'short' }),\n }),\n [code],\n )\n}\n\n/** The calendar's chrome buttons: the same corner every other control draws. */\nconst STEP =\n 'grid size-8 shrink-0 place-items-center rounded-(--radius) text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\nconst CELL =\n 'rounded-(--radius-row) py-2 text-sm transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n/** The one that is showing, marked the way a chosen day is marked. */\nconst CELL_ON = 'bg-(--accent) text-(--accent-foreground)'\nconst CELL_OFF = 'text-(--ink-2) hover:bg-(--stone) hover:text-(--ink)'\n\n/**\n * The month and year picker, drawn IN PLACE of the day grid.\n *\n * The previous arrangement was two `Select`s in the caption, which portalled\n * their option lists over the grid — a ten-rem box showing three months at a\n * time with a scroll arrow at each end, floating on top of the calendar the\n * reader opened it to change. Three visible options out of twelve is not a\n * picker, and it covered the thing being picked for.\n *\n * Twelve months fit in a 3×4 grid at the exact size of the grid they replace,\n * so nothing overlaps, nothing scrolls, and the panel is the same shape as the\n * surface underneath it. Twenty years fit the same box in 4×5, which is why the\n * default span is ten years either side: one page, no paging.\n */\nfunction MonthYearPanel({\n index,\n view,\n anchor,\n year: browsing,\n month,\n}: {\n index: number\n view: View\n anchor?: number\n year?: number\n month: Date\n}) {\n const { setOpen, startMonth, endMonth } = useContext(PickerContext)\n const { goToMonth } = useDayPicker()\n const labels = useLabels()\n\n /** The year being browsed, which is the displayed one until an arrow moves it. */\n const year = browsing ?? month.getFullYear()\n /** The month marked as current — only when the browsed year IS the shown one. */\n const showing = year === month.getFullYear() ? month.getMonth() : -1\n const firstYear = startMonth.getFullYear()\n const lastYear = endMonth.getFullYear()\n\n /**\n * The offset is what makes this work in a two-month range picker: `goToMonth`\n * always sets the FIRST displayed month, so picking September in the\n * right-hand pane has to ask for August.\n */\n const go = (nextYear: number, nextMonth: number) => {\n goToMonth(new Date(nextYear, nextMonth - index, 1))\n setOpen(null)\n }\n\n const inRange = (nextYear: number, nextMonth: number) => {\n const value = nextYear * 12 + nextMonth\n const low = startMonth.getFullYear() * 12 + startMonth.getMonth()\n const high = endMonth.getFullYear() * 12 + endMonth.getMonth()\n return value >= low && value <= high\n }\n\n if (view === 'years') {\n /**\n * Pages TILE the range from its first year, rather than being centred on\n * whichever year is showing. Centring left the first year of a range on no\n * page at all when the range was not a whole number of pages long — the\n * calendar offered a year it could never reach, and nothing said so.\n */\n const page = (candidate: number) =>\n Math.floor((candidate - firstYear) / YEAR_PAGE) * YEAR_PAGE + firstYear\n const start = anchor ?? page(year)\n const years = Array.from({ length: YEAR_PAGE }, (_, offset) => start + offset).filter(\n (candidate) => candidate >= firstYear && candidate <= lastYear,\n )\n const back = start - YEAR_PAGE >= firstYear\n const forward = start + YEAR_PAGE <= lastYear\n const move = (delta: number) =>\n setOpen({ index, view: 'years', anchor: start + delta * YEAR_PAGE })\n\n return (\n <Panel key=\"years\" label=\"Year\">\n <div className=\"mb-1 flex items-center justify-between gap-1\">\n <button\n type=\"button\"\n aria-label=\"Earlier years\"\n disabled={!back}\n onClick={() => move(-1)}\n className={STEP}\n >\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden />\n </button>\n <span className=\"mono-meta tabular-nums text-(--ink-3-aa)\">\n {years[0]} – {years.at(-1)}\n </span>\n <button\n type=\"button\"\n aria-label=\"Later years\"\n disabled={!forward}\n onClick={() => move(1)}\n className={STEP}\n >\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden />\n </button>\n </div>\n <div data-picker-grid className=\"grid grid-cols-4 gap-1\">\n {years.map((candidate) => (\n <button\n key={candidate}\n type=\"button\"\n aria-current={candidate === year ? 'true' : undefined}\n onClick={() => setOpen({ index, view: 'months', year: candidate })}\n className={cn(CELL, 'tabular-nums', candidate === year ? CELL_ON : CELL_OFF)}\n >\n {candidate}\n </button>\n ))}\n </div>\n </Panel>\n )\n }\n\n return (\n <Panel key=\"months\" label=\"Month and year\">\n <div className=\"mb-1 flex items-center justify-between gap-1\">\n <button\n type=\"button\"\n aria-label=\"Previous year\"\n disabled={year <= firstYear}\n onClick={() => setOpen({ index, view: 'months', year: year - 1 })}\n className={STEP}\n >\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden />\n </button>\n <button\n type=\"button\"\n onClick={() => setOpen({ index, view: 'years', anchor })}\n className=\"rounded-(--radius-row) px-3 py-1 font-heading text-[length:var(--fs-item)] tabular-nums text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)\"\n >\n {year}\n </button>\n <button\n type=\"button\"\n aria-label=\"Next year\"\n disabled={year >= lastYear}\n onClick={() => setOpen({ index, view: 'months', year: year + 1 })}\n className={STEP}\n >\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden />\n </button>\n </div>\n <div data-picker-grid className=\"grid grid-cols-3 gap-1\">\n {Array.from({ length: 12 }, (_, monthIndex) => (\n <button\n key={monthIndex}\n type=\"button\"\n aria-current={monthIndex === showing ? 'true' : undefined}\n disabled={!inRange(year, monthIndex)}\n onClick={() => go(year, monthIndex)}\n className={cn(CELL, monthIndex === showing ? CELL_ON : CELL_OFF)}\n >\n {labels.month.format(new Date(year, monthIndex, 1))}\n </button>\n ))}\n </div>\n </Panel>\n )\n}\n\n/**\n * The panel's box.\n *\n * `top-12` is the caption's own height plus the gap under it, so the panel\n * starts exactly where the weekday row does and ends where the last week does.\n * Sized against the grid rather than given a height of its own, because a fixed\n * height would be wrong in the month that needs a sixth week.\n *\n * Focus moves in when it opens and the month showing is what receives it, so a\n * keyboard reader lands on the current value rather than at the top of a grid\n * they then have to traverse. It is a `group` rather than a `dialog`: nothing\n * here is modal, and announcing a dialog that Tab walks straight out of is\n * worse than announcing nothing.\n */\nfunction Panel({ label, children }: { label: string; children: ReactNode }) {\n const box = useRef<HTMLDivElement>(null)\n\n // Keyed by view at the call site, so switching from months to years remounts\n // this and the effect runs again. Without that the year grid replaced the\n // button that had just been clicked, focus fell back to `<body>`, and Escape\n // no longer reached the handler that closes the panel.\n useEffect(() => {\n // The current value if the panel is showing it, and otherwise the first\n // cell in the grid. Browsing to another year leaves nothing marked current,\n // and a panel that focuses nothing drops focus to `<body>` — where Escape\n // no longer reaches the handler that closes it.\n const grid = box.current?.querySelector('[data-picker-grid]')\n const target =\n box.current?.querySelector<HTMLButtonElement>('[aria-current]') ??\n grid?.querySelector<HTMLButtonElement>('button:not(:disabled)')\n target?.focus()\n }, [])\n\n return (\n <div\n ref={box}\n role=\"group\"\n aria-label={label}\n className=\"absolute inset-x-0 bottom-0 top-12 z-2 flex flex-col justify-center bg-(--paper)\"\n >\n {children}\n </div>\n )\n}\n\n/**\n * The caption: the month and year, as one control that opens the picker.\n *\n * One button rather than two dropdowns. \"September 2026\" is how the date is\n * said, and splitting it into two controls with a chevron each put four\n * interactive things in a 250px caption that also has to hold two arrows.\n */\nfunction CalendarCaption({ calendarMonth, displayIndex, className, ...rest }: MonthCaptionProps) {\n const { open, setOpen } = useContext(PickerContext)\n const labels = useLabels()\n const trigger = useRef<HTMLButtonElement>(null)\n const mine = open !== null && open.index === displayIndex\n\n return (\n <div\n className={className}\n // Escape closes the picker and puts focus back where it came from. A\n // panel that covers the grid and cannot be dismissed from the keyboard is\n // a trap, however briefly.\n onKeyDown={(event) => {\n if (event.key !== 'Escape' || !mine) return\n event.stopPropagation()\n setOpen(null)\n trigger.current?.focus()\n }}\n {...rest}\n >\n <button\n ref={trigger}\n type=\"button\"\n aria-expanded={mine}\n onClick={() => setOpen(mine ? null : { index: displayIndex, view: 'months' })}\n className=\"group flex items-center gap-1.5 rounded-(--radius-row) px-2 py-1 font-heading text-[length:var(--fs-item)] text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)\"\n >\n {labels.caption.format(calendarMonth.date)}\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className={cn(\n 'shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast)',\n mine && 'rotate-180',\n )}\n />\n </button>\n {mine && open !== null && (\n <MonthYearPanel\n index={displayIndex}\n view={open.view}\n anchor={open.anchor}\n year={open.year}\n month={calendarMonth.date}\n />\n )}\n </div>\n )\n}\n\n/**\n * A month, as a grid of days.\n *\n * Wraps react-day-picker rather than building a calendar, and the reason is the\n * long tail: a month grid is easy, and everything around it is not — the\n * keyboard contract (arrows move a day, Page moves a month, Home and End reach\n * the week's ends), the `aria-live` announcement when the month changes, week\n * numbering, the first day of the week varying by locale, and the fact that\n * \"today\" and \"selected\" are different states a screen reader must hear apart.\n *\n * Every class is replaced rather than layered over the library's stylesheet, so\n * nothing here depends on a CSS file this package does not control — but the\n * library's OWN class is kept on each slot, because passing a class REPLACES it\n * and every `.rdp-*` selector downstream depends on those.\n *\n * @example\n * <Calendar mode=\"single\" selected={date} onSelect={setDate} />\n * @example\n * // A birth date: widen the years, since the default span is deliberately short.\n * <Calendar mode=\"single\" startMonth={new Date(1920, 0)} endMonth={new Date()} />\n */\nexport function Calendar({ className, classNames, components, ...props }: CalendarProps) {\n const now = new Date()\n const [open, setOpen] = useState<PickerState['open']>(null)\n\n // Ten years either side, NOT a century. A hundred-and-eleven-row year list is\n // a scroll, not a choice — and the case that needs one (a birth date) is rare\n // enough to ask for it explicitly. Both ends are props, so widening is one\n // line at the call site.\n const startMonth = props.startMonth ?? new Date(now.getFullYear() - DEFAULT_SPAN, 0)\n const endMonth = props.endMonth ?? new Date(now.getFullYear() + DEFAULT_SPAN, 11)\n\n /**\n * A one-day range is marked start AND end AND middle, all three.\n *\n * That is what made the earlier arrangement collapse: `range_middle` cleared\n * the button's fill so a middle day would not look chosen, and on a one-day\n * range that same rule cleared the fill of the only day there was. Deciding\n * by mode instead of fighting it with `!important` means the two never\n * describe the same button — in a range the fill belongs to the ends, and in\n * single mode it belongs to `selected`.\n */\n const isRange = props.mode === 'range'\n const FILL =\n '[&>button]:bg-(--accent) [&>button]:text-(--accent-foreground) [&>button:hover]:bg-(--accent) [&>button:hover]:text-(--accent-foreground)'\n\n return (\n <PickerContext value={{ open, setOpen, startMonth, endMonth }}>\n <DayPicker\n showOutsideDays\n // The caption is one control that swaps the grid for a month picker, so\n // the library's own dropdown layout is not wanted.\n captionLayout=\"label\"\n startMonth={startMonth}\n endMonth={endMonth}\n className={cn('w-fit p-3', className)}\n components={{\n Chevron: ({ orientation, ...rest }) =>\n orientation === 'left' ? (\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ) : (\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ),\n MonthCaption: CalendarCaption,\n ...components,\n }}\n classNames={{\n // `rdp-root` is kept deliberately. Passing a class for a slot REPLACES\n // the library's own, so overriding `root` silently removed the hook\n // that every `.rdp-*` selector — ours and a consumer's — depends on.\n root: 'rdp-root relative',\n // The nav renders BEFORE the months in the DOM, so left in the flow it\n // stacks its two arrows above the grid. Taking it out of the flow puts\n // them where they belong — one at each end of the caption.\n //\n // `h-9` is what makes them line up. The caption is a 36px row and the\n // nav was a 16px glyph pinned at the same top edge, so the arrows sat\n // ten pixels above the month they belong to — close enough to read as\n // a mistake and not close enough to read as a decision. Same height,\n // same centre line.\n //\n // It steps away while the picker is open: two ways to change the\n // month, one of them behind a panel, is one too many.\n //\n // `pointer-events-none` on the STRIP and back on for the two buttons:\n // at the caption's full height it lies across the caption, and a bar\n // with nothing in the middle of it was still eating the click on the\n // month label underneath.\n nav: cn(\n 'pointer-events-none absolute inset-x-3 top-3 z-1 flex h-9 items-center justify-between transition-opacity duration-(--duration-fast)',\n open && 'opacity-0',\n ),\n // The arrows are pointer targets, not glyphs: a 16px chevron with no\n // box is a 16px hit area, which is under every guideline there is.\n button_previous: cn(STEP, open ? 'pointer-events-none' : 'pointer-events-auto'),\n button_next: cn(STEP, open ? 'pointer-events-none' : 'pointer-events-auto'),\n months: 'flex flex-col gap-4 sm:flex-row',\n // Positioned, because the month picker covers this month's grid and\n // nothing else — in a two-month range picker each pane opens its own.\n month: 'relative flex flex-col gap-3',\n month_caption: 'flex h-9 items-center justify-center',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'w-9 mono-meta font-normal text-(--ink-3-aa)',\n week: 'mt-1 flex w-full',\n // The band lives on the CELL and the mark lives on the BUTTON. Keeping\n // them apart is what lets a range be one continuous wash while both of\n // its ends stay round — the previous arrangement flattened the corners\n // of whichever day was an end, and a one-day range is BOTH ends, so it\n // came out as a square.\n day: 'relative p-0 text-center',\n day_button:\n 'relative z-1 inline-flex size-9 items-center justify-center rounded-(--radius-pill) text-sm text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n selected: isRange ? '' : FILL,\n // Today is an outline, not a fill — it is a fact about the calendar,\n // not a choice the reader made, and the two must not look alike.\n today: '[&>button]:ring-1 [&>button]:ring-(--rule-hard) [&>button]:ring-inset',\n outside: '[&>button]:text-(--ink-3-aa)',\n disabled: '[&>button]:opacity-(--disabled-opacity) [&>button]:pointer-events-none',\n // The wash lives on the CELL and the mark on the BUTTON, which is what\n // lets a range read as one continuous band while both of its ends stay\n // round. A middle day gets the wash and no fill; the ends get both.\n range_middle: 'bg-(--accent-muted) [&>button]:text-(--ink) [&>button:hover]:bg-(--stone)',\n range_start: cn('bg-(--accent-muted) rounded-s-(--radius-pill)', FILL),\n range_end: cn('bg-(--accent-muted) rounded-e-(--radius-pill)', FILL),\n hidden: 'invisible',\n ...classNames,\n }}\n {...props}\n />\n </PickerContext>\n )\n}\n\nexport default Calendar\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["'use client'\n\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react'\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactNode,\n} from 'react'\nimport {\n DayPicker,\n useDayPicker,\n type DayPickerProps,\n type MonthCaptionProps,\n} from 'react-day-picker'\nimport { cn } from '../../lib/cn'\n\n/**\n * The strings the month-and-year picker says, none of which react-day-picker\n * knows about — its own `labels` prop covers its own chrome, and this panel is\n * ours. English by default, and exposed for the same reason `AppShell` exposes\n * `openLabel`: every one of these controls is an icon or a bare number, so the\n * string IS the control as far as a screen reader is concerned.\n *\n * The month names already follow `locale`, which is what made the chrome around\n * them read as an oversight rather than as a policy.\n */\nexport interface CalendarLabels {\n /** Names the panel showing twelve months. */\n monthPanelLabel?: string\n /** Names the panel showing a page of years. */\n yearPanelLabel?: string\n /** The two chevrons that browse a year at a time, inside the month panel. */\n previousYearLabel?: string\n nextYearLabel?: string\n /** The two chevrons that page the year grid, twenty-four at a time. */\n earlierYearsLabel?: string\n laterYearsLabel?: string\n}\n\nexport type CalendarProps = DayPickerProps & CalendarLabels\n\n/** Ten years either side of now — see the note on `startMonth` below. */\nconst DEFAULT_SPAN = 10\n\n/** How many years one page of the year grid holds: four columns, six rows. */\nconst YEAR_PAGE = 24\n\ntype View = 'months' | 'years'\n\ninterface PickerOpen {\n /** Which displayed month has its picker open. */\n index: number\n view: View\n /** The first year of the page the year grid is showing, when it is showing. */\n anchor?: number\n /**\n * The year the MONTH grid is showing, when it is not the displayed month's.\n *\n * The picker browses; it does not navigate. Stepping the year used to call\n * `goToMonth` and close the panel, so `‹` on \"2026\" left the reader looking\n * at a different month with the picker gone — one click that did two things,\n * neither of them the one asked for. Now the arrows move this, the grid\n * re-renders under them, and the calendar moves only when a month is chosen.\n */\n year?: number\n}\n\n/** Every label resolved, so nothing downstream has to know the defaults. */\ntype ResolvedLabels = Required<CalendarLabels>\n\nconst DEFAULT_LABELS: ResolvedLabels = {\n monthPanelLabel: 'Month and year',\n yearPanelLabel: 'Year',\n previousYearLabel: 'Previous year',\n nextYearLabel: 'Next year',\n earlierYearsLabel: 'Earlier years',\n laterYearsLabel: 'Later years',\n}\n\ninterface PickerState {\n open: PickerOpen | null\n setOpen: (next: PickerOpen | null) => void\n startMonth: Date\n endMonth: Date\n labels: ResolvedLabels\n}\n\nconst PickerContext = createContext<PickerState>({\n open: null,\n setOpen: () => {},\n startMonth: new Date(),\n endMonth: new Date(),\n labels: DEFAULT_LABELS,\n})\n\n/** `Intl` beats the library's formatter here: it takes a plain BCP 47 tag. */\nfunction useLabels() {\n const { dayPickerProps } = useDayPicker()\n const code = dayPickerProps.locale?.code ?? 'en-US'\n return useMemo(\n () => ({\n caption: new Intl.DateTimeFormat(code, { month: 'long', year: 'numeric' }),\n month: new Intl.DateTimeFormat(code, { month: 'short' }),\n }),\n [code],\n )\n}\n\n/** The calendar's chrome buttons: the same corner every other control draws. */\nconst STEP =\n 'grid size-8 shrink-0 place-items-center rounded-(--radius) text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\nconst CELL =\n 'rounded-(--radius-row) py-2 text-sm transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n/** The one that is showing, marked the way a chosen day is marked. */\nconst CELL_ON = 'bg-(--accent) text-(--accent-foreground)'\nconst CELL_OFF = 'text-(--ink-2) hover:bg-(--stone) hover:text-(--ink)'\n\n/**\n * The month and year picker, drawn IN PLACE of the day grid.\n *\n * The previous arrangement was two `Select`s in the caption, which portalled\n * their option lists over the grid — a ten-rem box showing three months at a\n * time with a scroll arrow at each end, floating on top of the calendar the\n * reader opened it to change. Three visible options out of twelve is not a\n * picker, and it covered the thing being picked for.\n *\n * Twelve months fit in a 3×4 grid at the exact size of the grid they replace,\n * so nothing overlaps, nothing scrolls, and the panel is the same shape as the\n * surface underneath it. Twenty years fit the same box in 4×5, which is why the\n * default span is ten years either side: one page, no paging.\n */\nfunction MonthYearPanel({\n index,\n view,\n anchor,\n year: browsing,\n month,\n}: {\n index: number\n view: View\n anchor?: number\n year?: number\n month: Date\n}) {\n const { setOpen, startMonth, endMonth, labels: names } = useContext(PickerContext)\n const { goToMonth } = useDayPicker()\n const labels = useLabels()\n\n /** The year being browsed, which is the displayed one until an arrow moves it. */\n const year = browsing ?? month.getFullYear()\n /** The month marked as current — only when the browsed year IS the shown one. */\n const showing = year === month.getFullYear() ? month.getMonth() : -1\n const firstYear = startMonth.getFullYear()\n const lastYear = endMonth.getFullYear()\n\n /**\n * The offset is what makes this work in a two-month range picker: `goToMonth`\n * always sets the FIRST displayed month, so picking September in the\n * right-hand pane has to ask for August.\n */\n const go = (nextYear: number, nextMonth: number) => {\n goToMonth(new Date(nextYear, nextMonth - index, 1))\n setOpen(null)\n }\n\n const inRange = (nextYear: number, nextMonth: number) => {\n const value = nextYear * 12 + nextMonth\n const low = startMonth.getFullYear() * 12 + startMonth.getMonth()\n const high = endMonth.getFullYear() * 12 + endMonth.getMonth()\n return value >= low && value <= high\n }\n\n if (view === 'years') {\n /**\n * Pages TILE the range from its first year, rather than being centred on\n * whichever year is showing. Centring left the first year of a range on no\n * page at all when the range was not a whole number of pages long — the\n * calendar offered a year it could never reach, and nothing said so.\n */\n const page = (candidate: number) =>\n Math.floor((candidate - firstYear) / YEAR_PAGE) * YEAR_PAGE + firstYear\n const start = anchor ?? page(year)\n const years = Array.from({ length: YEAR_PAGE }, (_, offset) => start + offset).filter(\n (candidate) => candidate >= firstYear && candidate <= lastYear,\n )\n const back = start - YEAR_PAGE >= firstYear\n const forward = start + YEAR_PAGE <= lastYear\n const move = (delta: number) =>\n setOpen({ index, view: 'years', anchor: start + delta * YEAR_PAGE })\n\n return (\n <Panel key=\"years\" label={names.yearPanelLabel}>\n <div className=\"mb-1 flex items-center justify-between gap-1\">\n <button\n type=\"button\"\n aria-label={names.earlierYearsLabel}\n disabled={!back}\n onClick={() => move(-1)}\n className={STEP}\n >\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden />\n </button>\n <span className=\"mono-meta tabular-nums text-(--ink-3-aa)\">\n {years[0]} – {years.at(-1)}\n </span>\n <button\n type=\"button\"\n aria-label={names.laterYearsLabel}\n disabled={!forward}\n onClick={() => move(1)}\n className={STEP}\n >\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden />\n </button>\n </div>\n <div data-picker-grid className=\"grid grid-cols-4 gap-1\">\n {years.map((candidate) => (\n <button\n key={candidate}\n type=\"button\"\n aria-current={candidate === year ? 'true' : undefined}\n onClick={() => setOpen({ index, view: 'months', year: candidate })}\n className={cn(CELL, 'tabular-nums', candidate === year ? CELL_ON : CELL_OFF)}\n >\n {candidate}\n </button>\n ))}\n </div>\n </Panel>\n )\n }\n\n return (\n <Panel key=\"months\" label={names.monthPanelLabel}>\n <div className=\"mb-1 flex items-center justify-between gap-1\">\n <button\n type=\"button\"\n aria-label={names.previousYearLabel}\n disabled={year <= firstYear}\n onClick={() => setOpen({ index, view: 'months', year: year - 1 })}\n className={STEP}\n >\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden />\n </button>\n <button\n type=\"button\"\n onClick={() => setOpen({ index, view: 'years', anchor })}\n className=\"rounded-(--radius-row) px-3 py-1 font-heading text-[length:var(--fs-item)] tabular-nums text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)\"\n >\n {year}\n </button>\n <button\n type=\"button\"\n aria-label={names.nextYearLabel}\n disabled={year >= lastYear}\n onClick={() => setOpen({ index, view: 'months', year: year + 1 })}\n className={STEP}\n >\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden />\n </button>\n </div>\n <div data-picker-grid className=\"grid grid-cols-3 gap-1\">\n {Array.from({ length: 12 }, (_, monthIndex) => (\n <button\n key={monthIndex}\n type=\"button\"\n aria-current={monthIndex === showing ? 'true' : undefined}\n disabled={!inRange(year, monthIndex)}\n onClick={() => go(year, monthIndex)}\n className={cn(CELL, monthIndex === showing ? CELL_ON : CELL_OFF)}\n >\n {labels.month.format(new Date(year, monthIndex, 1))}\n </button>\n ))}\n </div>\n </Panel>\n )\n}\n\n/**\n * The panel's box.\n *\n * `top-12` is the caption's own height plus the gap under it, so the panel\n * starts exactly where the weekday row does and ends where the last week does.\n * Sized against the grid rather than given a height of its own, because a fixed\n * height would be wrong in the month that needs a sixth week.\n *\n * Focus moves in when it opens and the month showing is what receives it, so a\n * keyboard reader lands on the current value rather than at the top of a grid\n * they then have to traverse. Tab then WRAPS inside the panel: it is drawn in\n * `--paper` over a day grid that is still mounted and still focusable, so Tab\n * used to leave the reader on a day they could not see, past the caption that\n * owns the Escape handler and therefore with no way back.\n *\n * `dialog` follows from that rather than decorating it. The role was `group`\n * for as long as Tab walked out — announcing a dialog nobody is held inside is\n * worse than announcing nothing — and the same rule, read the other way, is\n * what makes `dialog` correct now. No `aria-modal`: the panel covers this\n * month's grid, not the page, and the rest of the document is still there.\n */\nfunction Panel({ label, children }: { label: string; children: ReactNode }) {\n const box = useRef<HTMLDivElement>(null)\n\n // Keyed by view at the call site, so switching from months to years remounts\n // this and the effect runs again. Without that the year grid replaced the\n // button that had just been clicked, focus fell back to `<body>`, and Escape\n // no longer reached the handler that closes the panel.\n useEffect(() => {\n // The current value if the panel is showing it, and otherwise the first\n // cell in the grid. Browsing to another year leaves nothing marked current,\n // and a panel that focuses nothing drops focus to `<body>` — where Escape\n // no longer reaches the handler that closes it.\n const grid = box.current?.querySelector('[data-picker-grid]')\n const target =\n box.current?.querySelector<HTMLButtonElement>('[aria-current]') ??\n grid?.querySelector<HTMLButtonElement>('button:not(:disabled)')\n target?.focus()\n }, [])\n\n // Every stop in here is a button; there is nothing else to collect, and a\n // general focusable-element query would be a guess about markup this file\n // writes itself. Disabled months are out of range and out of the tab order.\n const stops = () =>\n Array.from(box.current?.querySelectorAll<HTMLButtonElement>('button:not([disabled])') ?? [])\n\n const wrap = (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key !== 'Tab') return\n const buttons = stops()\n const first = buttons[0]\n const last = buttons[buttons.length - 1]\n if (!first || !last) return\n\n const [edge, opposite] = event.shiftKey ? [first, last] : [last, first]\n if (document.activeElement !== edge) return\n\n event.preventDefault()\n opposite.focus()\n }\n\n return (\n <div\n ref={box}\n role=\"dialog\"\n aria-label={label}\n onKeyDown={wrap}\n className=\"absolute inset-x-0 bottom-0 top-12 z-2 flex flex-col justify-center bg-(--paper)\"\n >\n {children}\n </div>\n )\n}\n\n/**\n * The caption: the month and year, as one control that opens the picker.\n *\n * One button rather than two dropdowns. \"September 2026\" is how the date is\n * said, and splitting it into two controls with a chevron each put four\n * interactive things in a 250px caption that also has to hold two arrows.\n */\nfunction CalendarCaption({ calendarMonth, displayIndex, className, ...rest }: MonthCaptionProps) {\n const { open, setOpen } = useContext(PickerContext)\n const labels = useLabels()\n const trigger = useRef<HTMLButtonElement>(null)\n const mine = open !== null && open.index === displayIndex\n\n return (\n <div\n className={className}\n // Escape closes the picker and puts focus back where it came from. A\n // panel that covers the grid and cannot be dismissed from the keyboard is\n // a trap, however briefly.\n onKeyDown={(event) => {\n if (event.key !== 'Escape' || !mine) return\n event.stopPropagation()\n setOpen(null)\n trigger.current?.focus()\n }}\n {...rest}\n >\n <button\n ref={trigger}\n type=\"button\"\n aria-expanded={mine}\n onClick={() => setOpen(mine ? null : { index: displayIndex, view: 'months' })}\n className=\"group flex items-center gap-1.5 rounded-(--radius-row) px-2 py-1 font-heading text-[length:var(--fs-item)] text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)\"\n >\n {labels.caption.format(calendarMonth.date)}\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className={cn(\n 'shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast)',\n mine && 'rotate-180',\n )}\n />\n </button>\n {mine && open !== null && (\n <MonthYearPanel\n index={displayIndex}\n view={open.view}\n anchor={open.anchor}\n year={open.year}\n month={calendarMonth.date}\n />\n )}\n </div>\n )\n}\n\n/**\n * A month, as a grid of days.\n *\n * Wraps react-day-picker rather than building a calendar, and the reason is the\n * long tail: a month grid is easy, and everything around it is not — the\n * keyboard contract (arrows move a day, Page moves a month, Home and End reach\n * the week's ends), the `aria-live` announcement when the month changes, week\n * numbering, the first day of the week varying by locale, and the fact that\n * \"today\" and \"selected\" are different states a screen reader must hear apart.\n *\n * Every class is replaced rather than layered over the library's stylesheet, so\n * nothing here depends on a CSS file this package does not control — but the\n * library's OWN class is kept on each slot, because passing a class REPLACES it\n * and every `.rdp-*` selector downstream depends on those.\n *\n * The month names follow `locale`. The picker's own chrome — the two panels and\n * the four chevrons that browse them — follows `CalendarLabels`, because\n * `locale` cannot supply strings this package invented.\n *\n * @example\n * <Calendar mode=\"single\" selected={date} onSelect={setDate} />\n * @example\n * // A birth date: widen the years, since the default span is deliberately short.\n * <Calendar mode=\"single\" startMonth={new Date(1920, 0)} endMonth={new Date()} />\n */\nexport function Calendar({\n className,\n classNames,\n components,\n monthPanelLabel,\n yearPanelLabel,\n previousYearLabel,\n nextYearLabel,\n earlierYearsLabel,\n laterYearsLabel,\n ...props\n}: CalendarProps) {\n const now = new Date()\n const [open, setOpen] = useState<PickerState['open']>(null)\n\n // Destructured out of `props` above rather than forwarded: everything left in\n // `props` goes to DayPicker, and these six are this package's own.\n const labels: ResolvedLabels = {\n monthPanelLabel: monthPanelLabel ?? DEFAULT_LABELS.monthPanelLabel,\n yearPanelLabel: yearPanelLabel ?? DEFAULT_LABELS.yearPanelLabel,\n previousYearLabel: previousYearLabel ?? DEFAULT_LABELS.previousYearLabel,\n nextYearLabel: nextYearLabel ?? DEFAULT_LABELS.nextYearLabel,\n earlierYearsLabel: earlierYearsLabel ?? DEFAULT_LABELS.earlierYearsLabel,\n laterYearsLabel: laterYearsLabel ?? DEFAULT_LABELS.laterYearsLabel,\n }\n\n // Ten years either side, NOT a century. A hundred-and-eleven-row year list is\n // a scroll, not a choice — and the case that needs one (a birth date) is rare\n // enough to ask for it explicitly. Both ends are props, so widening is one\n // line at the call site.\n const startMonth = props.startMonth ?? new Date(now.getFullYear() - DEFAULT_SPAN, 0)\n const endMonth = props.endMonth ?? new Date(now.getFullYear() + DEFAULT_SPAN, 11)\n\n /**\n * A one-day range is marked start AND end AND middle, all three.\n *\n * That is what made the earlier arrangement collapse: `range_middle` cleared\n * the button's fill so a middle day would not look chosen, and on a one-day\n * range that same rule cleared the fill of the only day there was. Deciding\n * by mode instead of fighting it with `!important` means the two never\n * describe the same button — in a range the fill belongs to the ends, and in\n * single mode it belongs to `selected`.\n */\n const isRange = props.mode === 'range'\n const FILL =\n '[&>button]:bg-(--accent) [&>button]:text-(--accent-foreground) [&>button:hover]:bg-(--accent) [&>button:hover]:text-(--accent-foreground)'\n\n return (\n <PickerContext value={{ open, setOpen, startMonth, endMonth, labels }}>\n <DayPicker\n showOutsideDays\n // The caption is one control that swaps the grid for a month picker, so\n // the library's own dropdown layout is not wanted.\n captionLayout=\"label\"\n startMonth={startMonth}\n endMonth={endMonth}\n className={cn('w-fit p-3', className)}\n components={{\n Chevron: ({ orientation, ...rest }) =>\n orientation === 'left' ? (\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ) : (\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ),\n MonthCaption: CalendarCaption,\n ...components,\n }}\n classNames={{\n // `rdp-root` is kept deliberately. Passing a class for a slot REPLACES\n // the library's own, so overriding `root` silently removed the hook\n // that every `.rdp-*` selector — ours and a consumer's — depends on.\n root: 'rdp-root relative',\n // The nav renders BEFORE the months in the DOM, so left in the flow it\n // stacks its two arrows above the grid. Taking it out of the flow puts\n // them where they belong — one at each end of the caption.\n //\n // `h-9` is what makes them line up. The caption is a 36px row and the\n // nav was a 16px glyph pinned at the same top edge, so the arrows sat\n // ten pixels above the month they belong to — close enough to read as\n // a mistake and not close enough to read as a decision. Same height,\n // same centre line.\n //\n // It steps away while the picker is open: two ways to change the\n // month, one of them behind a panel, is one too many.\n //\n // `pointer-events-none` on the STRIP and back on for the two buttons:\n // at the caption's full height it lies across the caption, and a bar\n // with nothing in the middle of it was still eating the click on the\n // month label underneath.\n nav: cn(\n 'pointer-events-none absolute inset-x-3 top-3 z-1 flex h-9 items-center justify-between transition-opacity duration-(--duration-fast)',\n open && 'opacity-0',\n ),\n // The arrows are pointer targets, not glyphs: a 16px chevron with no\n // box is a 16px hit area, which is under every guideline there is.\n button_previous: cn(STEP, open ? 'pointer-events-none' : 'pointer-events-auto'),\n button_next: cn(STEP, open ? 'pointer-events-none' : 'pointer-events-auto'),\n months: 'flex flex-col gap-4 sm:flex-row',\n // Positioned, because the month picker covers this month's grid and\n // nothing else — in a two-month range picker each pane opens its own.\n month: 'relative flex flex-col gap-3',\n month_caption: 'flex h-9 items-center justify-center',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'w-9 mono-meta font-normal text-(--ink-3-aa)',\n week: 'mt-1 flex w-full',\n // The band lives on the CELL and the mark lives on the BUTTON. Keeping\n // them apart is what lets a range be one continuous wash while both of\n // its ends stay round — the previous arrangement flattened the corners\n // of whichever day was an end, and a one-day range is BOTH ends, so it\n // came out as a square.\n day: 'relative p-0 text-center',\n day_button:\n 'relative z-1 inline-flex size-9 items-center justify-center rounded-(--radius-pill) text-sm text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n selected: isRange ? '' : FILL,\n // Today is an outline, not a fill — it is a fact about the calendar,\n // not a choice the reader made, and the two must not look alike.\n today: '[&>button]:ring-1 [&>button]:ring-(--rule-hard) [&>button]:ring-inset',\n outside: '[&>button]:text-(--ink-3-aa)',\n disabled: '[&>button]:opacity-(--disabled-opacity) [&>button]:pointer-events-none',\n // The wash lives on the CELL and the mark on the BUTTON, which is what\n // lets a range read as one continuous band while both of its ends stay\n // round. A middle day gets the wash and no fill; the ends get both.\n range_middle: 'bg-(--accent-muted) [&>button]:text-(--ink) [&>button:hover]:bg-(--stone)',\n range_start: cn('bg-(--accent-muted) rounded-s-(--radius-pill)', FILL),\n range_end: cn('bg-(--accent-muted) rounded-e-(--radius-pill)', FILL),\n hidden: 'invisible',\n ...classNames,\n }}\n {...props}\n />\n </PickerContext>\n )\n}\n\nexport default Calendar\n"],"mappings":"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","names":[]}
|
|
@@ -18,6 +18,12 @@ interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
18
18
|
* never a blur. A card that needs to read as raised is a `plate`, which
|
|
19
19
|
* separates by reversal instead.
|
|
20
20
|
*
|
|
21
|
+
* It clips to its own corner. A card that rounds and does not clip lays a
|
|
22
|
+
* full-bleed image's square corners over its round ones at all four corners,
|
|
23
|
+
* and the same goes for a filled first child — a defect that is invisible until
|
|
24
|
+
* the first card with an image at the top of it. Pass `overflow-visible` for
|
|
25
|
+
* the rarer card that deliberately overhangs.
|
|
26
|
+
*
|
|
21
27
|
* Compose with the named sub-parts, or drop children straight in when the card
|
|
22
28
|
* has no header or footer to speak of.
|
|
23
29
|
*
|
|
@@ -6,7 +6,7 @@ const VARIANT = {
|
|
|
6
6
|
flat: "border border-transparent bg-transparent"
|
|
7
7
|
};
|
|
8
8
|
function Card({ variant = "outline", className, children, ...rest }) {
|
|
9
|
-
return /* @__PURE__ */ jsx("div", { className: cn("rounded-(--radius-lg)", VARIANT[variant], className), ...rest, children });
|
|
9
|
+
return /* @__PURE__ */ jsx("div", { className: cn("overflow-hidden rounded-(--radius-lg)", VARIANT[variant], className), ...rest, children });
|
|
10
10
|
}
|
|
11
11
|
function CardHeader({ className, children, ...rest }) {
|
|
12
12
|
return /* @__PURE__ */ jsx(
|