@nanisoft/prism-ui 0.10.1 → 0.13.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/LICENSE +21 -21
- package/dist/.tsbuildinfo +1 -1
- package/dist/blocks/app-shell-01/app-shell.js +4 -4
- package/dist/blocks/app-shell-01/index.d.ts +2 -2
- package/dist/blocks/app-shell-01/index.js +1 -1
- package/dist/blocks/auth-form-01/auth-form.d.ts +11 -1
- package/dist/blocks/auth-form-01/auth-form.d.ts.map +1 -1
- package/dist/blocks/auth-form-01/auth-form.js +11 -8
- package/dist/blocks/auth-form-01/index.d.ts +2 -2
- package/dist/blocks/auth-form-01/index.js +1 -1
- package/dist/blocks/cta-01/cta.d.ts +26 -21
- package/dist/blocks/cta-01/cta.d.ts.map +1 -1
- package/dist/blocks/cta-01/cta.js +2 -2
- package/dist/blocks/cta-01/index.d.ts +2 -2
- package/dist/blocks/cta-01/index.js +1 -1
- package/dist/blocks/data-table-01/data-table.js +7 -7
- package/dist/blocks/data-table-01/index.d.ts +2 -2
- package/dist/blocks/data-table-01/index.js +1 -1
- package/dist/blocks/empty-state-01/empty-state.d.ts +143 -0
- package/dist/blocks/empty-state-01/empty-state.d.ts.map +1 -0
- package/dist/blocks/empty-state-01/empty-state.js +104 -0
- package/dist/blocks/empty-state-01/index.d.ts +3 -0
- package/dist/blocks/empty-state-01/index.d.ts.map +1 -0
- package/dist/blocks/empty-state-01/index.js +1 -0
- package/dist/blocks/feature-grid-01/feature-grid.d.ts +28 -1
- package/dist/blocks/feature-grid-01/feature-grid.d.ts.map +1 -1
- package/dist/blocks/feature-grid-01/feature-grid.js +34 -3
- package/dist/blocks/feature-grid-01/index.d.ts +2 -2
- package/dist/blocks/feature-grid-01/index.js +1 -1
- package/dist/blocks/hero-01/hero.d.ts +49 -17
- package/dist/blocks/hero-01/hero.d.ts.map +1 -1
- package/dist/blocks/hero-01/hero.js +14 -9
- package/dist/blocks/hero-01/index.d.ts +2 -1
- package/dist/blocks/hero-01/index.d.ts.map +1 -1
- package/dist/blocks/hero-01/index.js +1 -1
- package/dist/blocks/index.d.ts +20 -20
- package/dist/blocks/index.js +19 -19
- package/dist/blocks/instrument-panel-01/index.d.ts +2 -2
- package/dist/blocks/instrument-panel-01/index.js +1 -1
- package/dist/blocks/instrument-panel-01/instrument-panel.js +1 -1
- package/dist/blocks/logo-strip-01/index.d.ts +2 -2
- package/dist/blocks/logo-strip-01/index.js +1 -1
- package/dist/blocks/logo-strip-01/logo-strip.d.ts +1 -1
- package/dist/blocks/logo-strip-01/logo-strip.js +2 -2
- package/dist/blocks/note-grid-01/index.d.ts +2 -2
- package/dist/blocks/note-grid-01/index.js +1 -1
- package/dist/blocks/note-grid-01/note-grid.d.ts +1 -1
- package/dist/blocks/note-grid-01/note-grid.js +1 -1
- package/dist/blocks/page-header-01/index.d.ts +2 -2
- package/dist/blocks/page-header-01/index.js +1 -1
- package/dist/blocks/page-header-01/page-header.d.ts +1 -1
- package/dist/blocks/page-header-01/page-header.js +4 -4
- package/dist/blocks/pricing-01/index.d.ts +1 -1
- package/dist/blocks/pricing-01/index.js +1 -1
- package/dist/blocks/pricing-01/pricing.d.ts +8 -1
- package/dist/blocks/pricing-01/pricing.d.ts.map +1 -1
- package/dist/blocks/pricing-01/pricing.js +15 -5
- package/dist/blocks/process-flow-01/index.d.ts +3 -0
- package/dist/blocks/process-flow-01/index.d.ts.map +1 -0
- package/dist/blocks/process-flow-01/index.js +1 -0
- package/dist/blocks/process-flow-01/process-flow.d.ts +118 -0
- package/dist/blocks/process-flow-01/process-flow.d.ts.map +1 -0
- package/dist/blocks/process-flow-01/process-flow.js +71 -0
- package/dist/blocks/process-rail-01/index.d.ts +2 -2
- package/dist/blocks/process-rail-01/index.js +1 -1
- package/dist/blocks/process-rail-01/process-rail.d.ts +1 -1
- package/dist/blocks/process-rail-01/process-rail.js +2 -2
- package/dist/blocks/product-grid-01/index.d.ts +2 -2
- package/dist/blocks/product-grid-01/index.js +1 -1
- package/dist/blocks/product-grid-01/product-grid.d.ts +37 -5
- package/dist/blocks/product-grid-01/product-grid.d.ts.map +1 -1
- package/dist/blocks/product-grid-01/product-grid.js +20 -6
- package/dist/blocks/run-console-01/index.d.ts +3 -0
- package/dist/blocks/run-console-01/index.d.ts.map +1 -0
- package/dist/blocks/run-console-01/index.js +1 -0
- package/dist/blocks/run-console-01/run-console.d.ts +111 -0
- package/dist/blocks/run-console-01/run-console.d.ts.map +1 -0
- package/dist/blocks/run-console-01/run-console.js +48 -0
- package/dist/blocks/settings-panel-01/index.d.ts +2 -2
- package/dist/blocks/settings-panel-01/index.js +1 -1
- package/dist/blocks/settings-panel-01/settings-panel.d.ts +11 -1
- package/dist/blocks/settings-panel-01/settings-panel.d.ts.map +1 -1
- package/dist/blocks/settings-panel-01/settings-panel.js +15 -10
- package/dist/blocks/site-footer/index.d.ts +2 -2
- package/dist/blocks/site-footer/index.js +1 -1
- package/dist/blocks/site-footer/site-footer.d.ts +1 -1
- package/dist/blocks/site-footer/site-footer.js +2 -2
- package/dist/blocks/site-header/index.d.ts +2 -2
- package/dist/blocks/site-header/index.js +1 -1
- package/dist/blocks/site-header/site-header.d.ts +2 -2
- package/dist/blocks/site-header/site-header.js +3 -3
- package/dist/blocks/site-navbar/index.d.ts +3 -0
- package/dist/blocks/site-navbar/index.d.ts.map +1 -0
- package/dist/blocks/site-navbar/index.js +1 -0
- package/dist/blocks/site-navbar/mobile-nav.d.ts +48 -0
- package/dist/blocks/site-navbar/mobile-nav.d.ts.map +1 -0
- package/dist/blocks/site-navbar/mobile-nav.js +35 -0
- package/dist/blocks/site-navbar/mode-toggle.d.ts +39 -0
- package/dist/blocks/site-navbar/mode-toggle.d.ts.map +1 -0
- package/dist/blocks/site-navbar/mode-toggle.js +39 -0
- package/dist/blocks/site-navbar/navbar-controls.d.ts +79 -0
- package/dist/blocks/site-navbar/navbar-controls.d.ts.map +1 -0
- package/dist/blocks/site-navbar/navbar-controls.js +40 -0
- package/dist/blocks/site-navbar/search-entry.d.ts +29 -0
- package/dist/blocks/site-navbar/search-entry.d.ts.map +1 -0
- package/dist/blocks/site-navbar/search-entry.js +52 -0
- package/dist/blocks/site-navbar/site-navbar.d.ts +212 -0
- package/dist/blocks/site-navbar/site-navbar.d.ts.map +1 -0
- package/dist/blocks/site-navbar/site-navbar.js +61 -0
- package/dist/blocks/site-navbar/sites-menu.d.ts +40 -0
- package/dist/blocks/site-navbar/sites-menu.d.ts.map +1 -0
- package/dist/blocks/site-navbar/sites-menu.js +48 -0
- package/dist/blocks/site-navbar/theme-menu.d.ts +47 -0
- package/dist/blocks/site-navbar/theme-menu.d.ts.map +1 -0
- package/dist/blocks/site-navbar/theme-menu.js +55 -0
- package/dist/blocks/stack-grid-01/index.d.ts +2 -2
- package/dist/blocks/stack-grid-01/index.js +1 -1
- package/dist/blocks/stack-grid-01/stack-grid.d.ts +42 -11
- package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -1
- package/dist/blocks/stack-grid-01/stack-grid.js +15 -7
- package/dist/blocks/stats-01/index.d.ts +9 -1
- package/dist/blocks/stats-01/index.d.ts.map +1 -1
- package/dist/blocks/stats-01/index.js +8 -1
- package/dist/blocks/stats-01/stats.d.ts +1 -1
- package/dist/blocks/stats-01/stats.js +2 -2
- package/dist/blocks/status-ledger-01/index.d.ts +2 -2
- package/dist/blocks/status-ledger-01/index.js +1 -1
- package/dist/blocks/status-ledger-01/status-ledger.d.ts +1 -1
- package/dist/blocks/status-ledger-01/status-ledger.js +2 -2
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +473 -1
- package/dist/components/index.d.ts +104 -66
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +74 -40
- package/dist/components/ui/accordion.js +1 -1
- package/dist/components/ui/alert-dialog.d.ts +119 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +82 -0
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/aspect-ratio.d.ts +60 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +61 -0
- package/dist/components/ui/avatar.js +1 -1
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.js +1 -1
- package/dist/components/ui/button-group.d.ts +68 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button-group.js +54 -0
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/calendar.d.ts +158 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +216 -0
- package/dist/components/ui/card.d.ts +18 -6
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/card.js +23 -8
- package/dist/components/ui/carousel.d.ts +118 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +152 -0
- package/dist/components/ui/chart-frame.d.ts +216 -0
- package/dist/components/ui/chart-frame.d.ts.map +1 -0
- package/dist/components/ui/chart-frame.js +286 -0
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/collapsible.d.ts +97 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +54 -0
- package/dist/components/ui/combobox.d.ts +141 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +243 -0
- package/dist/components/ui/command-palette.d.ts +106 -0
- package/dist/components/ui/command-palette.d.ts.map +1 -0
- package/dist/components/ui/command-palette.js +176 -0
- package/dist/components/ui/command.d.ts +82 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +37 -0
- package/dist/components/ui/context-menu.d.ts +151 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +97 -0
- package/dist/components/ui/cta-link.js +1 -1
- package/dist/components/ui/date-picker.d.ts +111 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +74 -0
- package/dist/components/ui/diagram.d.ts +84 -15
- package/dist/components/ui/diagram.d.ts.map +1 -1
- package/dist/components/ui/diagram.js +77 -7
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/diff.d.ts +105 -0
- package/dist/components/ui/diff.d.ts.map +1 -0
- package/dist/components/ui/diff.js +113 -0
- package/dist/components/ui/dropdown-menu.d.ts +18 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -1
- package/dist/components/ui/fact-list.js +1 -1
- package/dist/components/ui/field.js +1 -1
- package/dist/components/ui/form.d.ts +151 -0
- package/dist/components/ui/form.d.ts.map +1 -0
- package/dist/components/ui/form.js +73 -0
- package/dist/components/ui/hover-card.d.ts +105 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +49 -0
- package/dist/components/ui/input-group.d.ts +99 -0
- package/dist/components/ui/input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group.js +83 -0
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/item.d.ts +113 -0
- package/dist/components/ui/item.d.ts.map +1 -0
- package/dist/components/ui/item.js +61 -0
- package/dist/components/ui/kbd.js +1 -1
- package/dist/components/ui/label.d.ts +58 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +29 -0
- package/dist/components/ui/live-region.d.ts +9 -0
- package/dist/components/ui/live-region.d.ts.map +1 -1
- package/dist/components/ui/live-region.js +10 -2
- package/dist/components/ui/mark.js +1 -1
- package/dist/components/ui/menubar.d.ts +197 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +109 -0
- package/dist/components/ui/meter.d.ts +121 -0
- package/dist/components/ui/meter.d.ts.map +1 -0
- package/dist/components/ui/meter.js +82 -0
- package/dist/components/ui/native-select.d.ts +72 -0
- package/dist/components/ui/native-select.d.ts.map +1 -0
- package/dist/components/ui/native-select.js +55 -0
- package/dist/components/ui/navigation-menu.d.ts +186 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +94 -0
- package/dist/components/ui/number-field.d.ts +111 -0
- package/dist/components/ui/number-field.d.ts.map +1 -0
- package/dist/components/ui/number-field.js +84 -0
- package/dist/components/ui/one-time-code.d.ts +127 -0
- package/dist/components/ui/one-time-code.d.ts.map +1 -0
- package/dist/components/ui/one-time-code.js +57 -0
- package/dist/components/ui/pagination.js +1 -1
- package/dist/components/ui/popover.js +1 -1
- package/dist/components/ui/product-mark.d.ts +1 -1
- package/dist/components/ui/product-mark.js +1 -1
- package/dist/components/ui/product-switcher.d.ts +1 -1
- package/dist/components/ui/product-switcher.js +2 -2
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/prose.js +1 -1
- package/dist/components/ui/pulse-graph.js +1 -1
- package/dist/components/ui/pulse-series.js +1 -1
- package/dist/components/ui/radio-group.js +1 -1
- package/dist/components/ui/resizable.d.ts +129 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +205 -0
- package/dist/components/ui/scroll-area.d.ts +97 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +68 -0
- package/dist/components/ui/search-dialog.d.ts +139 -0
- package/dist/components/ui/search-dialog.d.ts.map +1 -0
- package/dist/components/ui/search-dialog.js +178 -0
- package/dist/components/ui/section.d.ts +26 -0
- package/dist/components/ui/section.d.ts.map +1 -1
- package/dist/components/ui/section.js +32 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/separator.js +1 -1
- package/dist/components/ui/sheet.d.ts +103 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +58 -0
- package/dist/components/ui/sidebar.d.ts +228 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +172 -0
- package/dist/components/ui/signal-field.js +1 -1
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/spinner.d.ts +79 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +79 -0
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/table-sort.d.ts +97 -0
- package/dist/components/ui/table-sort.d.ts.map +1 -0
- package/dist/components/ui/table-sort.js +102 -0
- package/dist/components/ui/table.js +1 -1
- package/dist/components/ui/tabs.js +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/timeline.d.ts +106 -0
- package/dist/components/ui/timeline.d.ts.map +1 -0
- package/dist/components/ui/timeline.js +111 -0
- package/dist/components/ui/toast.d.ts +219 -0
- package/dist/components/ui/toast.d.ts.map +1 -0
- package/dist/components/ui/toast.js +270 -0
- package/dist/components/ui/toggle-group.d.ts +118 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +194 -0
- package/dist/components/ui/toggle.d.ts +73 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +44 -0
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tree.js +1 -1
- package/dist/components/ui/typography.js +1 -1
- package/dist/fonts/Inter-OFL.txt +93 -93
- package/dist/index.d.ts +72 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -60
- package/dist/lib/rank.d.ts +69 -0
- package/dist/lib/rank.d.ts.map +1 -0
- package/dist/lib/rank.js +77 -0
- package/dist/live/index.d.ts +22 -0
- package/dist/live/index.d.ts.map +1 -0
- package/dist/live/index.js +20 -0
- package/dist/live/run-stream-01/index.d.ts +3 -0
- package/dist/live/run-stream-01/index.d.ts.map +1 -0
- package/dist/live/run-stream-01/index.js +1 -0
- package/dist/live/run-stream-01/run-stream.d.ts +125 -0
- package/dist/live/run-stream-01/run-stream.d.ts.map +1 -0
- package/dist/live/run-stream-01/run-stream.js +111 -0
- package/dist/pages/auth-page/index.d.ts +15 -2
- package/dist/pages/auth-page/index.d.ts.map +1 -1
- package/dist/pages/auth-page/index.js +8 -4
- package/dist/pages/blog-post-page/index.js +5 -5
- package/dist/pages/dashboard-page/index.d.ts +4 -4
- package/dist/pages/dashboard-page/index.js +4 -4
- package/dist/pages/docs-shell/docs-shell.d.ts +37 -4
- package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
- package/dist/pages/docs-shell/docs-shell.js +107 -16
- package/dist/pages/docs-shell/index.d.ts +2 -2
- package/dist/pages/docs-shell/index.js +1 -1
- package/dist/pages/index.d.ts +14 -14
- package/dist/pages/index.js +7 -7
- package/dist/pages/marketing-page/index.d.ts +6 -6
- package/dist/pages/marketing-page/index.js +5 -5
- package/dist/pages/not-found-page/index.js +4 -4
- package/dist/pages/settings-page/index.d.ts +3 -3
- package/dist/pages/settings-page/index.js +4 -4
- package/dist/provider/index.d.ts +4 -4
- package/dist/provider/index.js +2 -2
- package/dist/provider/provider.d.ts +1 -1
- package/dist/provider/provider.js +1 -1
- package/dist/provider/theme-script.d.ts +1 -1
- package/dist/provider/theme-script.js +1 -1
- package/dist/styles.css +689 -0
- package/package.json +9 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The props a Spinner accepts.
|
|
4
|
+
*
|
|
5
|
+
* `label` is required and there is no default, which is the Component's one
|
|
6
|
+
* design decision and the reason it is not simply "a div with a border on it".
|
|
7
|
+
*/
|
|
8
|
+
export interface SpinnerProps extends Omit<ComponentProps<'span'>, 'children'> {
|
|
9
|
+
/**
|
|
10
|
+
* The words a screen reader hears while the work is happening.
|
|
11
|
+
*
|
|
12
|
+
* Required, and required to be a real sentence rather than a verb. A busy
|
|
13
|
+
* indicator with no name is an animation, and an animation is nothing to a
|
|
14
|
+
* reader who cannot see it; the whole reason this Component exists rather
|
|
15
|
+
* than a border on a `div` is that it has to be able to say what is busy in
|
|
16
|
+
* the consumer's own words, because a shared library cannot know whether the
|
|
17
|
+
* answer is "Loading invoices" or "Carregando faturas".
|
|
18
|
+
*
|
|
19
|
+
* The words name the work, not the Component. "Loading" alone is a weaker
|
|
20
|
+
* name than "Loading invoices", and a caller who has two spinners on a page
|
|
21
|
+
* needs to tell them apart in the same way a sighted reader does.
|
|
22
|
+
*/
|
|
23
|
+
label: string;
|
|
24
|
+
/**
|
|
25
|
+
* The diameter of the ring, in one of three authored steps.
|
|
26
|
+
*
|
|
27
|
+
* A prop rather than something a caller sets through `className`, because a
|
|
28
|
+
* ring whose size is a layout decision is a ring that can be 2px and become
|
|
29
|
+
* invisible, and `className` is layout only on every Component in this
|
|
30
|
+
* package. Three steps is what a spinner is ever drawn at: beside body text,
|
|
31
|
+
* beside a control's label, and alone in a region of its own.
|
|
32
|
+
*
|
|
33
|
+
* @defaultValue 'default'
|
|
34
|
+
*/
|
|
35
|
+
size?: 'sm' | 'default' | 'lg';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A mark that says work is happening and nothing about how much of it is left.
|
|
39
|
+
*
|
|
40
|
+
* **It is not a Progress and it is not a Skeleton, and the three are confused
|
|
41
|
+
* often enough to be worth stating.** A Skeleton stands in for content: it has
|
|
42
|
+
* the shape of the thing that is arriving, so the page does not move when the
|
|
43
|
+
* content lands. A Progress reports a position: it carries a value, a minimum
|
|
44
|
+
* and a maximum, and a screen reader is told "68 percent" rather than the width
|
|
45
|
+
* of a coloured bar. A Spinner reports exactly one fact, that something is
|
|
46
|
+
* happening, and carries neither a shape nor a value. Pick by the question:
|
|
47
|
+
* *what will arrive* is a Skeleton, *how far along* is a Progress, and *is
|
|
48
|
+
* anything happening at all* is a Spinner.
|
|
49
|
+
*
|
|
50
|
+
* **The honest reason a Spinner is often the wrong answer is that it causes a
|
|
51
|
+
* layout shift.** The ring is a fixed square, so the content it stands in for
|
|
52
|
+
* is not, and everything below it moves twice: once when the content is
|
|
53
|
+
* replaced by the ring, and again when the ring is replaced by the content.
|
|
54
|
+
* A reader's eye has to find the thing twice. That is the whole cost of this
|
|
55
|
+
* Component and it is not paid when the shape of what is coming is already
|
|
56
|
+
* known, which is why:
|
|
57
|
+
*
|
|
58
|
+
* - **Do not use a Spinner as a page-level loading state where the layout is
|
|
59
|
+
* known.** A first paint of Skeletons is a better answer, and
|
|
60
|
+
* `Skeleton` is the Component for it. A Spinner alone in the middle of an
|
|
61
|
+
* empty page tells the reader nothing about the page they are about to get.
|
|
62
|
+
* - **Do not use a Spinner for a task of known length.** A countable upload
|
|
63
|
+
* is a `Progress`, and a bar that shows real information beats a ring that
|
|
64
|
+
* shows anxiety.
|
|
65
|
+
* - **Do not use a Spinner for a wait under about a second.** A control that
|
|
66
|
+
* disables itself for 400ms and then re-enables is a control that flickers.
|
|
67
|
+
* A `Skeleton` that never gets to paint, or nothing at all, is quieter.
|
|
68
|
+
* - **Do not use a Spinner inside a Button next to a label that already says
|
|
69
|
+
* what is happening.** The label is the announcement; the ring is the
|
|
70
|
+
* confirmation, and beside a label that already reads "Saving..." it is a
|
|
71
|
+
* second copy of a sentence the reader can already see.
|
|
72
|
+
*
|
|
73
|
+
* **It is a server Component.** It reads its props and renders, so the client
|
|
74
|
+
* directive would be a claim about work this Component does not do. A hundred
|
|
75
|
+
* spinners on a page cost no JavaScript.
|
|
76
|
+
*/
|
|
77
|
+
declare function Spinner({ className, label, size, ...props }: SpinnerProps): import("react").JSX.Element;
|
|
78
|
+
export { Spinner };
|
|
79
|
+
//# sourceMappingURL=spinner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../src/components/ui/spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3C;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC5E;;;;;;;;;;;;;OAaG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,CAAA;CAC/B;AA0BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,iBAAS,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,IAAgB,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,+BA+B9E;AAED,OAAO,EAAE,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* The ring, at each authored size.
|
|
5
|
+
*
|
|
6
|
+
* `size-*` on the box and `border-2` on the arc. A spinner is drawn as a
|
|
7
|
+
* rounded box with one transparent edge, which is the shape that reads as
|
|
8
|
+
* "turning" without a keyframe: the gap in the border is what moves, and the
|
|
9
|
+
* box is a fixed frame around it.
|
|
10
|
+
*
|
|
11
|
+
* The one animation is `animate-spin`, and it is the only motion in this
|
|
12
|
+
* package that is a *rotation* rather than a transition between two states.
|
|
13
|
+
* A keyframe cannot be authored here - the design system emits no keyframes
|
|
14
|
+
* and a Component cannot add one - and `animate-spin` is a Tailwind utility
|
|
15
|
+
* that already carries its own, so this is the honest ceiling: a spinner that
|
|
16
|
+
* cannot be built from a transition between two states needs a loop, and a
|
|
17
|
+
* loop is what `animate-spin` is. It is state feedback rather than decoration
|
|
18
|
+
* for the reason `Timeline`'s pulsing mark is: it is running because work is
|
|
19
|
+
* running, and it stops when the work does.
|
|
20
|
+
*/
|
|
21
|
+
const RING = {
|
|
22
|
+
sm: 'size-4 border-2',
|
|
23
|
+
default: 'size-5 border-2',
|
|
24
|
+
lg: 'size-8 border-[3px]',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A mark that says work is happening and nothing about how much of it is left.
|
|
28
|
+
*
|
|
29
|
+
* **It is not a Progress and it is not a Skeleton, and the three are confused
|
|
30
|
+
* often enough to be worth stating.** A Skeleton stands in for content: it has
|
|
31
|
+
* the shape of the thing that is arriving, so the page does not move when the
|
|
32
|
+
* content lands. A Progress reports a position: it carries a value, a minimum
|
|
33
|
+
* and a maximum, and a screen reader is told "68 percent" rather than the width
|
|
34
|
+
* of a coloured bar. A Spinner reports exactly one fact, that something is
|
|
35
|
+
* happening, and carries neither a shape nor a value. Pick by the question:
|
|
36
|
+
* *what will arrive* is a Skeleton, *how far along* is a Progress, and *is
|
|
37
|
+
* anything happening at all* is a Spinner.
|
|
38
|
+
*
|
|
39
|
+
* **The honest reason a Spinner is often the wrong answer is that it causes a
|
|
40
|
+
* layout shift.** The ring is a fixed square, so the content it stands in for
|
|
41
|
+
* is not, and everything below it moves twice: once when the content is
|
|
42
|
+
* replaced by the ring, and again when the ring is replaced by the content.
|
|
43
|
+
* A reader's eye has to find the thing twice. That is the whole cost of this
|
|
44
|
+
* Component and it is not paid when the shape of what is coming is already
|
|
45
|
+
* known, which is why:
|
|
46
|
+
*
|
|
47
|
+
* - **Do not use a Spinner as a page-level loading state where the layout is
|
|
48
|
+
* known.** A first paint of Skeletons is a better answer, and
|
|
49
|
+
* `Skeleton` is the Component for it. A Spinner alone in the middle of an
|
|
50
|
+
* empty page tells the reader nothing about the page they are about to get.
|
|
51
|
+
* - **Do not use a Spinner for a task of known length.** A countable upload
|
|
52
|
+
* is a `Progress`, and a bar that shows real information beats a ring that
|
|
53
|
+
* shows anxiety.
|
|
54
|
+
* - **Do not use a Spinner for a wait under about a second.** A control that
|
|
55
|
+
* disables itself for 400ms and then re-enables is a control that flickers.
|
|
56
|
+
* A `Skeleton` that never gets to paint, or nothing at all, is quieter.
|
|
57
|
+
* - **Do not use a Spinner inside a Button next to a label that already says
|
|
58
|
+
* what is happening.** The label is the announcement; the ring is the
|
|
59
|
+
* confirmation, and beside a label that already reads "Saving..." it is a
|
|
60
|
+
* second copy of a sentence the reader can already see.
|
|
61
|
+
*
|
|
62
|
+
* **It is a server Component.** It reads its props and renders, so the client
|
|
63
|
+
* directive would be a claim about work this Component does not do. A hundred
|
|
64
|
+
* spinners on a page cost no JavaScript.
|
|
65
|
+
*/
|
|
66
|
+
function Spinner({ className, label, size = 'default', ...props }) {
|
|
67
|
+
return (_jsx("span", { "data-slot": "spinner",
|
|
68
|
+
// The role is what makes the label legal, and the reason is the same one
|
|
69
|
+
// `LiveRegion` gives: a `span` with no role has no accessible name, so
|
|
70
|
+
// `aria-label` on one is prohibited rather than merely ignored. `status`
|
|
71
|
+
// is also the honest role, because a spinner is content that reports a
|
|
72
|
+
// state and is not waiting for the reader. It is deliberately NOT
|
|
73
|
+
// `aria-busy`: a busy *region* is still readable, and which regions are
|
|
74
|
+
// busy is the caller's knowledge rather than this Component's, so a
|
|
75
|
+
// consumer that wants it wraps the region in a `LiveRegion` and sets
|
|
76
|
+
// `busy` there.
|
|
77
|
+
role: "status", "aria-label": label, className: cn('inline-flex shrink-0', className), ...props, children: _jsx("span", { "data-slot": "spinner-ring", "aria-hidden": "true", className: cn('border-current animate-spin rounded-full border-transparent border-t-current', RING[size]) }) }));
|
|
78
|
+
}
|
|
79
|
+
export { Spinner };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Switch as SwitchPrimitive } from '@base-ui/react/switch';
|
|
4
|
-
import { cn } from '../../lib/utils';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
5
|
/**
|
|
6
6
|
* A binary control that takes effect immediately.
|
|
7
7
|
*
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
/** Which way a column is sorted, and `none` for a column that is not. */
|
|
3
|
+
export type TableSortDirection = 'none' | 'ascending' | 'descending';
|
|
4
|
+
/** What activating a header will do, given where the column is now. */
|
|
5
|
+
export type TableSortAnnouncement = (direction: TableSortDirection) => string;
|
|
6
|
+
/** The props the TableSort accepts. */
|
|
7
|
+
export interface TableSortProps extends Omit<ComponentProps<'th'>, 'children'> {
|
|
8
|
+
/**
|
|
9
|
+
* The column's name, and the visible text of the header.
|
|
10
|
+
*
|
|
11
|
+
* It is a `th` with `scope="col"` rather than a `button` dressed as a header,
|
|
12
|
+
* because the header is what a screen reader announces while a reader is moving
|
|
13
|
+
* across the table, and a header that is not a header is a row of buttons above a
|
|
14
|
+
* grid with no column names in it.
|
|
15
|
+
*/
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
/** The column this header sorts. */
|
|
18
|
+
column: string;
|
|
19
|
+
/** The direction the column is currently sorted in. @defaultValue 'none' */
|
|
20
|
+
direction?: TableSortDirection;
|
|
21
|
+
/**
|
|
22
|
+
* Called with the direction the column will be in after this activation.
|
|
23
|
+
*
|
|
24
|
+
* The caller owns the sort and the rows, because a Component cannot sort an
|
|
25
|
+
* arbitrary set of records: it does not know what the column means, whether it
|
|
26
|
+
* is a number or a date or a name, or what the original order was. So the
|
|
27
|
+
* header reports the direction and the caller's table reorders.
|
|
28
|
+
*/
|
|
29
|
+
onDirectionChange?: (direction: TableSortDirection) => void;
|
|
30
|
+
/**
|
|
31
|
+
* The words read for what activating the header will do.
|
|
32
|
+
*
|
|
33
|
+
* Required, and required as a **function of the next direction** rather than as
|
|
34
|
+
* a string, because the announcement has to say what *will* happen, not what is.
|
|
35
|
+
* A header that announces "sorted ascending" when it is already ascending tells
|
|
36
|
+
* a reader nothing about the press they are about to make, and one that
|
|
37
|
+
* announces "sort" says nothing about which way. The words are the caller's for
|
|
38
|
+
* the reason every other reader-facing string in this package is: a shared
|
|
39
|
+
* library cannot know whether the word for this is "sort", "trier" or
|
|
40
|
+
* "sortieren", and a sentence it chose would be inherited by every consumer in
|
|
41
|
+
* a language they did not write.
|
|
42
|
+
*/
|
|
43
|
+
announce: TableSortAnnouncement;
|
|
44
|
+
/** Whether this header ignores user interaction. */
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
/** Layout only. */
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The sortable header affordance for a `Table`.
|
|
51
|
+
*
|
|
52
|
+
* **`aria-sort` belongs on the `th`, and this is a `th`.** Not a `button` inside a
|
|
53
|
+
* `th`, and not a `th` with a `role="button"` on it. A column header is what a
|
|
54
|
+
* screen reader announces while a reader is moving across a table, and the sort
|
|
55
|
+
* state is a property of that header rather than of a control inside it, so
|
|
56
|
+
* `aria-sort` sits on the cell where the reader is told about it. It is set on
|
|
57
|
+
* exactly one header at a time, which is the rule: two columns both reporting
|
|
58
|
+
* `aria-sort` is a table claiming to be ordered two ways at once, and a reader
|
|
59
|
+
* comparing the two hears a contradiction.
|
|
60
|
+
*
|
|
61
|
+
* **The announcement says what will happen, not what is.** That is the decision
|
|
62
|
+
* this Component is built around, and it is why `announce` is a function of the
|
|
63
|
+
* next direction rather than a string. A reader who is about to press a header
|
|
64
|
+
* needs to know what the press will do, and the honest way to say it depends on
|
|
65
|
+
* the column's current state: on an unsorted column the press sorts ascending, on
|
|
66
|
+
* an ascending one it reverses, on a descending one it returns the original
|
|
67
|
+
* order. One string cannot be right for all three, and a string chosen for one of
|
|
68
|
+
* them is wrong for the other two in a way the reader cannot detect.
|
|
69
|
+
*
|
|
70
|
+
* **Two clicks to descending, three back to unsorted, and the third is the point.**
|
|
71
|
+
* The cycle is `none`, `ascending`, `descending`, and back to `none`, and the last
|
|
72
|
+
* step is a decision rather than an omission. A reader who has sorted to find the
|
|
73
|
+
* largest value and no longer cares has to be able to get the original order back
|
|
74
|
+
* from the control they already know, because any other route is a second
|
|
75
|
+
* affordance this package would then have to document. The announcement prop
|
|
76
|
+
* exists so a caller can say what that third press does in their own words, which
|
|
77
|
+
* is the one sentence in the whole interaction Prism cannot write for them.
|
|
78
|
+
*
|
|
79
|
+
* **The sort is the caller's.** A Component cannot sort an arbitrary set of
|
|
80
|
+
* records: it does not know whether the column is a number, a date or a name, it
|
|
81
|
+
* does not know the locale a name sorts in, and it does not know the original
|
|
82
|
+
* order it would have to restore. So this header owns the state it displays and
|
|
83
|
+
* reports the direction, and the caller's table reorders the rows.
|
|
84
|
+
*
|
|
85
|
+
* **The mark is a shape as well as a colour.** An unsorted column carries the
|
|
86
|
+
* two-headed mark, an ascending one an up arrow and a descending one a down
|
|
87
|
+
* arrow, and the mark is `aria-hidden` because the state is already on the cell in
|
|
88
|
+
* `aria-sort` and a reader is not told twice. A reader who cannot separate the
|
|
89
|
+
* three marks still gets the state from the attribute, which is the same order the
|
|
90
|
+
* whole package keeps: the attribute is the fact and the drawing is the reminder.
|
|
91
|
+
*
|
|
92
|
+
* It is a client Component, because a header that reports a direction and holds
|
|
93
|
+
* the announcement is holding state and attaching a handler.
|
|
94
|
+
*/
|
|
95
|
+
declare function TableSort({ children, column, direction, onDirectionChange, announce, disabled, className, ...props }: TableSortProps): import("react").JSX.Element;
|
|
96
|
+
export { TableSort };
|
|
97
|
+
//# sourceMappingURL=table-sort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-sort.d.ts","sourceRoot":"","sources":["../../../src/components/ui/table-sort.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAmB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAI5E,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,CAAA;AAEpE,uEAAuE;AACvE,MAAM,MAAM,qBAAqB,GAAG,CAAC,SAAS,EAAE,kBAAkB,KAAK,MAAM,CAAA;AAE7E,uCAAuC;AACvC,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC5E;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC3D;;;;;;;;;;;;OAYG;IACH,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,iBAAS,SAAS,CAAC,EACjB,QAAQ,EACR,MAAM,EACN,SAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,QAAgB,EAChB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,+BAmFhB;AAED,OAAO,EAAE,SAAS,EAAE,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ArrowDownIcon, ArrowUpIcon, ChevronsUpDownIcon } from 'lucide-react';
|
|
4
|
+
import { useId, useState } from 'react';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
/** The direction a click on a header in `from` produces. */
|
|
7
|
+
function nextDirection(from) {
|
|
8
|
+
// Unsorted, then ascending, then descending, then unsorted again.
|
|
9
|
+
//
|
|
10
|
+
// Returning to `none` is the decision worth making, and it is why the cycle has
|
|
11
|
+
// three states rather than two. A reader who has sorted a column to find the
|
|
12
|
+
// extreme and no longer cares has to get the original order back, and the only
|
|
13
|
+
// route to it is a third click on the header they already know. A two-state
|
|
14
|
+
// control makes that order unreachable from the control, which means the caller
|
|
15
|
+
// has to invent a second affordance for it, and a second affordance is a second
|
|
16
|
+
// thing a reader has to find. So the cycle closes.
|
|
17
|
+
if (from === 'none')
|
|
18
|
+
return 'ascending';
|
|
19
|
+
if (from === 'ascending')
|
|
20
|
+
return 'descending';
|
|
21
|
+
return 'none';
|
|
22
|
+
}
|
|
23
|
+
/** The mark each state draws, which is a shape and not only a colour. */
|
|
24
|
+
const MARK = {
|
|
25
|
+
// The unsorted mark is the two-headed one, because it says "this column can be
|
|
26
|
+
// ordered either way" rather than "this column is ordered". A single upward
|
|
27
|
+
// arrow on an unsorted column claims an order that is not in force.
|
|
28
|
+
none: _jsx(ChevronsUpDownIcon, { className: "size-3.5" }),
|
|
29
|
+
ascending: _jsx(ArrowUpIcon, { className: "size-3.5" }),
|
|
30
|
+
descending: _jsx(ArrowDownIcon, { className: "size-3.5" }),
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The sortable header affordance for a `Table`.
|
|
34
|
+
*
|
|
35
|
+
* **`aria-sort` belongs on the `th`, and this is a `th`.** Not a `button` inside a
|
|
36
|
+
* `th`, and not a `th` with a `role="button"` on it. A column header is what a
|
|
37
|
+
* screen reader announces while a reader is moving across a table, and the sort
|
|
38
|
+
* state is a property of that header rather than of a control inside it, so
|
|
39
|
+
* `aria-sort` sits on the cell where the reader is told about it. It is set on
|
|
40
|
+
* exactly one header at a time, which is the rule: two columns both reporting
|
|
41
|
+
* `aria-sort` is a table claiming to be ordered two ways at once, and a reader
|
|
42
|
+
* comparing the two hears a contradiction.
|
|
43
|
+
*
|
|
44
|
+
* **The announcement says what will happen, not what is.** That is the decision
|
|
45
|
+
* this Component is built around, and it is why `announce` is a function of the
|
|
46
|
+
* next direction rather than a string. A reader who is about to press a header
|
|
47
|
+
* needs to know what the press will do, and the honest way to say it depends on
|
|
48
|
+
* the column's current state: on an unsorted column the press sorts ascending, on
|
|
49
|
+
* an ascending one it reverses, on a descending one it returns the original
|
|
50
|
+
* order. One string cannot be right for all three, and a string chosen for one of
|
|
51
|
+
* them is wrong for the other two in a way the reader cannot detect.
|
|
52
|
+
*
|
|
53
|
+
* **Two clicks to descending, three back to unsorted, and the third is the point.**
|
|
54
|
+
* The cycle is `none`, `ascending`, `descending`, and back to `none`, and the last
|
|
55
|
+
* step is a decision rather than an omission. A reader who has sorted to find the
|
|
56
|
+
* largest value and no longer cares has to be able to get the original order back
|
|
57
|
+
* from the control they already know, because any other route is a second
|
|
58
|
+
* affordance this package would then have to document. The announcement prop
|
|
59
|
+
* exists so a caller can say what that third press does in their own words, which
|
|
60
|
+
* is the one sentence in the whole interaction Prism cannot write for them.
|
|
61
|
+
*
|
|
62
|
+
* **The sort is the caller's.** A Component cannot sort an arbitrary set of
|
|
63
|
+
* records: it does not know whether the column is a number, a date or a name, it
|
|
64
|
+
* does not know the locale a name sorts in, and it does not know the original
|
|
65
|
+
* order it would have to restore. So this header owns the state it displays and
|
|
66
|
+
* reports the direction, and the caller's table reorders the rows.
|
|
67
|
+
*
|
|
68
|
+
* **The mark is a shape as well as a colour.** An unsorted column carries the
|
|
69
|
+
* two-headed mark, an ascending one an up arrow and a descending one a down
|
|
70
|
+
* arrow, and the mark is `aria-hidden` because the state is already on the cell in
|
|
71
|
+
* `aria-sort` and a reader is not told twice. A reader who cannot separate the
|
|
72
|
+
* three marks still gets the state from the attribute, which is the same order the
|
|
73
|
+
* whole package keeps: the attribute is the fact and the drawing is the reminder.
|
|
74
|
+
*
|
|
75
|
+
* It is a client Component, because a header that reports a direction and holds
|
|
76
|
+
* the announcement is holding state and attaching a handler.
|
|
77
|
+
*/
|
|
78
|
+
function TableSort({ children, column, direction = 'none', onDirectionChange, announce, disabled = false, className, ...props }) {
|
|
79
|
+
// Uncontrolled use is a state the header keeps for itself, so a caller who wants
|
|
80
|
+
// to read the sort back has a `direction` to pass. The two together are the
|
|
81
|
+
// usual React shape, and the state is a convenience rather than the contract:
|
|
82
|
+
// `onDirectionChange` is the contract.
|
|
83
|
+
const [uncontrolled, setUncontrolled] = useState('none');
|
|
84
|
+
const active = direction === 'none' && onDirectionChange === undefined ? uncontrolled : direction;
|
|
85
|
+
const next = nextDirection(active);
|
|
86
|
+
// The id that ties the announcement to the button. Generated rather than
|
|
87
|
+
// derived from the column, because two tables on a page may sort the same column
|
|
88
|
+
// and an id built from its name would collide, leaving both headers describing
|
|
89
|
+
// each other.
|
|
90
|
+
const descriptionId = useId();
|
|
91
|
+
return (_jsxs("th", { "data-slot": "table-sort", "data-column": column, "data-direction": active, scope: "col", "aria-sort": active === 'none' ? undefined : active, className: cn('p-0', className), ...props, children: [_jsx("span", { id: descriptionId, "data-slot": "table-sort-announcement", className: "sr-only", children: announce(next) }), _jsxs("button", { type: "button", "data-slot": "table-sort-button", "aria-describedby": descriptionId, disabled: disabled, onClick: () => {
|
|
92
|
+
if (onDirectionChange === undefined)
|
|
93
|
+
setUncontrolled(next);
|
|
94
|
+
onDirectionChange?.(next);
|
|
95
|
+
}, className: cn('text-muted-foreground hover:text-foreground flex h-10 w-full items-center gap-1.5 px-2 text-left align-middle text-sm font-medium whitespace-nowrap outline-none', 'transition-colors duration-fast ease-out', 'focus-visible:ring-ring focus-visible:ring-[3px]', 'disabled:pointer-events-none disabled:opacity-50',
|
|
96
|
+
// The sorted column says so in the foreground ink rather than the muted
|
|
97
|
+
// one, which is a second channel on top of the arrow and the attribute:
|
|
98
|
+
// a reader who can see neither the arrow nor the attribute still sees
|
|
99
|
+
// which column is ordered.
|
|
100
|
+
active === 'none' ? '' : 'text-foreground'), children: [_jsx("span", { "data-slot": "table-sort-label", className: "truncate", children: children }), _jsx("span", { "data-slot": "table-sort-mark", "aria-hidden": "true", className: "ml-auto inline-flex shrink-0 items-center", children: MARK[active] })] })] }));
|
|
101
|
+
}
|
|
102
|
+
export { TableSort };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Tabs as TabsPrimitive } from '@base-ui/react/tabs';
|
|
4
|
-
import { cn } from '../../lib/utils';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
5
|
/**
|
|
6
6
|
* A set of layered panels shown one at a time.
|
|
7
7
|
*
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* How far along an event is, where the run is still going.
|
|
4
|
+
*
|
|
5
|
+
* Four states, and this is a different vocabulary from the four product-capability
|
|
6
|
+
* tiers a StatusLedger01 row carries. Those answer "what can this product do
|
|
7
|
+
* today" and these answer "what happened to this run", so they are two lists
|
|
8
|
+
* about two different subjects rather than two lists about one. A `planned` agent
|
|
9
|
+
* run is a category error, and a `failed` capability is a category error, which is
|
|
10
|
+
* the test that tells them apart.
|
|
11
|
+
*
|
|
12
|
+
* `running` is the only one that moves. It is the state a run is in while it is
|
|
13
|
+
* still arriving events, and it is the reason a Timeline is normally wrapped in a
|
|
14
|
+
* LiveRegion rather than being one.
|
|
15
|
+
*/
|
|
16
|
+
export type TimelineState = 'pending' | 'running' | 'done' | 'failed';
|
|
17
|
+
/** One event in a run. */
|
|
18
|
+
export type TimelineEntry = {
|
|
19
|
+
/**
|
|
20
|
+
* A stable key for the entry.
|
|
21
|
+
*
|
|
22
|
+
* Required, because a run's entries arrive and are reordered as it goes, and a
|
|
23
|
+
* list keyed by index would have a reader's focus jump to a different step when
|
|
24
|
+
* an earlier one is inserted.
|
|
25
|
+
*/
|
|
26
|
+
id: string;
|
|
27
|
+
/** The words naming what happened. */
|
|
28
|
+
title: string;
|
|
29
|
+
/**
|
|
30
|
+
* How long the step took, in milliseconds.
|
|
31
|
+
*
|
|
32
|
+
* Omit it for a step whose duration is meaningless: a run's opening event has no
|
|
33
|
+
* length, and a bar drawn for it would be a bar comparing nothing.
|
|
34
|
+
*/
|
|
35
|
+
duration?: number;
|
|
36
|
+
/** Where the step is. @defaultValue 'done' */
|
|
37
|
+
state?: TimelineState;
|
|
38
|
+
/** Anything the caller wants under the step: a command, a diff, a payload. */
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
};
|
|
41
|
+
/** The props the Timeline accepts. */
|
|
42
|
+
export interface TimelineProps extends Omit<ComponentProps<'ol'>, 'children'> {
|
|
43
|
+
/**
|
|
44
|
+
* The events, in the order they happened.
|
|
45
|
+
*
|
|
46
|
+
* Order is the caller's, not this Component's: a run log arrives in the order
|
|
47
|
+
* the transport delivered it, and a Timeline that sorted by duration or by state
|
|
48
|
+
* would be a different component that answers a different question.
|
|
49
|
+
*/
|
|
50
|
+
entries: readonly TimelineEntry[];
|
|
51
|
+
/**
|
|
52
|
+
* The accessible name of the list, read before its entries.
|
|
53
|
+
*
|
|
54
|
+
* Required rather than defaulted, because two timelines on a page are two lists a
|
|
55
|
+
* reader cannot tell apart, and the name is the caller's word.
|
|
56
|
+
*/
|
|
57
|
+
label: string;
|
|
58
|
+
/**
|
|
59
|
+
* The words for a duration, given the number of milliseconds.
|
|
60
|
+
*
|
|
61
|
+
* The default renders seconds with at most one decimal, which is a measurement
|
|
62
|
+
* rather than a sentence. A caller whose steps are measured in minutes or whose
|
|
63
|
+
* readers want a different precision passes its own.
|
|
64
|
+
*/
|
|
65
|
+
format?: (ms: number) => string;
|
|
66
|
+
/** Layout only. */
|
|
67
|
+
className?: string;
|
|
68
|
+
/**
|
|
69
|
+
* What a reader is told when the run has no events yet.
|
|
70
|
+
*
|
|
71
|
+
* An empty list is the state a run spends its first moments in, so it is
|
|
72
|
+
* authored rather than left to a caller to notice.
|
|
73
|
+
*/
|
|
74
|
+
empty?: ReactNode;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A run's events in order, with the time each one took drawn to scale.
|
|
78
|
+
*
|
|
79
|
+
* The second half of an agent console is always the same question: not what
|
|
80
|
+
* happened, but where the time went. A list of events in order answers the first
|
|
81
|
+
* and is silent on the second, so a reader who wants to know why a run took forty
|
|
82
|
+
* seconds has to read every number and do the arithmetic themselves.
|
|
83
|
+
*
|
|
84
|
+
* **The bars share a left edge and are drawn to scale against the longest step in
|
|
85
|
+
* the run.** That is the whole idea. The scale is relative rather than absolute,
|
|
86
|
+
* which is the right choice here: the shape of the run is the question, and a
|
|
87
|
+
* shared zero baseline would be a second axis nobody reads. The consequence worth
|
|
88
|
+
* stating is that the bars answer "which step was slow" and deliberately do not
|
|
89
|
+
* answer "how long did the run take", because only the caller knows whether its
|
|
90
|
+
* steps were sequential or overlapping, and a bar that implied otherwise would be
|
|
91
|
+
* claiming something about the run it cannot see.
|
|
92
|
+
*
|
|
93
|
+
* **A step with no duration draws no bar.** A run's opening event has no length,
|
|
94
|
+
* and a zero-width bar beside it would read as "this was instant" rather than
|
|
95
|
+
* "this has no duration", which are different claims and only one is true.
|
|
96
|
+
*
|
|
97
|
+
* **It is a list, not a live region.** The order is the accessibility tree: an
|
|
98
|
+
* ordered list, so a reader hears the sequence and the count. A run that is still
|
|
99
|
+
* arriving is normally wrapped in a LiveRegion by the caller, because the arrival
|
|
100
|
+
* is the event worth announcing and the list is the thing worth reading
|
|
101
|
+
* afterwards. Making the list itself live would announce the whole run again on
|
|
102
|
+
* every append.
|
|
103
|
+
*/
|
|
104
|
+
declare function Timeline({ entries, label, format, className, empty, ...props }: TimelineProps): import("react").JSX.Element;
|
|
105
|
+
export { Timeline };
|
|
106
|
+
//# sourceMappingURL=timeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../../../src/components/ui/timeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAErE,0BAA0B;AAC1B,MAAM,MAAM,aAAa,GAAG;IAC1B;;;;;;OAMG;IACH,EAAE,EAAE,MAAM,CAAA;IACV,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,sCAAsC;AACtC,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3E;;;;;;OAMG;IACH,OAAO,EAAE,SAAS,aAAa,EAAE,CAAA;IACjC;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/B,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAgDD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,iBAAS,QAAQ,CAAC,EAChB,OAAO,EACP,KAAK,EACL,MAAsB,EACtB,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,aAAa,+BAkIf;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* The border each state draws its mark in.
|
|
5
|
+
*
|
|
6
|
+
* Every mark is a ring and none of them is filled, for two reasons that point the
|
|
7
|
+
* same way. The first is the measurement the StatusLedger01 mark gives: a filled
|
|
8
|
+
* pastel dot fails contrast against a light card, and a state a reader cannot see
|
|
9
|
+
* is not a state. The second is uniformity: one geometry for all four states means
|
|
10
|
+
* a reader scanning the column is comparing colours rather than shapes, and a
|
|
11
|
+
* column where some marks are solid and some are hollow is a column to decode.
|
|
12
|
+
*
|
|
13
|
+
* A ring also needs no background of its own. An unfilled mark shows the surface
|
|
14
|
+
* behind it, which is the page ground or the card it is on, and a ground-coloured
|
|
15
|
+
* interior inside a coloured ring is exactly what a ring looks like. A filled
|
|
16
|
+
* variant had to paint `bg-background` into the middle to avoid rendering as a
|
|
17
|
+
* transparent hole, and that is a fill declaring a surface while carrying nothing
|
|
18
|
+
* on it.
|
|
19
|
+
*
|
|
20
|
+
* The state is not carried by the colour alone. The mark is `aria-hidden` and the
|
|
21
|
+
* entry's own words carry it, so a reader who cannot separate the four borders
|
|
22
|
+
* still reads "Failed to fetch" on the entry itself.
|
|
23
|
+
*/
|
|
24
|
+
const STATE_MARK = {
|
|
25
|
+
pending: 'border-border',
|
|
26
|
+
running: 'border-primary',
|
|
27
|
+
done: 'border-brand-ink',
|
|
28
|
+
failed: 'border-destructive',
|
|
29
|
+
};
|
|
30
|
+
const STATE_BAR = {
|
|
31
|
+
// A pending step's bar is the border colour rather than a muted fill, because a
|
|
32
|
+
// bar that is drawn but empty says "this took no time" rather than "this has not
|
|
33
|
+
// run yet". Those are different claims and only one of them is true.
|
|
34
|
+
pending: 'bg-border',
|
|
35
|
+
running: 'bg-primary',
|
|
36
|
+
done: 'bg-brand-ink',
|
|
37
|
+
failed: 'bg-destructive',
|
|
38
|
+
};
|
|
39
|
+
/** A duration in the compact form the default uses, and the guard against a non-number. */
|
|
40
|
+
function defaultFormat(ms) {
|
|
41
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
42
|
+
return '';
|
|
43
|
+
if (ms < 1000)
|
|
44
|
+
return `${Math.round(ms)}ms`;
|
|
45
|
+
const seconds = ms / 1000;
|
|
46
|
+
return `${seconds >= 10 ? Math.round(seconds) : Math.round(seconds * 10) / 10}s`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A run's events in order, with the time each one took drawn to scale.
|
|
50
|
+
*
|
|
51
|
+
* The second half of an agent console is always the same question: not what
|
|
52
|
+
* happened, but where the time went. A list of events in order answers the first
|
|
53
|
+
* and is silent on the second, so a reader who wants to know why a run took forty
|
|
54
|
+
* seconds has to read every number and do the arithmetic themselves.
|
|
55
|
+
*
|
|
56
|
+
* **The bars share a left edge and are drawn to scale against the longest step in
|
|
57
|
+
* the run.** That is the whole idea. The scale is relative rather than absolute,
|
|
58
|
+
* which is the right choice here: the shape of the run is the question, and a
|
|
59
|
+
* shared zero baseline would be a second axis nobody reads. The consequence worth
|
|
60
|
+
* stating is that the bars answer "which step was slow" and deliberately do not
|
|
61
|
+
* answer "how long did the run take", because only the caller knows whether its
|
|
62
|
+
* steps were sequential or overlapping, and a bar that implied otherwise would be
|
|
63
|
+
* claiming something about the run it cannot see.
|
|
64
|
+
*
|
|
65
|
+
* **A step with no duration draws no bar.** A run's opening event has no length,
|
|
66
|
+
* and a zero-width bar beside it would read as "this was instant" rather than
|
|
67
|
+
* "this has no duration", which are different claims and only one is true.
|
|
68
|
+
*
|
|
69
|
+
* **It is a list, not a live region.** The order is the accessibility tree: an
|
|
70
|
+
* ordered list, so a reader hears the sequence and the count. A run that is still
|
|
71
|
+
* arriving is normally wrapped in a LiveRegion by the caller, because the arrival
|
|
72
|
+
* is the event worth announcing and the list is the thing worth reading
|
|
73
|
+
* afterwards. Making the list itself live would announce the whole run again on
|
|
74
|
+
* every append.
|
|
75
|
+
*/
|
|
76
|
+
function Timeline({ entries, label, format = defaultFormat, className, empty, ...props }) {
|
|
77
|
+
if (entries.length === 0) {
|
|
78
|
+
// A run spends its first moments with nothing in it, and an empty list with no
|
|
79
|
+
// words in it is a gap rather than a state.
|
|
80
|
+
return (_jsx("div", { "data-slot": "timeline-empty", className: cn('text-muted-foreground text-sm', className), children: empty ?? null }));
|
|
81
|
+
}
|
|
82
|
+
// The longest step with a duration sets the scale. A run whose steps all report
|
|
83
|
+
// no duration has no scale, and every bar is then omitted rather than drawn at
|
|
84
|
+
// an arbitrary width.
|
|
85
|
+
const longest = Math.max(0, ...entries
|
|
86
|
+
.map((entry) => entry.duration)
|
|
87
|
+
.filter((duration) => typeof duration === 'number' && duration > 0));
|
|
88
|
+
return (_jsx("ol", { "data-slot": "timeline", "aria-label": label, className: cn('flex flex-col', className), ...props, children: entries.map((entry, index) => {
|
|
89
|
+
const state = entry.state ?? 'done';
|
|
90
|
+
// Bound to a local before the test, because a boolean derived from a
|
|
91
|
+
// narrowing check does not carry the narrowing to its own use: reading
|
|
92
|
+
// `entry.duration` after `hasDuration` is still `number | undefined` to
|
|
93
|
+
// the checker, which is the sort of thing a boolean is a poor carrier for.
|
|
94
|
+
const duration = entry.duration;
|
|
95
|
+
const hasDuration = typeof duration === 'number' && duration > 0 && longest > 0;
|
|
96
|
+
const width = hasDuration ? (duration / longest) * 100 : 0;
|
|
97
|
+
const isLast = index === entries.length - 1;
|
|
98
|
+
return (_jsxs("li", { "data-slot": "timeline-entry", "data-state": state, className: "grid grid-cols-[auto_1fr] gap-x-3", children: [_jsxs("div", { "data-slot": "timeline-rail", "aria-hidden": "true", className: "flex flex-col items-center", children: [_jsx("span", { "data-slot": "timeline-mark", className: cn('mt-1.5 size-2 shrink-0 rounded-full border-2', STATE_MARK[state],
|
|
99
|
+
// A running step is the one that is still moving, and a ring that
|
|
100
|
+
// pulses is the only motion in the Component. It is state
|
|
101
|
+
// feedback, not decoration: it stops when the step stops.
|
|
102
|
+
state === 'running' && 'animate-pulse') }), isLast ? null : _jsx("span", { "data-slot": "timeline-spine", className: "bg-border w-px flex-1" })] }), _jsxs("div", { className: cn(
|
|
103
|
+
// The last entry has nothing under it, so its bottom padding is
|
|
104
|
+
// dropped rather than leaving a gap the reader reads as a
|
|
105
|
+
// missing step.
|
|
106
|
+
'flex min-w-0 flex-col gap-1.5', isLast ? 'pb-0' : 'pb-4'), children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("span", { "data-slot": "timeline-title", className: cn('min-w-0 truncate text-sm', state === 'pending' && 'text-muted-foreground', state === 'failed' && 'text-destructive', state === 'done' || state === 'running'
|
|
107
|
+
? 'text-foreground'
|
|
108
|
+
: 'text-muted-foreground'), children: entry.title }), typeof entry.duration === 'number' && entry.duration > 0 ? (_jsx("span", { "data-slot": "timeline-duration", className: "text-muted-foreground shrink-0 text-xs tabular-nums", children: format(entry.duration) })) : null] }), hasDuration ? (_jsx("span", { "data-slot": "timeline-bar", className: cn('block h-1 w-full overflow-hidden rounded-full', STATE_BAR[state]), style: { width: `${width}%` } })) : null, entry.children === undefined ? null : (_jsx("div", { "data-slot": "timeline-detail", className: "text-muted-foreground min-w-0 text-sm", children: entry.children }))] })] }, entry.id));
|
|
109
|
+
}) }));
|
|
110
|
+
}
|
|
111
|
+
export { Timeline };
|