@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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { CalendarDays } from "lucide-react";
|
|
4
|
-
import { useRef, useState } from "react";
|
|
4
|
+
import { useId, useRef, useState } from "react";
|
|
5
|
+
import { dateMatchModifiers } from "react-day-picker";
|
|
5
6
|
import { cn } from "../../lib/cn.js";
|
|
6
7
|
import { Calendar } from "../Calendar/Calendar.js";
|
|
8
|
+
import { useFieldControl } from "../Field/field-control.js";
|
|
7
9
|
import { Popover, PopoverContent, PopoverTrigger } from "../Popover/Popover.js";
|
|
8
10
|
const daysAgo = (n) => {
|
|
9
11
|
const to = /* @__PURE__ */ new Date();
|
|
@@ -61,7 +63,8 @@ const DATE_PRESETS = [
|
|
|
61
63
|
function PresetRail({
|
|
62
64
|
presets,
|
|
63
65
|
onPick,
|
|
64
|
-
label
|
|
66
|
+
label,
|
|
67
|
+
blocked
|
|
65
68
|
}) {
|
|
66
69
|
return /* @__PURE__ */ jsx(
|
|
67
70
|
"div",
|
|
@@ -73,8 +76,13 @@ function PresetRail({
|
|
|
73
76
|
"button",
|
|
74
77
|
{
|
|
75
78
|
type: "button",
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
disabled: blocked?.(preset.value()) ?? false,
|
|
80
|
+
onClick: () => {
|
|
81
|
+
const next = preset.value();
|
|
82
|
+
if (blocked?.(next)) return;
|
|
83
|
+
onPick(next);
|
|
84
|
+
},
|
|
85
|
+
className: "rounded-(--radius-sm) px-2.5 py-1.5 text-start text-[13px] text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
|
|
78
86
|
children: preset.label
|
|
79
87
|
},
|
|
80
88
|
preset.label
|
|
@@ -94,32 +102,53 @@ function DatePicker({
|
|
|
94
102
|
disabledDates,
|
|
95
103
|
format = formatDate,
|
|
96
104
|
presets,
|
|
97
|
-
className
|
|
105
|
+
className,
|
|
106
|
+
id,
|
|
107
|
+
"aria-describedby": describedBy,
|
|
108
|
+
"aria-invalid": ariaInvalid
|
|
98
109
|
}) {
|
|
99
110
|
const rail = presets === true ? DATE_PRESETS : presets || void 0;
|
|
100
111
|
const [open, setOpen] = useState(false);
|
|
101
112
|
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
102
113
|
const current = value ?? uncontrolled;
|
|
114
|
+
const field = useFieldControl();
|
|
115
|
+
const generated = useId();
|
|
116
|
+
const triggerId = id ?? generated;
|
|
117
|
+
const valueId = `${triggerId}-value`;
|
|
118
|
+
const nameId = field?.labelId ?? `${triggerId}-name`;
|
|
103
119
|
const choose = (next) => {
|
|
104
120
|
if (value === void 0) setUncontrolled(next);
|
|
105
121
|
onValueChange?.(next);
|
|
106
122
|
if (next) setOpen(false);
|
|
107
123
|
};
|
|
124
|
+
const refuses = (date) => disabledDates !== void 0 && dateMatchModifiers(date, disabledDates);
|
|
108
125
|
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
109
126
|
/* @__PURE__ */ jsxs(
|
|
110
127
|
PopoverTrigger,
|
|
111
128
|
{
|
|
112
|
-
|
|
129
|
+
id: triggerId,
|
|
130
|
+
"aria-labelledby": `${nameId} ${valueId}`,
|
|
131
|
+
"aria-describedby": describedBy,
|
|
132
|
+
"aria-invalid": ariaInvalid || void 0,
|
|
113
133
|
disabled,
|
|
114
134
|
className: cn(TRIGGER, current ? "text-(--ink)" : "text-(--ink-3-aa)", className),
|
|
115
135
|
children: [
|
|
116
|
-
|
|
136
|
+
field?.labelId == null && /* @__PURE__ */ jsx("span", { id: nameId, className: "sr-only", children: label }),
|
|
137
|
+
/* @__PURE__ */ jsx("span", { id: valueId, className: "truncate", children: current ? format(current) : placeholder }),
|
|
117
138
|
/* @__PURE__ */ jsx(CalendarDays, { size: 14, strokeWidth: 1.5, "aria-hidden": true, className: "shrink-0 text-(--ink-3-aa)" })
|
|
118
139
|
]
|
|
119
140
|
}
|
|
120
141
|
),
|
|
121
142
|
/* @__PURE__ */ jsx(PopoverContent, { label, align: "start", className: "w-auto overflow-hidden p-0", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row", children: [
|
|
122
|
-
rail && /* @__PURE__ */ jsx(
|
|
143
|
+
rail && /* @__PURE__ */ jsx(
|
|
144
|
+
PresetRail,
|
|
145
|
+
{
|
|
146
|
+
presets: rail,
|
|
147
|
+
onPick: choose,
|
|
148
|
+
label: `${label} shortcuts`,
|
|
149
|
+
blocked: refuses
|
|
150
|
+
}
|
|
151
|
+
),
|
|
123
152
|
/* @__PURE__ */ jsx(
|
|
124
153
|
Calendar,
|
|
125
154
|
{
|
|
@@ -144,12 +173,21 @@ function DateRangePicker({
|
|
|
144
173
|
months = 2,
|
|
145
174
|
format = formatDate,
|
|
146
175
|
presets = true,
|
|
147
|
-
className
|
|
176
|
+
className,
|
|
177
|
+
id,
|
|
178
|
+
"aria-describedby": describedBy,
|
|
179
|
+
"aria-invalid": ariaInvalid
|
|
148
180
|
}) {
|
|
149
181
|
const rail = presets === true ? RANGE_PRESETS : presets || void 0;
|
|
150
182
|
const [open, setOpen] = useState(false);
|
|
151
183
|
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
152
184
|
const current = value ?? uncontrolled;
|
|
185
|
+
const field = useFieldControl();
|
|
186
|
+
const generated = useId();
|
|
187
|
+
const triggerId = id ?? generated;
|
|
188
|
+
const valueId = `${triggerId}-value`;
|
|
189
|
+
const nameId = field?.labelId ?? `${triggerId}-name`;
|
|
190
|
+
const refuses = (range) => disabledDates !== void 0 && [range.from, range.to].some((date) => date != null && dateMatchModifiers(date, disabledDates));
|
|
153
191
|
const clicks = useRef(0);
|
|
154
192
|
const choose = (next) => {
|
|
155
193
|
clicks.current += 1;
|
|
@@ -167,11 +205,15 @@ function DateRangePicker({
|
|
|
167
205
|
/* @__PURE__ */ jsxs(
|
|
168
206
|
PopoverTrigger,
|
|
169
207
|
{
|
|
170
|
-
|
|
208
|
+
id: triggerId,
|
|
209
|
+
"aria-labelledby": `${nameId} ${valueId}`,
|
|
210
|
+
"aria-describedby": describedBy,
|
|
211
|
+
"aria-invalid": ariaInvalid || void 0,
|
|
171
212
|
disabled,
|
|
172
213
|
className: cn(TRIGGER, current?.from ? "text-(--ink)" : "text-(--ink-3-aa)", className),
|
|
173
214
|
children: [
|
|
174
|
-
/* @__PURE__ */ jsx("span", { className: "
|
|
215
|
+
field?.labelId == null && /* @__PURE__ */ jsx("span", { id: nameId, className: "sr-only", children: label }),
|
|
216
|
+
/* @__PURE__ */ jsx("span", { id: valueId, className: "truncate", children: printed }),
|
|
175
217
|
/* @__PURE__ */ jsx(CalendarDays, { size: 14, strokeWidth: 1.5, "aria-hidden": true, className: "shrink-0 text-(--ink-3-aa)" })
|
|
176
218
|
]
|
|
177
219
|
}
|
|
@@ -186,7 +228,8 @@ function DateRangePicker({
|
|
|
186
228
|
onValueChange?.(next);
|
|
187
229
|
setOpen(false);
|
|
188
230
|
},
|
|
189
|
-
label: `${label} shortcuts
|
|
231
|
+
label: `${label} shortcuts`,
|
|
232
|
+
blocked: refuses
|
|
190
233
|
}
|
|
191
234
|
),
|
|
192
235
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["'use client'\n\nimport { CalendarDays } from 'lucide-react'\nimport { useRef, useState, type ComponentProps } from 'react'\nimport type { DateRange } from 'react-day-picker'\nimport { cn } from '../../lib/cn'\nimport { Calendar } from '../Calendar/Calendar'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport type { DateRange }\n\n/** A named shortcut in the rail beside the calendar. */\nexport interface DatePreset<T> {\n label: string\n /** Computed when clicked, not when rendered — \"today\" must mean today. */\n value: () => T\n}\n\n/** `n` days back from today, inclusive of today. */\nconst daysAgo = (n: number): DateRange => {\n const to = new Date()\n const from = new Date()\n from.setDate(from.getDate() - (n - 1))\n return { from, to }\n}\n\n/**\n * The shortcuts a range picker is asked for on nearly every screen it appears\n * on, so they ship rather than being rebuilt per dashboard.\n *\n * Computed on click: a preset list built at render time freezes \"today\" at\n * whenever the page loaded, which is wrong for anything left open overnight.\n */\nexport const RANGE_PRESETS: DatePreset<DateRange>[] = [\n { label: 'Last 7 days', value: () => daysAgo(7) },\n { label: 'Last 30 days', value: () => daysAgo(30) },\n { label: 'Last 90 days', value: () => daysAgo(90) },\n { label: 'Last 12 months', value: () => daysAgo(365) },\n {\n label: 'Month to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), to.getMonth(), 1), to }\n },\n },\n {\n label: 'Year to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), 0, 1), to }\n },\n },\n]\n\n/** The single-date equivalent. */\nexport const DATE_PRESETS: DatePreset<Date>[] = [\n { label: 'Today', value: () => new Date() },\n {\n label: 'Tomorrow',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 1)\n return date\n },\n },\n {\n label: 'In a week',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 7)\n return date\n },\n },\n {\n label: 'In a month',\n value: () => {\n const date = new Date()\n date.setMonth(date.getMonth() + 1)\n return date\n },\n },\n]\n\n/**\n * The rail of shortcuts.\n *\n * A `<nav>`-less list of plain buttons rather than a menu: they set the same\n * value the grid beside them sets, so they are part of one control and should\n * Tab in the same pass rather than opening something.\n */\nfunction PresetRail<T>({\n presets,\n onPick,\n label,\n}: {\n presets: DatePreset<T>[]\n onPick: (value: T) => void\n label: string\n}) {\n return (\n <div\n role=\"group\"\n aria-label={label}\n className=\"flex shrink-0 flex-col gap-0.5 border-b border-(--rule) p-2 sm:border-b-0 sm:border-e max-sm:flex-row max-sm:flex-wrap\"\n >\n {presets.map((preset) => (\n <button\n key={preset.label}\n type=\"button\"\n onClick={() => onPick(preset.value())}\n className=\"rounded-(--radius-sm) px-2.5 py-1.5 text-start text-[13px] text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n {preset.label}\n </button>\n ))}\n </div>\n )\n}\n\nconst TRIGGER =\n 'flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\n/**\n * The visitor's own locale, not a fixed `dd/mm/yyyy`.\n *\n * `03/04` is March the fourth in one country and the third of April in the\n * next, and there is no format that is unambiguous to everyone. `Intl` already\n * knows which one this reader expects.\n */\nconst formatDate = (date: Date) =>\n new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)\n\nexport interface DatePickerProps {\n value?: Date\n defaultValue?: Date\n onValueChange?: (value: Date | undefined) => void\n /** Names the control. Required — the trigger's text is a value, not a label. */\n label: string\n placeholder?: string\n disabled?: boolean\n /** Days the reader may not choose. Passed straight to the calendar. */\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How the chosen date is printed on the trigger. */\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid. Pass `true` for the built-in set, an array\n * for your own, or leave it off for none.\n */\n presets?: boolean | DatePreset<Date>[]\n className?: string\n}\n\n/**\n * A date, chosen from a calendar.\n *\n * A trigger and a `Calendar` in a `Popover` — not a new component so much as\n * the composition people otherwise assemble slightly differently on every\n * screen.\n *\n * It is deliberately NOT a text input with a calendar attached. A typed date\n * needs parsing, and parsing needs a format, and a format is a locale argument\n * nobody wins. When typing genuinely matters — a birth date, a long way back —\n * the calendar's month and year are dropdowns, which is the same journey\n * without the ambiguity.\n *\n * @example\n * <Field label=\"Publish on\"><DatePicker label=\"Publish on\" /></Field>\n */\nexport function DatePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a date',\n disabled = false,\n disabledDates,\n format = formatDate,\n presets,\n className,\n}: DatePickerProps) {\n const rail = presets === true ? DATE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<Date | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n const choose = (next: Date | undefined) => {\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (next) setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n aria-label={label}\n disabled={disabled}\n className={cn(TRIGGER, current ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n {current ? format(current) : placeholder}\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && <PresetRail presets={rail} onPick={choose} label={`${label} shortcuts`} />}\n <Calendar\n mode=\"single\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport interface DateRangePickerProps {\n value?: DateRange\n defaultValue?: DateRange\n onValueChange?: (value: DateRange | undefined) => void\n label: string\n placeholder?: string\n disabled?: boolean\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How many months are shown side by side. Falls back to one under `sm`. */\n months?: number\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid — Last 30 days and its neighbours. `true`\n * for the built-in set, an array for your own.\n *\n * On by default here and off on the single picker, because \"last 30 days\" is\n * most of what a range picker is ever asked for, while a single date is\n * usually a specific one.\n */\n presets?: boolean | DatePreset<DateRange>[]\n className?: string\n}\n\n/**\n * A span of dates — a stay, a reporting period, a filter.\n *\n * Two months side by side, because a range that crosses a month boundary is the\n * common case, and paging back and forth to see both ends is what makes a range\n * picker tiring. They stack under `sm`, where two would not fit — the calendar's\n * own `months` class already carries that, so there is nothing to override.\n *\n * The panel stays open until both ends are chosen: a range is not a value until\n * it has a second date, and closing on the first one would mean re-opening to\n * finish.\n *\n * @example\n * <DateRangePicker label=\"Reporting period\" />\n */\nexport function DateRangePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a range',\n disabled = false,\n disabledDates,\n months = 2,\n format = formatDate,\n presets = true,\n className,\n}: DateRangePickerProps) {\n const rail = presets === true ? RANGE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<DateRange | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n /**\n * How many days have been clicked since the panel opened.\n *\n * Counted, rather than inferred from the range being \"complete\", because the\n * first click returns `{ from: day, to: day }` — a complete range by any\n * reading of the value. Closing on that meant every attempt at a range\n * produced a one-day one and shut the panel, which is the opposite of what a\n * range picker is for.\n */\n const clicks = useRef(0)\n\n const choose = (next: DateRange | undefined) => {\n clicks.current += 1\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (clicks.current >= 2 && next?.from && next.to) setOpen(false)\n }\n\n const setPanel = (next: boolean) => {\n if (next) clicks.current = 0\n setOpen(next)\n }\n\n const complete = current?.from && current.to && current.from.getTime() !== current.to.getTime()\n const printed = current?.from\n ? complete\n ? `${format(current.from)} – ${format(current.to!)}`\n : `${format(current.from)} – …`\n : placeholder\n\n return (\n <Popover open={open} onOpenChange={setPanel}>\n <PopoverTrigger\n aria-label={label}\n disabled={disabled}\n className={cn(TRIGGER, current?.from ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n <span className=\"truncate\">{printed}</span>\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && (\n <PresetRail\n presets={rail}\n onPick={(next) => {\n // A preset is a complete range in one gesture, so the panel\n // closes — unlike a first click on the grid, which is half an\n // answer.\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n setOpen(false)\n }}\n label={`${label} shortcuts`}\n />\n )}\n <Calendar\n mode=\"range\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n numberOfMonths={months}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default DatePicker\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["'use client'\n\nimport { CalendarDays } from 'lucide-react'\nimport { useId, useRef, useState, type ComponentProps } from 'react'\nimport { dateMatchModifiers, type DateRange } from 'react-day-picker'\nimport { cn } from '../../lib/cn'\nimport { Calendar } from '../Calendar/Calendar'\nimport { useFieldControl } from '../Field/field-control'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport type { DateRange }\n\n/** A named shortcut in the rail beside the calendar. */\nexport interface DatePreset<T> {\n label: string\n /** Computed when clicked, not when rendered — \"today\" must mean today. */\n value: () => T\n}\n\n/** `n` days back from today, inclusive of today. */\nconst daysAgo = (n: number): DateRange => {\n const to = new Date()\n const from = new Date()\n from.setDate(from.getDate() - (n - 1))\n return { from, to }\n}\n\n/**\n * The shortcuts a range picker is asked for on nearly every screen it appears\n * on, so they ship rather than being rebuilt per dashboard.\n *\n * Computed on click: a preset list built at render time freezes \"today\" at\n * whenever the page loaded, which is wrong for anything left open overnight.\n */\nexport const RANGE_PRESETS: DatePreset<DateRange>[] = [\n { label: 'Last 7 days', value: () => daysAgo(7) },\n { label: 'Last 30 days', value: () => daysAgo(30) },\n { label: 'Last 90 days', value: () => daysAgo(90) },\n { label: 'Last 12 months', value: () => daysAgo(365) },\n {\n label: 'Month to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), to.getMonth(), 1), to }\n },\n },\n {\n label: 'Year to date',\n value: () => {\n const to = new Date()\n return { from: new Date(to.getFullYear(), 0, 1), to }\n },\n },\n]\n\n/** The single-date equivalent. */\nexport const DATE_PRESETS: DatePreset<Date>[] = [\n { label: 'Today', value: () => new Date() },\n {\n label: 'Tomorrow',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 1)\n return date\n },\n },\n {\n label: 'In a week',\n value: () => {\n const date = new Date()\n date.setDate(date.getDate() + 7)\n return date\n },\n },\n {\n label: 'In a month',\n value: () => {\n const date = new Date()\n date.setMonth(date.getMonth() + 1)\n return date\n },\n },\n]\n\n/**\n * The rail of shortcuts.\n *\n * A `<nav>`-less list of plain buttons rather than a menu: they set the same\n * value the grid beside them sets, so they are part of one control and should\n * Tab in the same pass rather than opening something.\n *\n * Setting the same value means being refused for the same reasons: a shortcut\n * that commits a date the grid beside it will not accept hands the caller back\n * a value the control itself calls invalid. `blocked` is asked at render, so the\n * button is visibly unavailable rather than silently inert, and again on click,\n * because the value is computed then — \"today\" has to mean today.\n */\nfunction PresetRail<T>({\n presets,\n onPick,\n label,\n blocked,\n}: {\n presets: DatePreset<T>[]\n onPick: (value: T) => void\n label: string\n blocked?: (value: T) => boolean\n}) {\n return (\n <div\n role=\"group\"\n aria-label={label}\n className=\"flex shrink-0 flex-col gap-0.5 border-b border-(--rule) p-2 sm:border-b-0 sm:border-e max-sm:flex-row max-sm:flex-wrap\"\n >\n {presets.map((preset) => (\n <button\n key={preset.label}\n type=\"button\"\n disabled={blocked?.(preset.value()) ?? false}\n onClick={() => {\n const next = preset.value()\n if (blocked?.(next)) return\n onPick(next)\n }}\n className=\"rounded-(--radius-sm) px-2.5 py-1.5 text-start text-[13px] text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none\"\n >\n {preset.label}\n </button>\n ))}\n </div>\n )\n}\n\nconst TRIGGER =\n 'flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\n/**\n * The visitor's own locale, not a fixed `dd/mm/yyyy`.\n *\n * `03/04` is March the fourth in one country and the third of April in the\n * next, and there is no format that is unambiguous to everyone. `Intl` already\n * knows which one this reader expects.\n */\nconst formatDate = (date: Date) =>\n new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)\n\nexport interface DatePickerProps {\n value?: Date\n defaultValue?: Date\n onValueChange?: (value: Date | undefined) => void\n /**\n * Names the control. Required — the trigger's text is a value, not a label.\n *\n * Announced together with the printed date rather than instead of it, so\n * `format` reaches a screen reader as well as the screen. Inside a `Field`\n * with a label, that label names the trigger and this one is not repeated.\n */\n label: string\n placeholder?: string\n disabled?: boolean\n /**\n * Days the reader may not choose.\n *\n * Reaches the calendar AND the shortcut rail: a preset landing on a blocked\n * day is drawn unavailable and refuses the click, rather than committing a\n * value the grid beside it would not accept.\n */\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How the chosen date is printed on the trigger. */\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid. Pass `true` for the built-in set, an array\n * for your own, or leave it off for none.\n */\n presets?: boolean | DatePreset<Date>[]\n className?: string\n /** The TRIGGER's id — the element a label points at. A `Field` sets it. */\n id?: string\n /** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */\n 'aria-describedby'?: string\n /** Announced on the trigger. A `Field` sets it from `error`. */\n 'aria-invalid'?: boolean | 'true' | 'false'\n}\n\n/**\n * A date, chosen from a calendar.\n *\n * A trigger and a `Calendar` in a `Popover` — not a new component so much as\n * the composition people otherwise assemble slightly differently on every\n * screen.\n *\n * It is deliberately NOT a text input with a calendar attached. A typed date\n * needs parsing, and parsing needs a format, and a format is a locale argument\n * nobody wins. When typing genuinely matters — a birth date, a long way back —\n * the calendar's month and year are dropdowns, which is the same journey\n * without the ambiguity.\n *\n * @example\n * <Field label=\"Publish on\"><DatePicker label=\"Publish on\" /></Field>\n */\nexport function DatePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a date',\n disabled = false,\n disabledDates,\n format = formatDate,\n presets,\n className,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n}: DatePickerProps) {\n const rail = presets === true ? DATE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<Date | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n const field = useFieldControl()\n const generated = useId()\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const choose = (next: Date | undefined) => {\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (next) setOpen(false)\n }\n\n const refuses = (date: Date) =>\n disabledDates !== undefined && dateMatchModifiers(date, disabledDates)\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n id={triggerId}\n // The label and the printed date, in that order. `aria-label` outranked\n // the trigger's own text, so `format` reached the screen and nothing\n // else — the chosen date was invisible to a screen reader.\n aria-labelledby={`${nameId} ${valueId}`}\n aria-describedby={describedBy}\n aria-invalid={ariaInvalid || undefined}\n disabled={disabled}\n className={cn(TRIGGER, current ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n <span id={valueId} className=\"truncate\">\n {current ? format(current) : placeholder}\n </span>\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && (\n <PresetRail\n presets={rail}\n onPick={choose}\n label={`${label} shortcuts`}\n blocked={refuses}\n />\n )}\n <Calendar\n mode=\"single\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport interface DateRangePickerProps {\n value?: DateRange\n defaultValue?: DateRange\n onValueChange?: (value: DateRange | undefined) => void\n /** Names the control. Announced together with the printed range, not instead of it. */\n label: string\n placeholder?: string\n disabled?: boolean\n /**\n * Days the reader may not choose.\n *\n * Reaches the shortcut rail as well as the grid, at the ENDS of each preset\n * range — a shortcut whose interior straddles a blocked day is still offered,\n * the way the grid still lets a reader drag a range across one.\n */\n disabledDates?: ComponentProps<typeof Calendar>['disabled']\n /** How many months are shown side by side. Falls back to one under `sm`. */\n months?: number\n format?: (date: Date) => string\n /**\n * Shortcuts shown beside the grid — Last 30 days and its neighbours. `true`\n * for the built-in set, an array for your own.\n *\n * On by default here and off on the single picker, because \"last 30 days\" is\n * most of what a range picker is ever asked for, while a single date is\n * usually a specific one.\n */\n presets?: boolean | DatePreset<DateRange>[]\n className?: string\n /** The TRIGGER's id — the element a label points at. A `Field` sets it. */\n id?: string\n /** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */\n 'aria-describedby'?: string\n /** Announced on the trigger. A `Field` sets it from `error`. */\n 'aria-invalid'?: boolean | 'true' | 'false'\n}\n\n/**\n * A span of dates — a stay, a reporting period, a filter.\n *\n * Two months side by side, because a range that crosses a month boundary is the\n * common case, and paging back and forth to see both ends is what makes a range\n * picker tiring. They stack under `sm`, where two would not fit — the calendar's\n * own `months` class already carries that, so there is nothing to override.\n *\n * The panel stays open until both ends are chosen: a range is not a value until\n * it has a second date, and closing on the first one would mean re-opening to\n * finish.\n *\n * @example\n * <DateRangePicker label=\"Reporting period\" />\n */\nexport function DateRangePicker({\n value,\n defaultValue,\n onValueChange,\n label,\n placeholder = 'Pick a range',\n disabled = false,\n disabledDates,\n months = 2,\n format = formatDate,\n presets = true,\n className,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n}: DateRangePickerProps) {\n const rail = presets === true ? RANGE_PRESETS : presets || undefined\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<DateRange | undefined>(defaultValue)\n const current = value ?? uncontrolled\n\n const field = useFieldControl()\n const generated = useId()\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const refuses = (range: DateRange) =>\n disabledDates !== undefined &&\n [range.from, range.to].some((date) => date != null && dateMatchModifiers(date, disabledDates))\n\n /**\n * How many days have been clicked since the panel opened.\n *\n * Counted, rather than inferred from the range being \"complete\", because the\n * first click returns `{ from: day, to: day }` — a complete range by any\n * reading of the value. Closing on that meant every attempt at a range\n * produced a one-day one and shut the panel, which is the opposite of what a\n * range picker is for.\n */\n const clicks = useRef(0)\n\n const choose = (next: DateRange | undefined) => {\n clicks.current += 1\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n if (clicks.current >= 2 && next?.from && next.to) setOpen(false)\n }\n\n const setPanel = (next: boolean) => {\n if (next) clicks.current = 0\n setOpen(next)\n }\n\n const complete = current?.from && current.to && current.from.getTime() !== current.to.getTime()\n const printed = current?.from\n ? complete\n ? `${format(current.from)} – ${format(current.to!)}`\n : `${format(current.from)} – …`\n : placeholder\n\n return (\n <Popover open={open} onOpenChange={setPanel}>\n <PopoverTrigger\n id={triggerId}\n // The label and the printed range together — see DatePicker above.\n aria-labelledby={`${nameId} ${valueId}`}\n aria-describedby={describedBy}\n aria-invalid={ariaInvalid || undefined}\n disabled={disabled}\n className={cn(TRIGGER, current?.from ? 'text-(--ink)' : 'text-(--ink-3-aa)', className)}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n <span id={valueId} className=\"truncate\">\n {printed}\n </span>\n <CalendarDays size={14} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n </PopoverTrigger>\n <PopoverContent label={label} align=\"start\" className=\"w-auto overflow-hidden p-0\">\n <div className=\"flex flex-col sm:flex-row\">\n {rail && (\n <PresetRail\n presets={rail}\n onPick={(next) => {\n // A preset is a complete range in one gesture, so the panel\n // closes — unlike a first click on the grid, which is half an\n // answer.\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n setOpen(false)\n }}\n label={`${label} shortcuts`}\n blocked={refuses}\n />\n )}\n <Calendar\n mode=\"range\"\n selected={current}\n onSelect={choose}\n disabled={disabledDates}\n numberOfMonths={months}\n autoFocus\n />\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default DatePicker\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface DescriptionListItem {
|
|
5
|
+
/**
|
|
6
|
+
* A stable key. Optional: the index is used when it is absent, which is
|
|
7
|
+
* correct for the fixed field list a record page renders and wrong for a list
|
|
8
|
+
* whose rows are added, removed or reordered — pass it there.
|
|
9
|
+
*/
|
|
10
|
+
id?: string;
|
|
11
|
+
/** The label. Rendered as a `<dt>`. */
|
|
12
|
+
term: ReactNode;
|
|
13
|
+
/** The value. Rendered as a `<dd>`, so it can be a `Badge` or a link. */
|
|
14
|
+
description: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Where the value sits relative to its label.
|
|
18
|
+
*
|
|
19
|
+
* `row` is the record-page shape: label in a fixed column at the inline start,
|
|
20
|
+
* value beside it, so a reader scans the labels down one edge. It collapses to
|
|
21
|
+
* one column under the `sm` breakpoint, because a 12rem label column on a phone
|
|
22
|
+
* leaves the value about eight characters wide.
|
|
23
|
+
*
|
|
24
|
+
* `stacked` puts the value under its label at every width — the right answer
|
|
25
|
+
* inside a card or a sidebar that is narrow by design rather than by viewport.
|
|
26
|
+
*/
|
|
27
|
+
type DescriptionListLayout = 'row' | 'stacked';
|
|
28
|
+
interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {
|
|
29
|
+
/** The pairs, in the order they should read. An empty array renders nothing. */
|
|
30
|
+
items: DescriptionListItem[];
|
|
31
|
+
/** How the value sits relative to its label. See {@link DescriptionListLayout}. */
|
|
32
|
+
layout?: DescriptionListLayout;
|
|
33
|
+
/**
|
|
34
|
+
* Draws a hairline under every pair but the last. Off for a list inside a
|
|
35
|
+
* card that already has an edge, on for a list that is the page's structure.
|
|
36
|
+
*/
|
|
37
|
+
divided?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A record's fields: label, value, label, value.
|
|
41
|
+
*
|
|
42
|
+
* The single most repeated shape in any detail page, and the one most often
|
|
43
|
+
* hand-built out of a `<div>` grid — which loses the only thing the markup was
|
|
44
|
+
* carrying. A `<dl>` tells a screen reader that "Owner" names the thing beside
|
|
45
|
+
* it; a grid of divs tells it there are two columns of unrelated text, and the
|
|
46
|
+
* reader has to infer the pairing from reading order alone.
|
|
47
|
+
*
|
|
48
|
+
* Reach for `Table` instead when there are several records. This is one record
|
|
49
|
+
* seen from the front; a table is many records seen from above.
|
|
50
|
+
*
|
|
51
|
+
* Each pair is wrapped in a `<div>` — which the HTML specification allows
|
|
52
|
+
* inside a `<dl>` precisely so a pair can be laid out as a unit — so the
|
|
53
|
+
* hairline runs the full width of the row rather than stopping in the column
|
|
54
|
+
* gap.
|
|
55
|
+
*
|
|
56
|
+
* An empty `items` renders `null` rather than an empty bordered box. A record
|
|
57
|
+
* with no fields is a state the page above should be handling with an
|
|
58
|
+
* `EmptyState`, and a hairline around nothing looks like a component that
|
|
59
|
+
* failed to load.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* <DescriptionList
|
|
63
|
+
* items={[
|
|
64
|
+
* { term: 'Owner', description: 'Henry Chen' },
|
|
65
|
+
* { term: 'Status', description: <Badge tone="success">Deployed</Badge> },
|
|
66
|
+
* ]}
|
|
67
|
+
* />
|
|
68
|
+
* @example
|
|
69
|
+
* <DescriptionList layout="stacked" divided={false} items={fields} />
|
|
70
|
+
*/
|
|
71
|
+
declare function DescriptionList({ items, layout, divided, className, ...rest }: DescriptionListProps): react.JSX.Element | null;
|
|
72
|
+
|
|
73
|
+
export { DescriptionList, type DescriptionListItem, type DescriptionListLayout, type DescriptionListProps, DescriptionList as default };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
const LAYOUT = {
|
|
4
|
+
row: "grid gap-x-6 gap-y-1 sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)]",
|
|
5
|
+
stacked: "grid gap-y-1"
|
|
6
|
+
};
|
|
7
|
+
function DescriptionList({
|
|
8
|
+
items,
|
|
9
|
+
layout = "row",
|
|
10
|
+
divided = true,
|
|
11
|
+
className,
|
|
12
|
+
...rest
|
|
13
|
+
}) {
|
|
14
|
+
if (items.length === 0) return null;
|
|
15
|
+
return /* @__PURE__ */ jsx("dl", { className: cn("m-0", !divided && "flex flex-col gap-3", className), ...rest, children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
className: cn(
|
|
19
|
+
LAYOUT[layout],
|
|
20
|
+
divided && "border-b border-(--rule) py-3 first:pt-0 last:border-b-0 last:pb-0"
|
|
21
|
+
),
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ jsx("dt", { className: "text-sm text-(--ink-3-aa)", children: item.term }),
|
|
24
|
+
/* @__PURE__ */ jsx("dd", { className: "m-0 text-sm text-(--ink-2)", children: item.description })
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
item.id ?? index
|
|
28
|
+
)) });
|
|
29
|
+
}
|
|
30
|
+
var DescriptionList_default = DescriptionList;
|
|
31
|
+
export {
|
|
32
|
+
DescriptionList,
|
|
33
|
+
DescriptionList_default as default
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=DescriptionList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/DescriptionList/DescriptionList.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface DescriptionListItem {\n /**\n * A stable key. Optional: the index is used when it is absent, which is\n * correct for the fixed field list a record page renders and wrong for a list\n * whose rows are added, removed or reordered — pass it there.\n */\n id?: string\n /** The label. Rendered as a `<dt>`. */\n term: ReactNode\n /** The value. Rendered as a `<dd>`, so it can be a `Badge` or a link. */\n description: ReactNode\n}\n\n/**\n * Where the value sits relative to its label.\n *\n * `row` is the record-page shape: label in a fixed column at the inline start,\n * value beside it, so a reader scans the labels down one edge. It collapses to\n * one column under the `sm` breakpoint, because a 12rem label column on a phone\n * leaves the value about eight characters wide.\n *\n * `stacked` puts the value under its label at every width — the right answer\n * inside a card or a sidebar that is narrow by design rather than by viewport.\n */\nexport type DescriptionListLayout = 'row' | 'stacked'\n\nexport interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {\n /** The pairs, in the order they should read. An empty array renders nothing. */\n items: DescriptionListItem[]\n /** How the value sits relative to its label. See {@link DescriptionListLayout}. */\n layout?: DescriptionListLayout\n /**\n * Draws a hairline under every pair but the last. Off for a list inside a\n * card that already has an edge, on for a list that is the page's structure.\n */\n divided?: boolean\n}\n\nconst LAYOUT: Record<DescriptionListLayout, string> = {\n row: 'grid gap-x-6 gap-y-1 sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)]',\n stacked: 'grid gap-y-1',\n}\n\n/**\n * A record's fields: label, value, label, value.\n *\n * The single most repeated shape in any detail page, and the one most often\n * hand-built out of a `<div>` grid — which loses the only thing the markup was\n * carrying. A `<dl>` tells a screen reader that \"Owner\" names the thing beside\n * it; a grid of divs tells it there are two columns of unrelated text, and the\n * reader has to infer the pairing from reading order alone.\n *\n * Reach for `Table` instead when there are several records. This is one record\n * seen from the front; a table is many records seen from above.\n *\n * Each pair is wrapped in a `<div>` — which the HTML specification allows\n * inside a `<dl>` precisely so a pair can be laid out as a unit — so the\n * hairline runs the full width of the row rather than stopping in the column\n * gap.\n *\n * An empty `items` renders `null` rather than an empty bordered box. A record\n * with no fields is a state the page above should be handling with an\n * `EmptyState`, and a hairline around nothing looks like a component that\n * failed to load.\n *\n * @example\n * <DescriptionList\n * items={[\n * { term: 'Owner', description: 'Henry Chen' },\n * { term: 'Status', description: <Badge tone=\"success\">Deployed</Badge> },\n * ]}\n * />\n * @example\n * <DescriptionList layout=\"stacked\" divided={false} items={fields} />\n */\nexport function DescriptionList({\n items,\n layout = 'row',\n divided = true,\n className,\n ...rest\n}: DescriptionListProps) {\n if (items.length === 0) return null\n\n return (\n <dl className={cn('m-0', !divided && 'flex flex-col gap-3', className)} {...rest}>\n {items.map((item, index) => (\n <div\n key={item.id ?? index}\n className={cn(\n LAYOUT[layout],\n divided && 'border-b border-(--rule) py-3 first:pt-0 last:border-b-0 last:pb-0',\n )}\n >\n <dt className=\"text-sm text-(--ink-3-aa)\">{item.term}</dt>\n <dd className=\"m-0 text-sm text-(--ink-2)\">{item.description}</dd>\n </div>\n ))}\n </dl>\n )\n}\n\nexport default DescriptionList\n"],"mappings":"AA0FQ,SAOE,KAPF;AAzFR,SAAS,UAAU;AAwCnB,MAAM,SAAgD;AAAA,EACpD,KAAK;AAAA,EACL,SAAS;AACX;AAkCO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,EACT,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACE,oBAAC,QAAG,WAAW,GAAG,OAAO,CAAC,WAAW,uBAAuB,SAAS,GAAI,GAAG,MACzE,gBAAM,IAAI,CAAC,MAAM,UAChB;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW;AAAA,QACT,OAAO,MAAM;AAAA,QACb,WAAW;AAAA,MACb;AAAA,MAEA;AAAA,4BAAC,QAAG,WAAU,6BAA6B,eAAK,MAAK;AAAA,QACrD,oBAAC,QAAG,WAAU,8BAA8B,eAAK,aAAY;AAAA;AAAA;AAAA,IAPxD,KAAK,MAAM;AAAA,EAQlB,CACD,GACH;AAEJ;AAEA,IAAO,0BAAQ;","names":[]}
|
|
@@ -24,7 +24,12 @@ interface DiagramSpec {
|
|
|
24
24
|
/** How the top-level nodes are laid out. Defaults to a row. */
|
|
25
25
|
direction?: 'row' | 'column';
|
|
26
26
|
nodes: DiagramNode[];
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Arrows between adjacent siblings, by id and in the order the rank runs.
|
|
29
|
+
* Each edge is spent the first time a pair matches it, at whatever depth
|
|
30
|
+
* that pair sits — and an edge that matches nothing says so in development
|
|
31
|
+
* rather than drawing nothing and reporting nothing.
|
|
32
|
+
*/
|
|
28
33
|
edges?: DiagramEdge[];
|
|
29
34
|
/** The figure's caption. */
|
|
30
35
|
caption?: string;
|
|
@@ -68,8 +73,8 @@ interface DiagramProps {
|
|
|
68
73
|
* edges: [{ from: 'edge', to: 'app', label: 'HTTPS' }],
|
|
69
74
|
* nodes: [
|
|
70
75
|
* { id: 'edge', label: 'Edge', note: 'CDN' },
|
|
71
|
-
* { id: 'app', label: 'Application',
|
|
72
|
-
* { label: 'Router' },
|
|
76
|
+
* { id: 'app', label: 'Application', children: [
|
|
77
|
+
* { label: 'Router', accent: true },
|
|
73
78
|
* { label: 'Handlers' },
|
|
74
79
|
* ] },
|
|
75
80
|
* ],
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../lib/cn.js";
|
|
3
|
+
import { DEV, warn } from "../../lib/warn.js";
|
|
3
4
|
function Diagram({ spec, className }) {
|
|
4
5
|
const { nodes, edges = [], direction = "row", caption, label } = spec;
|
|
6
|
+
const { leads, drawn } = resolveLeads(nodes, edges);
|
|
7
|
+
if (DEV) warnSpec(nodes, edges, drawn);
|
|
5
8
|
return /* @__PURE__ */ jsxs("figure", { className: cn("m-0", className), role: "group", "aria-label": label ?? caption ?? void 0, children: [
|
|
6
|
-
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim", children: /* @__PURE__ */ jsx(NodeList, { nodes,
|
|
9
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim", children: /* @__PURE__ */ jsx(NodeList, { nodes, leads, direction, depth: 0 }) }),
|
|
7
10
|
caption && // A <div>, not a <figcaption>. A long-form stylesheet dropped around
|
|
8
11
|
// this will style `figcaption` and `p` unlayered, and an unlayered rule
|
|
9
12
|
// beats a utility whatever the specificity — inside an article a real
|
|
@@ -13,9 +16,30 @@ function Diagram({ spec, className }) {
|
|
|
13
16
|
/* @__PURE__ */ jsx("div", { className: "mt-3 mono-meta leading-[1.6] text-(--ink-3-aa)", children: caption })
|
|
14
17
|
] });
|
|
15
18
|
}
|
|
19
|
+
function resolveLeads(nodes, edges) {
|
|
20
|
+
const leads = /* @__PURE__ */ new Map();
|
|
21
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
22
|
+
const ranks = [nodes];
|
|
23
|
+
while (ranks.length > 0) {
|
|
24
|
+
const rank = ranks.shift();
|
|
25
|
+
for (let index = 1; index < rank.length; index += 1) {
|
|
26
|
+
const from = rank[index - 1];
|
|
27
|
+
const to = rank[index];
|
|
28
|
+
if (!from.id || !to.id) continue;
|
|
29
|
+
const edge = edges.find(
|
|
30
|
+
(candidate) => !drawn.has(candidate) && candidate.from === from.id && candidate.to === to.id
|
|
31
|
+
);
|
|
32
|
+
if (!edge) continue;
|
|
33
|
+
drawn.add(edge);
|
|
34
|
+
leads.set(to, edge);
|
|
35
|
+
}
|
|
36
|
+
for (const node of rank) if (node.children?.length) ranks.push(node.children);
|
|
37
|
+
}
|
|
38
|
+
return { leads, drawn };
|
|
39
|
+
}
|
|
16
40
|
function NodeList({
|
|
17
41
|
nodes,
|
|
18
|
-
|
|
42
|
+
leads,
|
|
19
43
|
direction,
|
|
20
44
|
depth
|
|
21
45
|
}) {
|
|
@@ -24,21 +48,17 @@ function NodeList({
|
|
|
24
48
|
NodeFrame,
|
|
25
49
|
{
|
|
26
50
|
node,
|
|
27
|
-
|
|
51
|
+
leads,
|
|
28
52
|
depth,
|
|
29
|
-
lead:
|
|
53
|
+
lead: leads.get(node),
|
|
30
54
|
row
|
|
31
55
|
},
|
|
32
56
|
node.id ?? `${node.label}-${index}`
|
|
33
57
|
)) });
|
|
34
58
|
}
|
|
35
|
-
function findEdge(edges, from, to) {
|
|
36
|
-
if (!from.id || !to.id) return void 0;
|
|
37
|
-
return edges.find((edge) => edge.from === from.id && edge.to === to.id);
|
|
38
|
-
}
|
|
39
59
|
function NodeFrame({
|
|
40
60
|
node,
|
|
41
|
-
|
|
61
|
+
leads,
|
|
42
62
|
depth,
|
|
43
63
|
lead,
|
|
44
64
|
row
|
|
@@ -64,7 +84,7 @@ function NodeFrame({
|
|
|
64
84
|
NodeList,
|
|
65
85
|
{
|
|
66
86
|
nodes: children,
|
|
67
|
-
|
|
87
|
+
leads,
|
|
68
88
|
direction: node.direction ?? "row",
|
|
69
89
|
depth: depth + 1
|
|
70
90
|
}
|
|
@@ -131,6 +151,58 @@ function EdgeMark({ label, row }) {
|
|
|
131
151
|
}
|
|
132
152
|
);
|
|
133
153
|
}
|
|
154
|
+
function warnSpec(nodes, edges, drawn) {
|
|
155
|
+
const ids = /* @__PURE__ */ new Set();
|
|
156
|
+
warnNodes(nodes, ids);
|
|
157
|
+
for (const edge of edges) {
|
|
158
|
+
if (drawn.has(edge)) continue;
|
|
159
|
+
const pair = `${edge.from} \u2192 ${edge.to}`;
|
|
160
|
+
const unknown = !ids.has(edge.from) || !ids.has(edge.to);
|
|
161
|
+
warn({
|
|
162
|
+
code: unknown ? "DIAGRAM_EDGE_UNKNOWN_NODE" : "DIAGRAM_EDGE_NOT_ADJACENT",
|
|
163
|
+
problem: unknown ? `The edge ${pair} names an id no node in this spec carries, so no arrow is drawn and nothing else changes.` : `The edge ${pair} names two nodes that are never consecutive siblings, so no arrow is drawn anywhere.`,
|
|
164
|
+
field: `spec.edges: ${pair}`,
|
|
165
|
+
fix: unknown ? "Give the node the id the edge names, or correct the edge to an id the spec already has." : "Write the edge from a node to the node immediately after it in the same rank, in that order. Nesting is containment here; an edge is a step between siblings.",
|
|
166
|
+
component: "Diagram"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function warnNodes(nodes, ids) {
|
|
171
|
+
for (const node of nodes) {
|
|
172
|
+
if (node.id) {
|
|
173
|
+
if (ids.has(node.id)) {
|
|
174
|
+
warn({
|
|
175
|
+
code: "DIAGRAM_DUPLICATE_ID",
|
|
176
|
+
problem: `Two nodes carry the id "${node.id}", so an edge naming it lands on whichever comes first and silently means the other.`,
|
|
177
|
+
field: `spec.nodes: id "${node.id}"`,
|
|
178
|
+
fix: "Make every id unique across the whole spec, or drop it from the node no edge names.",
|
|
179
|
+
component: "Diagram"
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
ids.add(node.id);
|
|
183
|
+
}
|
|
184
|
+
const isGroup = (node.children?.length ?? 0) > 0;
|
|
185
|
+
if (isGroup && node.accent) {
|
|
186
|
+
warn({
|
|
187
|
+
code: "DIAGRAM_ACCENT_ON_CONTAINER",
|
|
188
|
+
problem: `"${node.label}" has children, so it is drawn as a band \u2014 and a band has no fill for accent to take. It type-checks and paints nothing.`,
|
|
189
|
+
field: `spec.nodes: accent on "${node.label}"`,
|
|
190
|
+
fix: "Move accent to the leaf the diagram is actually about, or drop the children so this node becomes that leaf.",
|
|
191
|
+
component: "Diagram"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (!isGroup && node.direction) {
|
|
195
|
+
warn({
|
|
196
|
+
code: "DIAGRAM_DIRECTION_ON_LEAF",
|
|
197
|
+
problem: `"${node.label}" has no children, so there is nothing here to lay out \u2014 direction is read only from a node that has some.`,
|
|
198
|
+
field: `spec.nodes: direction on "${node.label}"`,
|
|
199
|
+
fix: "Set direction on the parent whose axis you meant, or on spec.direction for the top rank.",
|
|
200
|
+
component: "Diagram"
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (node.children) warnNodes(node.children, ids);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
134
206
|
var Diagram_default = Diagram;
|
|
135
207
|
export {
|
|
136
208
|
Diagram,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Diagram/Diagram.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface DiagramNode {\n /** Only needed when an edge names this node. */\n id?: string\n /** The node's name, set in the interface face. */\n label: string\n /** What it is — one short line, a step back. */\n note?: string\n /** The one node the diagram is about. At most one or two per diagram. */\n accent?: boolean\n /** How this node's children are laid out. Defaults to a row. */\n direction?: 'row' | 'column'\n children?: DiagramNode[]\n /** A line printed under the frame, outside it — what the box means. */\n footnote?: string\n}\n\nexport interface DiagramEdge {\n from: string\n to: string\n label?: string\n}\n\nexport interface DiagramSpec {\n /** How the top-level nodes are laid out. Defaults to a row. */\n direction?: 'row' | 'column'\n nodes: DiagramNode[]\n /** Arrows between adjacent top-level nodes, by id. */\n edges?: DiagramEdge[]\n /** The figure's caption. */\n caption?: string\n /** Names the figure for assistive tech when the caption does not. */\n label?: string\n}\n\nexport interface DiagramProps {\n spec: DiagramSpec\n className?: string\n}\n\n/**\n * A flow or architecture diagram, drawn out of the system's own parts.\n *\n * The alternative it replaces is a fenced block of box-drawing characters —\n * `┌──────┬──────┐` — which is a picture rendered in a font chosen for code. It\n * inherits the code block's frame and scrollbar, so a diagram reads as terminal\n * output; it cannot wrap, so on a phone it either overflows or is scaled to\n * nothing; the box edges are text, so a screen reader reads the rules out loud;\n * and none of it responds to the theme.\n *\n * The other alternative is a diagramming library, which is several hundred\n * kilobytes of layout engine, renders after hydration, and draws in its own\n * palette. This is neither: hairline frames on the radius scale, mono labels,\n * the muted step for anything supporting — so a diagram belongs to the page it\n * sits on. It server-renders, because it is markup.\n *\n * NESTING IS CONTAINMENT, which is what most architecture diagrams actually\n * describe: this is inside that, and these two sit beside each other. Edges are\n * for the sequence between siblings, not for arbitrary wiring — a diagram that\n * needs arbitrary wiring is a diagram that wants a drawing, and this will not\n * pretend otherwise.\n *\n * It takes a spec rather than markup, so a fenced ```diagram block in an\n * article and a hand-written figure on a page are one renderer and one look —\n * and a wrong diagram is corrected by editing data.\n *\n * @example\n * <Diagram\n * spec={{\n * caption: 'One request, end to end.',\n * edges: [{ from: 'edge', to: 'app', label: 'HTTPS' }],\n * nodes: [\n * { id: 'edge', label: 'Edge', note: 'CDN' },\n * { id: 'app', label: 'Application', accent: true, children: [\n * { label: 'Router' },\n * { label: 'Handlers' },\n * ] },\n * ],\n * }}\n * />\n */\nexport function Diagram({ spec, className }: DiagramProps) {\n const { nodes, edges = [], direction = 'row', caption, label } = spec\n\n return (\n <figure className={cn('m-0', className)} role=\"group\" aria-label={label ?? caption ?? undefined}>\n <div className=\"overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim\">\n <NodeList nodes={nodes} edges={edges} direction={direction} depth={0} />\n </div>\n {caption && (\n // A <div>, not a <figcaption>. A long-form stylesheet dropped around\n // this will style `figcaption` and `p` unlayered, and an unlayered rule\n // beats a utility whatever the specificity — inside an article a real\n // figcaption came out uppercased and every label in the diagram\n // inherited a paragraph margin. Nothing here uses a tag prose reaches\n // for.\n <div className=\"mt-3 mono-meta leading-[1.6] text-(--ink-3-aa)\">{caption}</div>\n )}\n </figure>\n )\n}\n\n/** One rank of the diagram: the nodes, and any arrows between them. */\nfunction NodeList({\n nodes,\n edges,\n direction,\n depth,\n}: {\n nodes: DiagramNode[]\n edges: DiagramEdge[]\n direction: 'row' | 'column'\n depth: number\n}) {\n const row = direction === 'row'\n\n return (\n <div className={cn('flex min-w-0 gap-3', row ? 'flex-col sm:flex-row' : 'flex-col')}>\n {nodes.map((node, index) => (\n <NodeFrame\n key={node.id ?? `${node.label}-${index}`}\n node={node}\n edges={edges}\n depth={depth}\n lead={index > 0 ? findEdge(edges, nodes[index - 1]!, node) : undefined}\n row={row}\n />\n ))}\n </div>\n )\n}\n\n/** The arrow that sits between two adjacent nodes, when an edge names them. */\nfunction findEdge(edges: DiagramEdge[], from: DiagramNode, to: DiagramNode): DiagramEdge | undefined {\n if (!from.id || !to.id) return undefined\n return edges.find((edge) => edge.from === from.id && edge.to === to.id)\n}\n\n/**\n * A node is one of two things, and they are drawn differently on purpose.\n *\n * A LEAF is a plate: a bordered card on paper, or ink-filled when it is what\n * the diagram is about. A CONTAINER is a band: a labelled hairline with its\n * children underneath, and NO frame of its own.\n *\n * That distinction is the whole design. Drawing a container as another box puts\n * three borders around anything two levels deep — the figure's frame, the\n * group's, and the plate's — and the result reads as packaging rather than as\n * structure. A rule with a name on it says \"these belong together\" using the\n * device every section head already uses, and leaves the borders to the things\n * that are actually objects.\n */\nfunction NodeFrame({\n node,\n edges,\n depth,\n lead,\n row,\n}: {\n node: DiagramNode\n edges: DiagramEdge[]\n depth: number\n lead?: DiagramEdge\n row: boolean\n}) {\n const children = node.children ?? []\n const isGroup = children.length > 0\n\n return (\n <>\n {lead && <EdgeMark label={lead.label} row={row} />}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {isGroup ? (\n <div\n className={cn(\n 'min-w-0 flex-1 border-t pt-3',\n depth === 0 ? 'border-(--ink)' : 'border-(--rule-2)',\n )}\n >\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-0.5\">\n <span className=\"font-sans text-[13.5px] leading-[1.35] text-(--ink)\">\n {node.label}\n </span>\n {node.note && <span className=\"mono-meta text-(--ink-3-aa)\">{node.note}</span>}\n </div>\n <div className=\"mt-3\">\n <NodeList\n nodes={children}\n edges={edges}\n direction={node.direction ?? 'row'}\n depth={depth + 1}\n />\n </div>\n </div>\n ) : (\n <div\n className={cn(\n 'flex min-w-0 flex-1 flex-col justify-center rounded-(--radius) px-3.5 py-3',\n node.accent ? 'bg-(--accent)' : 'border border-(--rule-2) bg-(--paper)',\n )}\n >\n {/* An identifier can be one long word — `TenantMainMiddleware` —\n with no break opportunity, so it runs into the plate's edge\n unless it is allowed to break inside itself. */}\n <span\n className={cn(\n 'block break-words font-sans text-[13.5px] leading-[1.35]',\n node.accent ? 'text-(--accent-foreground)' : 'text-(--ink)',\n )}\n >\n {node.label}\n </span>\n {node.note && (\n <span\n className={cn(\n 'mt-1 block mono-meta',\n node.accent ? 'text-(--accent-foreground) opacity-70' : 'text-(--ink-3-aa)',\n )}\n >\n {node.note}\n </span>\n )}\n </div>\n )}\n\n {node.footnote && (\n <span className=\"mt-2 block mono-meta text-(--ink-3-aa)\">{node.footnote}</span>\n )}\n </div>\n </>\n )\n}\n\n/**\n * The connector. An arrow and, when the edge is named, the name under it —\n * hidden from assistive tech, which reads the nodes in document order anyway\n * and has no use for a glyph pointing at the next one.\n */\nfunction EdgeMark({ label, row }: { label?: string; row: boolean }): ReactNode {\n return (\n <div\n aria-hidden=\"true\"\n className={cn(\n 'flex flex-none items-center justify-center gap-1.5 text-(--ink-3-aa)',\n row ? 'sm:flex-col' : 'flex-col',\n )}\n >\n {/* One glyph, turned. A row stacks into a column below `sm`, so the arrow\n has to point the way the layout actually runs at that width. In RTL the\n inline axis reverses, so the flat arrow is mirrored with it. */}\n <span\n className={cn(\n 'font-mono text-[13px] leading-none',\n row ? 'rotate-90 sm:rotate-0 sm:rtl:-scale-x-100' : 'rotate-90',\n )}\n >\n →\n </span>\n {label && <span className=\"mono-meta whitespace-nowrap\">{label}</span>}\n </div>\n )\n}\n\nexport default Diagram\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Diagram/Diagram.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { DEV, warn } from '../../lib/warn'\n\nexport interface DiagramNode {\n /** Only needed when an edge names this node. */\n id?: string\n /** The node's name, set in the interface face. */\n label: string\n /** What it is — one short line, a step back. */\n note?: string\n /** The one node the diagram is about. At most one or two per diagram. */\n accent?: boolean\n /** How this node's children are laid out. Defaults to a row. */\n direction?: 'row' | 'column'\n children?: DiagramNode[]\n /** A line printed under the frame, outside it — what the box means. */\n footnote?: string\n}\n\nexport interface DiagramEdge {\n from: string\n to: string\n label?: string\n}\n\nexport interface DiagramSpec {\n /** How the top-level nodes are laid out. Defaults to a row. */\n direction?: 'row' | 'column'\n nodes: DiagramNode[]\n /**\n * Arrows between adjacent siblings, by id and in the order the rank runs.\n * Each edge is spent the first time a pair matches it, at whatever depth\n * that pair sits — and an edge that matches nothing says so in development\n * rather than drawing nothing and reporting nothing.\n */\n edges?: DiagramEdge[]\n /** The figure's caption. */\n caption?: string\n /** Names the figure for assistive tech when the caption does not. */\n label?: string\n}\n\nexport interface DiagramProps {\n spec: DiagramSpec\n className?: string\n}\n\n/**\n * A flow or architecture diagram, drawn out of the system's own parts.\n *\n * The alternative it replaces is a fenced block of box-drawing characters —\n * `┌──────┬──────┐` — which is a picture rendered in a font chosen for code. It\n * inherits the code block's frame and scrollbar, so a diagram reads as terminal\n * output; it cannot wrap, so on a phone it either overflows or is scaled to\n * nothing; the box edges are text, so a screen reader reads the rules out loud;\n * and none of it responds to the theme.\n *\n * The other alternative is a diagramming library, which is several hundred\n * kilobytes of layout engine, renders after hydration, and draws in its own\n * palette. This is neither: hairline frames on the radius scale, mono labels,\n * the muted step for anything supporting — so a diagram belongs to the page it\n * sits on. It server-renders, because it is markup.\n *\n * NESTING IS CONTAINMENT, which is what most architecture diagrams actually\n * describe: this is inside that, and these two sit beside each other. Edges are\n * for the sequence between siblings, not for arbitrary wiring — a diagram that\n * needs arbitrary wiring is a diagram that wants a drawing, and this will not\n * pretend otherwise.\n *\n * It takes a spec rather than markup, so a fenced ```diagram block in an\n * article and a hand-written figure on a page are one renderer and one look —\n * and a wrong diagram is corrected by editing data.\n *\n * @example\n * <Diagram\n * spec={{\n * caption: 'One request, end to end.',\n * edges: [{ from: 'edge', to: 'app', label: 'HTTPS' }],\n * nodes: [\n * { id: 'edge', label: 'Edge', note: 'CDN' },\n * { id: 'app', label: 'Application', children: [\n * { label: 'Router', accent: true },\n * { label: 'Handlers' },\n * ] },\n * ],\n * }}\n * />\n */\nexport function Diagram({ spec, className }: DiagramProps) {\n const { nodes, edges = [], direction = 'row', caption, label } = spec\n const { leads, drawn } = resolveLeads(nodes, edges)\n\n if (DEV) warnSpec(nodes, edges, drawn)\n\n return (\n <figure className={cn('m-0', className)} role=\"group\" aria-label={label ?? caption ?? undefined}>\n <div className=\"overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim\">\n <NodeList nodes={nodes} leads={leads} direction={direction} depth={0} />\n </div>\n {caption && (\n // A <div>, not a <figcaption>. A long-form stylesheet dropped around\n // this will style `figcaption` and `p` unlayered, and an unlayered rule\n // beats a utility whatever the specificity — inside an article a real\n // figcaption came out uppercased and every label in the diagram\n // inherited a paragraph margin. Nothing here uses a tag prose reaches\n // for.\n <div className=\"mt-3 mono-meta leading-[1.6] text-(--ink-3-aa)\">{caption}</div>\n )}\n </figure>\n )\n}\n\n/** The arrow that leads INTO a node, for every node that earned one. */\ntype Leads = Map<DiagramNode, DiagramEdge>\n\n/**\n * Every arrow the spec earns, resolved once for the whole figure.\n *\n * Keyed by the node the arrow leads into, by OBJECT rather than by id: two\n * nodes sharing an id are a defect in the spec, not a licence to draw the same\n * arrow in two places. The same `edges` array used to be handed to every rank,\n * so a pair of ids reused two levels down drew the arrow again down there — an\n * arrow the author asked for once and the figure asserted twice.\n *\n * Ranks are walked breadth-first from the top and an edge is spent at the first\n * pair that matches it, which makes \"one edge, one arrow\" a property of the\n * renderer rather than a rule the author has to keep.\n */\nfunction resolveLeads(nodes: DiagramNode[], edges: DiagramEdge[]): { leads: Leads; drawn: Set<DiagramEdge> } {\n const leads: Leads = new Map()\n const drawn = new Set<DiagramEdge>()\n const ranks: DiagramNode[][] = [nodes]\n\n while (ranks.length > 0) {\n const rank = ranks.shift()!\n for (let index = 1; index < rank.length; index += 1) {\n const from = rank[index - 1]!\n const to = rank[index]!\n if (!from.id || !to.id) continue\n const edge = edges.find(\n (candidate) =>\n !drawn.has(candidate) && candidate.from === from.id && candidate.to === to.id,\n )\n if (!edge) continue\n drawn.add(edge)\n leads.set(to, edge)\n }\n for (const node of rank) if (node.children?.length) ranks.push(node.children)\n }\n\n return { leads, drawn }\n}\n\n/** One rank of the diagram: the nodes, and any arrows between them. */\nfunction NodeList({\n nodes,\n leads,\n direction,\n depth,\n}: {\n nodes: DiagramNode[]\n leads: Leads\n direction: 'row' | 'column'\n depth: number\n}) {\n const row = direction === 'row'\n\n return (\n <div className={cn('flex min-w-0 gap-3', row ? 'flex-col sm:flex-row' : 'flex-col')}>\n {nodes.map((node, index) => (\n <NodeFrame\n key={node.id ?? `${node.label}-${index}`}\n node={node}\n leads={leads}\n depth={depth}\n lead={leads.get(node)}\n row={row}\n />\n ))}\n </div>\n )\n}\n\n/**\n * A node is one of two things, and they are drawn differently on purpose.\n *\n * A LEAF is a plate: a bordered card on paper, or ink-filled when it is what\n * the diagram is about. A CONTAINER is a band: a labelled hairline with its\n * children underneath, and NO frame of its own.\n *\n * That distinction is the whole design. Drawing a container as another box puts\n * three borders around anything two levels deep — the figure's frame, the\n * group's, and the plate's — and the result reads as packaging rather than as\n * structure. A rule with a name on it says \"these belong together\" using the\n * device every section head already uses, and leaves the borders to the things\n * that are actually objects.\n */\nfunction NodeFrame({\n node,\n leads,\n depth,\n lead,\n row,\n}: {\n node: DiagramNode\n leads: Leads\n depth: number\n lead?: DiagramEdge\n row: boolean\n}) {\n const children = node.children ?? []\n const isGroup = children.length > 0\n\n return (\n <>\n {lead && <EdgeMark label={lead.label} row={row} />}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {isGroup ? (\n <div\n className={cn(\n 'min-w-0 flex-1 border-t pt-3',\n depth === 0 ? 'border-(--ink)' : 'border-(--rule-2)',\n )}\n >\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-0.5\">\n <span className=\"font-sans text-[13.5px] leading-[1.35] text-(--ink)\">\n {node.label}\n </span>\n {node.note && <span className=\"mono-meta text-(--ink-3-aa)\">{node.note}</span>}\n </div>\n <div className=\"mt-3\">\n <NodeList\n nodes={children}\n leads={leads}\n direction={node.direction ?? 'row'}\n depth={depth + 1}\n />\n </div>\n </div>\n ) : (\n <div\n className={cn(\n 'flex min-w-0 flex-1 flex-col justify-center rounded-(--radius) px-3.5 py-3',\n node.accent ? 'bg-(--accent)' : 'border border-(--rule-2) bg-(--paper)',\n )}\n >\n {/* An identifier can be one long word — `TenantMainMiddleware` —\n with no break opportunity, so it runs into the plate's edge\n unless it is allowed to break inside itself. */}\n <span\n className={cn(\n 'block break-words font-sans text-[13.5px] leading-[1.35]',\n node.accent ? 'text-(--accent-foreground)' : 'text-(--ink)',\n )}\n >\n {node.label}\n </span>\n {node.note && (\n <span\n className={cn(\n 'mt-1 block mono-meta',\n node.accent ? 'text-(--accent-foreground) opacity-70' : 'text-(--ink-3-aa)',\n )}\n >\n {node.note}\n </span>\n )}\n </div>\n )}\n\n {node.footnote && (\n <span className=\"mt-2 block mono-meta text-(--ink-3-aa)\">{node.footnote}</span>\n )}\n </div>\n </>\n )\n}\n\n/**\n * The connector. An arrow and, when the edge is named, the name under it —\n * hidden from assistive tech, which reads the nodes in document order anyway\n * and has no use for a glyph pointing at the next one.\n */\nfunction EdgeMark({ label, row }: { label?: string; row: boolean }): ReactNode {\n return (\n <div\n aria-hidden=\"true\"\n className={cn(\n 'flex flex-none items-center justify-center gap-1.5 text-(--ink-3-aa)',\n row ? 'sm:flex-col' : 'flex-col',\n )}\n >\n {/* One glyph, turned. A row stacks into a column below `sm`, so the arrow\n has to point the way the layout actually runs at that width. In RTL the\n inline axis reverses, so the flat arrow is mirrored with it. */}\n <span\n className={cn(\n 'font-mono text-[13px] leading-none',\n row ? 'rotate-90 sm:rotate-0 sm:rtl:-scale-x-100' : 'rotate-90',\n )}\n >\n →\n </span>\n {label && <span className=\"mono-meta whitespace-nowrap\">{label}</span>}\n </div>\n )\n}\n\n/**\n * Everything the spec asked for that the renderer will not do, said out loud.\n *\n * All three failures here are silent by construction: the figure renders, it\n * renders beautifully, and it describes something other than what was written.\n * An author reading the picture has no way to tell an edge that was ignored\n * from an edge they forgot, so the console is the only place left to say it.\n *\n * Dev only, and guarded at the call site so a production bundle drops the walk\n * along with the messages.\n */\nfunction warnSpec(nodes: DiagramNode[], edges: DiagramEdge[], drawn: Set<DiagramEdge>): void {\n const ids = new Set<string>()\n warnNodes(nodes, ids)\n\n for (const edge of edges) {\n if (drawn.has(edge)) continue\n const pair = `${edge.from} → ${edge.to}`\n const unknown = !ids.has(edge.from) || !ids.has(edge.to)\n\n warn({\n code: unknown ? 'DIAGRAM_EDGE_UNKNOWN_NODE' : 'DIAGRAM_EDGE_NOT_ADJACENT',\n problem: unknown\n ? `The edge ${pair} names an id no node in this spec carries, so no arrow is drawn and nothing else changes.`\n : `The edge ${pair} names two nodes that are never consecutive siblings, so no arrow is drawn anywhere.`,\n field: `spec.edges: ${pair}`,\n fix: unknown\n ? 'Give the node the id the edge names, or correct the edge to an id the spec already has.'\n : 'Write the edge from a node to the node immediately after it in the same rank, in that order. Nesting is containment here; an edge is a step between siblings.',\n component: 'Diagram',\n })\n }\n}\n\n/** The node half of {@link warnSpec}: duplicate ids, and props read by nobody. */\nfunction warnNodes(nodes: DiagramNode[], ids: Set<string>): void {\n for (const node of nodes) {\n if (node.id) {\n if (ids.has(node.id)) {\n warn({\n code: 'DIAGRAM_DUPLICATE_ID',\n problem: `Two nodes carry the id \"${node.id}\", so an edge naming it lands on whichever comes first and silently means the other.`,\n field: `spec.nodes: id \"${node.id}\"`,\n fix: 'Make every id unique across the whole spec, or drop it from the node no edge names.',\n component: 'Diagram',\n })\n }\n ids.add(node.id)\n }\n\n const isGroup = (node.children?.length ?? 0) > 0\n\n if (isGroup && node.accent) {\n warn({\n code: 'DIAGRAM_ACCENT_ON_CONTAINER',\n problem: `\"${node.label}\" has children, so it is drawn as a band — and a band has no fill for accent to take. It type-checks and paints nothing.`,\n field: `spec.nodes: accent on \"${node.label}\"`,\n fix: 'Move accent to the leaf the diagram is actually about, or drop the children so this node becomes that leaf.',\n component: 'Diagram',\n })\n }\n\n if (!isGroup && node.direction) {\n warn({\n code: 'DIAGRAM_DIRECTION_ON_LEAF',\n problem: `\"${node.label}\" has no children, so there is nothing here to lay out — direction is read only from a node that has some.`,\n field: `spec.nodes: direction on \"${node.label}\"`,\n fix: 'Set direction on the parent whose axis you meant, or on spec.direction for the top rank.',\n component: 'Diagram',\n })\n }\n\n if (node.children) warnNodes(node.children, ids)\n }\n}\n\nexport default Diagram\n"],"mappings":"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","names":[]}
|
|
@@ -7,7 +7,14 @@ declare const Dialog: react.FC<DialogPrimitive.DialogProps>;
|
|
|
7
7
|
declare const DialogTrigger: react.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
8
8
|
declare const DialogClose: react.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & react.RefAttributes<HTMLButtonElement>>;
|
|
9
9
|
interface DialogContentProps extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title' | 'className'> {
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Heading text.
|
|
12
|
+
*
|
|
13
|
+
* Pass one even when `hideTitle` is set. Radix requires a title, so omitting
|
|
14
|
+
* it renders a hidden fallback reading the literal word "Dialog" — which
|
|
15
|
+
* satisfies an automated accessibility check and announces every unnamed
|
|
16
|
+
* modal in the application as the same thing. Development says so out loud.
|
|
17
|
+
*/
|
|
11
18
|
title?: ReactNode;
|
|
12
19
|
/** Sub-heading under the title. */
|
|
13
20
|
description?: ReactNode;
|
|
@@ -32,8 +39,16 @@ interface DialogContentProps extends Omit<ComponentProps<typeof DialogPrimitive.
|
|
|
32
39
|
* usually by leaving focus behind in the page underneath.
|
|
33
40
|
*
|
|
34
41
|
* Radix requires a `Dialog.Title` whether or not one is shown, so a dialog
|
|
35
|
-
* without a visible heading still renders a hidden one rather than
|
|
36
|
-
*
|
|
42
|
+
* without a visible heading still renders a hidden one rather than shipping an
|
|
43
|
+
* unnamed modal — and warns in development, because the fallback it renders is
|
|
44
|
+
* the literal word "Dialog" and a placeholder that passes an accessibility
|
|
45
|
+
* check is how the problem survives a review.
|
|
46
|
+
*
|
|
47
|
+
* Portals into the element an enclosing `OverlayContainer` names, and switches
|
|
48
|
+
* from viewport positioning to container positioning when there is one. A
|
|
49
|
+
* `fixed` panel covers the page whatever it is portalled into, so honouring
|
|
50
|
+
* the container without that swap would have moved the markup and left the
|
|
51
|
+
* picture unchanged.
|
|
37
52
|
*
|
|
38
53
|
* @example
|
|
39
54
|
* <Dialog>
|