@elabs-ai/components-ui 4.0.0 → 4.2.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/README.md +10 -8
- package/dist/chunk-FCH4ZN6G.js +57 -0
- package/dist/chunk-FCH4ZN6G.js.map +1 -0
- package/dist/form.d.ts +28 -0
- package/dist/form.js +123 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +2597 -188
- package/dist/index.js +12855 -3578
- package/dist/index.js.map +1 -1
- package/dist/lib/cn.js +10 -1
- package/dist/lib/cn.js.map +1 -1
- package/package.json +21 -7
- package/src/__contract__/attribution-panel.contract.test.tsx +49 -0
- package/src/__contract__/badge.contract.test.tsx +49 -0
- package/src/__contract__/bounded-number.contract.test.tsx +49 -0
- package/src/__contract__/button.contract.test.tsx +49 -0
- package/src/__contract__/color-picker.contract.test.tsx +49 -0
- package/src/__contract__/combobox.contract.test.tsx +49 -0
- package/src/__contract__/command-trigger.contract.test.tsx +49 -0
- package/src/__contract__/copyable-value.contract.test.tsx +49 -0
- package/src/__contract__/date-picker.contract.test.tsx +49 -0
- package/src/__contract__/date-range-picker.contract.test.tsx +49 -0
- package/src/__contract__/empty-state.contract.test.tsx +49 -0
- package/src/__contract__/error-state.contract.test.tsx +49 -0
- package/src/__contract__/icon-button.contract.test.tsx +49 -0
- package/src/__contract__/input.contract.test.tsx +49 -0
- package/src/__contract__/key-value-editor.contract.test.tsx +49 -0
- package/src/__contract__/list-editor.contract.test.tsx +49 -0
- package/src/__contract__/loading-state.contract.test.tsx +49 -0
- package/src/__contract__/metric-card.contract.test.tsx +49 -0
- package/src/__contract__/model-picker.contract.test.tsx +49 -0
- package/src/__contract__/nav-notifications.contract.test.tsx +49 -0
- package/src/__contract__/number-input.contract.test.tsx +49 -0
- package/src/__contract__/rating.contract.test.tsx +49 -0
- package/src/__contract__/schema-form.contract.test.tsx +49 -0
- package/src/__contract__/section-header.contract.test.tsx +49 -0
- package/src/__contract__/segmented-field.contract.test.tsx +49 -0
- package/src/__contract__/slider-number.contract.test.tsx +49 -0
- package/src/__contract__/status-badge.contract.test.tsx +49 -0
- package/src/__contract__/tag-input.contract.test.tsx +49 -0
- package/src/__contract__/text.contract.test.tsx +49 -0
- package/src/__contract__/textarea.contract.test.tsx +49 -0
- package/src/__contract__/timeline.contract.test.tsx +49 -0
- package/src/__contract__/toggle.contract.test.tsx +49 -0
- package/src/__contract__/transfer.contract.test.tsx +49 -0
- package/src/__contract__/tree-select.contract.test.tsx +49 -0
- package/src/__contract__/tree.contract.test.tsx +49 -0
- package/src/__contract__/virtual-select.contract.test.tsx +49 -0
- package/src/__contract__/workspace-picker.contract.test.tsx +49 -0
- package/src/_peer-deps.test.ts +59 -0
- package/src/blocks-comparison-table.stories.tsx +2 -2
- package/src/components/accordion/accordion.tsx +13 -4
- package/src/components/advanced-group/advanced-group.tsx +1 -1
- package/src/components/alert/alert.tsx +15 -4
- package/src/components/alert-dialog/alert-dialog.test.tsx +32 -0
- package/src/components/alert-dialog/alert-dialog.tsx +28 -6
- package/src/components/app-shell/app-shell.stories.tsx +44 -2
- package/src/components/app-shell/app-shell.test.tsx +99 -0
- package/src/components/app-shell/app-shell.tsx +56 -4
- package/src/components/app-sidebar/app-sidebar.stories.tsx +9 -1
- package/src/components/attribution-panel/attribution-panel.tsx +3 -1
- package/src/components/attribution-panel/attributions.generated.ts +83 -79
- package/src/components/avatar/avatar.tsx +4 -1
- package/src/components/badge/badge.stories.tsx +8 -0
- package/src/components/badge/badge.test.tsx +8 -0
- package/src/components/badge/badge.tsx +15 -5
- package/src/components/bento-grid/bento-grid.test.tsx +24 -3
- package/src/components/bento-grid/bento-grid.tsx +11 -1
- package/src/components/bounded-number/bounded-number.test.tsx +1 -1
- package/src/components/bounded-number/bounded-number.tsx +78 -75
- package/src/components/breadcrumb/breadcrumb.stories.tsx +38 -0
- package/src/components/breadcrumb/breadcrumb.test.tsx +24 -1
- package/src/components/breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/button/button.stories.tsx +116 -8
- package/src/components/button/button.tsx +18 -12
- package/src/components/button-group/button-group.tsx +1 -1
- package/src/components/calendar/calendar.tsx +3 -2
- package/src/components/card/card.stories.tsx +11 -6
- package/src/components/card/card.test.tsx +24 -2
- package/src/components/card/card.tsx +48 -7
- package/src/components/carousel/carousel.test.tsx +169 -0
- package/src/components/carousel/carousel.tsx +93 -32
- package/src/components/change-review/change-review.stories.tsx +145 -1
- package/src/components/change-review/change-review.test.tsx +143 -0
- package/src/components/change-review/change-review.tsx +214 -24
- package/src/components/checkbox/checkbox.tsx +2 -1
- package/src/components/collapsible-panel/use-collapsible-panel.ts +0 -3
- package/src/components/color-picker/color-picker.tsx +22 -15
- package/src/components/combobox/combobox.stories.tsx +10 -0
- package/src/components/combobox/combobox.test.tsx +54 -0
- package/src/components/combobox/combobox.tsx +25 -11
- package/src/components/command/command.stories.tsx +16 -6
- package/src/components/command/command.test.tsx +88 -0
- package/src/components/command/command.tsx +38 -13
- package/src/components/command/index.ts +1 -0
- package/src/components/command-trigger/command-trigger.stories.tsx +39 -0
- package/src/components/command-trigger/command-trigger.test.tsx +18 -0
- package/src/components/command-trigger/command-trigger.tsx +70 -0
- package/src/components/command-trigger/index.ts +1 -0
- package/src/components/context-menu/context-menu.tsx +16 -7
- package/src/components/context-rail/context-rail.stories.tsx +285 -0
- package/src/components/context-rail/context-rail.test.tsx +263 -0
- package/src/components/context-rail/context-rail.tsx +678 -0
- package/src/components/context-rail/index.ts +1 -0
- package/src/components/copyable-value/copyable-value.stories.tsx +10 -0
- package/src/components/copyable-value/copyable-value.test.tsx +28 -1
- package/src/components/copyable-value/copyable-value.tsx +13 -2
- package/src/components/date-picker/date-picker.stories.tsx +9 -0
- package/src/components/date-picker/date-picker.test.tsx +52 -0
- package/src/components/date-picker/date-picker.tsx +13 -15
- package/src/components/date-range-picker/date-range-picker.stories.tsx +9 -0
- package/src/components/date-range-picker/date-range-picker.test.tsx +76 -1
- package/src/components/date-range-picker/date-range-picker.tsx +39 -28
- package/src/components/descriptions/descriptions.tsx +1 -1
- package/src/components/dialog/dialog.test.tsx +8 -3
- package/src/components/dialog/dialog.tsx +14 -6
- package/src/components/drawer/drawer.tsx +23 -6
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +1 -1
- package/src/components/dropdown-menu/dropdown-menu.tsx +4 -4
- package/src/components/empty-state/empty-state.test.tsx +26 -0
- package/src/components/empty-state/empty-state.tsx +6 -1
- package/src/components/expand-dialog/expand-dialog.tsx +1 -2
- package/src/components/field/field-context.ts +59 -0
- package/src/components/field/field.stories.tsx +142 -0
- package/src/components/field/field.test.tsx +415 -0
- package/src/components/field/field.tsx +342 -0
- package/src/components/field/index.ts +9 -0
- package/src/components/field-row/field-row.stories.tsx +85 -1
- package/src/components/field-row/field-row.test.tsx +145 -0
- package/src/components/field-row/field-row.tsx +50 -6
- package/src/components/file-upload/file-upload.stories.tsx +16 -0
- package/src/components/file-upload/file-upload.test.tsx +90 -12
- package/src/components/file-upload/file-upload.tsx +164 -31
- package/src/components/form/form.tsx +8 -3
- package/src/components/hover-card/hover-card.tsx +1 -0
- package/src/components/icon-button/icon-button.test.tsx +1 -1
- package/src/components/icon-button/icon-button.tsx +3 -3
- package/src/components/input/input.stories.tsx +121 -0
- package/src/components/input/input.test.tsx +13 -0
- package/src/components/input/input.tsx +8 -4
- package/src/components/input-group/input-group.stories.tsx +74 -0
- package/src/components/input-group/input-group.tsx +18 -9
- package/src/components/input-otp/input-otp.tsx +8 -2
- package/src/components/keyboard-shortcuts/index.ts +6 -0
- package/src/components/keyboard-shortcuts/keyboard-shortcuts.stories.tsx +177 -0
- package/src/components/keyboard-shortcuts/keyboard-shortcuts.test.tsx +73 -0
- package/src/components/keyboard-shortcuts/keyboard-shortcuts.tsx +203 -0
- package/src/components/label/label.tsx +1 -1
- package/src/components/link-preview/link-preview.tsx +1 -1
- package/src/components/list-editor/list-editor.test.tsx +24 -0
- package/src/components/list-editor/list-editor.tsx +126 -52
- package/src/components/locale-provider/index.ts +6 -1
- package/src/components/locale-provider/locale-keys.test.ts +134 -0
- package/src/components/locale-provider/locale-provider.stories.tsx +89 -1
- package/src/components/locale-provider/locale-provider.test.tsx +257 -1
- package/src/components/locale-provider/locale-provider.tsx +63 -8
- package/src/components/locale-provider/messages.ts +822 -2
- package/src/components/match-highlight/match-highlight.stories.tsx +1 -1
- package/src/components/mention-input/mention-input-mirror.stories.tsx +9 -1
- package/src/components/mention-input/mention-input.stories.tsx +3 -3
- package/src/components/mention-input/mention-input.test.tsx +51 -1
- package/src/components/mention-input/mention-input.tsx +66 -46
- package/src/components/mention-input/mention-value.ts +8 -2
- package/src/components/menubar/menubar.tsx +16 -6
- package/src/components/metric-card/metric-card.stories.tsx +5 -3
- package/src/components/metric-card/metric-card.tsx +21 -2
- package/src/components/model-picker/model-picker.stories.tsx +63 -3
- package/src/components/model-picker/model-picker.test.tsx +63 -0
- package/src/components/model-picker/model-picker.tsx +62 -36
- package/src/components/nav-main/nav-main.tsx +8 -4
- package/src/components/nav-notifications/nav-notifications.stories.tsx +110 -0
- package/src/components/nav-notifications/nav-notifications.test.tsx +11 -0
- package/src/components/nav-notifications/nav-notifications.tsx +67 -9
- package/src/components/nav-user/nav-user.tsx +5 -5
- package/src/components/navigation-menu/navigation-menu.stories.tsx +22 -0
- package/src/components/navigation-menu/navigation-menu.test.tsx +174 -0
- package/src/components/navigation-menu/navigation-menu.tsx +204 -15
- package/src/components/number-input/number-input.stories.tsx +7 -3
- package/src/components/number-input/number-input.test.tsx +47 -0
- package/src/components/number-input/number-input.tsx +65 -24
- package/src/components/page-shell/page-shell.stories.tsx +163 -0
- package/src/components/page-shell/page-shell.test.tsx +229 -0
- package/src/components/page-shell/page-shell.tsx +108 -25
- package/src/components/pagination/pagination.test.tsx +109 -0
- package/src/components/pagination/pagination.tsx +27 -19
- package/src/components/popover/popover.tsx +1 -0
- package/src/components/progress/progress.tsx +2 -0
- package/src/components/radio-group/radio-group.tsx +1 -1
- package/src/components/rating/rating.tsx +3 -1
- package/src/components/resizable/resizable.stories.tsx +114 -2
- package/src/components/resizable/resizable.test.tsx +76 -0
- package/src/components/resizable/resizable.tsx +32 -6
- package/src/components/revision-timeline/revision-timeline.stories.tsx +8 -1
- package/src/components/revision-timeline/revision-timeline.test.tsx +26 -0
- package/src/components/revision-timeline/revision-timeline.tsx +53 -5
- package/src/components/schema-form/from-json-schema.test.ts +390 -0
- package/src/components/schema-form/from-json-schema.ts +375 -0
- package/src/components/schema-form/index.ts +81 -0
- package/src/components/schema-form/schema-form-spec.ts +698 -0
- package/src/components/schema-form/schema-form-store.ts +506 -0
- package/src/components/schema-form/schema-form.stories.tsx +566 -0
- package/src/components/schema-form/schema-form.test.tsx +1021 -0
- package/src/components/schema-form/schema-form.tsx +1381 -0
- package/src/components/section-header/section-header.stories.tsx +37 -0
- package/src/components/section-header/section-header.tsx +14 -3
- package/src/components/select/select.tsx +13 -7
- package/src/components/separator/separator.tsx +1 -0
- package/src/components/sheet/sheet.test.tsx +39 -0
- package/src/components/sheet/sheet.tsx +40 -9
- package/src/components/side-dock/index.ts +1 -0
- package/src/components/side-dock/side-dock.stories.tsx +137 -0
- package/src/components/side-dock/side-dock.test.tsx +299 -0
- package/src/components/side-dock/side-dock.tsx +515 -0
- package/src/components/sidebar/index.ts +1 -0
- package/src/components/sidebar/sidebar-frame.test.tsx +162 -0
- package/src/components/sidebar/sidebar.stories.tsx +421 -1
- package/src/components/sidebar/sidebar.test.tsx +71 -2
- package/src/components/sidebar/sidebar.tsx +264 -38
- package/src/components/skeleton/skeleton.tsx +1 -0
- package/src/components/skip-link/index.ts +1 -0
- package/src/components/skip-link/skip-link.stories.tsx +106 -0
- package/src/components/skip-link/skip-link.test.tsx +20 -0
- package/src/components/skip-link/skip-link.tsx +34 -0
- package/src/components/slider/slider.tsx +1 -1
- package/src/components/slider-number/slider-number.stories.tsx +4 -1
- package/src/components/slider-number/slider-number.test.tsx +1 -1
- package/src/components/sonner/sonner.tsx +2 -0
- package/src/components/spinner/spinner.test.tsx +18 -0
- package/src/components/spinner/spinner.tsx +14 -4
- package/src/components/split-panel/index.ts +6 -1
- package/src/components/split-panel/split-panel.stories.tsx +18 -2
- package/src/components/split-panel/split-panel.test.tsx +20 -0
- package/src/components/split-panel/split-panel.tsx +28 -8
- package/src/components/state-panel/state-panel.stories.tsx +52 -0
- package/src/components/state-panel/state-panel.test.tsx +125 -0
- package/src/components/state-panel/state-panel.tsx +110 -24
- package/src/components/status-badge/status-badge.stories.tsx +8 -0
- package/src/components/switch/switch.tsx +3 -1
- package/src/components/table/table.stories.tsx +64 -0
- package/src/components/table/table.test.tsx +149 -1
- package/src/components/table/table.tsx +71 -2
- package/src/components/tabs/index.ts +10 -1
- package/src/components/tabs/tabs.stories.tsx +48 -0
- package/src/components/tabs/tabs.test.tsx +67 -0
- package/src/components/tabs/tabs.tsx +176 -72
- package/src/components/tag-input/tag-input.tsx +6 -3
- package/src/components/team-switcher/team-switcher.tsx +35 -6
- package/src/components/textarea/textarea.tsx +2 -2
- package/src/components/theme-switcher/theme-switcher.stories.tsx +76 -0
- package/src/components/theme-switcher/theme-switcher.test.tsx +182 -0
- package/src/components/theme-switcher/theme-switcher.tsx +140 -12
- package/src/components/theme-switcher/use-theme-transition.ts +2 -2
- package/src/components/timeline/timeline.stories.tsx +27 -0
- package/src/components/timeline/timeline.tsx +10 -3
- package/src/components/toggle/toggle.tsx +7 -2
- package/src/components/toggle-group/toggle-group.tsx +2 -0
- package/src/components/toolbar/toolbar.stories.tsx +10 -2
- package/src/components/tooltip/tooltip.tsx +2 -1
- package/src/components/top-nav/top-nav.tsx +1 -1
- package/src/components/transfer/transfer.tsx +35 -30
- package/src/components/tree/tree.stories.tsx +2 -2
- package/src/components/tree/tree.test.tsx +41 -0
- package/src/components/tree/tree.tsx +91 -31
- package/src/components/tree/use-tree-keyboard.ts +37 -14
- package/src/components/tree-select/tree-select.stories.tsx +9 -0
- package/src/components/tree-select/tree-select.test.tsx +5 -0
- package/src/components/tree-select/tree-select.tsx +11 -14
- package/src/components/typography/prose.test.tsx +3 -2
- package/src/components/typography/prose.tsx +3 -3
- package/src/components/typography/typography.stories.tsx +228 -0
- package/src/components/view-toolbar/view-toolbar.stories.tsx +41 -1
- package/src/components/view-toolbar/view-toolbar.test.tsx +33 -0
- package/src/components/view-toolbar/view-toolbar.tsx +37 -4
- package/src/components/virtual-select/virtual-select.stories.tsx +9 -0
- package/src/components/virtual-select/virtual-select.test.tsx +29 -3
- package/src/components/virtual-select/virtual-select.tsx +56 -18
- package/src/components/wizard/wizard.test.tsx +9 -0
- package/src/components/wizard/wizard.tsx +13 -5
- package/src/components/workspace-picker/index.ts +3 -0
- package/src/components/workspace-picker/workspace-picker-state.ts +50 -0
- package/src/components/workspace-picker/workspace-picker.stories.tsx +126 -0
- package/src/components/workspace-picker/workspace-picker.test.tsx +207 -0
- package/src/components/workspace-picker/workspace-picker.tsx +172 -0
- package/src/illustrations/empty-list-illustration.tsx +26 -0
- package/src/illustrations/error-illustration.tsx +60 -0
- package/src/illustrations/first-run-illustration.tsx +27 -0
- package/src/illustrations/illustration-base.tsx +108 -0
- package/src/illustrations/illustrations.stories.tsx +94 -0
- package/src/illustrations/illustrations.test.tsx +98 -0
- package/src/illustrations/index.ts +16 -0
- package/src/illustrations/no-access-illustration.tsx +45 -0
- package/src/illustrations/no-results-illustration.tsx +46 -0
- package/src/illustrations/offline-illustration.tsx +37 -0
- package/src/illustrations/success-illustration.tsx +40 -0
- package/src/index.ts +80 -2
- package/src/lib/agent-event-model.test.ts +12 -0
- package/src/lib/agent-event-model.ts +42 -0
- package/src/lib/approval-option.test.ts +17 -0
- package/src/lib/approval-option.ts +53 -0
- package/src/lib/check-result.ts +33 -0
- package/src/lib/cn.ts +9 -0
- package/src/lib/csv.ts +48 -0
- package/src/lib/diff-line.test.ts +36 -0
- package/src/lib/diff-line.ts +63 -0
- package/src/lib/diff-rows.test.ts +76 -0
- package/src/lib/diff-rows.ts +94 -0
- package/src/lib/format-duration.test.ts +22 -0
- package/src/lib/format-duration.ts +30 -0
- package/src/lib/has-renderable-content.test.ts +49 -0
- package/src/lib/has-renderable-content.ts +32 -0
- package/src/lib/merge-refs.ts +2 -0
- package/src/lib/operating-mode.test.ts +36 -0
- package/src/lib/operating-mode.ts +81 -0
- package/src/lib/optional-peer.ts +59 -0
- package/src/lib/session-launch.ts +37 -0
- package/src/lib/slash-command.test.ts +44 -0
- package/src/lib/slash-command.ts +45 -0
- package/src/lib/trigger-query.test.ts +94 -0
- package/src/lib/trigger-query.ts +88 -0
- package/src/lib/use-controllable-state.ts +47 -0
- package/src/lib/use-mobile.test.tsx +47 -0
- package/src/lib/use-mobile.ts +34 -12
- package/src/motion.stories.tsx +6 -0
- package/src/templates-object-detail-hub.stories.tsx +2 -2
- package/src/templates-screen-states.stories.tsx +5 -5
- package/src/templates-settings.stories.tsx +1 -1
- package/src/blocks/sidebar-02/app-sidebar.tsx +0 -160
- package/src/blocks/sidebar-02/logo.tsx +0 -18
- package/src/blocks/sidebar-02/nav-main.tsx +0 -13
- package/src/blocks/sidebar-02/nav-notifications.tsx +0 -7
- package/src/blocks/sidebar-02/sidebar-02.stories.tsx +0 -23
- package/src/blocks/sidebar-02/team-switcher.tsx +0 -7
- package/src/blocks/sidebar-04/app-sidebar.tsx +0 -236
- package/src/blocks/sidebar-04/mail-context.tsx +0 -29
- package/src/blocks/sidebar-04/nav-user.tsx +0 -6
- package/src/blocks/sidebar-04/sidebar-04.stories.tsx +0 -150
- package/src/blocks/sidebar-05/app-sidebar.tsx +0 -375
- package/src/blocks/sidebar-05/sidebar-05.stories.tsx +0 -18
- package/src/blocks/sidebar-05/team-switcher.tsx +0 -6
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
illustrationSvgProps,
|
|
4
|
+
illustrationAccent,
|
|
5
|
+
type IllustrationProps,
|
|
6
|
+
} from "./illustration-base";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Search / filter with zero matches. A line-art magnifying glass with a
|
|
10
|
+
* `--primary-text` "×" inside the lens standing in for "nothing matched".
|
|
11
|
+
* The "×" is meaning-bearing (per the accent-semantics convention in
|
|
12
|
+
* `illustration-base.tsx`, #48 finding 1) — a bare magnifying glass alone
|
|
13
|
+
* reads as "search", not "no results".
|
|
14
|
+
*/
|
|
15
|
+
export const NoResultsIllustration = forwardRef<SVGSVGElement, IllustrationProps>(
|
|
16
|
+
function NoResultsIllustration({ size, className, ...props }, ref) {
|
|
17
|
+
return (
|
|
18
|
+
<svg
|
|
19
|
+
ref={ref}
|
|
20
|
+
{...illustrationSvgProps(size, className)}
|
|
21
|
+
data-slot="no-results-illustration"
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<circle cx="68" cy="66" r="30" />
|
|
25
|
+
<line x1="89.2" y1="87.2" x2="124" y2="122" />
|
|
26
|
+
<line
|
|
27
|
+
x1="56"
|
|
28
|
+
y1="54"
|
|
29
|
+
x2="80"
|
|
30
|
+
y2="78"
|
|
31
|
+
style={{ stroke: illustrationAccent("var(--primary-text)") }}
|
|
32
|
+
strokeWidth={3}
|
|
33
|
+
/>
|
|
34
|
+
<line
|
|
35
|
+
x1="80"
|
|
36
|
+
y1="54"
|
|
37
|
+
x2="56"
|
|
38
|
+
y2="78"
|
|
39
|
+
style={{ stroke: illustrationAccent("var(--primary-text)") }}
|
|
40
|
+
strokeWidth={3}
|
|
41
|
+
/>
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
NoResultsIllustration.displayName = "NoResultsIllustration";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import { illustrationSvgProps, type IllustrationProps } from "./illustration-base";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Disconnected / no-network state. A line-art cloud crossed by a slash — the
|
|
6
|
+
* universal "disabled/no signal" idiom — reads as "offline" on its own
|
|
7
|
+
* silhouette. No accent mark, per the accent-semantics convention in
|
|
8
|
+
* `illustration-base.tsx` (#48 finding 1).
|
|
9
|
+
*
|
|
10
|
+
* Redrawn for #24 fix round 1 (P1-4) — the previous cloud path's closing curve
|
|
11
|
+
* left a visible hook/spur, and the dashed tether + falling node read as a
|
|
12
|
+
* raindrop rather than a severed connection while pulling the ink centre 11.5
|
|
13
|
+
* units below the shared stage centre.
|
|
14
|
+
*
|
|
15
|
+
* Redrawn again for #48 finding 4 — the two small rounded-rect "chain link"
|
|
16
|
+
* fragments below the slash read as loose debris/dashes rather than a
|
|
17
|
+
* "broken link" gesture. Removed rather than redrawn: the cloud+slash
|
|
18
|
+
* silhouette already fully conveys "offline" without them (the same
|
|
19
|
+
* reasoning that dropped the decorative accents from `EmptyListIllustration`/
|
|
20
|
+
* `FirstRunIllustration`).
|
|
21
|
+
*/
|
|
22
|
+
export const OfflineIllustration = forwardRef<SVGSVGElement, IllustrationProps>(
|
|
23
|
+
function OfflineIllustration({ size, className, ...props }, ref) {
|
|
24
|
+
return (
|
|
25
|
+
<svg
|
|
26
|
+
ref={ref}
|
|
27
|
+
{...illustrationSvgProps(size, className)}
|
|
28
|
+
data-slot="offline-illustration"
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
<path d="M44.9,100.4 C44.9,83.6 56.4,72.4 67.9,75.2 C70.2,58.4 86.3,50 97.8,61.2 C111.6,55.6 125.4,69.6 120.8,86.4 C127.7,92 125.4,106 116.2,106 L54.1,106 C49.5,106 44.9,104.6 44.9,100.4 Z" />
|
|
32
|
+
<line x1="36" y1="112" x2="124" y2="36" strokeWidth={4} />
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
OfflineIllustration.displayName = "OfflineIllustration";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
illustrationSvgProps,
|
|
4
|
+
illustrationAccent,
|
|
5
|
+
type IllustrationProps,
|
|
6
|
+
} from "./illustration-base";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Completed / positive-outcome state. A line-art ring with a `--success-text`
|
|
10
|
+
* checkmark — a ring + check needs no rays. The checkmark is meaning-bearing
|
|
11
|
+
* (per the accent-semantics convention in `illustration-base.tsx`, #48
|
|
12
|
+
* finding 1) — a bare ring alone carries no state at all.
|
|
13
|
+
*
|
|
14
|
+
* Redrawn for #24 fix round 1 (P0-1, P1-4) — the checkmark previously used
|
|
15
|
+
* `--primary` (re-creating the "success chips read as primary" confusion #334
|
|
16
|
+
* removed from the tokens) at `strokeWidth={6}` (off the shared accent-weight
|
|
17
|
+
* rule), and the three radiating ticks straddled the now-removed dashed stage
|
|
18
|
+
* (two crossed it, one didn't), reading as a clock/sun rather than a
|
|
19
|
+
* celebration.
|
|
20
|
+
*/
|
|
21
|
+
export const SuccessIllustration = forwardRef<SVGSVGElement, IllustrationProps>(
|
|
22
|
+
function SuccessIllustration({ size, className, ...props }, ref) {
|
|
23
|
+
return (
|
|
24
|
+
<svg
|
|
25
|
+
ref={ref}
|
|
26
|
+
{...illustrationSvgProps(size, className)}
|
|
27
|
+
data-slot="success-illustration"
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
<circle cx="80" cy="78" r="44" />
|
|
31
|
+
<path
|
|
32
|
+
d="M59,81 L75,96 L103,60"
|
|
33
|
+
style={{ stroke: illustrationAccent("var(--success-text)") }}
|
|
34
|
+
strokeWidth={3}
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
SuccessIllustration.displayName = "SuccessIllustration";
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* components require their own peer libs (declared in package.json), e.g.
|
|
10
10
|
* Drawer (vaul), Command (cmdk), Toast (sonner), Calendar (react-day-picker),
|
|
11
11
|
* Carousel (embla-carousel-react), Resizable (react-resizable-panels),
|
|
12
|
-
* Form (react-hook-form + zod)
|
|
12
|
+
* InputOTP (input-otp). Form (react-hook-form + zod) is NOT on this barrel —
|
|
13
|
+
* it lives on the "@elabs-ai/components-ui/form" subpath (issue #26).
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
// Utilities
|
|
@@ -20,6 +21,11 @@ export { useIsMobile } from "./lib/use-mobile";
|
|
|
20
21
|
// Browser file-download mechanics — one home for the Blob/URL → <a download> dance
|
|
21
22
|
// (shared by ConversationDownload, downloadCsv, and Gallery). SSR-guarded.
|
|
22
23
|
export { downloadBlob, downloadUrl } from "./lib/download";
|
|
24
|
+
// Shared CSV (RFC 4180) value stringification + injection-guarded field
|
|
25
|
+
// quoting — one home for logic `@elabs-ai/components-data`'s `toCsv` and
|
|
26
|
+
// `@elabs-ai/components-charts`'s ChartFrame serializer both need (charts may
|
|
27
|
+
// not depend on data, so both build on this instead).
|
|
28
|
+
export { csvStringifyValue, csvQuoteField } from "./lib/csv";
|
|
23
29
|
// Clipboard write + transient "copied" flag — one implementation for the
|
|
24
30
|
// editor's copy button and `CopyableValue`, so the timing and the
|
|
25
31
|
// "no clipboard in this context" answer are defined once.
|
|
@@ -64,6 +70,63 @@ export {
|
|
|
64
70
|
type StreamdownTranslationKey,
|
|
65
71
|
type StreamdownTranslationMap,
|
|
66
72
|
} from "./lib/streamdown-translations";
|
|
73
|
+
// A verification verdict (linter/type-check/test run/policy hook) — shared
|
|
74
|
+
// vocabulary between `ChangeReview` (this package) and `ai`'s `AgentEvent`
|
|
75
|
+
// (docs/decisions/2026-09-01-brainless-adoption-architecture.md § 6). `ai`
|
|
76
|
+
// imports this rather than declaring its own.
|
|
77
|
+
export { type CheckResult, type CheckSummary } from "./lib/check-result";
|
|
78
|
+
// One elapsed-time formatter for every "how long did this take" surface —
|
|
79
|
+
// `TurnStatus` (ai), `ChangeReview`'s check rows, `AgentEvent`'s hook
|
|
80
|
+
// durations (§ 6 / § 6.1 of the doc above).
|
|
81
|
+
export { formatElapsed } from "./lib/format-duration";
|
|
82
|
+
// Optional-peer detection, promoted from `@elabs-ai/components-ai` (§ 2 of the
|
|
83
|
+
// doc above) — `ui` is upstream of every layer-2 package, so this is the one
|
|
84
|
+
// legal home for a helper shared by DAG siblings.
|
|
85
|
+
export { isModuleNotFoundMessage, isOptionalPeerMissing } from "./lib/optional-peer";
|
|
86
|
+
// The generic trigger-driven-query algorithm behind `MentionInput`'s popup,
|
|
87
|
+
// generalised (§ 5 of the doc above) so a second trigger surface (a
|
|
88
|
+
// slash-command palette) reuses it instead of re-implementing it.
|
|
89
|
+
export {
|
|
90
|
+
findTriggerQuery,
|
|
91
|
+
replaceTriggerRun,
|
|
92
|
+
type FindTriggerQueryOptions,
|
|
93
|
+
type TriggerBoundary,
|
|
94
|
+
type TriggerQuery,
|
|
95
|
+
} from "./lib/trigger-query";
|
|
96
|
+
// The T0 promotion wave (issue #117 — see
|
|
97
|
+
// docs/decisions/2026-09-01-brainless-adoption-architecture.md § 4/§ 10(b)):
|
|
98
|
+
// headless models/helpers behind several `@elabs-ai/components-ai` composer
|
|
99
|
+
// and transcript surfaces, promoted here (never duplicated) because
|
|
100
|
+
// `@elabs-ai/components-ai` and `@elabs-ai/components-terminal` are layer-2
|
|
101
|
+
// DAG siblings that may not import each other — `ui` is upstream of both.
|
|
102
|
+
export { defaultSlashCommandFilter, stepIndex, type SlashCommand } from "./lib/slash-command";
|
|
103
|
+
export {
|
|
104
|
+
APPROVAL_SCOPE_DESCRIPTION_KEYS,
|
|
105
|
+
type ApprovalOption,
|
|
106
|
+
type ApprovalScope,
|
|
107
|
+
} from "./lib/approval-option";
|
|
108
|
+
export {
|
|
109
|
+
diffLineAccessibleLabel,
|
|
110
|
+
diffLineMarker,
|
|
111
|
+
type DiffLine,
|
|
112
|
+
type DiffLineType,
|
|
113
|
+
} from "./lib/diff-line";
|
|
114
|
+
export { collapseDiffRows, useDiffRows, type DiffRow } from "./lib/diff-rows";
|
|
115
|
+
export { effortRungForIndex, type EffortLevel, type OperatingMode } from "./lib/operating-mode";
|
|
116
|
+
export {
|
|
117
|
+
agentEventOutcomeStatus,
|
|
118
|
+
type AgentEventOutcome,
|
|
119
|
+
type AgentEventPhase,
|
|
120
|
+
} from "./lib/agent-event-model";
|
|
121
|
+
export {
|
|
122
|
+
type SessionCapability,
|
|
123
|
+
type SessionQuickAction,
|
|
124
|
+
type SessionWhatsNewItem,
|
|
125
|
+
} from "./lib/session-launch";
|
|
126
|
+
// Empty/error/success/etc. state illustrations for StatePanel's `illustration`
|
|
127
|
+
// slot (#24) — token-driven line art, kept alongside the utilities above
|
|
128
|
+
// because it lives outside the folder-per-component `./components/` tree.
|
|
129
|
+
export * from "./illustrations";
|
|
67
130
|
|
|
68
131
|
// Components
|
|
69
132
|
export * from "./components/accordion";
|
|
@@ -91,8 +154,10 @@ export * from "./components/collapsible-panel";
|
|
|
91
154
|
export * from "./components/color-picker";
|
|
92
155
|
export * from "./components/combobox";
|
|
93
156
|
export * from "./components/command";
|
|
157
|
+
export * from "./components/command-trigger";
|
|
94
158
|
export * from "./components/confirm-dialog";
|
|
95
159
|
export * from "./components/context-menu";
|
|
160
|
+
export * from "./components/context-rail";
|
|
96
161
|
export * from "./components/copyable-value";
|
|
97
162
|
export * from "./components/date-picker";
|
|
98
163
|
export * from "./components/date-range-picker";
|
|
@@ -103,15 +168,24 @@ export * from "./components/dropdown-menu";
|
|
|
103
168
|
export * from "./components/empty-state";
|
|
104
169
|
export * from "./components/error-state";
|
|
105
170
|
export * from "./components/expand-dialog";
|
|
171
|
+
export * from "./components/field";
|
|
106
172
|
export * from "./components/field-row";
|
|
107
173
|
export * from "./components/file-upload";
|
|
108
|
-
|
|
174
|
+
// `Form`/`FormField`/… are NOT re-exported from the main barrel — they are the
|
|
175
|
+
// ONLY module in this package that imports `react-hook-form`/`@hookform/resolvers`
|
|
176
|
+
// (peer, optional per issue #26). Re-exporting them here would pull that static
|
|
177
|
+
// import into every consumer's bundled `dist/index.js` (bundlers must resolve a
|
|
178
|
+
// top-level ESM import to build the module graph, even if the binding is never
|
|
179
|
+
// used), forcing react-hook-form on every consumer regardless of whether they
|
|
180
|
+
// use Form. Import the RHF-bound family from the dedicated subpath instead:
|
|
181
|
+
// `import { Form, FormField, … } from "@elabs-ai/components-ui/form"`.
|
|
109
182
|
export * from "./components/hover-card";
|
|
110
183
|
export * from "./components/icon-button";
|
|
111
184
|
export * from "./components/input";
|
|
112
185
|
export * from "./components/input-group";
|
|
113
186
|
export * from "./components/input-otp";
|
|
114
187
|
export * from "./components/kbd";
|
|
188
|
+
export * from "./components/keyboard-shortcuts";
|
|
115
189
|
export * from "./components/key-value-editor";
|
|
116
190
|
export * from "./components/label";
|
|
117
191
|
export * from "./components/link-preview";
|
|
@@ -136,14 +210,17 @@ export * from "./components/radio-group";
|
|
|
136
210
|
export * from "./components/rating";
|
|
137
211
|
export * from "./components/resizable";
|
|
138
212
|
export * from "./components/reveal";
|
|
213
|
+
export * from "./components/schema-form";
|
|
139
214
|
export * from "./components/scroll-area";
|
|
140
215
|
export * from "./components/section-header";
|
|
141
216
|
export * from "./components/segmented-field";
|
|
142
217
|
export * from "./components/select";
|
|
143
218
|
export * from "./components/separator";
|
|
144
219
|
export * from "./components/sheet";
|
|
220
|
+
export * from "./components/side-dock";
|
|
145
221
|
export * from "./components/sidebar";
|
|
146
222
|
export * from "./components/skeleton";
|
|
223
|
+
export * from "./components/skip-link";
|
|
147
224
|
export * from "./components/slider";
|
|
148
225
|
export * from "./components/slider-number";
|
|
149
226
|
export * from "./components/sonner";
|
|
@@ -171,4 +248,5 @@ export * from "./components/tree-select";
|
|
|
171
248
|
export * from "./components/typography";
|
|
172
249
|
export * from "./components/view-toolbar";
|
|
173
250
|
export * from "./components/virtual-select";
|
|
251
|
+
export * from "./components/workspace-picker";
|
|
174
252
|
export * from "./components/wizard";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { agentEventOutcomeStatus, type AgentEventOutcome } from "./agent-event-model";
|
|
3
|
+
|
|
4
|
+
describe("agentEventOutcomeStatus", () => {
|
|
5
|
+
it.each([
|
|
6
|
+
["ok", "complete"],
|
|
7
|
+
["blocked", "denied"],
|
|
8
|
+
["failed", "failed"],
|
|
9
|
+
] as [AgentEventOutcome, string][])("maps %s to the %s status", (outcome, expected) => {
|
|
10
|
+
expect(agentEventOutcomeStatus(outcome)).toBe(expected);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lifecycle/hook-event model behind `AgentEvent` (`@elabs-ai/components-ai`,
|
|
3
|
+
* #109) and the terminal CLI look-alike family's own event line (issue #117).
|
|
4
|
+
*
|
|
5
|
+
* Promoted here (not duplicated) because `@elabs-ai/components-ai` and
|
|
6
|
+
* `@elabs-ai/components-terminal` are layer-2 DAG SIBLINGS and may not import
|
|
7
|
+
* each other — `ui` is upstream of both
|
|
8
|
+
* (docs/decisions/2026-09-01-brainless-adoption-architecture.md § 4).
|
|
9
|
+
* `agentEventOutcomeStatus` maps onto the existing closed 7-state `Status`
|
|
10
|
+
* (`components/status-badge/status-badge.tsx`), which already lives in
|
|
11
|
+
* `ui` — no eighth status is introduced.
|
|
12
|
+
*/
|
|
13
|
+
import type { Status } from "../components/status-badge/status-badge";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* When an event fired relative to the action it gates. A separate, WIDER
|
|
17
|
+
* vocabulary than `CheckResult.phase` (`"before" | "after"`, per-check): this
|
|
18
|
+
* one also admits `"lifecycle"` for events with no gated action (a turn's
|
|
19
|
+
* `user_prompt_submit`/`stop`). The two `phase` concepts answer different
|
|
20
|
+
* questions — "when did this event fire" vs "when did this check run" — and
|
|
21
|
+
* are never unified (§ 6.1 of the architecture decision above).
|
|
22
|
+
*/
|
|
23
|
+
export type AgentEventPhase = "before" | "after" | "lifecycle";
|
|
24
|
+
|
|
25
|
+
/** An event's verdict. Maps onto the existing closed `Status` — see `agentEventOutcomeStatus`. */
|
|
26
|
+
export type AgentEventOutcome = "ok" | "blocked" | "failed";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `outcome` → the canonical 7-state `Status`. No new status value is added
|
|
30
|
+
* (issue #109 acceptance criteria); `blocked` maps to `denied` — a runtime
|
|
31
|
+
* hook refusing an action is the same shape as a human denying one.
|
|
32
|
+
*/
|
|
33
|
+
export function agentEventOutcomeStatus(outcome: AgentEventOutcome): Status {
|
|
34
|
+
switch (outcome) {
|
|
35
|
+
case "ok":
|
|
36
|
+
return "complete";
|
|
37
|
+
case "blocked":
|
|
38
|
+
return "denied";
|
|
39
|
+
case "failed":
|
|
40
|
+
return "failed";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { APPROVAL_SCOPE_DESCRIPTION_KEYS, type ApprovalScope } from "./approval-option";
|
|
3
|
+
|
|
4
|
+
describe("APPROVAL_SCOPE_DESCRIPTION_KEYS", () => {
|
|
5
|
+
it("has one message key per scope", () => {
|
|
6
|
+
const scopes: ApprovalScope[] = ["once", "session", "always", "deny"];
|
|
7
|
+
for (const scope of scopes) {
|
|
8
|
+
expect(typeof APPROVAL_SCOPE_DESCRIPTION_KEYS[scope]).toBe("string");
|
|
9
|
+
expect(APPROVAL_SCOPE_DESCRIPTION_KEYS[scope].length).toBeGreaterThan(0);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("keys are distinct per scope", () => {
|
|
14
|
+
const values = Object.values(APPROVAL_SCOPE_DESCRIPTION_KEYS);
|
|
15
|
+
expect(new Set(values).size).toBe(values.length);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The N-option, scoped-approval model behind `ApprovalCardOptions`
|
|
3
|
+
* (`@elabs-ai/components-ai`'s `confirmation.tsx`, #103) and the terminal CLI
|
|
4
|
+
* look-alike family's own permission row (issue #117) — a real coding-agent
|
|
5
|
+
* permission prompt is rarely a plain yes/no; it asks an N-option question
|
|
6
|
+
* whose options encode SCOPE ("Yes", "Yes, and don't ask again this
|
|
7
|
+
* session", "No, and tell the agent what to do instead").
|
|
8
|
+
*
|
|
9
|
+
* Promoted here (not duplicated) because `@elabs-ai/components-ai` and
|
|
10
|
+
* `@elabs-ai/components-terminal` are layer-2 DAG SIBLINGS and may not import
|
|
11
|
+
* each other — `ui` is upstream of both
|
|
12
|
+
* (docs/decisions/2026-09-01-brainless-adoption-architecture.md § 4). The
|
|
13
|
+
* SYMBOLS have zero SDK coupling; only their previous host module
|
|
14
|
+
* (`confirmation.tsx`) imports `ai`'s `ToolUIPart`, so only the vocabulary —
|
|
15
|
+
* never the component — moves.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How far a chosen option's permission reaches. Deliberately a closed,
|
|
20
|
+
* app-agnostic vocabulary (not a free string) so a consumer can derive an
|
|
21
|
+
* accessible fallback description per option (see
|
|
22
|
+
* `APPROVAL_SCOPE_DESCRIPTION_KEYS` below) — the one thing every option of a
|
|
23
|
+
* given scope always means, regardless of the agent-specific label wording.
|
|
24
|
+
*/
|
|
25
|
+
export type ApprovalScope = "once" | "session" | "always" | "deny";
|
|
26
|
+
|
|
27
|
+
export interface ApprovalOption {
|
|
28
|
+
id: string;
|
|
29
|
+
label: string;
|
|
30
|
+
/**
|
|
31
|
+
* What choosing this option actually does. Optional: when omitted, the
|
|
32
|
+
* consumer renders a scope-derived sentence instead, so scope always
|
|
33
|
+
* reaches assistive tech through real text — never only through colour or
|
|
34
|
+
* a `data-*` attribute (WCAG 1.4.1).
|
|
35
|
+
*/
|
|
36
|
+
description?: string;
|
|
37
|
+
scope: ApprovalScope;
|
|
38
|
+
/** Optional key hint shown beside the option, e.g. "1". */
|
|
39
|
+
keyHint?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The locale-message KEY (not the translated text — `ui`'s locale catalogue,
|
|
44
|
+
* `components/locale-provider/messages.ts`, holds the English fallback and
|
|
45
|
+
* every other locale's translation) for the scope-derived sentence used only
|
|
46
|
+
* when `option.description` is omitted.
|
|
47
|
+
*/
|
|
48
|
+
export const APPROVAL_SCOPE_DESCRIPTION_KEYS: Record<ApprovalScope, string> = {
|
|
49
|
+
once: "ai.approvalCard.scopeOnceDescription",
|
|
50
|
+
session: "ai.approvalCard.scopeSessionDescription",
|
|
51
|
+
always: "ai.approvalCard.scopeAlwaysDescription",
|
|
52
|
+
deny: "ai.approvalCard.scopeDenyDescription",
|
|
53
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One verification verdict about an action — a linter, a type check, a test run,
|
|
3
|
+
* a policy hook. Shared vocabulary across packages: `ChangeReview` (#112) shows the
|
|
4
|
+
* checks a proposed edit already passed, `AgentEvent` (#109) shows the checks a
|
|
5
|
+
* runtime hook ran around a tool call. One runtime concept, one shape.
|
|
6
|
+
*
|
|
7
|
+
* Presentational only (D5): this type records a verdict someone else computed.
|
|
8
|
+
* Nothing in brand-ui ever runs a check.
|
|
9
|
+
*
|
|
10
|
+
* Rendering contract, binding on every consumer: `ok` must reach the user through
|
|
11
|
+
* an ICON and ACCESSIBLE TEXT, never through colour alone — the greyscale test in
|
|
12
|
+
* `.claude/rules/accessibility.md`.
|
|
13
|
+
*/
|
|
14
|
+
export interface CheckResult {
|
|
15
|
+
/** What ran, verbatim as the runtime names it: "eslint", "tsc", "pre_tool_use". */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Did it pass? The verdict; the only required field besides `label`. */
|
|
18
|
+
ok: boolean;
|
|
19
|
+
/** One line of explanation. Renders as a secondary line, collapsed when long. */
|
|
20
|
+
detail?: string;
|
|
21
|
+
/** Wall-clock duration in milliseconds. Format with `formatElapsed`. */
|
|
22
|
+
durationMs?: number;
|
|
23
|
+
/** When it ran relative to the action it gates. Groups results into sections. */
|
|
24
|
+
phase?: "before" | "after";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A count summary, when the individual verdicts are not available. */
|
|
28
|
+
export interface CheckSummary {
|
|
29
|
+
/** How many checks ran. */
|
|
30
|
+
ran: number;
|
|
31
|
+
/** How many of them passed. `passed <= ran`. */
|
|
32
|
+
passed: number;
|
|
33
|
+
}
|
package/src/lib/cn.ts
CHANGED
|
@@ -17,6 +17,15 @@ const twMerge = extendTailwindMerge({
|
|
|
17
17
|
"font-size": [{ text: TEXT_ROLES }],
|
|
18
18
|
// `font-display` is the display-face seam (sibling of font-mono, #187).
|
|
19
19
|
"font-family": [{ font: ["display"] }],
|
|
20
|
+
// Theme-driven control/table knobs (themes.css § CONTROL UTILITIES and the
|
|
21
|
+
// `@theme inline` bridge) — so a caller's `h-8` / `rounded-none` /
|
|
22
|
+
// `font-bold` / `shadow-none` still replaces the token default.
|
|
23
|
+
"font-weight": [{ font: ["control", "table-header"] }],
|
|
24
|
+
rounded: [{ rounded: ["control"] }],
|
|
25
|
+
shadow: [{ shadow: ["input"] }],
|
|
26
|
+
h: [{ h: ["control", "control-sm", "control-lg", "header"] }],
|
|
27
|
+
size: [{ size: ["control", "control-sm", "control-lg"] }],
|
|
28
|
+
"min-w": [{ "min-w": ["control", "control-sm", "control-lg"] }],
|
|
20
29
|
},
|
|
21
30
|
},
|
|
22
31
|
});
|
package/src/lib/csv.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CSV (RFC 4180) primitives — value stringification + injection-guarded
|
|
3
|
+
* field quoting. Pure, no DOM, SSR-safe.
|
|
4
|
+
*
|
|
5
|
+
* The single home for logic previously duplicated between `@elabs-ai/components-data`'s
|
|
6
|
+
* `toCsv` and `@elabs-ai/components-charts`'s `ChartFrame` local serializer (charts may not
|
|
7
|
+
* depend on data per the one-way package rule, so both import this instead of
|
|
8
|
+
* one importing the other).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Leading characters a spreadsheet may interpret as a formula trigger. */
|
|
12
|
+
const INJECTION_TRIGGER = /^[=+\-@]/;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* True when `field` parses as a plain finite number, e.g. "-5" or "+12.3".
|
|
16
|
+
* A negative/positive number is not a formula — only guard fields that
|
|
17
|
+
* START with a trigger character AND are not actually numeric.
|
|
18
|
+
*/
|
|
19
|
+
function isNumericLiteral(field: string): boolean {
|
|
20
|
+
const trimmed = field.trim();
|
|
21
|
+
if (trimmed === "") return false;
|
|
22
|
+
return Number.isFinite(Number(trimmed));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Stringify a cell value for CSV: dates → ISO, objects → JSON, else `String()`. */
|
|
26
|
+
export function csvStringifyValue(value: unknown): string {
|
|
27
|
+
if (value === null || value === undefined) return "";
|
|
28
|
+
if (value instanceof Date) return value.toISOString();
|
|
29
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
30
|
+
return String(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Quote/escape one CSV field: prefixes a leading `'` when the field starts
|
|
35
|
+
* with a formula-trigger character and is not a plain number (CSV-injection
|
|
36
|
+
* guard), then RFC-4180-quotes when it contains the delimiter, a double
|
|
37
|
+
* quote, or a line break.
|
|
38
|
+
*/
|
|
39
|
+
export function csvQuoteField(field: string, delimiter: string): string {
|
|
40
|
+
let out = field;
|
|
41
|
+
if (INJECTION_TRIGGER.test(out) && !isNumericLiteral(out)) {
|
|
42
|
+
out = "'" + out;
|
|
43
|
+
}
|
|
44
|
+
if (out.includes(delimiter) || out.includes('"') || out.includes("\n") || out.includes("\r")) {
|
|
45
|
+
return '"' + out.replaceAll('"', '""') + '"';
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { diffLineAccessibleLabel, diffLineMarker, type DiffLineType } from "./diff-line";
|
|
3
|
+
|
|
4
|
+
describe("diffLineMarker", () => {
|
|
5
|
+
it.each([
|
|
6
|
+
["add", "+"],
|
|
7
|
+
["del", "−"],
|
|
8
|
+
["context", " "],
|
|
9
|
+
["hunk", ""],
|
|
10
|
+
["meta", ""],
|
|
11
|
+
] as const)("returns %s's marker as %s", (type, expected) => {
|
|
12
|
+
expect(diffLineMarker(type)).toBe(expected);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("uses a NO-BREAK SPACE (U+00A0) for context, not a plain space — must survive JSX/HTML whitespace collapsing", () => {
|
|
16
|
+
expect(diffLineMarker("context")).toBe(" ");
|
|
17
|
+
expect(diffLineMarker("context").charCodeAt(0)).toBe(0xa0);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("diffLineAccessibleLabel", () => {
|
|
22
|
+
it("returns the added-line key for an add row", () => {
|
|
23
|
+
expect(diffLineAccessibleLabel("add")).toBe("ai.diffView.addedLine");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns the removed-line key for a del row", () => {
|
|
27
|
+
expect(diffLineAccessibleLabel("del")).toBe("ai.diffView.removedLine");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it.each(["context", "hunk", "meta"] as DiffLineType[])(
|
|
31
|
+
"returns undefined for %s (no polarity)",
|
|
32
|
+
(type) => {
|
|
33
|
+
expect(diffLineAccessibleLabel(type)).toBeUndefined();
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified-diff line model behind `DiffView` (`@elabs-ai/components-ai`,
|
|
3
|
+
* #102) and the terminal CLI look-alike family's own diff hunk (issue #117).
|
|
4
|
+
*
|
|
5
|
+
* Promoted here (not duplicated) because `@elabs-ai/components-ai` and
|
|
6
|
+
* `@elabs-ai/components-terminal` are layer-2 DAG SIBLINGS and may not import
|
|
7
|
+
* each other — `ui` is upstream of both
|
|
8
|
+
* (docs/decisions/2026-09-01-brainless-adoption-architecture.md § 4). This
|
|
9
|
+
* module is the shiki-free half: the line shape, the marker glyph and the
|
|
10
|
+
* screen-reader polarity label. Intra-line syntax highlighting (Shiki) and
|
|
11
|
+
* the diff's presentational chrome stay in `@elabs-ai/components-ai`'s
|
|
12
|
+
* `diff-view.tsx` — `ui` does not depend on Shiki and never will for one
|
|
13
|
+
* component (see the decision doc § 3).
|
|
14
|
+
*
|
|
15
|
+
* The `DiffLine` model and the polarity-prefix technique are adapted from
|
|
16
|
+
* `theswerd/brainless` (MIT) — see `ATTRIBUTION.md`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** What role a line plays in the diff. */
|
|
20
|
+
export type DiffLineType = "add" | "del" | "context" | "hunk" | "meta";
|
|
21
|
+
|
|
22
|
+
/** One line of a unified diff. Absent gutter numbers are load-bearing (see below). */
|
|
23
|
+
export interface DiffLine {
|
|
24
|
+
type: DiffLineType;
|
|
25
|
+
/** Line number in the original file. Absent on `add` — there is no old line. */
|
|
26
|
+
oldNumber?: number;
|
|
27
|
+
/** Line number in the new file. Absent on `del` — there is no new line. */
|
|
28
|
+
newNumber?: number;
|
|
29
|
+
/** The line's text, without its leading `+` / `-` / ` ` marker. */
|
|
30
|
+
text: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The single-character marker glyph for a real source line (`add`/`del`/
|
|
35
|
+
* `context`). `hunk`/`meta` lines are diff headers, not source, and render
|
|
36
|
+
* through a full-width row instead — this returns `""` for them.
|
|
37
|
+
*/
|
|
38
|
+
export function diffLineMarker(type: DiffLineType): string {
|
|
39
|
+
switch (type) {
|
|
40
|
+
case "add":
|
|
41
|
+
return "+";
|
|
42
|
+
case "del":
|
|
43
|
+
return "−";
|
|
44
|
+
case "context":
|
|
45
|
+
return " ";
|
|
46
|
+
default:
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The `ui` locale-message KEY (not the translated text) for the `sr-only`
|
|
53
|
+
* screen-reader polarity word an `add`/`del` row carries ALONGSIDE its marker
|
|
54
|
+
* glyph and its fill tint — colour is never the only channel (WCAG 1.4.1).
|
|
55
|
+
* `undefined` for `context`/`hunk`/`meta`, which carry no polarity.
|
|
56
|
+
*/
|
|
57
|
+
export function diffLineAccessibleLabel(
|
|
58
|
+
type: DiffLineType,
|
|
59
|
+
): "ai.diffView.addedLine" | "ai.diffView.removedLine" | undefined {
|
|
60
|
+
if (type === "add") return "ai.diffView.addedLine";
|
|
61
|
+
if (type === "del") return "ai.diffView.removedLine";
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|