@misoto22/design 0.7.0 → 0.9.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/ColorPicker.md +75 -0
- 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/NumberField.md +72 -0
- 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/Sidebar.md +193 -0
- package/dist/agent/Skeleton.md +24 -2
- package/dist/agent/Slider.md +31 -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 +28 -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 +5915 -699
- package/dist/agent/index.md +20 -8
- 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 +44 -9
- 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 +15 -11
- 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/ColorPicker/ColorPicker.d.ts +67 -0
- package/dist/components/ColorPicker/ColorPicker.js +334 -0
- package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
- 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 +14 -2
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
- package/dist/components/ContextMenu/ContextMenu.js +33 -3
- 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 +21 -3
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
- package/dist/components/DropdownMenu/DropdownMenu.js +33 -3
- 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 +12 -3
- 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/NavItem/NavItem.js +1 -1
- package/dist/components/NavItem/NavItem.js.map +1 -1
- package/dist/components/NumberField/NumberField.d.ts +69 -0
- package/dist/components/NumberField/NumberField.js +162 -0
- package/dist/components/NumberField/NumberField.js.map +1 -0
- 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/Popover/Popover.js +1 -1
- package/dist/components/Popover/Popover.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 +21 -4
- 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 +12 -3
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +349 -0
- package/dist/components/Sidebar/Sidebar.js +455 -0
- package/dist/components/Sidebar/Sidebar.js.map +1 -0
- 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 +42 -3
- package/dist/components/Slider/Slider.js +121 -30
- 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 +20 -0
- package/dist/components/Table/Table.js +3 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tabs/Tabs.js +7 -1
- package/dist/components/Tabs/Tabs.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/route.d.ts +22 -1
- package/dist/diagrams/lib/route.js +24 -1
- package/dist/diagrams/lib/route.js.map +1 -1
- package/dist/diagrams/lib/wires.js +9 -5
- package/dist/diagrams/lib/wires.js.map +1 -1
- package/dist/diagrams/spec.d.ts +64 -12
- package/dist/index.d.ts +19 -4
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/keyframes.css +57 -3
- package/dist/lib/color.d.ts +80 -0
- package/dist/lib/color.js +189 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/numeric.d.ts +35 -0
- package/dist/lib/numeric.js +29 -0
- package/dist/lib/numeric.js.map +1 -0
- 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/themes.css +8 -0
- package/dist/tokens.css +86 -5
- package/dist/tokens.d.ts +4 -1
- package/dist/tokens.js +56 -36
- package/dist/tokens.json +56 -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 +8 -1
|
@@ -10,9 +10,35 @@ interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {
|
|
|
10
10
|
* A range slider (two thumbs) needs one name per thumb — pass an array.
|
|
11
11
|
*/
|
|
12
12
|
label: string | [string, string];
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Prints the current value beside the label.
|
|
15
|
+
*
|
|
16
|
+
* A two-thumb range prints both names and both values, in the same order —
|
|
17
|
+
* the heading used to print the first name over a pair of numbers, which read
|
|
18
|
+
* as "Minimum" over "10 – 90".
|
|
19
|
+
*/
|
|
14
20
|
showValue?: boolean;
|
|
15
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Turns that readout into a box the number can be typed into, and implies
|
|
23
|
+
* `showValue`.
|
|
24
|
+
*
|
|
25
|
+
* A slider is a control for a NEIGHBOURHOOD; someone who needs 1,150 rather
|
|
26
|
+
* than roughly 1,200 is dragging a 16px thumb across a hundred steps to get
|
|
27
|
+
* it. This is the way out, in the place the value already is, rather than a
|
|
28
|
+
* second field beside the track that has to be kept in step by hand.
|
|
29
|
+
*
|
|
30
|
+
* The box shows `format`'s output at rest and the bare number while it has
|
|
31
|
+
* focus, so a reader still sees "$1,200" and a typist is never asked to type
|
|
32
|
+
* a currency symbol back.
|
|
33
|
+
*/
|
|
34
|
+
editable?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Renders the value with a unit or a currency, e.g. `(n) => n + '%'`.
|
|
37
|
+
*
|
|
38
|
+
* Reaches assistive tech as well as the readout: it becomes each thumb's
|
|
39
|
+
* `aria-valuetext`, so a thumb showing "$1,200" announces that rather than the
|
|
40
|
+
* bare number. Left off, the platform announces the value itself.
|
|
41
|
+
*/
|
|
16
42
|
format?: (value: number) => string;
|
|
17
43
|
}
|
|
18
44
|
/**
|
|
@@ -27,9 +53,22 @@ interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {
|
|
|
27
53
|
* A 44px hit area sits invisibly around the 16px thumb, because a thumb sized
|
|
28
54
|
* for the design is well under any pointer-target guideline.
|
|
29
55
|
*
|
|
56
|
+
* `editable` is the answer to the thing a slider cannot do. Reach for it
|
|
57
|
+
* whenever the exact figure is the point — a budget, a timeout, a price — and
|
|
58
|
+
* leave it off when the value is genuinely approximate, because a box invites
|
|
59
|
+
* precision the setting may not have.
|
|
60
|
+
*
|
|
61
|
+
* Inside a `Field`, the hint, the error and the requirement land on the THUMB,
|
|
62
|
+
* which is the element carrying `role="slider"` — on the root they would sit on
|
|
63
|
+
* a `<span>` with no role and announce nothing. The NAME still comes from
|
|
64
|
+
* `label` here: a field's label above a slider points at that same roleless
|
|
65
|
+
* root, so it neither names the control nor clicks through to it.
|
|
66
|
+
*
|
|
30
67
|
* @example
|
|
31
68
|
* <Slider label="Quality" defaultValue={[80]} max={100} step={5} showValue format={(n) => `${n}%`} />
|
|
69
|
+
* @example
|
|
70
|
+
* <Slider label="Monthly budget" defaultValue={[1200]} max={5000} step={50} editable format={(n) => `$${n}`} />
|
|
32
71
|
*/
|
|
33
|
-
declare function Slider({ label, showValue, format, className, onValueChange, ...props }: SliderProps): react.JSX.Element;
|
|
72
|
+
declare function Slider({ label, showValue, editable, format, className, onValueChange, value: controlledValue, defaultValue, min, max, step, 'aria-describedby': describedBy, 'aria-required': ariaRequired, 'aria-invalid': ariaInvalid, ...props }: SliderProps): react.JSX.Element;
|
|
34
73
|
|
|
35
74
|
export { Slider, type SliderProps, Slider as default };
|
|
@@ -1,48 +1,139 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
4
|
-
import { useState } from "react";
|
|
4
|
+
import { Fragment, useRef, useState } from "react";
|
|
5
5
|
import { cn } from "../../lib/cn.js";
|
|
6
|
+
import { clampToRange, parseNumber, snapToStep } from "../../lib/numeric.js";
|
|
7
|
+
import { warnBlankName } from "../../lib/warn.js";
|
|
6
8
|
function Slider({
|
|
7
9
|
label,
|
|
8
10
|
showValue = false,
|
|
9
|
-
|
|
11
|
+
editable = false,
|
|
12
|
+
format,
|
|
10
13
|
className,
|
|
11
14
|
onValueChange,
|
|
15
|
+
value: controlledValue,
|
|
16
|
+
defaultValue,
|
|
17
|
+
min = 0,
|
|
18
|
+
max = 100,
|
|
19
|
+
step = 1,
|
|
20
|
+
"aria-describedby": describedBy,
|
|
21
|
+
"aria-required": ariaRequired,
|
|
22
|
+
"aria-invalid": ariaInvalid,
|
|
12
23
|
...props
|
|
13
24
|
}) {
|
|
14
25
|
const names = Array.isArray(label) ? label : [label];
|
|
15
|
-
|
|
16
|
-
const
|
|
26
|
+
warnBlankName("Slider", "label", names[0], "the thumb is announced with no name and its value describes nothing");
|
|
27
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue ?? [min]);
|
|
28
|
+
const current = controlledValue ?? uncontrolled;
|
|
29
|
+
const [draft, setDraft] = useState(null);
|
|
30
|
+
const draftRef = useRef(null);
|
|
31
|
+
const edit = (next) => {
|
|
32
|
+
draftRef.current = next;
|
|
33
|
+
setDraft(next);
|
|
34
|
+
};
|
|
17
35
|
const handleChange = (next) => {
|
|
18
|
-
if (
|
|
36
|
+
if (controlledValue === void 0) setUncontrolled(next);
|
|
19
37
|
onValueChange?.(next);
|
|
20
38
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
const commit = (index, text) => {
|
|
40
|
+
const parsed = parseNumber(text);
|
|
41
|
+
if (parsed === null) return false;
|
|
42
|
+
const lower = index > 0 ? current[index - 1] ?? min : min;
|
|
43
|
+
const upper = index < current.length - 1 ? current[index + 1] ?? max : max;
|
|
44
|
+
const next = clampToRange(snapToStep(parsed, step, min), Math.max(min, lower), Math.min(max, upper));
|
|
45
|
+
if (next !== current[index]) handleChange(current.map((value, i) => i === index ? next : value));
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
const print = format ?? String;
|
|
49
|
+
const heading = names.length > 1 ? current.map((_, index) => names[index] ?? names[0]).join(" \u2013 ") : names[0];
|
|
50
|
+
const readout = showValue || editable;
|
|
51
|
+
return /* @__PURE__ */ jsxs(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
className: cn(
|
|
55
|
+
"flex w-full flex-col gap-3",
|
|
56
|
+
// On the wrapper, and driven by the prop: the thumb is a <span>, so a
|
|
57
|
+
// `disabled:` variant compiles to `&:disabled` and matches nothing —
|
|
58
|
+
// the control was drawn exactly like a live one while refusing to move.
|
|
59
|
+
// The wrapper is also what dims the readout above the track.
|
|
60
|
+
props.disabled && "opacity-(--disabled-opacity) pointer-events-none",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
children: [
|
|
64
|
+
readout && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-4 mono-meta text-(--ink-3-aa)", children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { children: heading }),
|
|
66
|
+
editable ? /* @__PURE__ */ jsx("span", { className: "flex items-baseline gap-1.5", children: current.map((value, index) => {
|
|
67
|
+
const editing = draft?.index === index;
|
|
68
|
+
const text = editing ? draft.text : print(value);
|
|
69
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
70
|
+
index > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2013" }),
|
|
71
|
+
/* @__PURE__ */ jsx(
|
|
72
|
+
"input",
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
inputMode: "decimal",
|
|
76
|
+
disabled: props.disabled,
|
|
77
|
+
value: text,
|
|
78
|
+
"aria-label": `${names[index] ?? names[0]}, exact value`,
|
|
79
|
+
"aria-describedby": describedBy,
|
|
80
|
+
"aria-required": ariaRequired,
|
|
81
|
+
"aria-invalid": editing && parseNumber(draft.text) === null || ariaInvalid || void 0,
|
|
82
|
+
onFocus: () => edit({ index, text: String(value) }),
|
|
83
|
+
onChange: (event) => edit({ index, text: event.target.value }),
|
|
84
|
+
onBlur: () => {
|
|
85
|
+
const pending = draftRef.current;
|
|
86
|
+
if (pending?.index === index) commit(index, pending.text);
|
|
87
|
+
edit(null);
|
|
88
|
+
},
|
|
89
|
+
onKeyDown: (event) => {
|
|
90
|
+
const pending = draftRef.current;
|
|
91
|
+
if (event.key === "Enter") {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
if (pending?.index === index && commit(index, pending.text)) edit(null);
|
|
94
|
+
}
|
|
95
|
+
if (event.key === "Escape") {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
edit(null);
|
|
98
|
+
event.currentTarget.blur();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
className: "w-20 rounded-(--radius-sm) border border-(--rule-2) bg-(--paper) px-1.5 py-0.5 text-end tabular-nums text-(--ink) transition-colors duration-(--duration-fast) hover:border-(--rule-hard) focus:border-(--ink) aria-invalid:border-(--danger)"
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] }, index);
|
|
105
|
+
}) }) : /* @__PURE__ */ jsx("span", { className: "tabular-nums text-(--ink)", children: current.map((value) => print(value)).join(" \u2013 ") })
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ jsxs(
|
|
108
|
+
SliderPrimitive.Root,
|
|
109
|
+
{
|
|
110
|
+
className: "group relative flex w-full touch-none select-none items-center py-3",
|
|
111
|
+
value: current,
|
|
112
|
+
min,
|
|
113
|
+
max,
|
|
114
|
+
step,
|
|
115
|
+
onValueChange: handleChange,
|
|
116
|
+
...props,
|
|
117
|
+
children: [
|
|
118
|
+
/* @__PURE__ */ jsx(SliderPrimitive.Track, { className: "relative h-1 w-full grow overflow-hidden rounded-(--radius-pill) bg-(--stone) transition-[height] duration-(--duration-fast) group-hover:h-1.5", children: /* @__PURE__ */ jsx(SliderPrimitive.Range, { className: "absolute h-full bg-(--accent)" }) }),
|
|
119
|
+
current.map((value, index) => /* @__PURE__ */ jsx(
|
|
120
|
+
SliderPrimitive.Thumb,
|
|
121
|
+
{
|
|
122
|
+
"aria-label": names[index] ?? names[0],
|
|
123
|
+
"aria-describedby": describedBy,
|
|
124
|
+
"aria-required": ariaRequired,
|
|
125
|
+
"aria-invalid": ariaInvalid,
|
|
126
|
+
"aria-valuetext": format ? format(value) : void 0,
|
|
127
|
+
className: "relative block size-4 rounded-(--radius-pill) border border-(--accent) bg-(--paper) transition-[transform,background-color] duration-(--duration-fast) ease-(--ease-out-expo) before:absolute before:left-1/2 before:top-1/2 before:size-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] hover:scale-110 hover:bg-(--stone) active:scale-125 data-[state=active]:scale-125 motion-reduce:transition-none"
|
|
128
|
+
},
|
|
129
|
+
index
|
|
130
|
+
))
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
);
|
|
46
137
|
}
|
|
47
138
|
var Slider_default = Slider;
|
|
48
139
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Slider/Slider.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@radix-ui/react-slider'\nimport { useState, type ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {\n /**\n * Names the control. Required: a slider with no name announces only a number,\n * and a number with no noun is not information.\n *\n * A range slider (two thumbs) needs one name per thumb — pass an array.\n */\n label: string | [string, string]\n /** Prints the current value beside the label. */\n showValue?: boolean\n /** Renders the value with a unit or a currency, e.g. `(n) => n + '%'`. */\n format?: (value: number) => string\n}\n\n/**\n * A value chosen along a range.\n *\n * Radix owns the keyboard contract — arrows step, Page keys jump, Home and End\n * reach the ends — and the ARIA that reports the value. What is here is the\n * look, and the labelling, which is the part a slider most often gets wrong: a\n * thumb that announces \"42\" and nothing else leaves a screen reader user with a\n * number and no idea what it measures.\n *\n * A 44px hit area sits invisibly around the 16px thumb, because a thumb sized\n * for the design is well under any pointer-target guideline.\n *\n * @example\n * <Slider label=\"Quality\" defaultValue={[80]} max={100} step={5} showValue format={(n) => `${n}%`} />\n */\nexport function Slider({\n label,\n showValue = false,\n format = String,\n className,\n onValueChange,\n ...props\n}: SliderProps) {\n const names = Array.isArray(label) ? label : [label]\n\n // Tracked here, not read off the props. An uncontrolled slider's\n // `defaultValue` never changes, so the printed figure sat at its starting\n // number while the thumb moved — the one thing `showValue` exists to avoid.\n const [uncontrolled, setUncontrolled] = useState<number[]>(props.defaultValue ?? [])\n const current = props.value ?? uncontrolled\n\n const handleChange = (next: number[]) => {\n if (props.value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n }\n\n return (\n <div className={cn('flex w-full flex-col gap-3', className)}>\n {showValue && (\n <div className=\"flex items-baseline justify-between mono-meta text-(--ink-3-aa)\">\n <span>{names[0]}</span>\n <span className=\"tabular-nums text-(--ink)\">\n {current.map((value) => format(value)).join(' – ')}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n className=\"group relative flex w-full touch-none select-none items-center py-3\"\n onValueChange={handleChange}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-1 w-full grow overflow-hidden rounded-(--radius-pill) bg-(--stone) transition-[height] duration-(--duration-fast) group-hover:h-1.5\">\n <SliderPrimitive.Range className=\"absolute h-full bg-(--accent)\" />\n </SliderPrimitive.Track>\n {current.map((_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n aria-label={names[index] ?? names[0]}\n // The visible thumb is 16px; the `before` pseudo-element widens the\n // hit area to 44px without changing what is drawn.\n // `transition-transform` and not `all`: the thumb's own position is\n // set by Radix as a `left` percentage, and transitioning that would\n // make it lag the pointer. Only the grow-on-grab is animated.\n className=\"relative block size-4 rounded-(--radius-pill) border border-(--accent) bg-(--paper) transition-[transform,background-color] duration-(--duration-fast) ease-(--ease-out-expo) before:absolute before:left-1/2 before:top-1/2 before:size-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] hover:scale-110 hover:bg-(--stone) active:scale-125 data-[state=active]:scale-125 disabled:opacity-(--disabled-opacity) motion-reduce:transition-none\"\n />\n ))}\n </SliderPrimitive.Root>\n </div>\n )\n}\n\nexport default Slider\n"],"mappings":";AA2DQ,SACE,KADF;AAzDR,YAAY,qBAAqB;AACjC,SAAS,gBAAqC;AAC9C,SAAS,UAAU;AA+BZ,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,YAAY;AAAA,EACZ,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAgB;AACd,QAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAKnD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAmB,MAAM,gBAAgB,CAAC,CAAC;AACnF,QAAM,UAAU,MAAM,SAAS;AAE/B,QAAM,eAAe,CAAC,SAAmB;AACvC,QAAI,MAAM,UAAU,OAAW,iBAAgB,IAAI;AACnD,oBAAgB,IAAI;AAAA,EACtB;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,8BAA8B,SAAS,GACvD;AAAA,iBACC,qBAAC,SAAI,WAAU,mEACb;AAAA,0BAAC,UAAM,gBAAM,CAAC,GAAE;AAAA,MAChB,oBAAC,UAAK,WAAU,6BACb,kBAAQ,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC,EAAE,KAAK,UAAK,GACnD;AAAA,OACF;AAAA,IAEF;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,WAAU;AAAA,QACV,eAAe;AAAA,QACd,GAAG;AAAA,QAEJ;AAAA,8BAAC,gBAAgB,OAAhB,EAAsB,WAAU,kJAC/B,8BAAC,gBAAgB,OAAhB,EAAsB,WAAU,iCAAgC,GACnE;AAAA,UACC,QAAQ,IAAI,CAAC,GAAG,UACf;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cAEC,cAAY,MAAM,KAAK,KAAK,MAAM,CAAC;AAAA,cAMnC,WAAU;AAAA;AAAA,YAPL;AAAA,UAQP,CACD;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,iBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Slider/Slider.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@radix-ui/react-slider'\nimport { Fragment, useRef, useState, type ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { clampToRange, parseNumber, snapToStep } from '../../lib/numeric'\nimport { warnBlankName } from '../../lib/warn'\n\nexport interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {\n /**\n * Names the control. Required: a slider with no name announces only a number,\n * and a number with no noun is not information.\n *\n * A range slider (two thumbs) needs one name per thumb — pass an array.\n */\n label: string | [string, string]\n /**\n * Prints the current value beside the label.\n *\n * A two-thumb range prints both names and both values, in the same order —\n * the heading used to print the first name over a pair of numbers, which read\n * as \"Minimum\" over \"10 – 90\".\n */\n showValue?: boolean\n /**\n * Turns that readout into a box the number can be typed into, and implies\n * `showValue`.\n *\n * A slider is a control for a NEIGHBOURHOOD; someone who needs 1,150 rather\n * than roughly 1,200 is dragging a 16px thumb across a hundred steps to get\n * it. This is the way out, in the place the value already is, rather than a\n * second field beside the track that has to be kept in step by hand.\n *\n * The box shows `format`'s output at rest and the bare number while it has\n * focus, so a reader still sees \"$1,200\" and a typist is never asked to type\n * a currency symbol back.\n */\n editable?: boolean\n /**\n * Renders the value with a unit or a currency, e.g. `(n) => n + '%'`.\n *\n * Reaches assistive tech as well as the readout: it becomes each thumb's\n * `aria-valuetext`, so a thumb showing \"$1,200\" announces that rather than the\n * bare number. Left off, the platform announces the value itself.\n */\n format?: (value: number) => string\n}\n\n/** Which box is being typed into, and what is in it. Only one can have focus. */\ninterface Draft {\n index: number\n text: string\n}\n\n/**\n * A value chosen along a range.\n *\n * Radix owns the keyboard contract — arrows step, Page keys jump, Home and End\n * reach the ends — and the ARIA that reports the value. What is here is the\n * look, and the labelling, which is the part a slider most often gets wrong: a\n * thumb that announces \"42\" and nothing else leaves a screen reader user with a\n * number and no idea what it measures.\n *\n * A 44px hit area sits invisibly around the 16px thumb, because a thumb sized\n * for the design is well under any pointer-target guideline.\n *\n * `editable` is the answer to the thing a slider cannot do. Reach for it\n * whenever the exact figure is the point — a budget, a timeout, a price — and\n * leave it off when the value is genuinely approximate, because a box invites\n * precision the setting may not have.\n *\n * Inside a `Field`, the hint, the error and the requirement land on the THUMB,\n * which is the element carrying `role=\"slider\"` — on the root they would sit on\n * a `<span>` with no role and announce nothing. The NAME still comes from\n * `label` here: a field's label above a slider points at that same roleless\n * root, so it neither names the control nor clicks through to it.\n *\n * @example\n * <Slider label=\"Quality\" defaultValue={[80]} max={100} step={5} showValue format={(n) => `${n}%`} />\n * @example\n * <Slider label=\"Monthly budget\" defaultValue={[1200]} max={5000} step={50} editable format={(n) => `$${n}`} />\n */\nexport function Slider({\n label,\n showValue = false,\n editable = false,\n format,\n className,\n onValueChange,\n value: controlledValue,\n defaultValue,\n min = 0,\n max = 100,\n step = 1,\n 'aria-describedby': describedBy,\n 'aria-required': ariaRequired,\n 'aria-invalid': ariaInvalid,\n ...props\n}: SliderProps) {\n const names = Array.isArray(label) ? label : [label]\n\n // The other five guarded naming props are plain strings; this one is a\n // string or a pair, so the first name is what is checked — a range whose\n // lower thumb has no name is the same silence as a single one with none.\n // It catches a blank, not an absent: `label` is required, so a missing one\n // is a type error everywhere except a caller that has already left the\n // types behind.\n warnBlankName('Slider', 'label', names[0], 'the thumb is announced with no name and its value describes nothing')\n\n // Tracked here, not read off the props. An uncontrolled slider's\n // `defaultValue` never changes, so the printed figure sat at its starting\n // number while the thumb moved — the one thing `showValue` exists to avoid.\n //\n // The fallback is the primitive's own — one thumb at the minimum. The thumbs\n // are rendered from THIS array rather than from Radix's, so an empty one is a\n // track with nothing on it to drag, which is what `<Slider label=\"Volume\" />`\n // used to render.\n const [uncontrolled, setUncontrolled] = useState<number[]>(defaultValue ?? [min])\n const current = controlledValue ?? uncontrolled\n\n // Mirrored into a ref because Escape clears the draft and blurs the box in\n // the SAME event: the state update has not been applied by the time the\n // blur handler runs, so a handler reading the state variable would see the\n // abandoned text and commit exactly what Escape was pressed to discard.\n const [draft, setDraft] = useState<Draft | null>(null)\n const draftRef = useRef<Draft | null>(null)\n const edit = (next: Draft | null) => {\n draftRef.current = next\n setDraft(next)\n }\n\n const handleChange = (next: number[]) => {\n if (controlledValue === undefined) setUncontrolled(next)\n onValueChange?.(next)\n }\n\n /**\n * Commits one typed box, or reports that there was no number in it.\n *\n * A thumb is also bounded by its NEIGHBOURS, which is the bound a typed\n * value can cross and a dragged one cannot: typing 90 into the lower end of a\n * range whose upper end is at 70 would otherwise hand Radix an unsorted array\n * and leave the two thumbs crossed over.\n */\n const commit = (index: number, text: string): boolean => {\n const parsed = parseNumber(text)\n if (parsed === null) return false\n const lower = index > 0 ? (current[index - 1] ?? min) : min\n const upper = index < current.length - 1 ? (current[index + 1] ?? max) : max\n const next = clampToRange(snapToStep(parsed, step, min), Math.max(min, lower), Math.min(max, upper))\n if (next !== current[index]) handleChange(current.map((value, i) => (i === index ? next : value)))\n return true\n }\n\n const print = format ?? String\n // One name per thumb, in the thumbs' own order, so the heading lines up with\n // the numbers under it. A single name stays a single name however many thumbs\n // there are — a price filter called \"Price\" is not \"Price – Price\".\n const heading =\n names.length > 1 ? current.map((_, index) => names[index] ?? names[0]).join(' – ') : names[0]\n\n const readout = showValue || editable\n\n return (\n <div\n className={cn(\n 'flex w-full flex-col gap-3',\n // On the wrapper, and driven by the prop: the thumb is a <span>, so a\n // `disabled:` variant compiles to `&:disabled` and matches nothing —\n // the control was drawn exactly like a live one while refusing to move.\n // The wrapper is also what dims the readout above the track.\n props.disabled && 'opacity-(--disabled-opacity) pointer-events-none',\n className,\n )}\n >\n {readout && (\n <div className=\"flex items-baseline justify-between gap-4 mono-meta text-(--ink-3-aa)\">\n <span>{heading}</span>\n {editable ? (\n <span className=\"flex items-baseline gap-1.5\">\n {current.map((value, index) => {\n const editing = draft?.index === index\n const text = editing ? draft.text : print(value)\n return (\n <Fragment key={index}>\n {index > 0 && <span aria-hidden=\"true\">–</span>}\n <input\n type=\"text\"\n // Not `type=\"number\"`: the box holds `format`'s output at\n // rest — \"$1,200\" — which a number input refuses to\n // display and silently blanks. `inputMode` is what still\n // brings up the numeric keypad on a phone.\n inputMode=\"decimal\"\n // `disabled` on the wrapper is `pointer-events-none`,\n // which a keyboard walks straight past. Without this, the\n // one editable part of a disabled slider stays editable.\n disabled={props.disabled}\n value={text}\n // The name has to differ from the thumb's: both carry the\n // same value, and two controls announcing \"Quality\" is a\n // reader hearing the same control twice.\n aria-label={`${names[index] ?? names[0]}, exact value`}\n aria-describedby={describedBy}\n aria-required={ariaRequired}\n aria-invalid={(editing && parseNumber(draft.text) === null) || ariaInvalid || undefined}\n onFocus={() => edit({ index, text: String(value) })}\n onChange={(event) => edit({ index, text: event.target.value })}\n onBlur={() => {\n const pending = draftRef.current\n if (pending?.index === index) commit(index, pending.text)\n edit(null)\n }}\n onKeyDown={(event) => {\n const pending = draftRef.current\n if (event.key === 'Enter') {\n event.preventDefault()\n if (pending?.index === index && commit(index, pending.text)) edit(null)\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n edit(null)\n event.currentTarget.blur()\n }\n }}\n className=\"w-20 rounded-(--radius-sm) border border-(--rule-2) bg-(--paper) px-1.5 py-0.5 text-end tabular-nums text-(--ink) transition-colors duration-(--duration-fast) hover:border-(--rule-hard) focus:border-(--ink) aria-invalid:border-(--danger)\"\n />\n </Fragment>\n )\n })}\n </span>\n ) : (\n <span className=\"tabular-nums text-(--ink)\">\n {current.map((value) => print(value)).join(' – ')}\n </span>\n )}\n </div>\n )}\n {/* Controlled from THIS component's state even when the caller left it\n uncontrolled, because the thumbs have to answer to more than dragging:\n a number typed into the readout never passes through Radix, so a Root\n holding its own `defaultValue` would keep the thumb where it was and\n leave the figure above it disagreeing with the track. `min`, `max` and\n `step` are passed on rather than spread for the same reason — the\n typed value is reconciled against them here. */}\n <SliderPrimitive.Root\n className=\"group relative flex w-full touch-none select-none items-center py-3\"\n value={current}\n min={min}\n max={max}\n step={step}\n onValueChange={handleChange}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-1 w-full grow overflow-hidden rounded-(--radius-pill) bg-(--stone) transition-[height] duration-(--duration-fast) group-hover:h-1.5\">\n <SliderPrimitive.Range className=\"absolute h-full bg-(--accent)\" />\n </SliderPrimitive.Track>\n {current.map((value, index) => (\n <SliderPrimitive.Thumb\n key={index}\n aria-label={names[index] ?? names[0]}\n // The thumb is what carries role=\"slider\", so this is where a\n // Field's description and requirement have to land — on the root\n // they sit on a <span> with no role, which announces nothing.\n aria-describedby={describedBy}\n aria-required={ariaRequired}\n aria-invalid={ariaInvalid}\n // Only when a format was given: setting it otherwise would replace\n // the platform's own reading of the value with the same digits.\n aria-valuetext={format ? format(value) : undefined}\n // The visible thumb is 16px; the `before` pseudo-element widens the\n // hit area to 44px without changing what is drawn.\n // `transition-transform` and not `all`: the thumb's own position is\n // set by Radix as a `left` percentage, and transitioning that would\n // make it lag the pointer. Only the grow-on-grab is animated.\n className=\"relative block size-4 rounded-(--radius-pill) border border-(--accent) bg-(--paper) transition-[transform,background-color] duration-(--duration-fast) ease-(--ease-out-expo) before:absolute before:left-1/2 before:top-1/2 before:size-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] hover:scale-110 hover:bg-(--stone) active:scale-125 data-[state=active]:scale-125 motion-reduce:transition-none\"\n />\n ))}\n </SliderPrimitive.Root>\n </div>\n )\n}\n\nexport default Slider\n"],"mappings":"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","names":[]}
|
|
@@ -11,6 +11,8 @@ interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
11
11
|
* ink ground an ink ring is invisible.
|
|
12
12
|
*/
|
|
13
13
|
tone?: 'default' | 'current';
|
|
14
|
+
/** Merged onto the RING, after `size` and `tone`, so it overrides both. */
|
|
15
|
+
className?: string;
|
|
14
16
|
/**
|
|
15
17
|
* Announced to assistive tech. Pass the specific thing being waited on
|
|
16
18
|
* ("Loading projects"), not the generic word — a screen reader user hearing
|
|
@@ -39,6 +41,12 @@ interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
39
41
|
* page is broken, prefer `Skeleton` — a shape that describes what is coming
|
|
40
42
|
* beats a dot that describes nothing.
|
|
41
43
|
*
|
|
44
|
+
* `className` reaches the RING, alongside `size` and `tone`, and overrides
|
|
45
|
+
* them: every utility a caller has for a spinner is about the ring, and merged
|
|
46
|
+
* onto the wrapper instead `className="size-8"` grew an invisible box around an
|
|
47
|
+
* unchanged 18px circle. Layout still works from there — the wrapper is
|
|
48
|
+
* `inline-flex` and takes the ring's margin box as its own.
|
|
49
|
+
*
|
|
42
50
|
* @example
|
|
43
51
|
* <Spinner size="lg" label="Loading projects" />
|
|
44
52
|
* @example
|
|
@@ -21,7 +21,7 @@ function Spinner({
|
|
|
21
21
|
{
|
|
22
22
|
role: label === null ? void 0 : "status",
|
|
23
23
|
"aria-hidden": label === null ? true : void 0,
|
|
24
|
-
className:
|
|
24
|
+
className: "inline-flex shrink-0 items-center justify-center",
|
|
25
25
|
...rest,
|
|
26
26
|
children: [
|
|
27
27
|
/* @__PURE__ */ jsx(
|
|
@@ -31,7 +31,8 @@ function Spinner({
|
|
|
31
31
|
className: cn(
|
|
32
32
|
"inline-block rounded-full border-solid motion-safe:animate-[m22-spin_0.7s_linear_infinite]",
|
|
33
33
|
SIZE[size],
|
|
34
|
-
TONE[tone]
|
|
34
|
+
TONE[tone],
|
|
35
|
+
className
|
|
35
36
|
)
|
|
36
37
|
}
|
|
37
38
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Spinner/Spinner.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {\n /** 14px / 18px / 26px. Match the size of the type it sits beside. */\n size?: 'sm' | 'md' | 'lg'\n /**\n * `default` draws the ring against the page — a hairline track in the rule\n * colour with the leading quarter in ink. `current` draws it in the inherited\n * text colour, which is what a spinner inside a filled button needs: on an\n * ink ground an ink ring is invisible.\n */\n tone?: 'default' | 'current'\n /**\n * Announced to assistive tech. Pass the specific thing being waited on\n * (\"Loading projects\"), not the generic word — a screen reader user hearing\n * \"Loading\" three times cannot tell which three things.\n *\n * Pass `null` for a spinner that sits inside a control which already names\n * the operation (a button whose own label changes to \"Saving…\"), so the two\n * are not read out twice.\n */\n label?: string | null\n}\n\nconst SIZE = {\n sm: 'size-3.5 border-[1.5px]',\n md: 'size-[18px] border-2',\n lg: 'size-6.5 border-2',\n} as const\n\nconst TONE = {\n default: 'border-(--rule-2) border-t-(--ink)',\n current: 'border-current/25 border-t-current',\n} as const\n\n/**\n * The system's one \"working\" indicator.\n *\n * A ring, not a blurred glow and not a shimmer: the White Reset has no light\n * source, so depth and softness are not available to it. The leading quarter is\n * the only thing that distinguishes the ring from a plain circle, which is why\n * the track stays a hairline — a spinner is a hint that time is passing, not a\n * feature of the page.\n *\n * It spins under `motion-safe` only. A reader who has asked for less motion\n * gets a static ring, which still reads as \"not finished\" because the leading\n * quarter is darker than the track.\n *\n * For a wait long enough that the reader would otherwise wonder whether the\n * page is broken, prefer `Skeleton` — a shape that describes what is coming\n * beats a dot that describes nothing.\n *\n * @example\n * <Spinner size=\"lg\" label=\"Loading projects\" />\n * @example\n * // Inside a filled button, where the ground is ink:\n * <Spinner size=\"sm\" tone=\"current\" label={null} />\n */\nexport function Spinner({\n size = 'md',\n tone = 'default',\n className,\n label = 'Loading',\n ...rest\n}: SpinnerProps) {\n return (\n <span\n role={label === null ? undefined : 'status'}\n aria-hidden={label === null ? true : undefined}\n className
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Spinner/Spinner.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {\n /** 14px / 18px / 26px. Match the size of the type it sits beside. */\n size?: 'sm' | 'md' | 'lg'\n /**\n * `default` draws the ring against the page — a hairline track in the rule\n * colour with the leading quarter in ink. `current` draws it in the inherited\n * text colour, which is what a spinner inside a filled button needs: on an\n * ink ground an ink ring is invisible.\n */\n tone?: 'default' | 'current'\n /** Merged onto the RING, after `size` and `tone`, so it overrides both. */\n className?: string\n /**\n * Announced to assistive tech. Pass the specific thing being waited on\n * (\"Loading projects\"), not the generic word — a screen reader user hearing\n * \"Loading\" three times cannot tell which three things.\n *\n * Pass `null` for a spinner that sits inside a control which already names\n * the operation (a button whose own label changes to \"Saving…\"), so the two\n * are not read out twice.\n */\n label?: string | null\n}\n\nconst SIZE = {\n sm: 'size-3.5 border-[1.5px]',\n md: 'size-[18px] border-2',\n lg: 'size-6.5 border-2',\n} as const\n\nconst TONE = {\n default: 'border-(--rule-2) border-t-(--ink)',\n current: 'border-current/25 border-t-current',\n} as const\n\n/**\n * The system's one \"working\" indicator.\n *\n * A ring, not a blurred glow and not a shimmer: the White Reset has no light\n * source, so depth and softness are not available to it. The leading quarter is\n * the only thing that distinguishes the ring from a plain circle, which is why\n * the track stays a hairline — a spinner is a hint that time is passing, not a\n * feature of the page.\n *\n * It spins under `motion-safe` only. A reader who has asked for less motion\n * gets a static ring, which still reads as \"not finished\" because the leading\n * quarter is darker than the track.\n *\n * For a wait long enough that the reader would otherwise wonder whether the\n * page is broken, prefer `Skeleton` — a shape that describes what is coming\n * beats a dot that describes nothing.\n *\n * `className` reaches the RING, alongside `size` and `tone`, and overrides\n * them: every utility a caller has for a spinner is about the ring, and merged\n * onto the wrapper instead `className=\"size-8\"` grew an invisible box around an\n * unchanged 18px circle. Layout still works from there — the wrapper is\n * `inline-flex` and takes the ring's margin box as its own.\n *\n * @example\n * <Spinner size=\"lg\" label=\"Loading projects\" />\n * @example\n * // Inside a filled button, where the ground is ink:\n * <Spinner size=\"sm\" tone=\"current\" label={null} />\n */\nexport function Spinner({\n size = 'md',\n tone = 'default',\n className,\n label = 'Loading',\n ...rest\n}: SpinnerProps) {\n return (\n <span\n role={label === null ? undefined : 'status'}\n aria-hidden={label === null ? true : undefined}\n className=\"inline-flex shrink-0 items-center justify-center\"\n {...rest}\n >\n <span\n data-m22-animated\n className={cn(\n 'inline-block rounded-full border-solid motion-safe:animate-[m22-spin_0.7s_linear_infinite]',\n SIZE[size],\n TONE[tone],\n className,\n )}\n />\n {label !== null && <span className=\"sr-only\">{label}</span>}\n </span>\n )\n}\n\nexport default Spinner\n"],"mappings":"AA2EI,SAME,KANF;AA1EJ,SAAS,UAAU;AA0BnB,MAAM,OAAO;AAAA,EACX,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,OAAO;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AACX;AA+BO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,EACP;AAAA,EACA,QAAQ;AAAA,EACR,GAAG;AACL,GAAiB;AACf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,UAAU,OAAO,SAAY;AAAA,MACnC,eAAa,UAAU,OAAO,OAAO;AAAA,MACrC,WAAU;AAAA,MACT,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,qBAAiB;AAAA,YACjB,WAAW;AAAA,cACT;AAAA,cACA,KAAK,IAAI;AAAA,cACT,KAAK,IAAI;AAAA,cACT;AAAA,YACF;AAAA;AAAA,QACF;AAAA,QACC,UAAU,QAAQ,oBAAC,UAAK,WAAU,WAAW,iBAAM;AAAA;AAAA;AAAA,EACtD;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
|
|
@@ -15,6 +15,11 @@ interface StatusPillProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
15
15
|
* how the same "available for work" chip ended up with three different dot
|
|
16
16
|
* sizes and two different pulse timings on one site.
|
|
17
17
|
*
|
|
18
|
+
* A warning or danger tone is doubled by a visually-hidden severity word, so
|
|
19
|
+
* the tone survives the dot being hidden. It does not survive monochrome: the
|
|
20
|
+
* pill's own text is `--ink-2` at every tone, and the state itself still
|
|
21
|
+
* belongs in the words the call site writes.
|
|
22
|
+
*
|
|
18
23
|
* @example
|
|
19
24
|
* <StatusPill>Available for work</StatusPill>
|
|
20
25
|
* @example
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../lib/cn.js";
|
|
3
3
|
import { StatusDot } from "../StatusDot/StatusDot.js";
|
|
4
|
+
const SEVERITY = {
|
|
5
|
+
warning: "Warning",
|
|
6
|
+
danger: "Error"
|
|
7
|
+
};
|
|
4
8
|
function StatusPill({
|
|
5
9
|
children,
|
|
6
10
|
tone = "success",
|
|
@@ -8,6 +12,7 @@ function StatusPill({
|
|
|
8
12
|
className,
|
|
9
13
|
...rest
|
|
10
14
|
}) {
|
|
15
|
+
const severity = SEVERITY[tone];
|
|
11
16
|
return /* @__PURE__ */ jsxs(
|
|
12
17
|
"span",
|
|
13
18
|
{
|
|
@@ -18,6 +23,7 @@ function StatusPill({
|
|
|
18
23
|
...rest,
|
|
19
24
|
children: [
|
|
20
25
|
/* @__PURE__ */ jsx(StatusDot, { tone, pulse }),
|
|
26
|
+
severity && /* @__PURE__ */ jsx("span", { className: "sr-only", children: severity }),
|
|
21
27
|
children
|
|
22
28
|
]
|
|
23
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/StatusPill/StatusPill.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { StatusDot, type StatusTone } from '../StatusDot/StatusDot'\n\nexport interface StatusPillProps extends HTMLAttributes<HTMLSpanElement> {\n children: ReactNode\n tone?: StatusTone\n /** Pulsing live dot (default) vs a settled one. */\n pulse?: boolean\n}\n\n/**\n * A live state, named: a dot plus an uppercase mono label in an outlined pill.\n *\n * One component rather than a dot and a pill assembled per call site, which is\n * how the same \"available for work\" chip ended up with three different dot\n * sizes and two different pulse timings on one site.\n *\n * @example\n * <StatusPill>Available for work</StatusPill>\n * @example\n * <StatusPill tone=\"warning\" pulse={false}>Degraded</StatusPill>\n */\nexport function StatusPill({\n children,\n tone = 'success',\n pulse = true,\n className,\n ...rest\n}: StatusPillProps) {\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-2.5 rounded-(--radius-pill) border border-(--rule-2) bg-(--paper) py-1.5 ps-2.5 pe-3 eyebrow tracking-[0.12em] text-(--ink-2)',\n className,\n )}\n {...rest}\n >\n <StatusDot tone={tone} pulse={pulse} />\n {children}\n </span>\n )\n}\n\nexport default StatusPill\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/components/StatusPill/StatusPill.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { StatusDot, type StatusTone } from '../StatusDot/StatusDot'\n\nexport interface StatusPillProps extends HTMLAttributes<HTMLSpanElement> {\n children: ReactNode\n tone?: StatusTone\n /** Pulsing live dot (default) vs a settled one. */\n pulse?: boolean\n}\n\n/**\n * The two tones a reader must not miss, in the words the dot cannot say.\n *\n * `tone` reaches only the StatusDot, and the dot is `aria-hidden` by law — so\n * before this, \"Degraded\" in a warning pill and \"Degraded\" in a neutral one\n * were the same sentence to anyone who could not see the colour.\n *\n * `success` and `neutral` add nothing on purpose. They are the absence of\n * alarm, which is what a reader assumes; announcing it before every settled\n * pill is noise charged to the two tones that are worth interrupting for.\n */\nconst SEVERITY: Partial<Record<StatusTone, string>> = {\n warning: 'Warning',\n danger: 'Error',\n}\n\n/**\n * A live state, named: a dot plus an uppercase mono label in an outlined pill.\n *\n * One component rather than a dot and a pill assembled per call site, which is\n * how the same \"available for work\" chip ended up with three different dot\n * sizes and two different pulse timings on one site.\n *\n * A warning or danger tone is doubled by a visually-hidden severity word, so\n * the tone survives the dot being hidden. It does not survive monochrome: the\n * pill's own text is `--ink-2` at every tone, and the state itself still\n * belongs in the words the call site writes.\n *\n * @example\n * <StatusPill>Available for work</StatusPill>\n * @example\n * <StatusPill tone=\"warning\" pulse={false}>Degraded</StatusPill>\n */\nexport function StatusPill({\n children,\n tone = 'success',\n pulse = true,\n className,\n ...rest\n}: StatusPillProps) {\n const severity = SEVERITY[tone]\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-2.5 rounded-(--radius-pill) border border-(--rule-2) bg-(--paper) py-1.5 ps-2.5 pe-3 eyebrow tracking-[0.12em] text-(--ink-2)',\n className,\n )}\n {...rest}\n >\n <StatusDot tone={tone} pulse={pulse} />\n {/* Absolutely positioned by `sr-only`, so it is out of flow and costs the\n pill neither a gap nor a pixel. */}\n {severity && <span className=\"sr-only\">{severity}</span>}\n {children}\n </span>\n )\n}\n\nexport default StatusPill\n"],"mappings":"AAsDI,SAOE,KAPF;AArDJ,SAAS,UAAU;AACnB,SAAS,iBAAkC;AAoB3C,MAAM,WAAgD;AAAA,EACpD,SAAS;AAAA,EACT,QAAQ;AACV;AAmBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,WAAW,SAAS,IAAI;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,aAAU,MAAY,OAAc;AAAA,QAGpC,YAAY,oBAAC,UAAK,WAAU,WAAW,oBAAS;AAAA,QAChD;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
|
|
@@ -45,6 +45,12 @@ interface TableProps extends TableHTMLAttributes<HTMLTableElement> {
|
|
|
45
45
|
* element to Tab to and therefore no way to press an arrow key at it, so the
|
|
46
46
|
* columns past the fold simply do not exist for anyone not using a mouse.
|
|
47
47
|
*
|
|
48
|
+
* It is also POSITIONED, which is not decoration. `sr-only` is
|
|
49
|
+
* `position: absolute`, so a visually-hidden label in a cell resolves against
|
|
50
|
+
* the nearest positioned ancestor — and with none between it and the document,
|
|
51
|
+
* it escapes the scroll container and every `overflow-hidden` around it, and
|
|
52
|
+
* widens the page by however far the table happens to be scrolled.
|
|
53
|
+
*
|
|
48
54
|
* No zebra striping at any border setting. In a monochrome system a striped row
|
|
49
55
|
* is a second surface competing with the page ground, and the hairline between
|
|
50
56
|
* rows is already enough to track a line across.
|
|
@@ -91,6 +97,20 @@ interface TDProps extends Omit<TdHTMLAttributes<HTMLTableCellElement>, 'align'>
|
|
|
91
97
|
/** Which edge the cell's contents sit against. Match the column's header. */
|
|
92
98
|
align?: TableAlign;
|
|
93
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* A body cell.
|
|
102
|
+
*
|
|
103
|
+
* TOP-aligned, and that is a choice about which kind of table is the common
|
|
104
|
+
* one here: a cell holding a paragraph. Top is what lines the first lines of a
|
|
105
|
+
* row up with each other, and centring a two-word term against a four-line
|
|
106
|
+
* description puts the term in the middle of nothing.
|
|
107
|
+
*
|
|
108
|
+
* It is the wrong default for the other kind — a row of one-line values with a
|
|
109
|
+
* control in it. A 36px button next to 16px of text makes a 52px row, and every
|
|
110
|
+
* other cell then hangs at the top of it with twenty pixels of nothing
|
|
111
|
+
* underneath, which reads as a column that has slipped. Pass
|
|
112
|
+
* `className="align-middle"` on those rows; the data-table template does.
|
|
113
|
+
*/
|
|
94
114
|
declare function TD({ align, className, ...rest }: TDProps): react.JSX.Element;
|
|
95
115
|
|
|
96
116
|
export { type SortDirection, TBody, TD, type TDProps, TH, type THProps, THead, TR, Table, type TableAlign, type TableBorders, type TableProps, Table as default };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react";
|
|
3
3
|
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { warnBlankName } from "../../lib/warn.js";
|
|
4
5
|
const ALIGN = {
|
|
5
6
|
start: "text-start",
|
|
6
7
|
center: "text-center",
|
|
@@ -27,6 +28,7 @@ function Table({
|
|
|
27
28
|
children,
|
|
28
29
|
...rest
|
|
29
30
|
}) {
|
|
31
|
+
warnBlankName("Table", "caption", caption, "the table is announced with no name and a reader arrives in it with no idea what it lists");
|
|
30
32
|
return /* @__PURE__ */ jsx(
|
|
31
33
|
"div",
|
|
32
34
|
{
|
|
@@ -34,7 +36,7 @@ function Table({
|
|
|
34
36
|
role: "region",
|
|
35
37
|
"aria-label": caption,
|
|
36
38
|
"data-table-density": density,
|
|
37
|
-
className: cn("w-full overflow-x-auto scroll-slim", BORDERS[borders]),
|
|
39
|
+
className: cn("relative w-full overflow-x-auto scroll-slim", BORDERS[borders]),
|
|
38
40
|
children: /* @__PURE__ */ jsxs(
|
|
39
41
|
"table",
|
|
40
42
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Table/Table.tsx"],"sourcesContent":["import { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'\nimport type { HTMLAttributes, TableHTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport type TableAlign = 'start' | 'center' | 'end'\n\n/**\n * Which rules the table draws.\n *\n * `rows` is the default and the right answer for reading down a column: one\n * hairline between records and nothing else, so the eye tracks a line across\n * without a grid competing for it. `grid` adds the vertical rules a dense\n * numeric table needs to keep columns apart. `bordered` puts an edge around the\n * whole thing, for a table that sits loose on a page rather than inside a card\n * that already has one. `none` is for a table inside something that draws its\n * own structure.\n */\nexport type TableBorders = 'rows' | 'grid' | 'bordered' | 'bordered-grid' | 'none'\n\nconst ALIGN: Record<TableAlign, string> = {\n start: 'text-start',\n center: 'text-center',\n end: 'text-end',\n}\n\n/**\n * Every rule the table draws, expressed from ONE element.\n *\n * Not React context, which was the first attempt: `createContext` makes the\n * module client-only, which turned every table on every page into a hydration\n * boundary and took the static build down with it. Descendant selectors from\n * the wrapper reach the same cells, cost nothing at runtime, and keep the whole\n * component server-renderable.\n *\n * `--table-pad-x` rather than a padding class per mode, because the ruled\n * variants need symmetric padding inside their boxes while the plain one wants\n * a trailing gap and no leading one.\n */\nconst ROWS =\n '[&_thead]:border-b [&_thead]:border-(--rule-hard) [&_tbody_tr]:border-b [&_tbody_tr]:border-(--rule) [&_tbody_tr:last-child]:border-b-0'\nconst SOFT_HEAD = '[&_thead]:border-(--rule-2)'\nconst COLUMNS =\n '[&_th]:border-e [&_td]:border-e [&_th]:border-(--rule) [&_td]:border-(--rule) [&_th:last-child]:border-e-0 [&_td:last-child]:border-e-0'\nconst BOXED = 'rounded-(--radius) border border-(--rule-2) [--table-pad-x:0.75rem]'\n\nconst BORDERS: Record<TableBorders, string> = {\n rows: ROWS,\n grid: cn(ROWS, COLUMNS, '[--table-pad-x:0.75rem]'),\n bordered: cn(ROWS, SOFT_HEAD, BOXED),\n 'bordered-grid': cn(ROWS, SOFT_HEAD, COLUMNS, BOXED),\n none: '',\n}\n\nexport interface TableProps extends TableHTMLAttributes<HTMLTableElement> {\n /**\n * Describes the table for a screen reader, which cannot see the heading above\n * it. Rendered as a visually-hidden `<caption>` — required, because an\n * unnamed table in a page with three tables is unnavigable.\n */\n caption: string\n /** Prints the caption instead of hiding it. */\n showCaption?: boolean\n /**\n * Pins the header row while the body scrolls. Needs a bounded height on the\n * container — otherwise the page scrolls, not the table, and nothing sticks.\n */\n stickyHeader?: boolean\n /** Which rules to draw. See {@link TableBorders}. */\n borders?: TableBorders\n /** Tightens the row padding, for a table that is mostly numbers. */\n density?: 'comfortable' | 'compact'\n}\n\n/**\n * A ruled data table.\n *\n * Wrapped in its own horizontally scrolling container, because a table is the\n * one block that legitimately exceeds the measure — and a page that scrolls\n * sideways as a whole is a layout bug, while a table that does is a table.\n *\n * That container is focusable, and it has to be. A scrollable region whose\n * contents are not themselves focusable is unreachable by keyboard: there is no\n * element to Tab to and therefore no way to press an arrow key at it, so the\n * columns past the fold simply do not exist for anyone not using a mouse.\n *\n * No zebra striping at any border setting. In a monochrome system a striped row\n * is a second surface competing with the page ground, and the hairline between\n * rows is already enough to track a line across.\n *\n * @example\n * <Table caption=\"Deploy history\">\n * <THead><TR><TH>Commit</TH><TH align=\"end\" sortable sortDirection={dir} onSort={sort}>Duration</TH></TR></THead>\n * <TBody><TR><TD>a1b2c3d</TD><TD align=\"end\">2m 14s</TD></TR></TBody>\n * </Table>\n * @example\n * <Table caption=\"Inventory\" borders=\"bordered-grid\" density=\"compact\">…</Table>\n */\nexport function Table({\n caption,\n showCaption = false,\n stickyHeader = false,\n borders = 'rows',\n density = 'comfortable',\n className,\n children,\n ...rest\n}: TableProps) {\n return (\n <div\n tabIndex={0}\n role=\"region\"\n aria-label={caption}\n data-table-density={density}\n className={cn('w-full overflow-x-auto scroll-slim', BORDERS[borders])}\n >\n <table\n className={cn(\n 'w-full border-collapse text-start text-sm',\n stickyHeader &&\n '[&_thead]:sticky [&_thead]:top-0 [&_thead]:z-(--z-rule) [&_thead]:bg-(--paper)',\n className,\n )}\n {...rest}\n >\n <caption className={cn(showCaption ? 'pb-3 text-start eyebrow text-(--ink-3-aa)' : 'sr-only')}>\n {caption}\n </caption>\n {children}\n </table>\n </div>\n )\n}\n\n/** Header group. The rule under it is drawn by the table's border setting. */\nexport function THead({ className, ...rest }: HTMLAttributes<HTMLTableSectionElement>) {\n return <thead className={className} {...rest} />\n}\n\nexport function TBody({ className, ...rest }: HTMLAttributes<HTMLTableSectionElement>) {\n return <tbody className={className} {...rest} />\n}\n\nexport function TR({ className, ...rest }: HTMLAttributes<HTMLTableRowElement>) {\n return <tr className={className} {...rest} />\n}\n\nexport type SortDirection = 'ascending' | 'descending' | 'none'\n\nexport interface THProps extends Omit<ThHTMLAttributes<HTMLTableCellElement>, 'align'> {\n /** Which edge the column's contents sit against. Numbers belong at `end`. */\n align?: TableAlign\n /** Makes the label a button and shows the sort marker. */\n sortable?: boolean\n /**\n * Which way this column is sorted. Also set as `aria-sort`, which is the\n * only way a screen reader learns a table is ordered at all — a caret drawn\n * in the header tells it nothing.\n */\n sortDirection?: SortDirection\n onSort?: () => void\n}\n\n/**\n * Cell padding, in one string both cells share.\n *\n * `--table-pad-x` is 0 by default and set by the ruled variants, so the plain\n * table keeps its trailing gap and no leading one while a boxed table gets\n * symmetric padding inside its border.\n */\nconst CELL =\n 'px-[var(--table-pad-x,0)] [&:not(:last-child)]:pe-[calc(var(--table-pad-x,0px)+1.5rem)]'\n\nconst SORT_ICON = {\n ascending: ArrowUp,\n descending: ArrowDown,\n none: ChevronsUpDown,\n} as const\n\n/**\n * A column label. Mono and uppercase, so it never reads as data.\n *\n * When sortable, the label becomes a `<button>` INSIDE the `<th>` rather than\n * the `<th>` becoming clickable: a cell with a click handler is not focusable\n * and not announced as a control, so the sort exists only for a mouse.\n *\n * Sorting is per column and opt-in. A table where every header is a button\n * invites the reader to try sorting a column the data cannot be ordered by.\n */\nexport function TH({\n align = 'start',\n sortable = false,\n sortDirection = 'none',\n onSort,\n className,\n children,\n ...rest\n}: THProps) {\n const Icon = SORT_ICON[sortDirection]\n\n return (\n <th\n scope=\"col\"\n aria-sort={sortable ? sortDirection : undefined}\n className={cn(CELL, 'py-3 align-bottom eyebrow text-(--ink-3-aa)', ALIGN[align], className)}\n {...rest}\n >\n {sortable ? (\n <button\n type=\"button\"\n onClick={onSort}\n className={cn(\n 'group inline-flex items-center gap-1.5 eyebrow transition-colors duration-(--duration-fast) hover:text-(--ink)',\n sortDirection === 'none' ? 'text-(--ink-3-aa)' : 'text-(--ink)',\n )}\n >\n {children}\n <Icon\n size={12}\n strokeWidth={2}\n aria-hidden\n className={cn(\n 'shrink-0 transition-opacity',\n sortDirection === 'none' && 'opacity-40 group-hover:opacity-100',\n )}\n />\n </button>\n ) : (\n children\n )}\n </th>\n )\n}\n\nexport interface TDProps extends Omit<TdHTMLAttributes<HTMLTableCellElement>, 'align'> {\n /** Which edge the cell's contents sit against. Match the column's header. */\n align?: TableAlign\n}\n\nexport function TD({ align = 'start', className, ...rest }: TDProps) {\n return (\n <td\n className={cn(CELL, 'py-[var(--table-pad-y)] align-top text-(--ink-2)', ALIGN[align], className)}\n {...rest}\n />\n )\n}\n\nexport default Table\n"],"mappings":"AAmHM,SASE,KATF;AAnHN,SAAS,WAAW,SAAS,sBAAsB;AAEnD,SAAS,UAAU;AAiBnB,MAAM,QAAoC;AAAA,EACxC,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,KAAK;AACP;AAeA,MAAM,OACJ;AACF,MAAM,YAAY;AAClB,MAAM,UACJ;AACF,MAAM,QAAQ;AAEd,MAAM,UAAwC;AAAA,EAC5C,MAAM;AAAA,EACN,MAAM,GAAG,MAAM,SAAS,yBAAyB;AAAA,EACjD,UAAU,GAAG,MAAM,WAAW,KAAK;AAAA,EACnC,iBAAiB,GAAG,MAAM,WAAW,SAAS,KAAK;AAAA,EACnD,MAAM;AACR;AA8CO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA,cAAc;AAAA,EACd,eAAe;AAAA,EACf,UAAU;AAAA,EACV,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAe;AACb,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,sBAAoB;AAAA,MACpB,WAAW,GAAG,sCAAsC,QAAQ,OAAO,CAAC;AAAA,MAEpE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,gBACE;AAAA,YACF;AAAA,UACF;AAAA,UACC,GAAG;AAAA,UAEJ;AAAA,gCAAC,aAAQ,WAAW,GAAG,cAAc,8CAA8C,SAAS,GACzF,mBACH;AAAA,YACC;AAAA;AAAA;AAAA,MACH;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,MAAM,EAAE,WAAW,GAAG,KAAK,GAA4C;AACrF,SAAO,oBAAC,WAAM,WAAuB,GAAG,MAAM;AAChD;AAEO,SAAS,MAAM,EAAE,WAAW,GAAG,KAAK,GAA4C;AACrF,SAAO,oBAAC,WAAM,WAAuB,GAAG,MAAM;AAChD;AAEO,SAAS,GAAG,EAAE,WAAW,GAAG,KAAK,GAAwC;AAC9E,SAAO,oBAAC,QAAG,WAAuB,GAAG,MAAM;AAC7C;AAyBA,MAAM,OACJ;AAEF,MAAM,YAAY;AAAA,EAChB,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,MAAM;AACR;AAYO,SAAS,GAAG;AAAA,EACjB,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAY;AACV,QAAM,OAAO,UAAU,aAAa;AAEpC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,aAAW,WAAW,gBAAgB;AAAA,MACtC,WAAW,GAAG,MAAM,+CAA+C,MAAM,KAAK,GAAG,SAAS;AAAA,MACzF,GAAG;AAAA,MAEH,qBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAW;AAAA,YACT;AAAA,YACA,kBAAkB,SAAS,sBAAsB;AAAA,UACnD;AAAA,UAEC;AAAA;AAAA,YACD;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,aAAa;AAAA,gBACb,eAAW;AAAA,gBACX,WAAW;AAAA,kBACT;AAAA,kBACA,kBAAkB,UAAU;AAAA,gBAC9B;AAAA;AAAA,YACF;AAAA;AAAA;AAAA,MACF,IAEA;AAAA;AAAA,EAEJ;AAEJ;AAOO,SAAS,GAAG,EAAE,QAAQ,SAAS,WAAW,GAAG,KAAK,GAAY;AACnE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,MAAM,oDAAoD,MAAM,KAAK,GAAG,SAAS;AAAA,MAC9F,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,gBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Table/Table.tsx"],"sourcesContent":["import { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'\nimport type { HTMLAttributes, TableHTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\nimport { warnBlankName } from '../../lib/warn'\n\nexport type TableAlign = 'start' | 'center' | 'end'\n\n/**\n * Which rules the table draws.\n *\n * `rows` is the default and the right answer for reading down a column: one\n * hairline between records and nothing else, so the eye tracks a line across\n * without a grid competing for it. `grid` adds the vertical rules a dense\n * numeric table needs to keep columns apart. `bordered` puts an edge around the\n * whole thing, for a table that sits loose on a page rather than inside a card\n * that already has one. `none` is for a table inside something that draws its\n * own structure.\n */\nexport type TableBorders = 'rows' | 'grid' | 'bordered' | 'bordered-grid' | 'none'\n\nconst ALIGN: Record<TableAlign, string> = {\n start: 'text-start',\n center: 'text-center',\n end: 'text-end',\n}\n\n/**\n * Every rule the table draws, expressed from ONE element.\n *\n * Not React context, which was the first attempt: `createContext` makes the\n * module client-only, which turned every table on every page into a hydration\n * boundary and took the static build down with it. Descendant selectors from\n * the wrapper reach the same cells, cost nothing at runtime, and keep the whole\n * component server-renderable.\n *\n * `--table-pad-x` rather than a padding class per mode, because the ruled\n * variants need symmetric padding inside their boxes while the plain one wants\n * a trailing gap and no leading one.\n */\nconst ROWS =\n '[&_thead]:border-b [&_thead]:border-(--rule-hard) [&_tbody_tr]:border-b [&_tbody_tr]:border-(--rule) [&_tbody_tr:last-child]:border-b-0'\nconst SOFT_HEAD = '[&_thead]:border-(--rule-2)'\nconst COLUMNS =\n '[&_th]:border-e [&_td]:border-e [&_th]:border-(--rule) [&_td]:border-(--rule) [&_th:last-child]:border-e-0 [&_td:last-child]:border-e-0'\nconst BOXED = 'rounded-(--radius) border border-(--rule-2) [--table-pad-x:0.75rem]'\n\nconst BORDERS: Record<TableBorders, string> = {\n rows: ROWS,\n grid: cn(ROWS, COLUMNS, '[--table-pad-x:0.75rem]'),\n bordered: cn(ROWS, SOFT_HEAD, BOXED),\n 'bordered-grid': cn(ROWS, SOFT_HEAD, COLUMNS, BOXED),\n none: '',\n}\n\nexport interface TableProps extends TableHTMLAttributes<HTMLTableElement> {\n /**\n * Describes the table for a screen reader, which cannot see the heading above\n * it. Rendered as a visually-hidden `<caption>` — required, because an\n * unnamed table in a page with three tables is unnavigable.\n */\n caption: string\n /** Prints the caption instead of hiding it. */\n showCaption?: boolean\n /**\n * Pins the header row while the body scrolls. Needs a bounded height on the\n * container — otherwise the page scrolls, not the table, and nothing sticks.\n */\n stickyHeader?: boolean\n /** Which rules to draw. See {@link TableBorders}. */\n borders?: TableBorders\n /** Tightens the row padding, for a table that is mostly numbers. */\n density?: 'comfortable' | 'compact'\n}\n\n/**\n * A ruled data table.\n *\n * Wrapped in its own horizontally scrolling container, because a table is the\n * one block that legitimately exceeds the measure — and a page that scrolls\n * sideways as a whole is a layout bug, while a table that does is a table.\n *\n * That container is focusable, and it has to be. A scrollable region whose\n * contents are not themselves focusable is unreachable by keyboard: there is no\n * element to Tab to and therefore no way to press an arrow key at it, so the\n * columns past the fold simply do not exist for anyone not using a mouse.\n *\n * It is also POSITIONED, which is not decoration. `sr-only` is\n * `position: absolute`, so a visually-hidden label in a cell resolves against\n * the nearest positioned ancestor — and with none between it and the document,\n * it escapes the scroll container and every `overflow-hidden` around it, and\n * widens the page by however far the table happens to be scrolled.\n *\n * No zebra striping at any border setting. In a monochrome system a striped row\n * is a second surface competing with the page ground, and the hairline between\n * rows is already enough to track a line across.\n *\n * @example\n * <Table caption=\"Deploy history\">\n * <THead><TR><TH>Commit</TH><TH align=\"end\" sortable sortDirection={dir} onSort={sort}>Duration</TH></TR></THead>\n * <TBody><TR><TD>a1b2c3d</TD><TD align=\"end\">2m 14s</TD></TR></TBody>\n * </Table>\n * @example\n * <Table caption=\"Inventory\" borders=\"bordered-grid\" density=\"compact\">…</Table>\n */\nexport function Table({\n caption,\n showCaption = false,\n stickyHeader = false,\n borders = 'rows',\n density = 'comfortable',\n className,\n children,\n ...rest\n}: TableProps) {\n warnBlankName('Table', 'caption', caption, 'the table is announced with no name and a reader arrives in it with no idea what it lists')\n return (\n <div\n tabIndex={0}\n role=\"region\"\n aria-label={caption}\n data-table-density={density}\n className={cn('relative w-full overflow-x-auto scroll-slim', BORDERS[borders])}\n >\n <table\n className={cn(\n 'w-full border-collapse text-start text-sm',\n stickyHeader &&\n '[&_thead]:sticky [&_thead]:top-0 [&_thead]:z-(--z-rule) [&_thead]:bg-(--paper)',\n className,\n )}\n {...rest}\n >\n <caption className={cn(showCaption ? 'pb-3 text-start eyebrow text-(--ink-3-aa)' : 'sr-only')}>\n {caption}\n </caption>\n {children}\n </table>\n </div>\n )\n}\n\n/** Header group. The rule under it is drawn by the table's border setting. */\nexport function THead({ className, ...rest }: HTMLAttributes<HTMLTableSectionElement>) {\n return <thead className={className} {...rest} />\n}\n\nexport function TBody({ className, ...rest }: HTMLAttributes<HTMLTableSectionElement>) {\n return <tbody className={className} {...rest} />\n}\n\nexport function TR({ className, ...rest }: HTMLAttributes<HTMLTableRowElement>) {\n return <tr className={className} {...rest} />\n}\n\nexport type SortDirection = 'ascending' | 'descending' | 'none'\n\nexport interface THProps extends Omit<ThHTMLAttributes<HTMLTableCellElement>, 'align'> {\n /** Which edge the column's contents sit against. Numbers belong at `end`. */\n align?: TableAlign\n /** Makes the label a button and shows the sort marker. */\n sortable?: boolean\n /**\n * Which way this column is sorted. Also set as `aria-sort`, which is the\n * only way a screen reader learns a table is ordered at all — a caret drawn\n * in the header tells it nothing.\n */\n sortDirection?: SortDirection\n onSort?: () => void\n}\n\n/**\n * Cell padding, in one string both cells share.\n *\n * `--table-pad-x` is 0 by default and set by the ruled variants, so the plain\n * table keeps its trailing gap and no leading one while a boxed table gets\n * symmetric padding inside its border.\n */\nconst CELL =\n 'px-[var(--table-pad-x,0)] [&:not(:last-child)]:pe-[calc(var(--table-pad-x,0px)+1.5rem)]'\n\nconst SORT_ICON = {\n ascending: ArrowUp,\n descending: ArrowDown,\n none: ChevronsUpDown,\n} as const\n\n/**\n * A column label. Mono and uppercase, so it never reads as data.\n *\n * When sortable, the label becomes a `<button>` INSIDE the `<th>` rather than\n * the `<th>` becoming clickable: a cell with a click handler is not focusable\n * and not announced as a control, so the sort exists only for a mouse.\n *\n * Sorting is per column and opt-in. A table where every header is a button\n * invites the reader to try sorting a column the data cannot be ordered by.\n */\nexport function TH({\n align = 'start',\n sortable = false,\n sortDirection = 'none',\n onSort,\n className,\n children,\n ...rest\n}: THProps) {\n const Icon = SORT_ICON[sortDirection]\n\n return (\n <th\n scope=\"col\"\n aria-sort={sortable ? sortDirection : undefined}\n className={cn(CELL, 'py-3 align-bottom eyebrow text-(--ink-3-aa)', ALIGN[align], className)}\n {...rest}\n >\n {sortable ? (\n <button\n type=\"button\"\n onClick={onSort}\n className={cn(\n 'group inline-flex items-center gap-1.5 eyebrow transition-colors duration-(--duration-fast) hover:text-(--ink)',\n sortDirection === 'none' ? 'text-(--ink-3-aa)' : 'text-(--ink)',\n )}\n >\n {children}\n <Icon\n size={12}\n strokeWidth={2}\n aria-hidden\n className={cn(\n 'shrink-0 transition-opacity',\n sortDirection === 'none' && 'opacity-40 group-hover:opacity-100',\n )}\n />\n </button>\n ) : (\n children\n )}\n </th>\n )\n}\n\nexport interface TDProps extends Omit<TdHTMLAttributes<HTMLTableCellElement>, 'align'> {\n /** Which edge the cell's contents sit against. Match the column's header. */\n align?: TableAlign\n}\n\n/**\n * A body cell.\n *\n * TOP-aligned, and that is a choice about which kind of table is the common\n * one here: a cell holding a paragraph. Top is what lines the first lines of a\n * row up with each other, and centring a two-word term against a four-line\n * description puts the term in the middle of nothing.\n *\n * It is the wrong default for the other kind — a row of one-line values with a\n * control in it. A 36px button next to 16px of text makes a 52px row, and every\n * other cell then hangs at the top of it with twenty pixels of nothing\n * underneath, which reads as a column that has slipped. Pass\n * `className=\"align-middle\"` on those rows; the data-table template does.\n */\nexport function TD({ align = 'start', className, ...rest }: TDProps) {\n return (\n <td\n className={cn(CELL, 'py-[var(--table-pad-y)] align-top text-(--ink-2)', ALIGN[align], className)}\n {...rest}\n />\n )\n}\n\nexport default Table\n"],"mappings":"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","names":[]}
|
|
@@ -8,7 +8,13 @@ function TabsList({ className, ...rest }) {
|
|
|
8
8
|
TabsPrimitive.List,
|
|
9
9
|
{
|
|
10
10
|
className: cn(
|
|
11
|
-
|
|
11
|
+
// `overflow-y-hidden` is not decoration. A strip only ever scrolls
|
|
12
|
+
// sideways, but `overflow-x: auto` on its own computes `overflow-y` to
|
|
13
|
+
// `auto` as well — and the active trigger's `-mb-px` rule leaves the
|
|
14
|
+
// content exactly one pixel taller than the box, which is enough for a
|
|
15
|
+
// vertical scrollbar to appear beside a row of tabs that has nothing
|
|
16
|
+
// to scroll.
|
|
17
|
+
"flex items-center overflow-x-auto overflow-y-hidden gap-1 border-b border-(--rule-2) scroll-slim",
|
|
12
18
|
className
|
|
13
19
|
),
|
|
14
20
|
...rest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Tabs/Tabs.tsx"],"sourcesContent":["'use client'\n\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Tabs root, re-exported as a typed passthrough. */\nexport const Tabs = TabsPrimitive.Root\n\n/**\n * The tab strip.\n *\n * Scrolls horizontally rather than wrapping. A wrapped second row of tabs moves\n * every tab below it when the strip grows, and the reader loses the one they\n * were about to click.\n *\n * @example\n * <Tabs defaultValue=\"preview\">\n * <TabsList>\n * <TabsTrigger value=\"preview\">Preview</TabsTrigger>\n * <TabsTrigger value=\"code\">Code</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"preview\">…</TabsContent>\n * </Tabs>\n */\nexport function TabsList({ className, ...rest }: ComponentProps<typeof TabsPrimitive.List>) {\n return (\n <TabsPrimitive.List\n className={cn(\n 'flex items-center
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Tabs/Tabs.tsx"],"sourcesContent":["'use client'\n\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Tabs root, re-exported as a typed passthrough. */\nexport const Tabs = TabsPrimitive.Root\n\n/**\n * The tab strip.\n *\n * Scrolls horizontally rather than wrapping. A wrapped second row of tabs moves\n * every tab below it when the strip grows, and the reader loses the one they\n * were about to click.\n *\n * @example\n * <Tabs defaultValue=\"preview\">\n * <TabsList>\n * <TabsTrigger value=\"preview\">Preview</TabsTrigger>\n * <TabsTrigger value=\"code\">Code</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"preview\">…</TabsContent>\n * </Tabs>\n */\nexport function TabsList({ className, ...rest }: ComponentProps<typeof TabsPrimitive.List>) {\n return (\n <TabsPrimitive.List\n className={cn(\n // `overflow-y-hidden` is not decoration. A strip only ever scrolls\n // sideways, but `overflow-x: auto` on its own computes `overflow-y` to\n // `auto` as well — and the active trigger's `-mb-px` rule leaves the\n // content exactly one pixel taller than the box, which is enough for a\n // vertical scrollbar to appear beside a row of tabs that has nothing\n // to scroll.\n 'flex items-center overflow-x-auto overflow-y-hidden gap-1 border-b border-(--rule-2) scroll-slim',\n className,\n )}\n {...rest}\n />\n )\n}\n\n/**\n * One tab.\n *\n * The active marker is a 2px ink rule pulled onto the strip's own border with\n * `-mb-px`, so the two occupy the same line instead of stacking into a 3px\n * edge. 44px tall, because a tab is a pointer target like any other.\n */\nexport function TabsTrigger({ className, ...rest }: ComponentProps<typeof TabsPrimitive.Trigger>) {\n return (\n <TabsPrimitive.Trigger\n className={cn(\n '-mb-px min-h-(--control-h-md) shrink-0 whitespace-nowrap border-b-2 border-transparent px-3.5 py-2 text-sm text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:text-(--ink) data-[state=active]:border-(--accent) data-[state=active]:text-(--accent-on-muted)',\n className,\n )}\n {...rest}\n />\n )\n}\n\n/** The panel paired to a {@link TabsTrigger} by matching `value`. */\nexport function TabsContent({ className, ...rest }: ComponentProps<typeof TabsPrimitive.Content>) {\n return (\n <TabsPrimitive.Content\n data-m22-animated\n className={cn('pt-5 data-[state=active]:animate-[m22-panel-in_var(--duration-fast)_var(--ease)]', className)}\n {...rest}\n />\n )\n}\n\nexport default Tabs\n"],"mappings":";AA2BI;AAzBJ,YAAY,mBAAmB;AAE/B,SAAS,UAAU;AAGZ,MAAM,OAAO,cAAc;AAkB3B,SAAS,SAAS,EAAE,WAAW,GAAG,KAAK,GAA8C;AAC1F,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AASO,SAAS,YAAY,EAAE,WAAW,GAAG,KAAK,GAAiD;AAChG,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAGO,SAAS,YAAY,EAAE,WAAW,GAAG,KAAK,GAAiD;AAChG,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,qBAAiB;AAAA,MACjB,WAAW,GAAG,oFAAoF,SAAS;AAAA,MAC1G,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,eAAQ;","names":[]}
|