@misoto22/design 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +87 -0
- package/CLAUDE.md +1 -0
- package/README.md +50 -5
- package/bin/misoto22-design.mjs +59 -13
- package/dist/agent/Accordion.md +24 -1
- package/dist/agent/Alert.md +23 -1
- package/dist/agent/AppShell.md +30 -2
- package/dist/agent/ArchitectureFigure.md +25 -2
- package/dist/agent/AreaChart.md +27 -2
- package/dist/agent/Article.md +26 -3
- package/dist/agent/AspectRatio.md +57 -0
- package/dist/agent/Avatar.md +25 -5
- package/dist/agent/Badge.md +22 -1
- package/dist/agent/BarChart.md +29 -2
- package/dist/agent/BarList.md +27 -1
- package/dist/agent/BigNumber.md +26 -2
- package/dist/agent/BoxPlot.md +24 -1
- package/dist/agent/Breadcrumb.md +24 -2
- package/dist/agent/BulletChart.md +23 -1
- package/dist/agent/Button.md +25 -1
- package/dist/agent/Calendar.md +37 -3
- package/dist/agent/Card.md +24 -2
- package/dist/agent/Checkbox.md +23 -2
- package/dist/agent/Code.md +50 -0
- package/dist/agent/CodeBlock.md +74 -0
- package/dist/agent/Collapsible.md +22 -1
- package/dist/agent/Combobox.md +29 -3
- package/dist/agent/Command.md +25 -2
- package/dist/agent/ComposedChart.md +26 -2
- package/dist/agent/ContextMenu.md +37 -4
- package/dist/agent/DataflowFigure.md +22 -1
- package/dist/agent/DatePicker.md +34 -6
- package/dist/agent/DescriptionList.md +68 -0
- package/dist/agent/Diagram.md +27 -3
- package/dist/agent/DiagramCanvas.md +24 -1
- package/dist/agent/DiagramExportMenu.md +29 -3
- package/dist/agent/DiagramInspector.md +23 -1
- package/dist/agent/DiagramLegend.md +22 -1
- package/dist/agent/DiagramMinimap.md +24 -4
- package/dist/agent/DiagramToolbar.md +23 -1
- package/dist/agent/Dialog.md +26 -3
- package/dist/agent/DropdownMenu.md +45 -6
- package/dist/agent/EmptyState.md +26 -1
- package/dist/agent/ErrorState.md +27 -2
- package/dist/agent/Facet.md +25 -1
- package/dist/agent/Field.md +45 -4
- package/dist/agent/FigureBand.md +24 -1
- package/dist/agent/FloatingIconButton.md +23 -1
- package/dist/agent/FunnelChart.md +23 -1
- package/dist/agent/Heading.md +64 -0
- package/dist/agent/Heatmap.md +25 -1
- package/dist/agent/Histogram.md +26 -2
- package/dist/agent/Input.md +22 -1
- package/dist/agent/Kbd.md +19 -1
- package/dist/agent/LifecycleFigure.md +22 -1
- package/dist/agent/LineChart.md +26 -2
- package/dist/agent/LinkArrow.md +20 -1
- package/dist/agent/Markdown.md +68 -0
- package/dist/agent/NativeSelect.md +23 -2
- package/dist/agent/NavItem.md +21 -1
- package/dist/agent/Pagination.md +28 -2
- package/dist/agent/PieChart.md +26 -2
- package/dist/agent/Popover.md +23 -1
- package/dist/agent/Progress.md +27 -3
- package/dist/agent/RadarChart.md +26 -2
- package/dist/agent/RadialChart.md +26 -3
- package/dist/agent/RadioGroup.md +23 -2
- package/dist/agent/SankeyChart.md +25 -2
- package/dist/agent/ScatterChart.md +27 -2
- package/dist/agent/ScrollArea.md +24 -3
- package/dist/agent/SearchableMenu.md +23 -1
- package/dist/agent/Select.md +29 -3
- package/dist/agent/Separator.md +28 -4
- package/dist/agent/SequenceFigure.md +23 -1
- package/dist/agent/Sheet.md +25 -2
- package/dist/agent/Skeleton.md +24 -2
- package/dist/agent/Slider.md +27 -4
- package/dist/agent/Sparkline.md +23 -1
- package/dist/agent/Spinner.md +24 -2
- package/dist/agent/StatusDot.md +20 -1
- package/dist/agent/StatusPill.md +28 -2
- package/dist/agent/Steps.md +24 -1
- package/dist/agent/Switch.md +21 -1
- package/dist/agent/Table.md +26 -3
- package/dist/agent/Tabs.md +24 -2
- package/dist/agent/Tag.md +40 -9
- package/dist/agent/Text.md +65 -0
- package/dist/agent/Textarea.md +20 -1
- package/dist/agent/Timestamp.md +64 -0
- package/dist/agent/Toast.md +26 -2
- package/dist/agent/ToggleGroup.md +24 -2
- package/dist/agent/Toolbar.md +71 -0
- package/dist/agent/Tooltip.md +23 -1
- package/dist/agent/TreemapChart.md +24 -1
- package/dist/agent/WaterfallChart.md +24 -1
- package/dist/agent/WorkflowFigure.md +24 -2
- package/dist/agent/catalog.json +5602 -745
- package/dist/agent/index.md +13 -4
- package/dist/article.css +16 -4
- package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
- package/dist/charts/AreaChart/AreaChart.js +12 -13
- package/dist/charts/AreaChart/AreaChart.js.map +1 -1
- package/dist/charts/BarChart/BarChart.d.ts +14 -3
- package/dist/charts/BarChart/BarChart.js +20 -12
- package/dist/charts/BarChart/BarChart.js.map +1 -1
- package/dist/charts/BarList/BarList.d.ts +9 -1
- package/dist/charts/BarList/BarList.js +9 -1
- package/dist/charts/BarList/BarList.js.map +1 -1
- package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
- package/dist/charts/BigNumber/BigNumber.js +25 -5
- package/dist/charts/BigNumber/BigNumber.js.map +1 -1
- package/dist/charts/BoxPlot/BoxPlot.js +4 -2
- package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
- package/dist/charts/BulletChart/BulletChart.js +28 -7
- package/dist/charts/BulletChart/BulletChart.js.map +1 -1
- package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
- package/dist/charts/ComposedChart/ComposedChart.js +10 -10
- package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
- package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
- package/dist/charts/FunnelChart/FunnelChart.js +6 -16
- package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
- package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
- package/dist/charts/Heatmap/Heatmap.js +20 -11
- package/dist/charts/Heatmap/Heatmap.js.map +1 -1
- package/dist/charts/Histogram/Histogram.d.ts +24 -1
- package/dist/charts/Histogram/Histogram.js +43 -11
- package/dist/charts/Histogram/Histogram.js.map +1 -1
- package/dist/charts/LineChart/LineChart.d.ts +9 -2
- package/dist/charts/LineChart/LineChart.js +11 -12
- package/dist/charts/LineChart/LineChart.js.map +1 -1
- package/dist/charts/PieChart/PieChart.d.ts +15 -3
- package/dist/charts/PieChart/PieChart.js +14 -6
- package/dist/charts/PieChart/PieChart.js.map +1 -1
- package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
- package/dist/charts/RadarChart/RadarChart.js +11 -9
- package/dist/charts/RadarChart/RadarChart.js.map +1 -1
- package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
- package/dist/charts/RadialChart/RadialChart.js +28 -6
- package/dist/charts/RadialChart/RadialChart.js.map +1 -1
- package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
- package/dist/charts/SankeyChart/SankeyChart.js +14 -6
- package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
- package/dist/charts/ScatterChart/ScatterChart.js +21 -15
- package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
- package/dist/charts/Sparkline/Sparkline.js +5 -3
- package/dist/charts/Sparkline/Sparkline.js.map +1 -1
- package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
- package/dist/charts/TreemapChart/TreemapChart.js +20 -11
- package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
- package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
- package/dist/charts/lib/export.d.ts +1 -0
- package/dist/charts/lib/figure.d.ts +21 -1
- package/dist/charts/lib/figure.js +5 -2
- package/dist/charts/lib/figure.js.map +1 -1
- package/dist/charts/lib/scale.d.ts +23 -0
- package/dist/charts/lib/scale.js +13 -0
- package/dist/charts/lib/scale.js.map +1 -0
- package/dist/charts/lib/selection.d.ts +20 -0
- package/dist/charts/lib/selection.js +18 -0
- package/dist/charts/lib/selection.js.map +1 -0
- package/dist/charts/lib/toolbar.d.ts +1 -0
- package/dist/components/Accordion/Accordion.js +8 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/AppShell/AppShell.d.ts +6 -0
- package/dist/components/AppShell/AppShell.js +34 -4
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Article/Article.d.ts +11 -4
- package/dist/components/Article/Article.js +13 -0
- package/dist/components/Article/Article.js.map +1 -1
- package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
- package/dist/components/AspectRatio/AspectRatio.js +33 -0
- package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
- package/dist/components/Avatar/Avatar.d.ts +12 -3
- package/dist/components/Avatar/Avatar.js +6 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/Button/Button.js +14 -0
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.d.ts +29 -3
- package/dist/components/Calendar/Calendar.js +60 -12
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.d.ts +6 -0
- package/dist/components/Card/Card.js +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +5 -2
- package/dist/components/Checkbox/Checkbox.js +12 -2
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Code/Code.d.ts +30 -0
- package/dist/components/Code/Code.js +21 -0
- package/dist/components/Code/Code.js.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
- package/dist/components/CodeBlock/CodeBlock.js +123 -0
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
- package/dist/components/Collapsible/Collapsible.js +10 -2
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts +15 -1
- package/dist/components/Combobox/Combobox.js +25 -5
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.d.ts +4 -2
- package/dist/components/Command/Command.js +13 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
- package/dist/components/ContextMenu/ContextMenu.js +32 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.d.ts +36 -4
- package/dist/components/DatePicker/DatePicker.js +55 -12
- package/dist/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
- package/dist/components/DescriptionList/DescriptionList.js +35 -0
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
- package/dist/components/Diagram/Diagram.d.ts +8 -3
- package/dist/components/Diagram/Diagram.js +82 -10
- package/dist/components/Diagram/Diagram.js.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +18 -3
- package/dist/components/Dialog/Dialog.js +20 -2
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
- package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.d.ts +25 -1
- package/dist/components/EmptyState/EmptyState.js +3 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.d.ts +22 -1
- package/dist/components/ErrorState/ErrorState.js +11 -2
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/Field/Field.d.ts +70 -3
- package/dist/components/Field/Field.js +53 -12
- package/dist/components/Field/Field.js.map +1 -1
- package/dist/components/Field/field-control.d.ts +31 -0
- package/dist/components/Field/field-control.js +12 -0
- package/dist/components/Field/field-control.js.map +1 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
- package/dist/components/Heading/Heading.d.ts +62 -0
- package/dist/components/Heading/Heading.js +44 -0
- package/dist/components/Heading/Heading.js.map +1 -0
- package/dist/components/Markdown/Markdown.d.ts +139 -0
- package/dist/components/Markdown/Markdown.js +135 -0
- package/dist/components/Markdown/Markdown.js.map +1 -0
- package/dist/components/Markdown/inline.d.ts +29 -0
- package/dist/components/Markdown/inline.js +74 -0
- package/dist/components/Markdown/inline.js.map +1 -0
- package/dist/components/Markdown/nodes.d.ts +65 -0
- package/dist/components/Markdown/nodes.js +1 -0
- package/dist/components/Markdown/nodes.js.map +1 -0
- package/dist/components/Markdown/parse.d.ts +24 -0
- package/dist/components/Markdown/parse.js +110 -0
- package/dist/components/Markdown/parse.js.map +1 -0
- package/dist/components/Markdown/slug.d.ts +15 -0
- package/dist/components/Markdown/slug.js +7 -0
- package/dist/components/Markdown/slug.js.map +1 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
- package/dist/components/NativeSelect/NativeSelect.js +2 -3
- package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
- package/dist/components/Pagination/Pagination.d.ts +24 -1
- package/dist/components/Pagination/Pagination.js +6 -3
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Progress/Progress.d.ts +17 -5
- package/dist/components/Progress/Progress.js +18 -6
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
- package/dist/components/RadioGroup/RadioGroup.js +11 -1
- package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
- package/dist/components/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
- package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
- package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
- package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
- package/dist/components/Select/Select.d.ts +18 -2
- package/dist/components/Select/Select.js +20 -3
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Separator/Separator.d.ts +21 -3
- package/dist/components/Separator/Separator.js +9 -1
- package/dist/components/Separator/Separator.js.map +1 -1
- package/dist/components/Sheet/Sheet.d.ts +3 -0
- package/dist/components/Sheet/Sheet.js +11 -2
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Skeleton/Skeleton.d.ts +8 -1
- package/dist/components/Skeleton/Skeleton.js +1 -1
- package/dist/components/Skeleton/Skeleton.js.map +1 -1
- package/dist/components/Slider/Slider.d.ts +21 -3
- package/dist/components/Slider/Slider.js +54 -27
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts +8 -0
- package/dist/components/Spinner/Spinner.js +3 -2
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/StatusPill/StatusPill.d.ts +5 -0
- package/dist/components/StatusPill/StatusPill.js +6 -0
- package/dist/components/StatusPill/StatusPill.js.map +1 -1
- package/dist/components/Table/Table.d.ts +6 -0
- package/dist/components/Table/Table.js +3 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tag/Tag.d.ts +68 -9
- package/dist/components/Tag/Tag.js +50 -10
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Text/Text.d.ts +62 -0
- package/dist/components/Text/Text.js +30 -0
- package/dist/components/Text/Text.js.map +1 -0
- package/dist/components/Timestamp/Timestamp.d.ts +59 -0
- package/dist/components/Timestamp/Timestamp.js +56 -0
- package/dist/components/Timestamp/Timestamp.js.map +1 -0
- package/dist/components/Toast/Toast.d.ts +9 -1
- package/dist/components/Toast/Toast.js +27 -4
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
- package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Toolbar/Toolbar.d.ts +55 -0
- package/dist/components/Toolbar/Toolbar.js +43 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
- package/dist/diagrams/index.d.ts +1 -0
- package/dist/diagrams/index.js +10 -0
- package/dist/diagrams/index.js.map +1 -1
- package/dist/diagrams/lib/dev.d.ts +61 -0
- package/dist/diagrams/lib/dev.js +96 -0
- package/dist/diagrams/lib/dev.js.map +1 -0
- package/dist/diagrams/lib/frame.d.ts +48 -7
- package/dist/diagrams/lib/frame.js +17 -2
- package/dist/diagrams/lib/frame.js.map +1 -1
- package/dist/diagrams/lib/wires.js.map +1 -1
- package/dist/diagrams/spec.d.ts +64 -12
- package/dist/index.d.ts +16 -4
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/keyframes.css +57 -3
- package/dist/lib/overlay-container.d.ts +14 -4
- package/dist/lib/overlay-container.js.map +1 -1
- package/dist/lib/warn.d.ts +73 -0
- package/dist/lib/warn.js +38 -0
- package/dist/lib/warn.js.map +1 -0
- package/dist/semantic.css +17 -6
- package/dist/styles.css +1 -1
- package/dist/tokens.css +20 -2
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +39 -36
- package/dist/tokens.json +39 -36
- package/llms.txt +35 -0
- package/package.json +5 -2
- package/skills/misoto22-design/evals/evals.json +84 -0
- package/skills/misoto22-design/rules/a11y.md +55 -3
- package/skills/misoto22-design/rules/forms.md +5 -0
|
@@ -1,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(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * `outline` is a hairline box on the page ground — the default, and correct\n * for a card sitting among other cards. `plate` fills with the one reversed\n * surface and is for a card that IS the point of its band; use at most one\n * per screen. `flat` drops the border entirely, for a card whose grid already\n * draws the rules between cells.\n */\n variant?: 'outline' | 'plate' | 'flat'\n}\n\nconst VARIANT = {\n outline: 'border border-(--rule) bg-(--paper)',\n plate:\n 'border border-(--feature-surface) bg-(--feature-surface) text-(--on-feature) [--card-title:var(--on-feature)]',\n flat: 'border border-transparent bg-transparent',\n} as const\n\n/**\n * A bounded surface.\n *\n * No shadow, by law: depth in this system is a hairline and a change of ground,\n * never a blur. A card that needs to read as raised is a `plate`, which\n * separates by reversal instead.\n *\n * Compose with the named sub-parts, or drop children straight in when the card\n * has no header or footer to speak of.\n *\n * @example\n * <Card>\n * <CardHeader><CardTitle>Recent deploys</CardTitle><Badge>12</Badge></CardHeader>\n * <CardBody>…</CardBody>\n * <CardFooter>Updated just now</CardFooter>\n * </Card>\n */\nexport function Card({ variant = 'outline', className, children, ...rest }: CardProps) {\n return (\n <div className={cn('rounded-(--radius-lg)', VARIANT[variant], className)} {...rest}>\n {children}\n </div>\n )\n}\n\n/** Header row — title on the left, an action or marker on the right. */\nexport function CardHeader({ className, children, ...rest }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\n 'flex items-center justify-between gap-4 border-b border-(--rule) px-5 py-4',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/**\n * The card's title, in the editorial serif.\n *\n * An `<h3>` by default, which is right inside a section with its own `<h2>` and\n * wrong nearly everywhere else — pass `as` rather than leaving a page with a\n * heading order that jumps.\n */\nexport function CardTitle({\n as: Comp = 'h3',\n className,\n children,\n ...rest\n}: HTMLAttributes<HTMLHeadingElement> & { as?: 'h2' | 'h3' | 'h4' }) {\n return (\n <Comp\n // Reads the card's own title colour rather than --ink directly. A plate\n // is a reversed surface, and a hardcoded ink title on it came out at\n // 1.25:1 — invisible, and invisible only on the one variant whose whole\n // job is to be different.\n className={cn(\n 'm-0 font-heading text-[length:var(--fs-item)] font-normal text-[var(--card-title,var(--ink))]',\n className,\n )}\n {...rest}\n >\n {children}\n </Comp>\n )\n}\n\n/** The content well. */\nexport function CardBody({ className, children, ...rest }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div className={cn('p-5 text-sm leading-relaxed text-(--ink-2)', className)} {...rest}>\n {children}\n </div>\n )\n}\n\n/** A quiet strip for metadata or secondary actions. */\nexport function CardFooter({ className, children, ...rest }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('border-t border-(--rule) px-5 py-4 mono-meta text-(--ink-3-aa)', className)}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\nexport default Card\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * `outline` is a hairline box on the page ground — the default, and correct\n * for a card sitting among other cards. `plate` fills with the one reversed\n * surface and is for a card that IS the point of its band; use at most one\n * per screen. `flat` drops the border entirely, for a card whose grid already\n * draws the rules between cells.\n */\n variant?: 'outline' | 'plate' | 'flat'\n}\n\nconst VARIANT = {\n outline: 'border border-(--rule) bg-(--paper)',\n plate:\n 'border border-(--feature-surface) bg-(--feature-surface) text-(--on-feature) [--card-title:var(--on-feature)]',\n flat: 'border border-transparent bg-transparent',\n} as const\n\n/**\n * A bounded surface.\n *\n * No shadow, by law: depth in this system is a hairline and a change of ground,\n * never a blur. A card that needs to read as raised is a `plate`, which\n * separates by reversal instead.\n *\n * It clips to its own corner. A card that rounds and does not clip lays a\n * full-bleed image's square corners over its round ones at all four corners,\n * and the same goes for a filled first child — a defect that is invisible until\n * the first card with an image at the top of it. Pass `overflow-visible` for\n * the rarer card that deliberately overhangs.\n *\n * Compose with the named sub-parts, or drop children straight in when the card\n * has no header or footer to speak of.\n *\n * @example\n * <Card>\n * <CardHeader><CardTitle>Recent deploys</CardTitle><Badge>12</Badge></CardHeader>\n * <CardBody>…</CardBody>\n * <CardFooter>Updated just now</CardFooter>\n * </Card>\n */\nexport function Card({ variant = 'outline', className, children, ...rest }: CardProps) {\n return (\n <div className={cn('overflow-hidden rounded-(--radius-lg)', VARIANT[variant], className)} {...rest}>\n {children}\n </div>\n )\n}\n\n/** Header row — title on the left, an action or marker on the right. */\nexport function CardHeader({ className, children, ...rest }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\n 'flex items-center justify-between gap-4 border-b border-(--rule) px-5 py-4',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/**\n * The card's title, in the editorial serif.\n *\n * An `<h3>` by default, which is right inside a section with its own `<h2>` and\n * wrong nearly everywhere else — pass `as` rather than leaving a page with a\n * heading order that jumps.\n */\nexport function CardTitle({\n as: Comp = 'h3',\n className,\n children,\n ...rest\n}: HTMLAttributes<HTMLHeadingElement> & { as?: 'h2' | 'h3' | 'h4' }) {\n return (\n <Comp\n // Reads the card's own title colour rather than --ink directly. A plate\n // is a reversed surface, and a hardcoded ink title on it came out at\n // 1.25:1 — invisible, and invisible only on the one variant whose whole\n // job is to be different.\n className={cn(\n 'm-0 font-heading text-[length:var(--fs-item)] font-normal text-[var(--card-title,var(--ink))]',\n className,\n )}\n {...rest}\n >\n {children}\n </Comp>\n )\n}\n\n/** The content well. */\nexport function CardBody({ className, children, ...rest }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div className={cn('p-5 text-sm leading-relaxed text-(--ink-2)', className)} {...rest}>\n {children}\n </div>\n )\n}\n\n/** A quiet strip for metadata or secondary actions. */\nexport function CardFooter({ className, children, ...rest }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('border-t border-(--rule) px-5 py-4 mono-meta text-(--ink-3-aa)', className)}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\nexport default Card\n"],"mappings":"AA8CI;AA7CJ,SAAS,UAAU;AAanB,MAAM,UAAU;AAAA,EACd,SAAS;AAAA,EACT,OACE;AAAA,EACF,MAAM;AACR;AAyBO,SAAS,KAAK,EAAE,UAAU,WAAW,WAAW,UAAU,GAAG,KAAK,GAAc;AACrF,SACE,oBAAC,SAAI,WAAW,GAAG,yCAAyC,QAAQ,OAAO,GAAG,SAAS,GAAI,GAAG,MAC3F,UACH;AAEJ;AAGO,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,KAAK,GAAmC;AAC3F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AASO,SAAS,UAAU;AAAA,EACxB,IAAI,OAAO;AAAA,EACX;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqE;AACnE,SACE;AAAA,IAAC;AAAA;AAAA,MAKC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,SAAS,EAAE,WAAW,UAAU,GAAG,KAAK,GAAmC;AACzF,SACE,oBAAC,SAAI,WAAW,GAAG,8CAA8C,SAAS,GAAI,GAAG,MAC9E,UACH;AAEJ;AAGO,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,KAAK,GAAmC;AAC3F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,kEAAkE,SAAS;AAAA,MACxF,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,eAAQ;","names":[]}
|
|
@@ -8,7 +8,10 @@ type CheckboxProps = ComponentProps<typeof CheckboxPrimitive.Root>;
|
|
|
8
8
|
*
|
|
9
9
|
* Supports the indeterminate state (`checked="indeterminate"`), which is what a
|
|
10
10
|
* "select all" header needs when only some rows are selected — a plain
|
|
11
|
-
* unchecked box there tells the reader the opposite of the truth.
|
|
11
|
+
* unchecked box there tells the reader the opposite of the truth. Controlled or
|
|
12
|
+
* not: the glyph follows the state the box is actually in, so an uncontrolled
|
|
13
|
+
* `defaultChecked="indeterminate"` draws the dash rather than the tick that says
|
|
14
|
+
* the opposite.
|
|
12
15
|
*
|
|
13
16
|
* Pair with `Field`, or wrap it in a `<label>` at the call site so the words
|
|
14
17
|
* beside it are part of the click target.
|
|
@@ -18,6 +21,6 @@ type CheckboxProps = ComponentProps<typeof CheckboxPrimitive.Root>;
|
|
|
18
21
|
* <Checkbox defaultChecked /> Subscribe to updates
|
|
19
22
|
* </label>
|
|
20
23
|
*/
|
|
21
|
-
declare function Checkbox({ className, ...props }: CheckboxProps): react.JSX.Element;
|
|
24
|
+
declare function Checkbox({ className, onCheckedChange, ...props }: CheckboxProps): react.JSX.Element;
|
|
22
25
|
|
|
23
26
|
export { Checkbox, type CheckboxProps, Checkbox as default };
|
|
@@ -2,17 +2,27 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
4
4
|
import { Check, Minus } from "lucide-react";
|
|
5
|
+
import { useState } from "react";
|
|
5
6
|
import { cn } from "../../lib/cn.js";
|
|
6
|
-
function Checkbox({ className, ...props }) {
|
|
7
|
+
function Checkbox({ className, onCheckedChange, ...props }) {
|
|
8
|
+
const [uncontrolled, setUncontrolled] = useState(
|
|
9
|
+
props.defaultChecked ?? false
|
|
10
|
+
);
|
|
11
|
+
const state = props.checked ?? uncontrolled;
|
|
12
|
+
const handleChange = (next) => {
|
|
13
|
+
if (props.checked === void 0) setUncontrolled(next);
|
|
14
|
+
onCheckedChange?.(next);
|
|
15
|
+
};
|
|
7
16
|
return /* @__PURE__ */ jsx(
|
|
8
17
|
CheckboxPrimitive.Root,
|
|
9
18
|
{
|
|
19
|
+
onCheckedChange: handleChange,
|
|
10
20
|
className: cn(
|
|
11
21
|
"inline-flex size-[18px] shrink-0 items-center justify-center rounded-(--radius-xs) border border-(--rule-2) bg-(--paper) text-(--accent-foreground) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) data-[state=checked]:bg-(--accent) data-[state=indeterminate]:border-(--accent) data-[state=indeterminate]:bg-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
|
|
12
22
|
className
|
|
13
23
|
),
|
|
14
24
|
...props,
|
|
15
|
-
children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: "flex items-center justify-center text-current", children:
|
|
25
|
+
children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: "flex items-center justify-center text-current", children: state === "indeterminate" ? /* @__PURE__ */ jsx(Minus, { className: "size-3", strokeWidth: 3, "aria-hidden": true }) : /* @__PURE__ */ jsx(Check, { className: "size-3", strokeWidth: 3, "aria-hidden": true }) })
|
|
16
26
|
}
|
|
17
27
|
);
|
|
18
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["'use client'\n\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox'\nimport { Check, Minus } from 'lucide-react'\nimport type
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["'use client'\n\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox'\nimport { Check, Minus } from 'lucide-react'\nimport { useState, type ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport type CheckboxProps = ComponentProps<typeof CheckboxPrimitive.Root>\n\n/**\n * A choice that takes effect when the form is submitted.\n *\n * Supports the indeterminate state (`checked=\"indeterminate\"`), which is what a\n * \"select all\" header needs when only some rows are selected — a plain\n * unchecked box there tells the reader the opposite of the truth. Controlled or\n * not: the glyph follows the state the box is actually in, so an uncontrolled\n * `defaultChecked=\"indeterminate\"` draws the dash rather than the tick that says\n * the opposite.\n *\n * Pair with `Field`, or wrap it in a `<label>` at the call site so the words\n * beside it are part of the click target.\n *\n * @example\n * <label className=\"flex items-center gap-2.5 text-sm\">\n * <Checkbox defaultChecked /> Subscribe to updates\n * </label>\n */\nexport function Checkbox({ className, onCheckedChange, ...props }: CheckboxProps) {\n // Tracked here rather than read off `props.checked`, which is set only by a\n // CONTROLLED box: an uncontrolled `defaultChecked=\"indeterminate\"` drew the\n // tick — the \"all of them\" picture on a partly selected list, which is the one\n // thing this state exists to avoid saying.\n const [uncontrolled, setUncontrolled] = useState<CheckboxProps['checked']>(\n props.defaultChecked ?? false,\n )\n const state = props.checked ?? uncontrolled\n\n const handleChange = (next: NonNullable<CheckboxProps['checked']>) => {\n if (props.checked === undefined) setUncontrolled(next)\n onCheckedChange?.(next)\n }\n\n return (\n <CheckboxPrimitive.Root\n onCheckedChange={handleChange}\n className={cn(\n 'inline-flex size-[18px] shrink-0 items-center justify-center rounded-(--radius-xs) border border-(--rule-2) bg-(--paper) text-(--accent-foreground) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) data-[state=checked]:bg-(--accent) data-[state=indeterminate]:border-(--accent) data-[state=indeterminate]:bg-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"flex items-center justify-center text-current\">\n {state === 'indeterminate' ? (\n <Minus className=\"size-3\" strokeWidth={3} aria-hidden />\n ) : (\n <Check className=\"size-3\" strokeWidth={3} aria-hidden />\n )}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n}\n\nexport default Checkbox\n"],"mappings":";AAqDU;AAnDV,YAAY,uBAAuB;AACnC,SAAS,OAAO,aAAa;AAC7B,SAAS,gBAAqC;AAC9C,SAAS,UAAU;AAsBZ,SAAS,SAAS,EAAE,WAAW,iBAAiB,GAAG,MAAM,GAAkB;AAKhF,QAAM,CAAC,cAAc,eAAe,IAAI;AAAA,IACtC,MAAM,kBAAkB;AAAA,EAC1B;AACA,QAAM,QAAQ,MAAM,WAAW;AAE/B,QAAM,eAAe,CAAC,SAAgD;AACpE,QAAI,MAAM,YAAY,OAAW,iBAAgB,IAAI;AACrD,sBAAkB,IAAI;AAAA,EACxB;AAEA,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,iBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,kBAAkB,WAAlB,EAA4B,WAAU,iDACpC,oBAAU,kBACT,oBAAC,SAAM,WAAU,UAAS,aAAa,GAAG,eAAW,MAAC,IAEtD,oBAAC,SAAM,WAAU,UAAS,aAAa,GAAG,eAAW,MAAC,GAE1D;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface CodeProps extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A function name, a flag, a filename — inside a sentence.
|
|
9
|
+
*
|
|
10
|
+
* Renders a `<code>`, which is the element that means this. A `<span>` in a
|
|
11
|
+
* mono face looks identical and tells a screen reader nothing, and "pass
|
|
12
|
+
* dash dash force" is not what the sentence said.
|
|
13
|
+
*
|
|
14
|
+
* Sized in `em` rather than in pixels, so the chip tracks whatever type it sits
|
|
15
|
+
* beside — the same rule `Kbd` follows, and for the same reason: fixed at one
|
|
16
|
+
* value, the identical token came out three different sizes on one page
|
|
17
|
+
* depending on whether it landed in body copy, a caption or a table cell.
|
|
18
|
+
*
|
|
19
|
+
* The block form is `CodeBlock`. This one is inline by construction: it draws
|
|
20
|
+
* no plate, keeps no whitespace, and a multi-line string handed to it collapses
|
|
21
|
+
* into one run.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* Pass <Code>--force</Code> to overwrite the existing file.
|
|
25
|
+
* @example
|
|
26
|
+
* The helper lives in <Code>src/lib/cn.ts</Code>.
|
|
27
|
+
*/
|
|
28
|
+
declare function Code({ children, className, ...rest }: CodeProps): react.JSX.Element;
|
|
29
|
+
|
|
30
|
+
export { Code, type CodeProps, Code as default };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
function Code({ children, className, ...rest }) {
|
|
4
|
+
return /* @__PURE__ */ jsx(
|
|
5
|
+
"code",
|
|
6
|
+
{
|
|
7
|
+
className: cn(
|
|
8
|
+
"rounded-(--radius-sm) bg-(--stone) px-[0.4em] py-[0.15em] font-mono text-[0.85em] text-(--ink)",
|
|
9
|
+
className
|
|
10
|
+
),
|
|
11
|
+
...rest,
|
|
12
|
+
children
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
var Code_default = Code;
|
|
17
|
+
export {
|
|
18
|
+
Code,
|
|
19
|
+
Code_default as default
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=Code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Code/Code.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface CodeProps extends HTMLAttributes<HTMLElement> {\n children: ReactNode\n}\n\n/**\n * A function name, a flag, a filename — inside a sentence.\n *\n * Renders a `<code>`, which is the element that means this. A `<span>` in a\n * mono face looks identical and tells a screen reader nothing, and \"pass\n * dash dash force\" is not what the sentence said.\n *\n * Sized in `em` rather than in pixels, so the chip tracks whatever type it sits\n * beside — the same rule `Kbd` follows, and for the same reason: fixed at one\n * value, the identical token came out three different sizes on one page\n * depending on whether it landed in body copy, a caption or a table cell.\n *\n * The block form is `CodeBlock`. This one is inline by construction: it draws\n * no plate, keeps no whitespace, and a multi-line string handed to it collapses\n * into one run.\n *\n * @example\n * Pass <Code>--force</Code> to overwrite the existing file.\n * @example\n * The helper lives in <Code>src/lib/cn.ts</Code>.\n */\nexport function Code({ children, className, ...rest }: CodeProps) {\n return (\n <code\n className={cn(\n 'rounded-(--radius-sm) bg-(--stone) px-[0.4em] py-[0.15em] font-mono text-[0.85em] text-(--ink)',\n className,\n )}\n {...rest}\n >\n {children}\n </code>\n )\n}\n\nexport default Code\n"],"mappings":"AA8BI;AA7BJ,SAAS,UAAU;AA2BZ,SAAS,KAAK,EAAE,UAAU,WAAW,GAAG,KAAK,GAAc;AAChE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,eAAQ;","names":[]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface CommonProps {
|
|
4
|
+
/**
|
|
5
|
+
* The snippet, verbatim.
|
|
6
|
+
*
|
|
7
|
+
* Required even when `html` is supplied, and that is the point: this is what
|
|
8
|
+
* the copy button puts on the clipboard. A block that copies its own rendered
|
|
9
|
+
* markup hands the reader a wall of `<span>`s, and one that copies
|
|
10
|
+
* `textContent` scraped back out of the DOM is a single non-breaking space
|
|
11
|
+
* away from pasting something that does not run.
|
|
12
|
+
*/
|
|
13
|
+
code: string;
|
|
14
|
+
/** A filename or a caption, printed at the start of the strip. */
|
|
15
|
+
title?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The language, printed at the end of the strip.
|
|
18
|
+
*
|
|
19
|
+
* Optional, and the omission is visible rather than silent: a block with no
|
|
20
|
+
* language gets no label instead of claiming a wrong one. A code block that
|
|
21
|
+
* does not say what it is written in makes the reader infer it from the
|
|
22
|
+
* syntax, which is the one thing they came to the block to learn.
|
|
23
|
+
*/
|
|
24
|
+
lang?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Caps the block's height and scrolls past it.
|
|
27
|
+
*
|
|
28
|
+
* A number is pixels; a string is any CSS length (`'24rem'`, `'50vh'`). The
|
|
29
|
+
* body scrolls in both axes and is focusable, so what is past the fold stays
|
|
30
|
+
* reachable with a keyboard rather than merely present in the DOM.
|
|
31
|
+
*/
|
|
32
|
+
maxHeight?: number | string;
|
|
33
|
+
/** Drops the copy button, for a block nobody is meant to run. */
|
|
34
|
+
copyable?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Names the scrollable body for a screen reader. Defaults to `title`, and to
|
|
37
|
+
* "Code" when there is not one.
|
|
38
|
+
*/
|
|
39
|
+
label?: string;
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The two forms, and why they are two.
|
|
44
|
+
*
|
|
45
|
+
* `html` is pre-highlighted markup for the SAME snippet. The package does not
|
|
46
|
+
* highlight, and will not: a highlighter is a few hundred kilobytes of grammar
|
|
47
|
+
* and a build-time job. The documentation site runs Shiki inside its generator
|
|
48
|
+
* and hands the result here, which is the shape that half exists for. Without
|
|
49
|
+
* `html` the block renders `code` as text, which is what a template, a README
|
|
50
|
+
* or an agent's answer has and all it needs.
|
|
51
|
+
*
|
|
52
|
+
* `lineNumbers` and `highlightLines` belong to the plain form only, and that is
|
|
53
|
+
* enforced in the TYPE rather than left to a note. They are a per-LINE
|
|
54
|
+
* structure, and `html` is one opaque string this component is not going to
|
|
55
|
+
* start parsing. Typed apart, passing both is a compile error at the call site;
|
|
56
|
+
* typed together it is a prop that renders nothing and says nothing.
|
|
57
|
+
*
|
|
58
|
+
* The plain form is written first because the emitted prop table reads the
|
|
59
|
+
* first arm of a union: this way `lineNumbers` documents as `boolean` rather
|
|
60
|
+
* than as the `never` the other arm gives it.
|
|
61
|
+
*/
|
|
62
|
+
type WithPlainSource = CommonProps & {
|
|
63
|
+
/**
|
|
64
|
+
* Absent on this form; `string` on the other.
|
|
65
|
+
*
|
|
66
|
+
* Pass pre-highlighted markup — Shiki's output, run at build time over this
|
|
67
|
+
* same `code` — and the block renders that instead of the plain text. Doing
|
|
68
|
+
* so takes `lineNumbers` and `highlightLines` away with it.
|
|
69
|
+
*/
|
|
70
|
+
html?: undefined;
|
|
71
|
+
/** Numbers every line, counting from one. */
|
|
72
|
+
lineNumbers?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Lines to band, counting from one. A number past the end of the snippet is
|
|
75
|
+
* ignored rather than throwing — the usual cause is a snippet that got
|
|
76
|
+
* shorter while the annotation did not.
|
|
77
|
+
*/
|
|
78
|
+
highlightLines?: number[];
|
|
79
|
+
};
|
|
80
|
+
type WithHighlightedHtml = CommonProps & {
|
|
81
|
+
html: string;
|
|
82
|
+
/** Not available alongside `html`, which the component does not parse. */
|
|
83
|
+
lineNumbers?: never;
|
|
84
|
+
/** Not available alongside `html`, which the component does not parse. */
|
|
85
|
+
highlightLines?: never;
|
|
86
|
+
};
|
|
87
|
+
type CodeBlockProps = WithPlainSource | WithHighlightedHtml;
|
|
88
|
+
/**
|
|
89
|
+
* A multi-line snippet, on a plate, with a way to take it away.
|
|
90
|
+
*
|
|
91
|
+
* The strip along the top carries the title, the language and the copy button,
|
|
92
|
+
* and it is there whenever there is anything to put in it. It is deliberately
|
|
93
|
+
* not a hover affordance: a control that appears on hover does not exist on a
|
|
94
|
+
* touch screen, which is where a reader is most likely to want the snippet and
|
|
95
|
+
* least able to select it by hand.
|
|
96
|
+
*
|
|
97
|
+
* Line numbers live INSIDE their own line's row rather than in a parallel gutter
|
|
98
|
+
* column. Two columns sharing a line-height align right up until one of them
|
|
99
|
+
* wraps, ships a different font, or scrolls on its own; a number that is a child
|
|
100
|
+
* of the line it numbers cannot come apart from it. The cost is that a number
|
|
101
|
+
* scrolls away with its line: it is inside the scrolling box, not in a gutter
|
|
102
|
+
* beside it. A gutter that stayed put would be a second column to keep in sync,
|
|
103
|
+
* which is the failure this avoids.
|
|
104
|
+
*
|
|
105
|
+
* The body is a focusable, named `role="group"`, and both halves of that are
|
|
106
|
+
* load-bearing. Focusable, because a scrollable box whose contents are not
|
|
107
|
+
* themselves focusable is unreachable by keyboard — there is nothing to Tab to
|
|
108
|
+
* and therefore no way to press an arrow key at it, so the right-hand half of a
|
|
109
|
+
* long line does not exist for anyone not using a mouse. Named, because a tab
|
|
110
|
+
* stop that announces nothing lands the reader in an anonymous box and leaves
|
|
111
|
+
* them to work out what they have arrived in.
|
|
112
|
+
*
|
|
113
|
+
* `group` rather than `region`, and that is the deliberate half. A region is a
|
|
114
|
+
* LANDMARK — one of the handful of major sections a reader navigates a whole
|
|
115
|
+
* page by — and a snippet is not one of those. Three fenced blocks in one
|
|
116
|
+
* article would put three landmarks called "Code" into that map, which is
|
|
117
|
+
* exactly the noise the `landmark-unique` rule exists to catch, and it would
|
|
118
|
+
* push the page's real landmarks down a list nobody can now skim. `group`
|
|
119
|
+
* carries the same accessible name to the same reader on the way in, and
|
|
120
|
+
* carries it nowhere else.
|
|
121
|
+
*
|
|
122
|
+
* For a function name inside a sentence, reach for `Code`.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* <CodeBlock code="pnpm add @misoto22/design" lang="bash" />
|
|
126
|
+
* @example
|
|
127
|
+
* <CodeBlock
|
|
128
|
+
* title="cn.ts"
|
|
129
|
+
* lang="ts"
|
|
130
|
+
* lineNumbers
|
|
131
|
+
* highlightLines={[2]}
|
|
132
|
+
* maxHeight="24rem"
|
|
133
|
+
* code={source}
|
|
134
|
+
* />
|
|
135
|
+
* @example
|
|
136
|
+
* // Highlighted at build time by the site's own Shiki pass.
|
|
137
|
+
* <CodeBlock code={source} html={highlighted} lang="tsx" />
|
|
138
|
+
*/
|
|
139
|
+
declare function CodeBlock({ code, html, title, lang, maxHeight, copyable, label, className, lineNumbers, highlightLines, }: CodeBlockProps): react.JSX.Element;
|
|
140
|
+
|
|
141
|
+
export { CodeBlock, type CodeBlockProps, CodeBlock as default };
|