@textui/widgets 0.1.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 -0
- package/README.md +69 -0
- package/dist/chart/area-chart.d.ts +4 -0
- package/dist/chart/area-chart.d.ts.map +1 -0
- package/dist/chart/area-chart.js +3 -0
- package/dist/chart/bar-chart.d.ts +32 -0
- package/dist/chart/bar-chart.d.ts.map +1 -0
- package/dist/chart/bar-chart.js +32 -0
- package/dist/chart/gauge.d.ts +26 -0
- package/dist/chart/gauge.d.ts.map +1 -0
- package/dist/chart/gauge.js +26 -0
- package/dist/chart/heatmap.d.ts +13 -0
- package/dist/chart/heatmap.d.ts.map +1 -0
- package/dist/chart/heatmap.js +27 -0
- package/dist/chart/histogram.d.ts +12 -0
- package/dist/chart/histogram.d.ts.map +1 -0
- package/dist/chart/histogram.js +22 -0
- package/dist/chart/index.d.ts +20 -0
- package/dist/chart/index.d.ts.map +1 -0
- package/dist/chart/index.js +32 -0
- package/dist/chart/line-chart.d.ts +7 -0
- package/dist/chart/line-chart.d.ts.map +1 -0
- package/dist/chart/line-chart.js +75 -0
- package/dist/chart/shared.d.ts +34 -0
- package/dist/chart/shared.d.ts.map +1 -0
- package/dist/chart/shared.js +78 -0
- package/dist/chart/sparkline.d.ts +15 -0
- package/dist/chart/sparkline.d.ts.map +1 -0
- package/dist/chart/sparkline.js +22 -0
- package/dist/control/button.d.ts +27 -0
- package/dist/control/button.d.ts.map +1 -0
- package/dist/control/button.js +130 -0
- package/dist/control/checkbox.d.ts +10 -0
- package/dist/control/checkbox.d.ts.map +1 -0
- package/dist/control/checkbox.js +31 -0
- package/dist/control/index.d.ts +20 -0
- package/dist/control/index.d.ts.map +1 -0
- package/dist/control/index.js +36 -0
- package/dist/control/radio-group.d.ts +17 -0
- package/dist/control/radio-group.d.ts.map +1 -0
- package/dist/control/radio-group.js +32 -0
- package/dist/control/search-box.d.ts +7 -0
- package/dist/control/search-box.d.ts.map +1 -0
- package/dist/control/search-box.js +3 -0
- package/dist/control/select.d.ts +42 -0
- package/dist/control/select.d.ts.map +1 -0
- package/dist/control/select.js +166 -0
- package/dist/control/shared.d.ts +41 -0
- package/dist/control/shared.d.ts.map +1 -0
- package/dist/control/shared.js +1 -0
- package/dist/control/slider.d.ts +14 -0
- package/dist/control/slider.d.ts.map +1 -0
- package/dist/control/slider.js +45 -0
- package/dist/control/switch.d.ts +10 -0
- package/dist/control/switch.d.ts.map +1 -0
- package/dist/control/switch.js +32 -0
- package/dist/control/text-area.d.ts +87 -0
- package/dist/control/text-area.d.ts.map +1 -0
- package/dist/control/text-area.js +299 -0
- package/dist/control/text-input.d.ts +10 -0
- package/dist/control/text-input.d.ts.map +1 -0
- package/dist/control/text-input.js +129 -0
- package/dist/data/code-viewer.d.ts +71 -0
- package/dist/data/code-viewer.d.ts.map +1 -0
- package/dist/data/code-viewer.js +232 -0
- package/dist/data/feed.d.ts +43 -0
- package/dist/data/feed.d.ts.map +1 -0
- package/dist/data/feed.js +178 -0
- package/dist/data/index.d.ts +54 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +187 -0
- package/dist/data/list.d.ts +31 -0
- package/dist/data/list.d.ts.map +1 -0
- package/dist/data/list.js +104 -0
- package/dist/data/log-viewer.d.ts +26 -0
- package/dist/data/log-viewer.d.ts.map +1 -0
- package/dist/data/log-viewer.js +69 -0
- package/dist/data/markdown-view.d.ts +31 -0
- package/dist/data/markdown-view.d.ts.map +1 -0
- package/dist/data/markdown-view.js +139 -0
- package/dist/data/pagination.d.ts +9 -0
- package/dist/data/pagination.d.ts.map +1 -0
- package/dist/data/pagination.js +18 -0
- package/dist/data/scroll-thumb.d.ts +15 -0
- package/dist/data/scroll-thumb.d.ts.map +1 -0
- package/dist/data/scroll-thumb.js +32 -0
- package/dist/data/shared.d.ts +12 -0
- package/dist/data/shared.d.ts.map +1 -0
- package/dist/data/shared.js +17 -0
- package/dist/data/tree.d.ts +35 -0
- package/dist/data/tree.d.ts.map +1 -0
- package/dist/data/tree.js +158 -0
- package/dist/decorations.d.ts +88 -0
- package/dist/decorations.d.ts.map +1 -0
- package/dist/decorations.js +135 -0
- package/dist/display/alert.d.ts +8 -0
- package/dist/display/alert.d.ts.map +1 -0
- package/dist/display/alert.js +12 -0
- package/dist/display/badge.d.ts +10 -0
- package/dist/display/badge.d.ts.map +1 -0
- package/dist/display/badge.js +21 -0
- package/dist/display/card.d.ts +8 -0
- package/dist/display/card.d.ts.map +1 -0
- package/dist/display/card.js +12 -0
- package/dist/display/empty-state.d.ts +10 -0
- package/dist/display/empty-state.d.ts.map +1 -0
- package/dist/display/empty-state.js +5 -0
- package/dist/display/error-state.d.ts +9 -0
- package/dist/display/error-state.d.ts.map +1 -0
- package/dist/display/error-state.js +7 -0
- package/dist/display/heading.d.ts +6 -0
- package/dist/display/heading.d.ts.map +1 -0
- package/dist/display/heading.js +8 -0
- package/dist/display/index.d.ts +25 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +52 -0
- package/dist/display/key-value.d.ts +14 -0
- package/dist/display/key-value.d.ts.map +1 -0
- package/dist/display/key-value.js +11 -0
- package/dist/display/label.d.ts +6 -0
- package/dist/display/label.d.ts.map +1 -0
- package/dist/display/label.js +3 -0
- package/dist/display/marquee.d.ts +36 -0
- package/dist/display/marquee.d.ts.map +1 -0
- package/dist/display/marquee.js +55 -0
- package/dist/display/progress.d.ts +39 -0
- package/dist/display/progress.d.ts.map +1 -0
- package/dist/display/progress.js +46 -0
- package/dist/display/skeleton.d.ts +20 -0
- package/dist/display/skeleton.d.ts.map +1 -0
- package/dist/display/skeleton.js +20 -0
- package/dist/display/spinner.d.ts +7 -0
- package/dist/display/spinner.d.ts.map +1 -0
- package/dist/display/spinner.js +9 -0
- package/dist/display/status-dot.d.ts +8 -0
- package/dist/display/status-dot.d.ts.map +1 -0
- package/dist/display/status-dot.js +13 -0
- package/dist/display/timeline.d.ts +12 -0
- package/dist/display/timeline.d.ts.map +1 -0
- package/dist/display/timeline.js +22 -0
- package/dist/find.d.ts +74 -0
- package/dist/find.d.ts.map +1 -0
- package/dist/find.js +102 -0
- package/dist/form/danger-zone.d.ts +11 -0
- package/dist/form/danger-zone.d.ts.map +1 -0
- package/dist/form/danger-zone.js +15 -0
- package/dist/form/field.d.ts +19 -0
- package/dist/form/field.d.ts.map +1 -0
- package/dist/form/field.js +35 -0
- package/dist/form/form-actions.d.ts +11 -0
- package/dist/form/form-actions.d.ts.map +1 -0
- package/dist/form/form-actions.js +16 -0
- package/dist/form/form-section.d.ts +7 -0
- package/dist/form/form-section.d.ts.map +1 -0
- package/dist/form/form-section.js +5 -0
- package/dist/form/form.d.ts +7 -0
- package/dist/form/form.d.ts.map +1 -0
- package/dist/form/form.js +3 -0
- package/dist/form/index.d.ts +42 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +140 -0
- package/dist/form/shared.d.ts +25 -0
- package/dist/form/shared.d.ts.map +1 -0
- package/dist/form/shared.js +9 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/layout/center.d.ts +7 -0
- package/dist/layout/center.d.ts.map +1 -0
- package/dist/layout/center.js +8 -0
- package/dist/layout/column.d.ts +3 -0
- package/dist/layout/column.d.ts.map +1 -0
- package/dist/layout/column.js +2 -0
- package/dist/layout/divider.d.ts +11 -0
- package/dist/layout/divider.d.ts.map +1 -0
- package/dist/layout/divider.js +13 -0
- package/dist/layout/grid.d.ts +10 -0
- package/dist/layout/grid.d.ts.map +1 -0
- package/dist/layout/grid.js +22 -0
- package/dist/layout/index.d.ts +19 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +36 -0
- package/dist/layout/panel.d.ts +37 -0
- package/dist/layout/panel.d.ts.map +1 -0
- package/dist/layout/panel.js +71 -0
- package/dist/layout/row.d.ts +7 -0
- package/dist/layout/row.d.ts.map +1 -0
- package/dist/layout/row.js +2 -0
- package/dist/layout/scroll-view.d.ts +27 -0
- package/dist/layout/scroll-view.d.ts.map +1 -0
- package/dist/layout/scroll-view.js +93 -0
- package/dist/layout/splitter.d.ts +10 -0
- package/dist/layout/splitter.d.ts.map +1 -0
- package/dist/layout/splitter.js +9 -0
- package/dist/layout/stack.d.ts +7 -0
- package/dist/layout/stack.d.ts.map +1 -0
- package/dist/layout/stack.js +5 -0
- package/dist/navigation/breadcrumb.d.ts +14 -0
- package/dist/navigation/breadcrumb.d.ts.map +1 -0
- package/dist/navigation/breadcrumb.js +24 -0
- package/dist/navigation/index.d.ts +17 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +30 -0
- package/dist/navigation/key-hints.d.ts +11 -0
- package/dist/navigation/key-hints.d.ts.map +1 -0
- package/dist/navigation/key-hints.js +13 -0
- package/dist/navigation/menu.d.ts +35 -0
- package/dist/navigation/menu.d.ts.map +1 -0
- package/dist/navigation/menu.js +88 -0
- package/dist/navigation/status-bar.d.ts +18 -0
- package/dist/navigation/status-bar.d.ts.map +1 -0
- package/dist/navigation/status-bar.js +12 -0
- package/dist/navigation/tabs.d.ts +20 -0
- package/dist/navigation/tabs.d.ts.map +1 -0
- package/dist/navigation/tabs.js +41 -0
- package/dist/navigation/toolbar.d.ts +14 -0
- package/dist/navigation/toolbar.d.ts.map +1 -0
- package/dist/navigation/toolbar.js +10 -0
- package/dist/navigation/wizard.d.ts +15 -0
- package/dist/navigation/wizard.d.ts.map +1 -0
- package/dist/navigation/wizard.js +17 -0
- package/dist/overlay/command-palette.d.ts +63 -0
- package/dist/overlay/command-palette.d.ts.map +1 -0
- package/dist/overlay/command-palette.js +415 -0
- package/dist/overlay/dialog.d.ts +16 -0
- package/dist/overlay/dialog.d.ts.map +1 -0
- package/dist/overlay/dialog.js +44 -0
- package/dist/overlay/index.d.ts +84 -0
- package/dist/overlay/index.d.ts.map +1 -0
- package/dist/overlay/index.js +157 -0
- package/dist/overlay/layer-scope.d.ts +21 -0
- package/dist/overlay/layer-scope.d.ts.map +1 -0
- package/dist/overlay/layer-scope.js +5 -0
- package/dist/overlay/path-picker.d.ts +14 -0
- package/dist/overlay/path-picker.d.ts.map +1 -0
- package/dist/overlay/path-picker.js +200 -0
- package/dist/overlay/prompt-dialog.d.ts +12 -0
- package/dist/overlay/prompt-dialog.d.ts.map +1 -0
- package/dist/overlay/prompt-dialog.js +29 -0
- package/dist/overlay/shared.d.ts +17 -0
- package/dist/overlay/shared.d.ts.map +1 -0
- package/dist/overlay/shared.js +17 -0
- package/dist/overlay/toast-host.d.ts +8 -0
- package/dist/overlay/toast-host.d.ts.map +1 -0
- package/dist/overlay/toast-host.js +19 -0
- package/dist/overlay/toast.d.ts +9 -0
- package/dist/overlay/toast.d.ts.map +1 -0
- package/dist/overlay/toast.js +20 -0
- package/dist/overlay/tooltip.d.ts +6 -0
- package/dist/overlay/tooltip.d.ts.map +1 -0
- package/dist/overlay/tooltip.js +11 -0
- package/dist/panel/index.d.ts +79 -0
- package/dist/panel/index.d.ts.map +1 -0
- package/dist/panel/index.js +256 -0
- package/dist/panel/resource-panel.d.ts +93 -0
- package/dist/panel/resource-panel.d.ts.map +1 -0
- package/dist/panel/resource-panel.js +213 -0
- package/dist/panel/shared.d.ts +6 -0
- package/dist/panel/shared.d.ts.map +1 -0
- package/dist/panel/shared.js +4 -0
- package/dist/shells/console-shell.d.ts +8 -0
- package/dist/shells/console-shell.d.ts.map +1 -0
- package/dist/shells/console-shell.js +30 -0
- package/dist/shells/index.d.ts +20 -0
- package/dist/shells/index.d.ts.map +1 -0
- package/dist/shells/index.js +61 -0
- package/dist/shells/paper-shell.d.ts +9 -0
- package/dist/shells/paper-shell.d.ts.map +1 -0
- package/dist/shells/paper-shell.js +23 -0
- package/dist/shells/plain-shell.d.ts +3 -0
- package/dist/shells/plain-shell.d.ts.map +1 -0
- package/dist/shells/plain-shell.js +11 -0
- package/dist/shells/shared.d.ts +12 -0
- package/dist/shells/shared.d.ts.map +1 -0
- package/dist/shells/shared.js +21 -0
- package/dist/shells/workbench-shell.d.ts +9 -0
- package/dist/shells/workbench-shell.d.ts.map +1 -0
- package/dist/shells/workbench-shell.js +43 -0
- package/dist/surface/bar-layout.d.ts +4 -0
- package/dist/surface/bar-layout.d.ts.map +1 -0
- package/dist/surface/bar-layout.js +7 -0
- package/dist/surface/index.d.ts +58 -0
- package/dist/surface/index.d.ts.map +1 -0
- package/dist/surface/index.js +48 -0
- package/dist/surface/inline-layout.d.ts +4 -0
- package/dist/surface/inline-layout.d.ts.map +1 -0
- package/dist/surface/inline-layout.js +7 -0
- package/dist/surface/mount-view.d.ts +7 -0
- package/dist/surface/mount-view.d.ts.map +1 -0
- package/dist/surface/mount-view.js +19 -0
- package/dist/surface/rail-layout.d.ts +4 -0
- package/dist/surface/rail-layout.d.ts.map +1 -0
- package/dist/surface/rail-layout.js +19 -0
- package/dist/surface/shared.d.ts +7 -0
- package/dist/surface/shared.d.ts.map +1 -0
- package/dist/surface/shared.js +1 -0
- package/dist/surface/single-layout.d.ts +4 -0
- package/dist/surface/single-layout.d.ts.map +1 -0
- package/dist/surface/single-layout.js +18 -0
- package/dist/surface/split-layout.d.ts +4 -0
- package/dist/surface/split-layout.d.ts.map +1 -0
- package/dist/surface/split-layout.js +7 -0
- package/dist/surface/stack-layout.d.ts +4 -0
- package/dist/surface/stack-layout.d.ts.map +1 -0
- package/dist/surface/stack-layout.js +22 -0
- package/dist/surface/surface-area.d.ts +16 -0
- package/dist/surface/surface-area.d.ts.map +1 -0
- package/dist/surface/surface-area.js +32 -0
- package/dist/surface/tabs-layout.d.ts +4 -0
- package/dist/surface/tabs-layout.d.ts.map +1 -0
- package/dist/surface/tabs-layout.js +24 -0
- package/dist/tone.d.ts +16 -0
- package/dist/tone.d.ts.map +1 -0
- package/dist/tone.js +45 -0
- package/dist/viewport.d.ts +30 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +17 -0
- package/package.json +54 -0
- package/src/chart/area-chart.ts +9 -0
- package/src/chart/bar-chart.ts +74 -0
- package/src/chart/gauge.ts +60 -0
- package/src/chart/heatmap.ts +49 -0
- package/src/chart/histogram.ts +36 -0
- package/src/chart/index.ts +36 -0
- package/src/chart/line-chart.ts +100 -0
- package/src/chart/shared.ts +112 -0
- package/src/chart/sparkline.ts +46 -0
- package/src/control/button.ts +184 -0
- package/src/control/checkbox.ts +48 -0
- package/src/control/index.ts +40 -0
- package/src/control/radio-group.ts +60 -0
- package/src/control/search-box.ts +15 -0
- package/src/control/select.ts +230 -0
- package/src/control/shared.ts +41 -0
- package/src/control/slider.ts +63 -0
- package/src/control/switch.ts +39 -0
- package/src/control/text-area.ts +390 -0
- package/src/control/text-input.ts +150 -0
- package/src/data/code-viewer.ts +337 -0
- package/src/data/feed.ts +204 -0
- package/src/data/index.ts +243 -0
- package/src/data/list.ts +149 -0
- package/src/data/log-viewer.ts +105 -0
- package/src/data/markdown-view.ts +182 -0
- package/src/data/pagination.ts +31 -0
- package/src/data/scroll-thumb.ts +40 -0
- package/src/data/shared.ts +23 -0
- package/src/data/tree.ts +215 -0
- package/src/decorations.ts +176 -0
- package/src/display/alert.ts +28 -0
- package/src/display/badge.ts +40 -0
- package/src/display/card.ts +23 -0
- package/src/display/empty-state.ts +21 -0
- package/src/display/error-state.ts +23 -0
- package/src/display/heading.ts +17 -0
- package/src/display/index.ts +55 -0
- package/src/display/key-value.ts +32 -0
- package/src/display/label.ts +11 -0
- package/src/display/marquee.ts +88 -0
- package/src/display/progress.ts +92 -0
- package/src/display/skeleton.ts +34 -0
- package/src/display/spinner.ts +20 -0
- package/src/display/status-dot.ts +24 -0
- package/src/display/timeline.ts +47 -0
- package/src/find.ts +138 -0
- package/src/form/danger-zone.ts +34 -0
- package/src/form/field.ts +61 -0
- package/src/form/form-actions.ts +35 -0
- package/src/form/form-section.ts +16 -0
- package/src/form/form.ts +13 -0
- package/src/form/index.ts +191 -0
- package/src/form/shared.ts +36 -0
- package/src/index.ts +63 -0
- package/src/layout/center.ts +17 -0
- package/src/layout/column.ts +6 -0
- package/src/layout/divider.ts +31 -0
- package/src/layout/grid.ts +34 -0
- package/src/layout/index.ts +39 -0
- package/src/layout/panel.ts +114 -0
- package/src/layout/row.ts +11 -0
- package/src/layout/scroll-view.ts +115 -0
- package/src/layout/splitter.ts +24 -0
- package/src/layout/stack.ts +12 -0
- package/src/navigation/breadcrumb.ts +43 -0
- package/src/navigation/index.ts +33 -0
- package/src/navigation/key-hints.ts +25 -0
- package/src/navigation/menu.ts +134 -0
- package/src/navigation/status-bar.ts +43 -0
- package/src/navigation/tabs.ts +73 -0
- package/src/navigation/toolbar.ts +25 -0
- package/src/navigation/wizard.ts +43 -0
- package/src/overlay/command-palette.ts +485 -0
- package/src/overlay/dialog.ts +65 -0
- package/src/overlay/index.ts +232 -0
- package/src/overlay/layer-scope.ts +26 -0
- package/src/overlay/path-picker.ts +215 -0
- package/src/overlay/prompt-dialog.ts +45 -0
- package/src/overlay/shared.ts +19 -0
- package/src/overlay/toast-host.ts +30 -0
- package/src/overlay/toast.ts +35 -0
- package/src/overlay/tooltip.ts +17 -0
- package/src/panel/index.ts +317 -0
- package/src/panel/resource-panel.ts +304 -0
- package/src/panel/shared.ts +8 -0
- package/src/shells/console-shell.ts +47 -0
- package/src/shells/index.ts +66 -0
- package/src/shells/paper-shell.ts +31 -0
- package/src/shells/plain-shell.ts +19 -0
- package/src/shells/shared.ts +24 -0
- package/src/shells/workbench-shell.ts +62 -0
- package/src/surface/bar-layout.ts +11 -0
- package/src/surface/index.ts +53 -0
- package/src/surface/inline-layout.ts +11 -0
- package/src/surface/mount-view.ts +30 -0
- package/src/surface/rail-layout.ts +27 -0
- package/src/surface/shared.ts +7 -0
- package/src/surface/single-layout.ts +25 -0
- package/src/surface/split-layout.ts +11 -0
- package/src/surface/stack-layout.ts +32 -0
- package/src/surface/surface-area.ts +46 -0
- package/src/surface/tabs-layout.ts +30 -0
- package/src/tone.ts +48 -0
- package/src/viewport.ts +45 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ComponentDefinition } from '@textui/core';
|
|
2
|
+
import { Alert } from './alert.js';
|
|
3
|
+
import { Badge } from './badge.js';
|
|
4
|
+
import { Card } from './card.js';
|
|
5
|
+
import { EmptyState } from './empty-state.js';
|
|
6
|
+
import { ErrorState } from './error-state.js';
|
|
7
|
+
import { Heading } from './heading.js';
|
|
8
|
+
import { KeyValue } from './key-value.js';
|
|
9
|
+
import { Label } from './label.js';
|
|
10
|
+
import { Marquee } from './marquee.js';
|
|
11
|
+
import { Progress } from './progress.js';
|
|
12
|
+
import { Skeleton } from './skeleton.js';
|
|
13
|
+
import { Spinner } from './spinner.js';
|
|
14
|
+
import { StatusDot } from './status-dot.js';
|
|
15
|
+
import { Timeline } from './timeline.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Display components.
|
|
19
|
+
*
|
|
20
|
+
* The rule that shapes all of them: **meaning never depends on colour alone.**
|
|
21
|
+
* A degraded service is a different glyph as well as a different colour,
|
|
22
|
+
* because a 16-colour ssh session, a colourblind reader and a piped log all
|
|
23
|
+
* lose the colour and keep the glyph.
|
|
24
|
+
*/
|
|
25
|
+
export * from './alert.js';
|
|
26
|
+
export * from './badge.js';
|
|
27
|
+
export * from './card.js';
|
|
28
|
+
export * from './empty-state.js';
|
|
29
|
+
export * from './error-state.js';
|
|
30
|
+
export * from './heading.js';
|
|
31
|
+
export * from './key-value.js';
|
|
32
|
+
export * from './label.js';
|
|
33
|
+
export * from './marquee.js';
|
|
34
|
+
export * from './progress.js';
|
|
35
|
+
export * from './skeleton.js';
|
|
36
|
+
export * from './spinner.js';
|
|
37
|
+
export * from './status-dot.js';
|
|
38
|
+
export * from './timeline.js';
|
|
39
|
+
|
|
40
|
+
export const DISPLAY_COMPONENTS: ComponentDefinition[] = [
|
|
41
|
+
{ component: 'Heading', category: 'display', renderer: { kind: 'function', render: Heading }, role: 'heading', description: 'Section heading, three levels.' },
|
|
42
|
+
{ component: 'Label', category: 'display', renderer: { kind: 'function', render: Label }, role: 'label', description: 'Secondary text with a semantic tone.' },
|
|
43
|
+
{ component: 'Badge', category: 'display', renderer: { kind: 'function', render: Badge }, variants: ['solid', 'outline', 'ghost'], description: 'Small status marker; carries a glyph as well as a colour.' },
|
|
44
|
+
{ component: 'StatusDot', category: 'display', renderer: { kind: 'function', render: StatusDot }, role: 'status', description: 'The shared status vocabulary: up, degraded, down.' },
|
|
45
|
+
{ component: 'Alert', category: 'feedback', renderer: { kind: 'function', render: Alert }, role: 'alert', description: 'A message with an icon and a tone.' },
|
|
46
|
+
{ component: 'Card', category: 'display', renderer: { kind: 'function', render: Card }, description: 'Bordered block with a title.' },
|
|
47
|
+
{ component: 'KeyValue', category: 'data', renderer: { kind: 'function', render: KeyValue }, description: 'Aligned label/value pairs.' },
|
|
48
|
+
{ component: 'Progress', category: 'feedback', renderer: { kind: 'function', render: Progress }, role: 'progressbar', description: 'Determinate or indeterminate bar, sub-cell resolution.' },
|
|
49
|
+
{ component: 'Spinner', category: 'feedback', renderer: { kind: 'function', render: Spinner }, role: 'status', description: 'Animated activity indicator.' },
|
|
50
|
+
{ component: 'Marquee', category: 'display', renderer: { kind: 'function', render: Marquee }, role: 'marquee', description: 'Text too long for its box, read by sliding it while it has the cursor.' },
|
|
51
|
+
{ component: 'Skeleton', category: 'feedback', renderer: { kind: 'function', render: Skeleton }, description: 'Loading placeholder.' },
|
|
52
|
+
{ component: 'EmptyState', category: 'feedback', renderer: { kind: 'function', render: EmptyState }, description: 'Nothing here, and what to do about it.' },
|
|
53
|
+
{ component: 'ErrorState', category: 'feedback', renderer: { kind: 'function', render: ErrorState }, role: 'alert', description: 'A failure, with its message.' },
|
|
54
|
+
{ component: 'Timeline', category: 'data', renderer: { kind: 'function', render: Timeline }, description: 'Ordered events down a rail.' },
|
|
55
|
+
];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, stringWidth } from '@textui/core';
|
|
3
|
+
import { TONE as TONE_COLOR } from '../tone.js';
|
|
4
|
+
|
|
5
|
+
export interface KeyValueProps extends BoxProps {
|
|
6
|
+
items: { label: string; value: string; tone?: SemanticVariant }[];
|
|
7
|
+
/** Cells reserved for labels. Computed from the longest when unset. */
|
|
8
|
+
labelWidth?: number;
|
|
9
|
+
columns?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Structured data as aligned label/value pairs. */
|
|
13
|
+
export const KeyValue = defineComponent<KeyValueProps>('KeyValue', (props) => {
|
|
14
|
+
const { items, labelWidth, columns = 1, ...rest } = props;
|
|
15
|
+
const width = labelWidth ?? Math.max(0, ...items.map((i) => stringWidth(i.label)));
|
|
16
|
+
|
|
17
|
+
const rows: typeof items[] = [];
|
|
18
|
+
for (let i = 0; i < items.length; i += columns) rows.push(items.slice(i, i + columns));
|
|
19
|
+
|
|
20
|
+
return h('box', { direction: 'column', ...rest },
|
|
21
|
+
...rows.map((row, i) =>
|
|
22
|
+
h('box', { key: i, direction: 'row', gap: 2 },
|
|
23
|
+
...row.map((item, j) =>
|
|
24
|
+
h('box', { key: j, direction: 'row', gap: 1, flex: 1 },
|
|
25
|
+
h('box', { width }, h('text', { content: item.label, fg: 'muted' })),
|
|
26
|
+
h('text', { content: item.value, fg: item.tone ? TONE_COLOR[item.tone] : undefined, truncate: 'end', flex: 1 }),
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SemanticVariant, TextProps } from '@textui/core';
|
|
2
|
+
import { defineComponent, h } from '@textui/core';
|
|
3
|
+
import { TONE as TONE_COLOR } from '../tone.js';
|
|
4
|
+
|
|
5
|
+
export interface LabelProps extends TextProps {
|
|
6
|
+
tone?: SemanticVariant;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Label = defineComponent<LabelProps>('Label', ({ tone = 'muted', ...props }) =>
|
|
10
|
+
h('text', { role: 'label', fg: TONE_COLOR[tone], ...props }),
|
|
11
|
+
);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { TextProps } from '@textui/core';
|
|
2
|
+
import {
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
stringWidth,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMeasure,
|
|
8
|
+
useRef,
|
|
9
|
+
useRuntime,
|
|
10
|
+
useState,
|
|
11
|
+
useTicker,
|
|
12
|
+
} from '@textui/core';
|
|
13
|
+
|
|
14
|
+
export interface MarqueeProps extends TextProps {
|
|
15
|
+
content: string;
|
|
16
|
+
/**
|
|
17
|
+
* Slide, or rest.
|
|
18
|
+
*
|
|
19
|
+
* Off is the resting state and the common one: a list of twenty rows is
|
|
20
|
+
* twenty still labels, and the one under the cursor is the one that moves.
|
|
21
|
+
* It is also what keeps the cost honest - a marquee that is not sliding
|
|
22
|
+
* holds no ticker at all, so a menu is one animation and not one per row.
|
|
23
|
+
*/
|
|
24
|
+
active?: boolean;
|
|
25
|
+
/** Cells a second. */
|
|
26
|
+
speed?: number;
|
|
27
|
+
/** How long it waits at each end, in milliseconds. */
|
|
28
|
+
dwell?: number;
|
|
29
|
+
fps?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Text too long for its box, read by sliding it.
|
|
34
|
+
*
|
|
35
|
+
* The alternative was making something else give up room, and in a list there
|
|
36
|
+
* is nothing else to take it from: a row is a label and a description and both
|
|
37
|
+
* of them are the answer to "what is this". Truncating is fine for the rows you
|
|
38
|
+
* are scanning past and useless for the one you have stopped on - which is
|
|
39
|
+
* exactly the row a marquee costs anything for.
|
|
40
|
+
*
|
|
41
|
+
* Rests at the start, slides to the end, waits, and comes back. Not a loop
|
|
42
|
+
* that wraps around: text that reappears from the right while its own tail is
|
|
43
|
+
* still leaving reads as two strings rather than one long one.
|
|
44
|
+
*
|
|
45
|
+
* Nothing moves where animation is off - a pipe, a CI log, a reader who asked
|
|
46
|
+
* for stillness - and there it is an ordinary truncated label.
|
|
47
|
+
*/
|
|
48
|
+
export const Marquee = defineComponent<MarqueeProps>('Marquee', (props) => {
|
|
49
|
+
const { content, active = false, speed = 8, dwell = 900, fps = 10, ...rest } = props;
|
|
50
|
+
const runtime = useRuntime();
|
|
51
|
+
// What the last layout gave this text. Zero on the first pass, which is the
|
|
52
|
+
// one where nothing is known and the honest answer is the whole string.
|
|
53
|
+
const width = useMeasure().width;
|
|
54
|
+
const travel = width > 0 ? Math.max(0, stringWidth(content) - width) : 0;
|
|
55
|
+
const sliding = active && travel > 0 && !runtime.animation.disabled;
|
|
56
|
+
|
|
57
|
+
// The ticker's own frame number, not a count of callbacks. A clock driven
|
|
58
|
+
// by hand - a test, a static render - advances several frames in one call,
|
|
59
|
+
// and a counter that adds one per call runs at whatever rate it was driven
|
|
60
|
+
// at rather than at the rate it asked for.
|
|
61
|
+
const [frame, setFrame] = useState(0);
|
|
62
|
+
const seen = useRef(0);
|
|
63
|
+
const from = useRef(0);
|
|
64
|
+
useTicker((at) => { seen.current = at; setFrame(at - from.current); }, { fps, enabled: sliding });
|
|
65
|
+
// Back to the start whenever it stops or the words change, so the row you
|
|
66
|
+
// leave is the row you saw when you arrived at it.
|
|
67
|
+
useEffect(() => { from.current = seen.current; setFrame(0); }, [sliding, content]);
|
|
68
|
+
|
|
69
|
+
if (!sliding) return h('text', { content, truncate: 'end', ...rest });
|
|
70
|
+
|
|
71
|
+
const perCell = Math.max(1, Math.round(fps / speed));
|
|
72
|
+
const hold = Math.max(1, Math.round((dwell / 1000) * fps));
|
|
73
|
+
const slide = travel * perCell;
|
|
74
|
+
const cycle = hold + slide + hold + slide;
|
|
75
|
+
const at = frame % cycle;
|
|
76
|
+
const offset = at < hold
|
|
77
|
+
? 0
|
|
78
|
+
: at < hold + slide
|
|
79
|
+
? Math.floor((at - hold) / perCell)
|
|
80
|
+
: at < hold + slide + hold
|
|
81
|
+
? travel
|
|
82
|
+
: travel - Math.floor((at - hold - slide - hold) / perCell);
|
|
83
|
+
|
|
84
|
+
// Pinned to what it measured. Without this the sliced string is narrower
|
|
85
|
+
// than the whole one, the layout hands the box back a different width, and
|
|
86
|
+
// the next frame measures something new - a row that shivers.
|
|
87
|
+
return h('text', { content: content.slice(offset, offset + width), width, ...rest });
|
|
88
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useFrame, useTheme } from '@textui/core';
|
|
3
|
+
import { TONE as TONE_COLOR } from '../tone.js';
|
|
4
|
+
|
|
5
|
+
export interface ProgressProps extends BoxProps {
|
|
6
|
+
/** 0..1. Omit for an indeterminate bar. */
|
|
7
|
+
value?: number;
|
|
8
|
+
total?: number;
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Show the percentage after the bar. */
|
|
11
|
+
showValue?: boolean;
|
|
12
|
+
tone?: SemanticVariant;
|
|
13
|
+
barWidth?: number;
|
|
14
|
+
/**
|
|
15
|
+
* A fixed gutter for the label, so a stack of bars starts at one column.
|
|
16
|
+
*
|
|
17
|
+
* Labels are their own width otherwise, which is right for one bar and
|
|
18
|
+
* wrong for three: "download", "index" and "working" each push their track
|
|
19
|
+
* to a different place and the group reads as three unrelated widgets.
|
|
20
|
+
* Nothing here can measure its siblings, so whoever stacks them says.
|
|
21
|
+
*/
|
|
22
|
+
labelWidth?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Push the bar away from the label, to the right edge of the row.
|
|
25
|
+
*
|
|
26
|
+
* The label's cell stretches, so it stays at the left and
|
|
27
|
+
* the track ends up hard against the right - which is what makes a column of
|
|
28
|
+
* these read as a table rather than as a ragged stack.
|
|
29
|
+
*
|
|
30
|
+
* Different from `labelWidth`, which pads the *label* to a fixed gutter: use
|
|
31
|
+
* that when the bars should start at one column, and this when they should
|
|
32
|
+
* end at one. Needs a row wider than its contents to have any effect, so the
|
|
33
|
+
* caller has to have given it a width or a `flex`.
|
|
34
|
+
*/
|
|
35
|
+
spacer?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A progress bar with sub-cell resolution: the partial block glyphs give eight
|
|
40
|
+
* steps per cell, so a 20-cell bar moves smoothly rather than in 5% jumps.
|
|
41
|
+
*/
|
|
42
|
+
export const Progress = defineComponent<ProgressProps>('Progress', (props) => {
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
const {
|
|
45
|
+
value, total = 1, label, showValue = true, tone = 'primary', barWidth, labelWidth,
|
|
46
|
+
spacer, ...rest
|
|
47
|
+
} = props;
|
|
48
|
+
const frame = useFrame(8);
|
|
49
|
+
|
|
50
|
+
const width = barWidth ?? 20;
|
|
51
|
+
const ratio = value === undefined ? 0 : Math.max(0, Math.min(1, value / total));
|
|
52
|
+
|
|
53
|
+
let bar: string;
|
|
54
|
+
if (value === undefined) {
|
|
55
|
+
// Indeterminate: a block that travels the track.
|
|
56
|
+
const pos = frame % Math.max(1, width);
|
|
57
|
+
bar = Array.from({ length: width }, (_, i) =>
|
|
58
|
+
Math.abs(i - pos) < 2 ? theme.glyphs.progressFull : theme.glyphs.progressEmpty,
|
|
59
|
+
).join('');
|
|
60
|
+
} else {
|
|
61
|
+
const exact = ratio * width;
|
|
62
|
+
const full = Math.floor(exact);
|
|
63
|
+
const partials = theme.glyphs.progressPartial;
|
|
64
|
+
const remainder = exact - full;
|
|
65
|
+
const partialIndex = Math.floor(remainder * partials.length);
|
|
66
|
+
const partial = full < width && remainder > 0 ? (partials[partialIndex] ?? '') : '';
|
|
67
|
+
bar =
|
|
68
|
+
theme.glyphs.progressFull.repeat(full) +
|
|
69
|
+
partial +
|
|
70
|
+
theme.glyphs.progressEmpty.repeat(Math.max(0, width - full - (partial ? 1 : 0)));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return h('box', { role: 'progressbar', label, direction: 'row', gap: 1, ...rest },
|
|
74
|
+
label !== undefined
|
|
75
|
+
? h('text', {
|
|
76
|
+
content: label,
|
|
77
|
+
fg: 'muted',
|
|
78
|
+
...(labelWidth === undefined ? {} : { width: labelWidth, truncate: 'end' as const }),
|
|
79
|
+
// The label's own cell stretches, rather than a `spacer` child going
|
|
80
|
+
// in beside it. A child would be free but its *gap* would not: with
|
|
81
|
+
// `gap: 1` on the row, one more child is one more cell, so a row
|
|
82
|
+
// sized exactly to its contents would truncate the bar to make room
|
|
83
|
+
// for a space. Stretching what is already there costs nothing.
|
|
84
|
+
...(spacer === true ? { flex: 1 } : {}),
|
|
85
|
+
})
|
|
86
|
+
: spacer === true ? h('spacer', {}) : null,
|
|
87
|
+
h('text', { content: bar, fg: TONE_COLOR[tone] }),
|
|
88
|
+
showValue && value !== undefined
|
|
89
|
+
? h('text', { content: `${Math.round(ratio * 100)}%`, fg: 'muted' })
|
|
90
|
+
: null,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
import { defineComponent, h } from '@textui/core';
|
|
3
|
+
|
|
4
|
+
export interface SkeletonProps extends BoxProps {
|
|
5
|
+
lines?: number;
|
|
6
|
+
/** Width of each line, in cells or as a fraction of the box. */
|
|
7
|
+
widths?: number[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The shape of content that has not arrived.
|
|
12
|
+
*
|
|
13
|
+
* Drawn in `borderStrong` rather than `borderSubtle`, and the difference is
|
|
14
|
+
* whether it is there at all: against the dark theme's canvas, subtle is
|
|
15
|
+
* 1.24:1 - the glyphs are in the buffer and nobody can see them, which is a
|
|
16
|
+
* placeholder that fails at the one thing it does. Strong is 2.28:1, still
|
|
17
|
+
* quiet enough not to read as content.
|
|
18
|
+
*
|
|
19
|
+
* `fg` overrides it, because how loud a placeholder should be depends on what
|
|
20
|
+
* it is standing in for.
|
|
21
|
+
*/
|
|
22
|
+
export const Skeleton = defineComponent<SkeletonProps>('Skeleton', ({ lines = 3, widths, fg, ...rest }) =>
|
|
23
|
+
h('box', { direction: 'column', gap: 0, ...rest },
|
|
24
|
+
...Array.from({ length: lines }, (_, i) =>
|
|
25
|
+
h('box', {
|
|
26
|
+
key: i,
|
|
27
|
+
height: 1,
|
|
28
|
+
width: widths?.[i] ?? (i === lines - 1 ? '60%' : '100%'),
|
|
29
|
+
fill: '░',
|
|
30
|
+
fg: fg ?? 'borderStrong',
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useFrame, useTheme } from '@textui/core';
|
|
3
|
+
import { TONE as TONE_COLOR } from '../tone.js';
|
|
4
|
+
|
|
5
|
+
export interface SpinnerProps extends BoxProps {
|
|
6
|
+
label?: string;
|
|
7
|
+
tone?: SemanticVariant;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Spinner = defineComponent<SpinnerProps>('Spinner', ({ label, tone = 'accent', ...rest }) => {
|
|
11
|
+
const theme = useTheme();
|
|
12
|
+
const frame = useFrame(10);
|
|
13
|
+
const frames = theme.glyphs.spinner;
|
|
14
|
+
const glyph = frames[frame % frames.length] ?? frames[0] ?? '*';
|
|
15
|
+
|
|
16
|
+
return h('box', { role: 'status', direction: 'row', gap: 1, ...rest },
|
|
17
|
+
h('text', { content: glyph, fg: TONE_COLOR[tone] }),
|
|
18
|
+
label ? h('text', { content: label }) : null,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BoxProps, StyleColor } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useTheme } from '@textui/core';
|
|
3
|
+
|
|
4
|
+
export interface StatusDotProps extends BoxProps {
|
|
5
|
+
status: 'up' | 'down' | 'degraded' | 'unknown' | 'pending';
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** The status vocabulary, so "degraded" looks the same everywhere. */
|
|
10
|
+
export const StatusDot = defineComponent<StatusDotProps>('StatusDot', ({ status, label, ...rest }) => {
|
|
11
|
+
const theme = useTheme();
|
|
12
|
+
const map = {
|
|
13
|
+
up: { glyph: theme.glyphs.bulletFilled, fg: 'success' as StyleColor },
|
|
14
|
+
degraded: { glyph: theme.glyphs.bulletHalf, fg: 'warning' as StyleColor },
|
|
15
|
+
down: { glyph: theme.glyphs.bulletHollow, fg: 'danger' as StyleColor },
|
|
16
|
+
pending: { glyph: theme.glyphs.bulletHollow, fg: 'info' as StyleColor },
|
|
17
|
+
unknown: { glyph: theme.glyphs.separator, fg: 'muted' as StyleColor },
|
|
18
|
+
}[status];
|
|
19
|
+
|
|
20
|
+
return h('box', { role: 'status', label: label ?? status, direction: 'row', gap: 1, ...rest },
|
|
21
|
+
h('text', { content: map.glyph, fg: map.fg }),
|
|
22
|
+
label ? h('text', { content: label }) : null,
|
|
23
|
+
);
|
|
24
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useTheme } from '@textui/core';
|
|
3
|
+
import { TONE as TONE_COLOR } from '../tone.js';
|
|
4
|
+
|
|
5
|
+
export interface TimelineProps extends BoxProps {
|
|
6
|
+
items: {
|
|
7
|
+
time?: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
tone?: SemanticVariant;
|
|
11
|
+
icon?: string;
|
|
12
|
+
}[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Timeline = defineComponent<TimelineProps>('Timeline', ({ items, ...rest }) => {
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
return h('box', { direction: 'column', ...rest },
|
|
18
|
+
...items.map((item, i) =>
|
|
19
|
+
h('box', { key: i, direction: 'row', gap: 1 },
|
|
20
|
+
// The gutter is as tall as the entry beside it, not two rows.
|
|
21
|
+
//
|
|
22
|
+
// The connector used to be a single `text`, so an entry whose
|
|
23
|
+
// description wrapped - or which had a description at all, in a narrow
|
|
24
|
+
// column - got one cell of line under its bullet and then a gap, and
|
|
25
|
+
// the thread that makes a timeline a timeline broke at every long
|
|
26
|
+
// entry. `fill` paints the glyph into every cell it is given and
|
|
27
|
+
// `flex` gives it whatever the row turned out to be.
|
|
28
|
+
h('box', { direction: 'column', width: 1, alignSelf: 'stretch' },
|
|
29
|
+
h('text', { content: item.icon ?? theme.glyphs.bulletFilled, fg: item.tone ? TONE_COLOR[item.tone] : 'accent' }),
|
|
30
|
+
i < items.length - 1
|
|
31
|
+
// `minHeight` as well as `flex`: an entry with nothing but a
|
|
32
|
+
// title is one row tall, so a purely elastic connector would get
|
|
33
|
+
// no rows at all and two short entries would sit against each
|
|
34
|
+
// other with no thread between them. One row is the floor, and
|
|
35
|
+
// what a short entry has always had.
|
|
36
|
+
? h('box', { flex: 1, minHeight: 1, fill: theme.borderChars().left, fg: 'borderSubtle' })
|
|
37
|
+
: null),
|
|
38
|
+
h('box', { direction: 'column', flex: 1 },
|
|
39
|
+
h('box', { direction: 'row', gap: 1 },
|
|
40
|
+
h('text', { content: item.title, bold: true }),
|
|
41
|
+
item.time ? h('spacer', { flex: 1 }) : null,
|
|
42
|
+
item.time ? h('text', { content: item.time, fg: 'muted' }) : null),
|
|
43
|
+
item.description ? h('text', { content: item.description, fg: 'muted', wrap: 'word' }) : null),
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
});
|
package/src/find.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { BindingPath, ReactiveStore } from '@textui/core';
|
|
2
|
+
import { useStoreValue } from '@textui/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Finding things in text.
|
|
6
|
+
*
|
|
7
|
+
* The query lives in the store rather than in whatever component is showing
|
|
8
|
+
* the text, for the usual reason: a search box, a status bar counting matches
|
|
9
|
+
* and the viewer painting them are three things that have to agree, and the
|
|
10
|
+
* only way three things agree is by reading one answer.
|
|
11
|
+
*
|
|
12
|
+
* The matching itself is here and not in the editor because a viewer wants it
|
|
13
|
+
* too, and so will a results panel: given a string and a query, which spans
|
|
14
|
+
* matched. Nothing here knows what a caret is.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const FIND_ROOT = '$/ui/find';
|
|
18
|
+
export const FIND_QUERY = `${FIND_ROOT}/query` as BindingPath;
|
|
19
|
+
export const FIND_CASE = `${FIND_ROOT}/matchCase` as BindingPath;
|
|
20
|
+
/** Bumped to ask whoever is showing the text to move to the next match. */
|
|
21
|
+
export const FIND_STEP = `${FIND_ROOT}/step` as BindingPath;
|
|
22
|
+
|
|
23
|
+
export interface FindQuery {
|
|
24
|
+
text: string;
|
|
25
|
+
matchCase?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Where one match is: the line it is on, and the columns it covers. */
|
|
29
|
+
export interface Match {
|
|
30
|
+
line: number;
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every match in a document, in reading order.
|
|
37
|
+
*
|
|
38
|
+
* Overlapping matches are not a thing: a search steps past what it found, so
|
|
39
|
+
* `aa` in `aaaa` is two matches and not three. That is what every editor does
|
|
40
|
+
* and what "next" means when you press it twice.
|
|
41
|
+
*/
|
|
42
|
+
export function findMatches(text: string, query: FindQuery): Match[] {
|
|
43
|
+
const needle = query.matchCase === true ? query.text : query.text.toLowerCase();
|
|
44
|
+
if (needle === '') return [];
|
|
45
|
+
|
|
46
|
+
const out: Match[] = [];
|
|
47
|
+
const lines = text.split('\n');
|
|
48
|
+
for (let line = 0; line < lines.length; line++) {
|
|
49
|
+
const raw = lines[line] as string;
|
|
50
|
+
const haystack = query.matchCase === true ? raw : raw.toLowerCase();
|
|
51
|
+
let at = haystack.indexOf(needle);
|
|
52
|
+
while (at !== -1) {
|
|
53
|
+
out.push({ line, start: at, end: at + needle.length });
|
|
54
|
+
at = haystack.indexOf(needle, at + needle.length);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The match a caret should go to next, wrapping at the end.
|
|
62
|
+
*
|
|
63
|
+
* From *after* the caret, not from the caret: pressing next while sitting on a
|
|
64
|
+
* match has to move, or "next" is a key that does nothing. Backwards is the
|
|
65
|
+
* mirror, and both wrap, because a search that stops at the end of the file is
|
|
66
|
+
* a search you have to restart by hand.
|
|
67
|
+
*/
|
|
68
|
+
export function stepMatch(
|
|
69
|
+
matches: Match[],
|
|
70
|
+
from: { line: number; column: number },
|
|
71
|
+
direction: 1 | -1,
|
|
72
|
+
): number {
|
|
73
|
+
if (matches.length === 0) return -1;
|
|
74
|
+
|
|
75
|
+
if (direction === 1) {
|
|
76
|
+
const found = matches.findIndex(
|
|
77
|
+
(m) => m.line > from.line || (m.line === from.line && m.start > from.column),
|
|
78
|
+
);
|
|
79
|
+
return found === -1 ? 0 : found;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
* Backwards, from *before* the caret - and the caret is usually sitting at
|
|
84
|
+
* the end of the match it just stepped to, so "before" has to mean the match
|
|
85
|
+
* *ends* before it. Comparing starts instead finds the match the caret is
|
|
86
|
+
* standing in and calls that the previous one, which is a key that does
|
|
87
|
+
* nothing every other press.
|
|
88
|
+
*/
|
|
89
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
90
|
+
const m = matches[i] as Match;
|
|
91
|
+
if (m.line < from.line || (m.line === from.line && m.end < from.column)) return i;
|
|
92
|
+
}
|
|
93
|
+
return matches.length - 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Which match the caret is sitting inside, or -1. */
|
|
97
|
+
export function matchAt(matches: Match[], at: { line: number; column: number }): number {
|
|
98
|
+
return matches.findIndex(
|
|
99
|
+
(m) => m.line === at.line && at.column >= m.start && at.column <= m.end,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function readQuery(store: ReactiveStore): FindQuery {
|
|
104
|
+
return {
|
|
105
|
+
text: store.get<string>(FIND_QUERY) ?? '',
|
|
106
|
+
matchCase: store.get<boolean>(FIND_CASE) === true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function setQuery(store: ReactiveStore, text: string): void {
|
|
111
|
+
store.set(FIND_QUERY, text);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Ask whoever is showing the text to move.
|
|
116
|
+
*
|
|
117
|
+
* A counter rather than a command, so the thing that moves is the thing that
|
|
118
|
+
* knows where the caret is - and so two panels showing the same file each move
|
|
119
|
+
* their own.
|
|
120
|
+
*/
|
|
121
|
+
export function stepFind(store: ReactiveStore, direction: 1 | -1): void {
|
|
122
|
+
const at = store.get<{ n: number }>(FIND_STEP);
|
|
123
|
+
store.set(FIND_STEP, { n: (at?.n ?? 0) + 1, direction });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface FindHandle {
|
|
127
|
+
query: FindQuery;
|
|
128
|
+
/** Changes when somebody asks for the next match. */
|
|
129
|
+
step: { n: number; direction: 1 | -1 } | undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The current query, and the ask to move, for a component showing text. */
|
|
133
|
+
export function useFind(): FindHandle {
|
|
134
|
+
const text = useStoreValue<string>(FIND_QUERY, '') ?? '';
|
|
135
|
+
const matchCase = useStoreValue<boolean>(FIND_CASE, false) === true;
|
|
136
|
+
const step = useStoreValue<{ n: number; direction: 1 | -1 }>(FIND_STEP);
|
|
137
|
+
return { query: { text, matchCase }, step };
|
|
138
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useTheme } from '@textui/core';
|
|
3
|
+
import { Button } from '../control/index.js';
|
|
4
|
+
|
|
5
|
+
export interface DangerZoneProps extends BoxProps {
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
actionLabel: string;
|
|
9
|
+
onAction?(): void;
|
|
10
|
+
/** Require typing this exact text before the action is enabled. */
|
|
11
|
+
confirmText?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DangerZone = defineComponent<DangerZoneProps>('DangerZone', (props) => {
|
|
15
|
+
const theme = useTheme();
|
|
16
|
+
const {
|
|
17
|
+
title = 'Danger zone', description, actionLabel, onAction, confirmText, ...rest
|
|
18
|
+
} = props;
|
|
19
|
+
|
|
20
|
+
return h('box', {
|
|
21
|
+
border: { style: theme.border, color: 'danger' },
|
|
22
|
+
padding: [0, 1],
|
|
23
|
+
direction: 'column',
|
|
24
|
+
title: ` ${title} `,
|
|
25
|
+
...rest,
|
|
26
|
+
},
|
|
27
|
+
description ? h('text', { content: description, fg: 'muted', wrap: 'word' }) : null,
|
|
28
|
+
confirmText
|
|
29
|
+
? h('text', { content: `Type "${confirmText}" to confirm.`, fg: 'subtle' })
|
|
30
|
+
: null,
|
|
31
|
+
h('box', { direction: 'row', justify: 'end' },
|
|
32
|
+
h(Button, { label: actionLabel, tone: 'danger', variant: 'outline', onPress: onAction })),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useTheme } from '@textui/core';
|
|
3
|
+
import { useFormContext } from './shared.js';
|
|
4
|
+
|
|
5
|
+
export interface FieldProps extends BoxProps {
|
|
6
|
+
name: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
hint?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
/** Cells reserved for the label, so a column of fields lines up. */
|
|
11
|
+
labelWidth?: number;
|
|
12
|
+
/** Label above the control rather than beside it. */
|
|
13
|
+
stacked?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A labelled control with its error.
|
|
18
|
+
*
|
|
19
|
+
* The error line is always reserved when one could appear, so a form does not
|
|
20
|
+
* jump a row every time validation fires.
|
|
21
|
+
*/
|
|
22
|
+
export const Field = defineComponent<FieldProps>('Field', (props) => {
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
const form = useFormContext();
|
|
25
|
+
const { name, label, hint, required, labelWidth, stacked, children, ...rest } = props;
|
|
26
|
+
const error = form.errorFor(name);
|
|
27
|
+
|
|
28
|
+
const labelNode = label
|
|
29
|
+
? h('box', { direction: 'row', gap: 0, width: stacked ? undefined : labelWidth },
|
|
30
|
+
h('text', { content: label, fg: error ? 'danger' : 'muted' }),
|
|
31
|
+
required ? h('text', { content: '*', fg: 'danger' }) : null)
|
|
32
|
+
: null;
|
|
33
|
+
|
|
34
|
+
return h('box', { direction: 'column', ...rest },
|
|
35
|
+
// Centred, so the label sits on the control's line of text rather than on
|
|
36
|
+
// the top edge of its frame.
|
|
37
|
+
//
|
|
38
|
+
// A row stretches its children, so a one-row label beside a three-row
|
|
39
|
+
// bordered input was drawn at the top of three rows - level with the
|
|
40
|
+
// border, one above the text it names. Borderless controls are one row
|
|
41
|
+
// and were fine, which is why a form mixing the two had half its labels
|
|
42
|
+
// aligned and half of them a row high.
|
|
43
|
+
h('box', {
|
|
44
|
+
direction: stacked ? 'column' : 'row',
|
|
45
|
+
gap: stacked ? 0 : 1,
|
|
46
|
+
...(stacked ? {} : { align: 'center' as const }),
|
|
47
|
+
},
|
|
48
|
+
labelNode,
|
|
49
|
+
h('box', { flex: 1 }, children)),
|
|
50
|
+
error
|
|
51
|
+
? h('box', { direction: 'row', gap: 1 },
|
|
52
|
+
stacked ? null : h('box', { width: labelWidth }),
|
|
53
|
+
h('text', { content: theme.glyphs.warning, fg: 'danger' }),
|
|
54
|
+
h('text', { content: error, fg: 'danger', wrap: 'word' }))
|
|
55
|
+
: hint
|
|
56
|
+
? h('box', { direction: 'row', gap: 1 },
|
|
57
|
+
stacked ? null : h('box', { width: labelWidth }),
|
|
58
|
+
h('text', { content: hint, fg: 'subtle' }))
|
|
59
|
+
: null,
|
|
60
|
+
);
|
|
61
|
+
});
|