@kerfjs/ui 4.4.1 → 5.0.0-beta.10
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 +240 -10
- package/ai/component-catalog.json +4766 -0
- package/ai/component-catalog.schema.json +79 -0
- package/ai/public-api-signatures-v1.md +1327 -0
- package/ai/skill.md +73 -11
- package/ai/webawesome-jsx-signatures-v1.md +106 -0
- package/dist/app-tab.d.ts +14 -2
- package/dist/app-tab.js +3 -1
- package/dist/browser/app-tab.js +3 -2
- package/dist/browser/disclosure-arrow.js +4 -0
- package/dist/browser/empty-state.js +3 -3
- package/dist/browser/list-action-row.js +4 -0
- package/dist/browser/list-header.js +6 -0
- package/dist/browser/list-item.js +4 -0
- package/dist/browser/loading-spinner.js +2 -2
- package/dist/browser/lucide-icon.js +2 -2
- package/dist/browser/panel-header.js +7 -0
- package/dist/browser/resizable-region.js +2 -2
- package/dist/browser/segmented-control.js +3 -2
- package/dist/browser/select.js +4 -3
- package/dist/browser/skeleton.js +3 -0
- package/dist/browser/state-banner.js +3 -2
- package/dist/browser/tab-bar.js +2 -2
- package/dist/browser/token-search-field.js +4 -0
- package/dist/browser/toolbar-control-group.js +2 -2
- package/dist/browser/toolbar-text.js +3 -2
- package/dist/browser/toolbar.js +2 -2
- package/dist/browser/value-table.js +3 -2
- package/dist/catalog.d.ts +119 -0
- package/dist/catalog.js +143 -0
- package/dist/catalog.js.map +1 -0
- package/dist/chunk-2Z5TWHKQ.js +34 -0
- package/dist/chunk-2Z5TWHKQ.js.map +1 -0
- package/dist/chunk-37T3VNIZ.js +19 -0
- package/dist/chunk-37T3VNIZ.js.map +1 -0
- package/dist/chunk-56RJXPHG.js +43 -0
- package/dist/chunk-56RJXPHG.js.map +1 -0
- package/dist/chunk-7BR3DO2A.js +31 -0
- package/dist/chunk-7BR3DO2A.js.map +1 -0
- package/dist/chunk-7JB7VPRI.js +34 -0
- package/dist/chunk-7JB7VPRI.js.map +1 -0
- package/dist/chunk-7LDCTU7D.js +134 -0
- package/dist/chunk-7LDCTU7D.js.map +1 -0
- package/dist/{chunk-4HHCYSGT.js → chunk-AQNKJRFH.js} +2 -2
- package/dist/{chunk-4HHCYSGT.js.map → chunk-AQNKJRFH.js.map} +1 -1
- package/dist/{chunk-MBR3TYNL.js → chunk-AVZHJN6C.js} +3 -3
- package/dist/chunk-AVZHJN6C.js.map +1 -0
- package/dist/chunk-B24C4A6T.js +22 -0
- package/dist/chunk-B24C4A6T.js.map +1 -0
- package/dist/chunk-BLK3UEEI.js +23 -0
- package/dist/chunk-BLK3UEEI.js.map +1 -0
- package/dist/chunk-CVZYNJJ5.js +265 -0
- package/dist/chunk-CVZYNJJ5.js.map +1 -0
- package/dist/chunk-I7INRJO4.js +17 -0
- package/dist/chunk-I7INRJO4.js.map +1 -0
- package/dist/chunk-L4OKFDJY.js +24 -0
- package/dist/chunk-L4OKFDJY.js.map +1 -0
- package/dist/chunk-LC3YSAZC.js +39 -0
- package/dist/chunk-LC3YSAZC.js.map +1 -0
- package/dist/{chunk-3O7ZEUW2.js → chunk-M5JRI6MN.js} +3 -3
- package/dist/chunk-M5JRI6MN.js.map +1 -0
- package/dist/chunk-RUNWEPR4.js +10 -0
- package/dist/chunk-RUNWEPR4.js.map +1 -0
- package/dist/chunk-W5EQ3HYQ.js +42 -0
- package/dist/chunk-W5EQ3HYQ.js.map +1 -0
- package/dist/{chunk-MQIZAD4A.js → chunk-XXMOARTH.js} +5 -3
- package/dist/chunk-XXMOARTH.js.map +1 -0
- package/dist/chunk-Y5MRDTLC.js +20 -0
- package/dist/chunk-Y5MRDTLC.js.map +1 -0
- package/dist/{chunk-TRNBHPNW.js → chunk-YGN7OMLT.js} +11 -8
- package/dist/chunk-YGN7OMLT.js.map +1 -0
- package/dist/collapsible-panel.d.ts +66 -0
- package/dist/collapsible-panel.js +22 -0
- package/dist/collapsible-panel.js.map +1 -0
- package/dist/device-class.d.ts +62 -0
- package/dist/device-class.js +75 -0
- package/dist/device-class.js.map +1 -0
- package/dist/disclosure-arrow.d.ts +14 -0
- package/dist/disclosure-arrow.js +4 -0
- package/dist/disclosure-arrow.js.map +1 -0
- package/dist/index.d.ts +10 -6
- package/dist/index.js +21 -16
- package/dist/list-action-row.d.ts +52 -0
- package/dist/list-action-row.js +5 -0
- package/dist/list-action-row.js.map +1 -0
- package/dist/list-header.d.ts +43 -0
- package/dist/list-header.js +7 -0
- package/dist/list-header.js.map +1 -0
- package/dist/list-item.d.ts +33 -0
- package/dist/list-item.js +5 -0
- package/dist/list-item.js.map +1 -0
- package/dist/nav-stack.d.ts +38 -0
- package/dist/nav-stack.js +5 -0
- package/dist/nav-stack.js.map +1 -0
- package/dist/panel-header.d.ts +36 -0
- package/dist/panel-header.js +7 -0
- package/dist/panel-header.js.map +1 -0
- package/dist/resizable-region.d.ts +3 -1
- package/dist/resizable-region.js +1 -1
- package/dist/segmented-control.d.ts +3 -1
- package/dist/segmented-control.js +2 -1
- package/dist/select-register.d.ts +5 -1
- package/dist/select-register.js +3 -0
- package/dist/select-register.js.map +1 -1
- package/dist/select.d.ts +5 -2
- package/dist/select.js +2 -1
- package/dist/skeleton.d.ts +24 -0
- package/dist/skeleton.js +3 -0
- package/dist/skeleton.js.map +1 -0
- package/dist/split-view.d.ts +42 -0
- package/dist/split-view.js +21 -0
- package/dist/split-view.js.map +1 -0
- package/dist/state-banner.d.ts +3 -1
- package/dist/state-banner.js +2 -1
- package/dist/styles/app-tab.css +152 -0
- package/dist/styles/catalog.css +498 -0
- package/dist/styles/collapsible-panel.css +146 -0
- package/dist/styles/disclosure-arrow.css +23 -0
- package/dist/styles/empty-state.css +47 -0
- package/dist/styles/foundation.css +347 -0
- package/dist/styles/layout.css +73 -0
- package/dist/styles/list-action-row.css +160 -0
- package/dist/styles/list-header.css +160 -0
- package/dist/styles/list-item.css +123 -0
- package/dist/styles/loading-spinner.css +21 -0
- package/dist/styles/lucide-icon.css +6 -0
- package/dist/styles/nav-stack.css +111 -0
- package/dist/styles/panel-header.css +64 -0
- package/dist/styles/resizable-region.css +111 -0
- package/{src → dist/styles}/segmented-control.css +62 -31
- package/dist/styles/select.css +138 -0
- package/dist/styles/skeleton.css +31 -0
- package/dist/styles/split-view.css +35 -0
- package/dist/styles/state-banner.css +173 -0
- package/dist/styles/styles.css +23 -0
- package/dist/styles/tab-bar.css +45 -0
- package/dist/styles/tab-scaffold.css +84 -0
- package/dist/styles/token-search-field.css +289 -0
- package/dist/styles/toolbar-control-group.css +211 -0
- package/dist/styles/toolbar-text.css +40 -0
- package/dist/styles/toolbar.css +61 -0
- package/dist/styles/value-table.css +75 -0
- package/{src → dist/styles}/webawesome.css +119 -29
- package/dist/styles/workbench.css +94 -0
- package/dist/tab-bar.d.ts +8 -1
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +30 -0
- package/dist/tab-scaffold.js +16 -0
- package/dist/tab-scaffold.js.map +1 -0
- package/dist/token-search-field.d.ts +43 -0
- package/dist/token-search-field.js +4 -0
- package/dist/token-search-field.js.map +1 -0
- package/dist/toolbar-text.d.ts +15 -3
- package/dist/toolbar-text.js +2 -1
- package/dist/value-table.d.ts +12 -2
- package/dist/value-table.js +2 -1
- package/dist/webawesome.d.ts +100 -0
- package/dist/webawesome.js +3 -0
- package/dist/webawesome.js.map +1 -0
- package/dist/wire-catalog.d.ts +26 -0
- package/dist/wire-catalog.js +48 -0
- package/dist/wire-catalog.js.map +1 -0
- package/dist/wire-nav-stack.d.ts +15 -0
- package/dist/wire-nav-stack.js +88 -0
- package/dist/wire-nav-stack.js.map +1 -0
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.d.ts +43 -0
- package/dist/wire-sidebar.js +117 -0
- package/dist/wire-sidebar.js.map +1 -0
- package/dist/wire-tab-bars.d.ts +15 -2
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.d.ts +11 -0
- package/dist/wire-tab-scaffold.js +16 -0
- package/dist/wire-tab-scaffold.js.map +1 -0
- package/dist/wire-token-search-fields.d.ts +97 -0
- package/dist/wire-token-search-fields.js +5 -0
- package/dist/wire-token-search-fields.js.map +1 -0
- package/dist/workbench.d.ts +33 -0
- package/dist/workbench.js +17 -0
- package/dist/workbench.js.map +1 -0
- package/docs/accessibility.md +101 -3
- package/docs/app-layouts.md +61 -0
- package/docs/catalog.md +155 -0
- package/docs/collapsible-panel.md +102 -0
- package/docs/component-contract.md +169 -4
- package/docs/component-selection.md +185 -0
- package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
- package/docs/design/templates/panel-header/icon-actions.svg +1 -0
- package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
- package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
- package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
- package/docs/design/templates/panel-header/no-icon.svg +1 -0
- package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
- package/docs/design/templates/panel-header/page-heading.svg +1 -0
- package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
- package/docs/design/templates/panel-header/title-only.svg +1 -0
- package/docs/design/templates/panel-header-dark.svg +13 -0
- package/docs/design/templates/panel-header.svg +13 -0
- package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
- package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
- package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
- package/docs/design/templates/toolbar-control-group-dark.svg +11 -0
- package/docs/design/templates/toolbar-control-group.svg +11 -0
- package/docs/design/templates.md +83 -0
- package/docs/design-philosophy.md +61 -1
- package/docs/device-class.md +54 -0
- package/docs/examples/command-palette-adapter.tsx +70 -0
- package/docs/layout.md +131 -0
- package/docs/nav-stack.md +47 -0
- package/docs/recipes.md +167 -0
- package/docs/split-view.md +49 -0
- package/docs/tab-scaffold.md +41 -0
- package/docs/ux-demo.md +23 -9
- package/docs/webawesome-theme.md +15 -0
- package/docs/workbench.md +47 -0
- package/llms.txt +123 -14
- package/package.json +139 -44
- package/ux-demo/recipes/app-shell.tsx +32 -0
- package/ux-demo/recipes/collapsible-sidebar.tsx +100 -0
- package/ux-demo/recipes/compact-toolbar.tsx +43 -0
- package/ux-demo/recipes/composer-form.tsx +63 -0
- package/ux-demo/recipes/list-detail-dialog.tsx +45 -0
- package/ux-demo/recipes/list-workspace-states.tsx +26 -0
- package/ux-demo/recipes/loaders.ts +33 -0
- package/ux-demo/recipes/loading-inspector.tsx +81 -0
- package/ux-demo/recipes/mount-recipe.ts +43 -0
- package/ux-demo/recipes/navigation-sidebar.tsx +26 -0
- package/ux-demo/recipes/navigation-stack.tsx +76 -0
- package/ux-demo/recipes/recipes.css +448 -0
- package/ux-demo/recipes/types.ts +17 -0
- package/ux-demo/recipes/workspace-header.tsx +12 -0
- package/dist/browser/dialog-header.js +0 -3
- package/dist/browser/menu-header.js +0 -3
- package/dist/browser/menu-item.js +0 -3
- package/dist/browser/page-header.js +0 -3
- package/dist/chunk-2PES33HS.js +0 -13
- package/dist/chunk-2PES33HS.js.map +0 -1
- package/dist/chunk-3O7ZEUW2.js.map +0 -1
- package/dist/chunk-723YNJBH.js +0 -17
- package/dist/chunk-723YNJBH.js.map +0 -1
- package/dist/chunk-GY5WH7TO.js +0 -10
- package/dist/chunk-GY5WH7TO.js.map +0 -1
- package/dist/chunk-MBR3TYNL.js.map +0 -1
- package/dist/chunk-MQIZAD4A.js.map +0 -1
- package/dist/chunk-NHXX4JI5.js +0 -28
- package/dist/chunk-NHXX4JI5.js.map +0 -1
- package/dist/chunk-OPCJRCYO.js +0 -19
- package/dist/chunk-OPCJRCYO.js.map +0 -1
- package/dist/chunk-T5XNMVOI.js +0 -24
- package/dist/chunk-T5XNMVOI.js.map +0 -1
- package/dist/chunk-TRNBHPNW.js.map +0 -1
- package/dist/chunk-VHW5YNIB.js +0 -17
- package/dist/chunk-VHW5YNIB.js.map +0 -1
- package/dist/chunk-VKH2JLDZ.js +0 -10
- package/dist/chunk-VKH2JLDZ.js.map +0 -1
- package/dist/chunk-WY4CAEPG.js +0 -14
- package/dist/chunk-WY4CAEPG.js.map +0 -1
- package/dist/dialog-header.d.ts +0 -14
- package/dist/dialog-header.js +0 -3
- package/dist/dialog-header.js.map +0 -1
- package/dist/menu-header.d.ts +0 -15
- package/dist/menu-header.js +0 -3
- package/dist/menu-header.js.map +0 -1
- package/dist/menu-item.d.ts +0 -22
- package/dist/menu-item.js +0 -3
- package/dist/menu-item.js.map +0 -1
- package/dist/page-header.d.ts +0 -9
- package/dist/page-header.js +0 -3
- package/dist/page-header.js.map +0 -1
- package/src/app-tab.css +0 -20
- package/src/dialog-header.css +0 -9
- package/src/empty-state.css +0 -6
- package/src/foundation.css +0 -106
- package/src/loading-spinner.css +0 -4
- package/src/lucide-icon.css +0 -1
- package/src/menu-header.css +0 -13
- package/src/menu-item.css +0 -12
- package/src/page-header.css +0 -3
- package/src/resizable-region.css +0 -19
- package/src/select.css +0 -18
- package/src/state-banner.css +0 -31
- package/src/styles.css +0 -18
- package/src/tab-bar.css +0 -4
- package/src/toolbar-control-group.css +0 -25
- package/src/toolbar-text.css +0 -4
- package/src/toolbar.css +0 -9
- package/src/value-table.css +0 -5
package/llms.txt
CHANGED
|
@@ -1,22 +1,131 @@
|
|
|
1
1
|
# @kerfjs/ui
|
|
2
2
|
|
|
3
|
-
Accessible, production-backed UI primitives for kerf
|
|
3
|
+
> Accessible, production-backed UI primitives for [kerf](https://github.com/brianwestphal/kerf)
|
|
4
|
+
> applications. Components are plain functions returning `SafeHtml`; the app owns
|
|
5
|
+
> state and wires stable `data-action` hooks once. This file orients an LLM to
|
|
6
|
+
> generate correct, good-looking kerf UI — it is not a catalog to browse.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- [Web Awesome theme contract](./docs/webawesome-theme.md)
|
|
9
|
-
- [Accessibility and keyboard contracts](./docs/accessibility.md)
|
|
10
|
-
- [Design philosophy](./docs/design-philosophy.md)
|
|
11
|
-
- [Apple HIG translation](./docs/apple-hig.md)
|
|
12
|
-
- [UX catalog](./docs/ux-demo.md)
|
|
8
|
+
```bash
|
|
9
|
+
npm install kerfjs @kerfjs/ui # kerfjs is a peer; Web Awesome is an optional peer
|
|
10
|
+
```
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Import each component from its own subpath (`@kerfjs/ui/list-item`, `@kerfjs/ui/toolbar`,
|
|
13
|
+
…); the browser build pulls that component's reachable CSS automatically, so do not
|
|
14
|
+
maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## How to build a screen (retrieval order)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
1. **Pick the component from the interface need**, not by appearance — read
|
|
19
|
+
[component-selection.md](./docs/component-selection.md) (the problem→component
|
|
20
|
+
matrix, with imports, wiring, ownership, and alternatives).
|
|
21
|
+
2. **Get exact signatures before emitting code** — read
|
|
22
|
+
[public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) (real prop names,
|
|
23
|
+
callback shapes, disposer return types).
|
|
24
|
+
3. **For exhaustive, structured facts** (ids, exports, variants, `publicClasses`,
|
|
25
|
+
relationships, side effects) load the
|
|
26
|
+
[Machine-readable component catalog](./ai/component-catalog.json) — the
|
|
27
|
+
canonical checked-in source; its schema is alongside it.
|
|
28
|
+
4. **Apply the rules below and in** [skill.md](./ai/skill.md).
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
## Core rules (do the right thing)
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
- **Reach for the primitive, not for CSS.** The package is built to look right
|
|
33
|
+
*unstyled*. Before adding `padding`, `margin`, `width`, `height`, `border`,
|
|
34
|
+
`background`, a wrapper card, or a decoration, check whether the component, the
|
|
35
|
+
pane, or the content-item already owns it — it almost always does. Prefer a
|
|
36
|
+
component prop or a `--kui-*` token; write CSS only for genuinely new structure,
|
|
37
|
+
and then only join a component's documented `publicClasses` (never reach in by
|
|
38
|
+
tag/id/attribute or an unlisted class).
|
|
39
|
+
- **Never double-inset.** A `.kui-pane` has no padding; its `.kui-content` children
|
|
40
|
+
own the geometry (8px inline margin + 1px border + 8px padding). Do not add a
|
|
41
|
+
container padding on top of a content-item's own margin — pick one owner. Use
|
|
42
|
+
`.kui-content` for 24px major vertical rhythm.
|
|
43
|
+
- **Trust the defaults.** Render a control at its natural size and color (a
|
|
44
|
+
`LucideIcon` is 24px); if it looks wrong, fix the surrounding layout, not the
|
|
45
|
+
control. Never force a component's width/height to size it — that leaves a halo
|
|
46
|
+
or a stretched oval.
|
|
47
|
+
- **Toolbars hold only `ToolbarText` and `ToolbarControlGroup`.** Never put a bare
|
|
48
|
+
button, input, link, or loose markup in a `leading`/`center`/`trailing` zone.
|
|
49
|
+
Wrap every control — `SegmentedControl`, `Select`, a collapsible
|
|
50
|
+
`TokenSearchField`, Web Awesome controls — inside a `ToolbarControlGroup`. A
|
|
51
|
+
toolbar popup menu is a `single` `ToolbarControlGroup` around a `wa-dropdown`
|
|
52
|
+
(its `slot="trigger"` `wa-button` is the toolbar button, kept under
|
|
53
|
+
`data-morph-skip-children`).
|
|
54
|
+
- **`PanelHeader` is *the* panel/dialog/page heading.** A plain toolbar with an
|
|
55
|
+
optional bordered icon group, an extra-large `ToolbarText` title, the trailing
|
|
56
|
+
controls you pass, and an optional subtitle below — do not hand-build a heading
|
|
57
|
+
row or restyle a toolbar into one.
|
|
58
|
+
- **Space by the five-step scale, by relationship not by eye.** `0`
|
|
59
|
+
`--kui-space-none`, `4px` `--kui-space-2xs`, `8px` `--kui-space-xs` (inside a
|
|
60
|
+
group), `16px` `--kui-space-m`, `24px` `--kui-space-l` (between major differing
|
|
61
|
+
regions). 12px/32px are off-scale exceptions.
|
|
62
|
+
- **Panes share one child-owned geometry.** Sidebars, main areas, inspectors, and
|
|
63
|
+
dialogs all use an unpadded `.kui-pane` (optional `.kui-pane__toolbar`, one
|
|
64
|
+
scrolling `.kui-pane__content`, optional `.kui-pane__footer`). A visible pane owns
|
|
65
|
+
its collapse control; a hidden pane's restore moves to the adjacent main
|
|
66
|
+
toolbar's matching edge — no icon-only rail. Keep one scroll owner per region.
|
|
67
|
+
- **State and behavior live in the app.** Components take semantic `data-action`
|
|
68
|
+
strings and controlled values; wire them once with `delegate()`/`delegateActions()`
|
|
69
|
+
or the disposer-returning `wire…` helpers, and own domain copy, transport, and
|
|
70
|
+
policy in a thin adapter. Every element must earn its place — cut chrome, labels,
|
|
71
|
+
and readouts that do not help a person decide or act.
|
|
72
|
+
- **App state is domain/persisted; helpers own only ephemeral mechanics.** Keep
|
|
73
|
+
navigation stacks, tab selection and order, pane sizes, and `collapsed`/`expanded`
|
|
74
|
+
disclosure flags in the app — they are data or persisted layout, and the app reads
|
|
75
|
+
them to render. The `wire…` helpers own the *ephemeral* parts: `wireNavStack` the
|
|
76
|
+
push/pop animation, `wireTabBars` the overflow autoscroll and drag preview,
|
|
77
|
+
`wireResizableRegions` the live drag preview; they report committed changes via
|
|
78
|
+
callbacks. The one richer case is `wireTokenSearchFields`, which manages the
|
|
79
|
+
collapsible field's expand/collapse/focus by default (on, per-behavior opt-out) and
|
|
80
|
+
exposes the `expanded` signal on its handle — bind it in render or hand in your own
|
|
81
|
+
via `collapsible.signals`. Do not hand-roll open/collapse/focus that helper
|
|
82
|
+
provides; but do keep a plain disclosure toggle (a one-line boolean the app already
|
|
83
|
+
renders from) in the app. For real-app composition it also offers a focusout
|
|
84
|
+
keep-open exception (`data-token-search-keep-open` regions or `collapsible.keepOpenOn`,
|
|
85
|
+
so a sibling suggestions/date popover does not collapse an empty field), opt-in
|
|
86
|
+
atomic-chip keyboard (`keyboard`: Backspace/Delete remove the adjacent chip via
|
|
87
|
+
`onRemoveToken`, ArrowRight moves the caret past a trailing chip), and an
|
|
88
|
+
`onEdit({id, editor})` input callback — so the app can drop its own keydown/input
|
|
89
|
+
boilerplate while still owning parsing and controlled token state.
|
|
90
|
+
- **Placeholder loading state, don't rebuild it.** For a value or component that is
|
|
91
|
+
still loading, set `placeholder` on the component (`Select`, `ListHeader`,
|
|
92
|
+
`ListItem`, `ValueTableRow`, `PanelHeader`, `SegmentedControl`, `StateBanner`,
|
|
93
|
+
`AppTab`, `ToolbarText`, `ListActionRow`): it renders its real chrome with value
|
|
94
|
+
slots as subtle **unanimated** skeleton blocks and interactivity disabled, so a
|
|
95
|
+
parent composes a faithful loading view (e.g. an inspector) without hand-rebuilding
|
|
96
|
+
markup. Use the standalone `Skeleton` for a custom slot. Prefer it over `wa-skeleton`
|
|
97
|
+
(the pure-Kerf primitives avoid Web Awesome), and never animate it — use
|
|
98
|
+
`LoadingSpinner` for known busy activity instead.
|
|
99
|
+
|
|
100
|
+
## Public exports
|
|
101
|
+
|
|
102
|
+
`LucideIcon`, `DisclosureArrow`, `Toolbar`, `ToolbarControlGroup`, `ToolbarText`,
|
|
103
|
+
`PanelHeader`, `ListItem`, `ListActionRow`, `ListHeader`, `SegmentedControl`,
|
|
104
|
+
`Select`, `TokenSearchField` (+ `readTokenSearchField`, `placeTokenSearchCaret`,
|
|
105
|
+
`wireTokenSearchFields`), `AppTab`, `TabBar` (+ `wireTabBars`, `reorderTabs`),
|
|
106
|
+
`ResizableRegion` (+ `wireResizableRegions`), `StateBanner`, `EmptyState`,
|
|
107
|
+
`LoadingSpinner`, `Skeleton`, `ValueTable`, `ValueTableRow`. Whole-screen layouts (opt-in,
|
|
108
|
+
tree-shakeable, declarative with `wire…` helpers, each with a companion CSS import):
|
|
109
|
+
`NavStack`, `SplitView`, `Workbench`, `TabScaffold`, driven by `deviceClass()` from
|
|
110
|
+
`@kerfjs/ui/device-class`. For a standalone collapsible rail/drawer outside a full
|
|
111
|
+
shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelToggle`)
|
|
112
|
+
+ `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence).
|
|
113
|
+
`@kerfjs/ui/catalog` is another opt-in shell — a
|
|
114
|
+
`Catalog` component + `wireCatalog` helper for building a component-gallery catalog
|
|
115
|
+
(collapsible category sidebar + preview stage + resources footer) from your own
|
|
116
|
+
entries; controlled/stateless like the layouts. `@kerfjs/ui/webawesome` is a
|
|
117
|
+
type-only boundary for `wa-*` JSX; `@kerfjs/ui/select/register` and the CSS are the
|
|
118
|
+
declared side effects.
|
|
119
|
+
|
|
120
|
+
## Reference
|
|
121
|
+
|
|
122
|
+
- [skill.md](./ai/skill.md): the full rule set + a common-mistakes table (the shipped AI skill).
|
|
123
|
+
- [design-philosophy.md](./docs/design-philosophy.md): why kerf UI is shaped this way — the decision principles behind the rules above.
|
|
124
|
+
- [layout.md](./docs/layout.md): the pane/content-item geometry, public roles/tokens, and the spacing scale.
|
|
125
|
+
- [component-contract.md](./docs/component-contract.md): what a component owns vs. what the app owns.
|
|
126
|
+
- [accessibility.md](./docs/accessibility.md): keyboard and ARIA contracts per component.
|
|
127
|
+
- [recipes.md](./docs/recipes.md): production compositions (app shell, list-detail dialog, composer form, …) as copyable references.
|
|
128
|
+
- [app-layouts.md](./docs/app-layouts.md): choosing `NavStack`/`SplitView`/`Workbench`/`TabScaffold` by data + interaction + device, and dialog presentation.
|
|
129
|
+
- [webawesome-theme.md](./docs/webawesome-theme.md): the optional Web Awesome theme; catalog coverage means themed-and-supported, not preferred (prefer the Kerf primitive when one exists).
|
|
130
|
+
- [component-catalog.json](./ai/component-catalog.json) + [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) + [webawesome-jsx-signatures-v1.md](./ai/webawesome-jsx-signatures-v1.md): machine-readable facts and exact signatures.
|
|
131
|
+
- [README.md](./README.md): package overview and the component subpath table.
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kerfjs/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.10",
|
|
4
4
|
"description": "Accessible, composable UI primitives for kerf applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Brian Westphal <brian.westphal@bleugris.com>",
|
|
8
|
-
"homepage": "https://brianwestphal.github.io/kerf/docs/
|
|
8
|
+
"homepage": "https://brianwestphal.github.io/kerf/docs/component-packages/",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/brianwestphal/kerf",
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"browser": "./dist/browser/lucide-icon.js",
|
|
45
45
|
"import": "./dist/lucide-icon.js"
|
|
46
46
|
},
|
|
47
|
+
"./disclosure-arrow": {
|
|
48
|
+
"types": "./dist/disclosure-arrow.d.ts",
|
|
49
|
+
"browser": "./dist/browser/disclosure-arrow.js",
|
|
50
|
+
"import": "./dist/disclosure-arrow.js"
|
|
51
|
+
},
|
|
47
52
|
"./toolbar": {
|
|
48
53
|
"types": "./dist/toolbar.d.ts",
|
|
49
54
|
"browser": "./dist/browser/toolbar.js",
|
|
@@ -59,15 +64,20 @@
|
|
|
59
64
|
"browser": "./dist/browser/toolbar-text.js",
|
|
60
65
|
"import": "./dist/toolbar-text.js"
|
|
61
66
|
},
|
|
62
|
-
"./
|
|
63
|
-
"types": "./dist/
|
|
64
|
-
"browser": "./dist/browser/
|
|
65
|
-
"import": "./dist/
|
|
67
|
+
"./list-action-row": {
|
|
68
|
+
"types": "./dist/list-action-row.d.ts",
|
|
69
|
+
"browser": "./dist/browser/list-action-row.js",
|
|
70
|
+
"import": "./dist/list-action-row.js"
|
|
71
|
+
},
|
|
72
|
+
"./list-item": {
|
|
73
|
+
"types": "./dist/list-item.d.ts",
|
|
74
|
+
"browser": "./dist/browser/list-item.js",
|
|
75
|
+
"import": "./dist/list-item.js"
|
|
66
76
|
},
|
|
67
|
-
"./
|
|
68
|
-
"types": "./dist/
|
|
69
|
-
"browser": "./dist/browser/
|
|
70
|
-
"import": "./dist/
|
|
77
|
+
"./list-header": {
|
|
78
|
+
"types": "./dist/list-header.d.ts",
|
|
79
|
+
"browser": "./dist/browser/list-header.js",
|
|
80
|
+
"import": "./dist/list-header.js"
|
|
71
81
|
},
|
|
72
82
|
"./resizable-region": {
|
|
73
83
|
"types": "./dist/resizable-region.d.ts",
|
|
@@ -78,11 +88,23 @@
|
|
|
78
88
|
"types": "./dist/wire-resizable-regions.d.ts",
|
|
79
89
|
"import": "./dist/wire-resizable-regions.js"
|
|
80
90
|
},
|
|
91
|
+
"./device-class": {
|
|
92
|
+
"types": "./dist/device-class.d.ts",
|
|
93
|
+
"import": "./dist/device-class.js"
|
|
94
|
+
},
|
|
81
95
|
"./app-tab": {
|
|
82
96
|
"types": "./dist/app-tab.d.ts",
|
|
83
97
|
"browser": "./dist/browser/app-tab.js",
|
|
84
98
|
"import": "./dist/app-tab.js"
|
|
85
99
|
},
|
|
100
|
+
"./catalog": {
|
|
101
|
+
"types": "./dist/catalog.d.ts",
|
|
102
|
+
"import": "./dist/catalog.js"
|
|
103
|
+
},
|
|
104
|
+
"./wire-catalog": {
|
|
105
|
+
"types": "./dist/wire-catalog.d.ts",
|
|
106
|
+
"import": "./dist/wire-catalog.js"
|
|
107
|
+
},
|
|
86
108
|
"./tab-bar": {
|
|
87
109
|
"types": "./dist/tab-bar.d.ts",
|
|
88
110
|
"browser": "./dist/browser/tab-bar.js",
|
|
@@ -92,10 +114,37 @@
|
|
|
92
114
|
"types": "./dist/wire-tab-bars.d.ts",
|
|
93
115
|
"import": "./dist/wire-tab-bars.js"
|
|
94
116
|
},
|
|
95
|
-
"./
|
|
96
|
-
"types": "./dist/
|
|
97
|
-
"
|
|
98
|
-
|
|
117
|
+
"./nav-stack": {
|
|
118
|
+
"types": "./dist/nav-stack.d.ts",
|
|
119
|
+
"import": "./dist/nav-stack.js"
|
|
120
|
+
},
|
|
121
|
+
"./wire-nav-stack": {
|
|
122
|
+
"types": "./dist/wire-nav-stack.d.ts",
|
|
123
|
+
"import": "./dist/wire-nav-stack.js"
|
|
124
|
+
},
|
|
125
|
+
"./split-view": {
|
|
126
|
+
"types": "./dist/split-view.d.ts",
|
|
127
|
+
"import": "./dist/split-view.js"
|
|
128
|
+
},
|
|
129
|
+
"./workbench": {
|
|
130
|
+
"types": "./dist/workbench.d.ts",
|
|
131
|
+
"import": "./dist/workbench.js"
|
|
132
|
+
},
|
|
133
|
+
"./collapsible-panel": {
|
|
134
|
+
"types": "./dist/collapsible-panel.d.ts",
|
|
135
|
+
"import": "./dist/collapsible-panel.js"
|
|
136
|
+
},
|
|
137
|
+
"./wire-sidebar": {
|
|
138
|
+
"types": "./dist/wire-sidebar.d.ts",
|
|
139
|
+
"import": "./dist/wire-sidebar.js"
|
|
140
|
+
},
|
|
141
|
+
"./tab-scaffold": {
|
|
142
|
+
"types": "./dist/tab-scaffold.d.ts",
|
|
143
|
+
"import": "./dist/tab-scaffold.js"
|
|
144
|
+
},
|
|
145
|
+
"./wire-tab-scaffold": {
|
|
146
|
+
"types": "./dist/wire-tab-scaffold.d.ts",
|
|
147
|
+
"import": "./dist/wire-tab-scaffold.js"
|
|
99
148
|
},
|
|
100
149
|
"./loading-spinner": {
|
|
101
150
|
"types": "./dist/loading-spinner.d.ts",
|
|
@@ -107,15 +156,33 @@
|
|
|
107
156
|
"browser": "./dist/browser/select.js",
|
|
108
157
|
"import": "./dist/select.js"
|
|
109
158
|
},
|
|
159
|
+
"./skeleton": {
|
|
160
|
+
"types": "./dist/skeleton.d.ts",
|
|
161
|
+
"browser": "./dist/browser/skeleton.js",
|
|
162
|
+
"import": "./dist/skeleton.js"
|
|
163
|
+
},
|
|
110
164
|
"./segmented-control": {
|
|
111
165
|
"types": "./dist/segmented-control.d.ts",
|
|
112
166
|
"browser": "./dist/browser/segmented-control.js",
|
|
113
167
|
"import": "./dist/segmented-control.js"
|
|
114
168
|
},
|
|
169
|
+
"./token-search-field": {
|
|
170
|
+
"types": "./dist/token-search-field.d.ts",
|
|
171
|
+
"browser": "./dist/browser/token-search-field.js",
|
|
172
|
+
"import": "./dist/token-search-field.js"
|
|
173
|
+
},
|
|
174
|
+
"./wire-token-search-fields": {
|
|
175
|
+
"types": "./dist/wire-token-search-fields.d.ts",
|
|
176
|
+
"import": "./dist/wire-token-search-fields.js"
|
|
177
|
+
},
|
|
115
178
|
"./select/register": {
|
|
116
179
|
"types": "./dist/select-register.d.ts",
|
|
117
180
|
"import": "./dist/select-register.js"
|
|
118
181
|
},
|
|
182
|
+
"./webawesome": {
|
|
183
|
+
"types": "./dist/webawesome.d.ts",
|
|
184
|
+
"import": "./dist/webawesome.js"
|
|
185
|
+
},
|
|
119
186
|
"./state-banner": {
|
|
120
187
|
"types": "./dist/state-banner.d.ts",
|
|
121
188
|
"browser": "./dist/browser/state-banner.js",
|
|
@@ -126,10 +193,10 @@
|
|
|
126
193
|
"browser": "./dist/browser/empty-state.js",
|
|
127
194
|
"import": "./dist/empty-state.js"
|
|
128
195
|
},
|
|
129
|
-
"./
|
|
130
|
-
"types": "./dist/
|
|
131
|
-
"browser": "./dist/browser/
|
|
132
|
-
"import": "./dist/
|
|
196
|
+
"./panel-header": {
|
|
197
|
+
"types": "./dist/panel-header.d.ts",
|
|
198
|
+
"browser": "./dist/browser/panel-header.js",
|
|
199
|
+
"import": "./dist/panel-header.js"
|
|
133
200
|
},
|
|
134
201
|
"./value-table": {
|
|
135
202
|
"types": "./dist/value-table.d.ts",
|
|
@@ -140,55 +207,80 @@
|
|
|
140
207
|
"types": "./dist/index.d.ts",
|
|
141
208
|
"import": "./dist/index.js"
|
|
142
209
|
},
|
|
143
|
-
"./foundation.css": "./
|
|
144
|
-
"./
|
|
145
|
-
"./
|
|
146
|
-
"./
|
|
147
|
-
"./
|
|
148
|
-
"./
|
|
149
|
-
"./toolbar
|
|
150
|
-
"./
|
|
151
|
-
"./
|
|
152
|
-
"./
|
|
153
|
-
"./
|
|
154
|
-
"./
|
|
155
|
-
"./
|
|
156
|
-
"./
|
|
157
|
-
"./
|
|
158
|
-
"./
|
|
159
|
-
"./
|
|
160
|
-
"./
|
|
161
|
-
"./
|
|
162
|
-
"./
|
|
210
|
+
"./foundation.css": "./dist/styles/foundation.css",
|
|
211
|
+
"./layout.css": "./dist/styles/layout.css",
|
|
212
|
+
"./webawesome.css": "./dist/styles/webawesome.css",
|
|
213
|
+
"./styles.css": "./dist/styles/styles.css",
|
|
214
|
+
"./lucide-icon.css": "./dist/styles/lucide-icon.css",
|
|
215
|
+
"./disclosure-arrow.css": "./dist/styles/disclosure-arrow.css",
|
|
216
|
+
"./toolbar.css": "./dist/styles/toolbar.css",
|
|
217
|
+
"./toolbar-control-group.css": "./dist/styles/toolbar-control-group.css",
|
|
218
|
+
"./toolbar-text.css": "./dist/styles/toolbar-text.css",
|
|
219
|
+
"./list-action-row.css": "./dist/styles/list-action-row.css",
|
|
220
|
+
"./list-item.css": "./dist/styles/list-item.css",
|
|
221
|
+
"./list-header.css": "./dist/styles/list-header.css",
|
|
222
|
+
"./resizable-region.css": "./dist/styles/resizable-region.css",
|
|
223
|
+
"./app-tab.css": "./dist/styles/app-tab.css",
|
|
224
|
+
"./catalog.css": "./dist/styles/catalog.css",
|
|
225
|
+
"./tab-bar.css": "./dist/styles/tab-bar.css",
|
|
226
|
+
"./nav-stack.css": "./dist/styles/nav-stack.css",
|
|
227
|
+
"./split-view.css": "./dist/styles/split-view.css",
|
|
228
|
+
"./workbench.css": "./dist/styles/workbench.css",
|
|
229
|
+
"./collapsible-panel.css": "./dist/styles/collapsible-panel.css",
|
|
230
|
+
"./tab-scaffold.css": "./dist/styles/tab-scaffold.css",
|
|
231
|
+
"./loading-spinner.css": "./dist/styles/loading-spinner.css",
|
|
232
|
+
"./select.css": "./dist/styles/select.css",
|
|
233
|
+
"./skeleton.css": "./dist/styles/skeleton.css",
|
|
234
|
+
"./segmented-control.css": "./dist/styles/segmented-control.css",
|
|
235
|
+
"./token-search-field.css": "./dist/styles/token-search-field.css",
|
|
236
|
+
"./state-banner.css": "./dist/styles/state-banner.css",
|
|
237
|
+
"./empty-state.css": "./dist/styles/empty-state.css",
|
|
238
|
+
"./panel-header.css": "./dist/styles/panel-header.css",
|
|
239
|
+
"./value-table.css": "./dist/styles/value-table.css",
|
|
163
240
|
"./ai/*": "./ai/*",
|
|
164
241
|
"./docs/*": "./docs/*"
|
|
165
242
|
},
|
|
166
243
|
"files": [
|
|
167
244
|
"dist",
|
|
168
|
-
"src/*.css",
|
|
169
245
|
"ai",
|
|
170
246
|
"docs",
|
|
247
|
+
"ux-demo/recipes",
|
|
171
248
|
"llms.txt",
|
|
172
249
|
"README.md",
|
|
173
250
|
"LICENSE"
|
|
174
251
|
],
|
|
175
252
|
"scripts": {
|
|
176
|
-
"build": "tsup && node ../scripts/clean-dist-output.mjs dist && node scripts/build-browser-entries.mjs",
|
|
253
|
+
"build": "tsup && node ../scripts/clean-dist-output.mjs dist && node scripts/build-css.mjs && node scripts/build-browser-entries.mjs",
|
|
177
254
|
"dev": "npm run build && vite --config ux-demo/vite.config.ts",
|
|
178
255
|
"demo:build": "npm run build && vite build --config ux-demo/vite.config.ts && node scripts/check-demo-bundle.mjs",
|
|
179
256
|
"demo:serve": "vite preview --config ux-demo/vite.config.ts",
|
|
180
|
-
"
|
|
257
|
+
"format:css": "prettier --write \"src/**/*.css\" \"ux-demo/**/*.css\"",
|
|
258
|
+
"check:format": "prettier --check \"src/**/*.css\" \"ux-demo/**/*.css\"",
|
|
259
|
+
"catalog:sync": "node scripts/sync-component-catalog.mjs",
|
|
260
|
+
"check:catalog": "node scripts/sync-component-catalog.mjs --check && node scripts/check-component-catalog.mjs",
|
|
261
|
+
"check:recipes": "node scripts/check-recipes.mjs",
|
|
262
|
+
"check:ai-regressions": "node scripts/check-ai-regressions.mjs && node scripts/audit-ai-regression-results.mjs",
|
|
263
|
+
"check:ai-signatures": "node scripts/sync-ai-public-signatures.mjs --check",
|
|
264
|
+
"ai:signatures:sync": "npm run build && node scripts/sync-ai-public-signatures.mjs",
|
|
265
|
+
"ai:regressions:compile": "node scripts/compile-ai-regression-response.mjs",
|
|
266
|
+
"ai:regressions:prepare": "node scripts/prepare-ai-regression.mjs",
|
|
267
|
+
"ai:regressions:record": "node scripts/record-ai-regression-run.mjs",
|
|
268
|
+
"ai:regressions:score": "node scripts/score-ai-regression.mjs",
|
|
269
|
+
"check:guidance": "node scripts/check-decision-guidance.mjs",
|
|
270
|
+
"lint": "eslint src tests ux-demo docs/examples --max-warnings 0",
|
|
181
271
|
"typecheck": "node node_modules/typescript7/bin/tsc --noEmit",
|
|
182
272
|
"test": "npm run test:unit && npm run test:bundle",
|
|
183
273
|
"test:unit": "vitest run tests/unit --coverage",
|
|
184
274
|
"test:bundle": "npm run build && vitest run tests/bundle",
|
|
275
|
+
"test:consumer-types": "npm run build && node node_modules/typescript7/bin/tsc -p tests/consumer-types/webawesome/tsconfig.json",
|
|
185
276
|
"test:e2e": "npm run demo:build && playwright test",
|
|
186
|
-
"check": "npm run lint && npm run typecheck && npm test && npm run demo:build && npm pack --dry-run",
|
|
187
|
-
"prepublishOnly": "npm run check"
|
|
277
|
+
"check": "npm run check:format && npm run check:catalog && npm run check:recipes && npm run check:guidance && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm pack --dry-run",
|
|
278
|
+
"prepublishOnly": "npm run check",
|
|
279
|
+
"design-templates:build": "node scripts/build-design-templates.mjs"
|
|
188
280
|
},
|
|
189
281
|
"peerDependencies": {
|
|
190
282
|
"@awesome.me/webawesome": "^3.12.0",
|
|
191
|
-
"kerfjs": "^
|
|
283
|
+
"kerfjs": "^5.0.0-0",
|
|
192
284
|
"lucide": "^1.43.0"
|
|
193
285
|
},
|
|
194
286
|
"peerDependenciesMeta": {
|
|
@@ -204,11 +296,14 @@
|
|
|
204
296
|
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
|
205
297
|
"@typescript-eslint/parser": "^8.65.0",
|
|
206
298
|
"@vitest/coverage-v8": "4.1.11",
|
|
299
|
+
"domotion-svg": "^0.29.0",
|
|
207
300
|
"esbuild": "^0.25.0",
|
|
208
301
|
"eslint": "^10.8.0",
|
|
209
302
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
210
303
|
"happy-dom": "^20.9.0",
|
|
211
304
|
"lucide": "1.43.0",
|
|
305
|
+
"postcss": "^8.5.28",
|
|
306
|
+
"prettier": "^3.9.6",
|
|
212
307
|
"tsup": "^8.3.0",
|
|
213
308
|
"typescript": "^6.0.3",
|
|
214
309
|
"typescript7": "npm:typescript@^7.0.2",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import '@kerfjs/ui/layout.css';
|
|
2
|
+
import './recipes.css';
|
|
3
|
+
|
|
4
|
+
import { ListHeader } from '@kerfjs/ui/list-header';
|
|
5
|
+
import { ListItem } from '@kerfjs/ui/list-item';
|
|
6
|
+
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
7
|
+
import { PanelHeader } from '@kerfjs/ui/panel-header';
|
|
8
|
+
import { ResizableRegion } from '@kerfjs/ui/resizable-region';
|
|
9
|
+
import { Toolbar } from '@kerfjs/ui/toolbar';
|
|
10
|
+
import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
|
|
11
|
+
import { ToolbarText } from '@kerfjs/ui/toolbar-text';
|
|
12
|
+
import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
|
|
13
|
+
import { signal } from 'kerfjs';
|
|
14
|
+
import { Bell, Folder, Inbox, Settings } from 'lucide';
|
|
15
|
+
|
|
16
|
+
import type { RecipeFactory } from './types.js';
|
|
17
|
+
|
|
18
|
+
export const createRecipe: RecipeFactory = (announce) => {
|
|
19
|
+
const selected = signal('inbox');
|
|
20
|
+
const navigationSize = signal(224);
|
|
21
|
+
const inspectorSize = signal(240);
|
|
22
|
+
const responsivePane = signal<'content' | 'navigation' | 'inspector'>('content');
|
|
23
|
+
const render = () => <section class="kui-recipe recipe-shell kui-recipe__surface kui-layout" data-recipe="recipe-app-shell" data-responsive-pane={responsivePane.value}>
|
|
24
|
+
<Toolbar label="Atlas workspace" leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Atlas" /></ToolbarControlGroup>} trailing={<div class="recipe-shell__toolbar-actions"><ToolbarControlGroup className="recipe-shell__responsive-controls" appearance="borderless" label="Workspace panes"><button type="button" aria-label="Show navigation" aria-controls="recipe-shell-navigation" aria-pressed={String(responsivePane.value === 'navigation')} data-action="recipe-action" data-recipe-command="show-navigation"><LucideIcon icon={Folder} name="folder" /></button><button type="button" aria-label="Show content" aria-controls="recipe-shell-content" aria-pressed={String(responsivePane.value === 'content')} data-action="recipe-action" data-recipe-command="show-content"><LucideIcon icon={Inbox} name="inbox" /></button><button type="button" aria-label="Show inspector" aria-controls="recipe-shell-inspector" aria-pressed={String(responsivePane.value === 'inspector')} data-action="recipe-action" data-recipe-command="show-inspector"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup><ToolbarControlGroup appearance="borderless" label="Workspace controls"><button type="button" aria-label="Notifications" data-action="recipe-action" data-recipe-command="notify"><LucideIcon icon={Bell} name="bell" /></button><button type="button" aria-label="Settings" data-action="recipe-action" data-recipe-command="settings"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup></div>} />
|
|
25
|
+
<div class="recipe-shell__body">
|
|
26
|
+
<div id="recipe-shell-navigation" class="recipe-shell__nav-region"><ResizableRegion id="recipe-navigation" label="Navigation" size={navigationSize.value} min={180} max={320}><aside class="recipe-shell__nav kui-pane"><ListHeader label="Workspace" /><nav class="recipe-shell__nav-list kui-pane__content kui-content" aria-label="Workspace"><section><ListItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} selected={selected.value === 'inbox'} /><ListItem action="recipe-action" itemId="projects" label="Projects with a deliberately wrapping title" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'projects'} multiline /></section></nav></aside></ResizableRegion></div>
|
|
27
|
+
<main id="recipe-shell-content" class="recipe-shell__main kui-pane"><PanelHeader title={selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'} titleId="recipe-shell-main-title" actions={<button class="kui-recipe__button" data-primary="true" type="button" data-action="recipe-action" data-recipe-command="new">New task</button>} /><div class="recipe-shell__main-body kui-pane__content kui-content"><p class="kui-recipe__ownership kui-content-item">Recipe owns pane geometry and one scroll owner per pane. The app owns routing, data, pane visibility, sizes, and persistence.</p><div class="recipe-shell__cards">{['Release accessibility audit', 'Prepare tablet navigation', 'Review stale-data states', 'Confirm package boundaries'].map((title) => <article class="recipe-shell__card kui-content-item"><strong>{title}</strong><p class="kui-recipe__muted">Assigned to the interface systems team · due this week</p></article>)}</div></div></main>
|
|
28
|
+
<div id="recipe-shell-inspector" class="recipe-shell__inspector-region"><ResizableRegion id="recipe-inspector" label="Inspector" size={inspectorSize.value} min={200} max={360} edge="start"><aside class="recipe-shell__inspector kui-pane"><PanelHeader title="Inspector" titleId="recipe-shell-inspector-title" /><div class="recipe-shell__inspector-body kui-pane__content kui-content"><ValueTable label="Selected task"><ValueTableRow label="Status" value="In review" /><ValueTableRow label="Owner" value="Mara Chen" /><ValueTableRow label="Priority" value="High" /></ValueTable></div></aside></ResizableRegion></div>
|
|
29
|
+
</div>
|
|
30
|
+
</section>;
|
|
31
|
+
return { render, action(command, element) { const pane = command.match(/^show-(navigation|content|inspector)$/)?.[1] as typeof responsivePane.value | undefined; if (pane) responsivePane.value = pane; const id = element.dataset.itemId; if (id) selected.value = id; announce(id ? `Opened ${id}` : pane ? `Showing ${pane}` : command === 'new' ? 'New task requested' : `${command} requested`); }, resize(id, size) { if (id === 'recipe-navigation') navigationSize.value = size; if (id === 'recipe-inspector') inspectorSize.value = size; announce(`${id} resized to ${size}px`); } };
|
|
32
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import '@kerfjs/ui/layout.css';
|
|
2
|
+
import '@kerfjs/ui/collapsible-panel.css';
|
|
3
|
+
import './recipes.css';
|
|
4
|
+
|
|
5
|
+
import { CollapsiblePanel, CollapsiblePanelToggle } from '@kerfjs/ui/collapsible-panel';
|
|
6
|
+
import { deviceClass } from '@kerfjs/ui/device-class';
|
|
7
|
+
import { ListHeader } from '@kerfjs/ui/list-header';
|
|
8
|
+
import { ListItem } from '@kerfjs/ui/list-item';
|
|
9
|
+
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
10
|
+
import { PanelHeader } from '@kerfjs/ui/panel-header';
|
|
11
|
+
import { Toolbar } from '@kerfjs/ui/toolbar';
|
|
12
|
+
import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
|
|
13
|
+
import { ToolbarText } from '@kerfjs/ui/toolbar-text';
|
|
14
|
+
import { type SidebarStorage, wireSidebar } from '@kerfjs/ui/wire-sidebar';
|
|
15
|
+
import { signal } from 'kerfjs';
|
|
16
|
+
import { Bell, Folder, Inbox, Users } from 'lucide';
|
|
17
|
+
|
|
18
|
+
import type { RecipeFactory } from './types.js';
|
|
19
|
+
|
|
20
|
+
const RAIL_ACTION = 'recipe-sidebar-rail';
|
|
21
|
+
const DRAWER_ACTION = 'recipe-sidebar-drawer';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A memory-backed store so the recipe demonstrates the persistence hook without
|
|
25
|
+
* writing to the shared `localStorage` the catalog page runs in.
|
|
26
|
+
*/
|
|
27
|
+
function memoryStorage(): SidebarStorage {
|
|
28
|
+
const map = new Map<string, string>();
|
|
29
|
+
return { getItem: (key) => map.get(key) ?? null, setItem: (key, value) => { map.set(key, value); } };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const createRecipe: RecipeFactory = (announce) => {
|
|
33
|
+
const selected = signal('inbox');
|
|
34
|
+
const railCollapsed = signal(false);
|
|
35
|
+
const drawerCollapsed = signal(true);
|
|
36
|
+
const storage = memoryStorage();
|
|
37
|
+
// A shared window-backed device class: when the viewport is compact the wire
|
|
38
|
+
// switches the rail/drawer to a dismissable overlay presentation.
|
|
39
|
+
const device = deviceClass();
|
|
40
|
+
|
|
41
|
+
const railToggle = (inPanel: boolean) => <CollapsiblePanelToggle side="left" collapsed={railCollapsed.value} action={RAIL_ACTION} panelId="sidebar-rail" label={railCollapsed.value ? 'Show navigation' : 'Hide navigation'} className={inPanel ? '' : 'recipe-collapsible-sidebar__reveal'} />;
|
|
42
|
+
const drawerToggle = () => <CollapsiblePanelToggle side="bottom" collapsed={drawerCollapsed.value} action={DRAWER_ACTION} panelId="sidebar-console" label={drawerCollapsed.value ? 'Show activity' : 'Hide activity'} />;
|
|
43
|
+
|
|
44
|
+
const render = () => <section class="kui-recipe recipe-collapsible-sidebar kui-recipe__surface" data-recipe="recipe-collapsible-sidebar" data-rail-collapsed={String(railCollapsed.value)} data-drawer-collapsed={String(drawerCollapsed.value)}>
|
|
45
|
+
<CollapsiblePanel id="sidebar-rail" side="left" size={232} collapsed={railCollapsed.value} label="Workspace navigation" className="recipe-collapsible-sidebar__rail kui-pane">
|
|
46
|
+
<div class="recipe-collapsible-sidebar__rail-head">
|
|
47
|
+
<ToolbarText text="Atlas" />
|
|
48
|
+
<ToolbarControlGroup appearance="borderless" single>{railToggle(true)}</ToolbarControlGroup>
|
|
49
|
+
</div>
|
|
50
|
+
<nav class="recipe-collapsible-sidebar__nav kui-pane__content kui-content" aria-label="Workspace">
|
|
51
|
+
<section>
|
|
52
|
+
<ListHeader label="Workspace" />
|
|
53
|
+
<ListItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} trailing={<span>12</span>} selected={selected.value === 'inbox'} />
|
|
54
|
+
<ListItem action="recipe-action" itemId="projects" label="Projects" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'projects'} />
|
|
55
|
+
<ListItem action="recipe-action" itemId="shared" label="Shared with me" icon={<LucideIcon icon={Users} name="users" />} selected={selected.value === 'shared'} />
|
|
56
|
+
</section>
|
|
57
|
+
</nav>
|
|
58
|
+
</CollapsiblePanel>
|
|
59
|
+
<main class="recipe-collapsible-sidebar__main kui-pane">
|
|
60
|
+
<PanelHeader
|
|
61
|
+
title={selected.value === 'projects' ? 'Projects' : selected.value === 'shared' ? 'Shared with me' : 'Inbox'}
|
|
62
|
+
titleId="recipe-collapsible-main-title"
|
|
63
|
+
icon={railToggle(false)}
|
|
64
|
+
actions={<ToolbarControlGroup appearance="borderless" single>{drawerToggle()}</ToolbarControlGroup>}
|
|
65
|
+
/>
|
|
66
|
+
<div class="recipe-collapsible-sidebar__body kui-pane__content kui-content">
|
|
67
|
+
<p class="kui-recipe__ownership kui-content-item">The app owns each panel's <code>collapsed</code> signal, sizes, and content; <code>wireSidebar</code> owns the toggle, focus move/restore, the compact overlay, and persistence.</p>
|
|
68
|
+
<article class="recipe-collapsible-sidebar__card kui-content-item"><strong>Narrow the window to a compact width</strong><p class="kui-recipe__muted">The rail and drawer become a dismissable overlay: a backdrop, Escape, and a trapped Tab ring, all managed by the wire.</p></article>
|
|
69
|
+
</div>
|
|
70
|
+
</main>
|
|
71
|
+
<CollapsiblePanel id="sidebar-console" side="bottom" size={168} collapsed={drawerCollapsed.value} label="Activity" className="recipe-collapsible-sidebar__drawer kui-pane">
|
|
72
|
+
<Toolbar label="Activity" divider leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Activity" size="small" /></ToolbarControlGroup>} trailing={<ToolbarControlGroup appearance="borderless" single>{drawerToggle()}</ToolbarControlGroup>} />
|
|
73
|
+
<div class="recipe-collapsible-sidebar__drawer-body kui-pane__content kui-content">
|
|
74
|
+
<ul class="recipe-collapsible-sidebar__log">
|
|
75
|
+
<li><LucideIcon icon={Bell} name="bell" />Deploy finished · 2m ago</li>
|
|
76
|
+
<li><LucideIcon icon={Bell} name="bell" />Review requested · 9m ago</li>
|
|
77
|
+
</ul>
|
|
78
|
+
</div>
|
|
79
|
+
</CollapsiblePanel>
|
|
80
|
+
</section>;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
render,
|
|
84
|
+
action(command, element) {
|
|
85
|
+
const id = element.dataset.itemId;
|
|
86
|
+
if (id) { selected.value = id; announce(`Opened ${id}`); return; }
|
|
87
|
+
announce(`${command} requested`);
|
|
88
|
+
},
|
|
89
|
+
wire(root) {
|
|
90
|
+
return wireSidebar(root, {
|
|
91
|
+
deviceClass: device,
|
|
92
|
+
storage,
|
|
93
|
+
panels: [
|
|
94
|
+
{ id: 'sidebar-rail', collapsed: railCollapsed, toggleAction: RAIL_ACTION, storageKey: 'recipe-sidebar-rail' },
|
|
95
|
+
{ id: 'sidebar-console', collapsed: drawerCollapsed, toggleAction: DRAWER_ACTION, storageKey: 'recipe-sidebar-console' },
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
};
|