@kerfjs/ui 5.0.0-beta.17 → 5.0.0-beta.18
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 +108 -79
- package/ai/component-catalog-extension.schema.json +79 -0
- package/ai/component-catalog.json +499 -0
- package/ai/component-catalog.schema.json +15 -0
- package/ai/public-api-signatures-v1.md +67 -31
- package/ai/skill.md +58 -49
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/app-tab.d.ts +1 -1
- package/dist/app-tab.js +1 -1
- package/dist/catalog.d.ts +10 -3
- package/dist/catalog.js +279 -90
- package/dist/catalog.js.map +1 -1
- package/dist/chunk-43FUQH35.js +83 -0
- package/dist/chunk-43FUQH35.js.map +1 -0
- package/dist/{chunk-YGN7OMLT.js → chunk-4TRDV6RL.js} +2 -2
- package/dist/chunk-4TRDV6RL.js.map +1 -0
- package/dist/chunk-6ETZKKAE.js +38 -0
- package/dist/chunk-6ETZKKAE.js.map +1 -0
- package/dist/chunk-6WTETUZ6.js +21 -0
- package/dist/chunk-6WTETUZ6.js.map +1 -0
- package/dist/chunk-7L6EHUMV.js +31 -0
- package/dist/chunk-7L6EHUMV.js.map +1 -0
- package/dist/chunk-BBRXX6PF.js +108 -0
- package/dist/chunk-BBRXX6PF.js.map +1 -0
- package/dist/chunk-DPDBVKNA.js +113 -0
- package/dist/chunk-DPDBVKNA.js.map +1 -0
- package/dist/chunk-ELSJX4W7.js +68 -0
- package/dist/chunk-ELSJX4W7.js.map +1 -0
- package/dist/{chunk-6NLRBSVX.js → chunk-ENVTNLYC.js} +125 -68
- package/dist/chunk-ENVTNLYC.js.map +1 -0
- package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
- package/dist/chunk-FEY65TBV.js.map +1 -0
- package/dist/chunk-JTHUXDN2.js +58 -0
- package/dist/chunk-JTHUXDN2.js.map +1 -0
- package/dist/chunk-LPOMRC4C.js +25 -0
- package/dist/chunk-LPOMRC4C.js.map +1 -0
- package/dist/chunk-NENBUYX5.js +79 -0
- package/dist/chunk-NENBUYX5.js.map +1 -0
- package/dist/{chunk-XXMOARTH.js → chunk-NVWXKU37.js} +57 -16
- package/dist/chunk-NVWXKU37.js.map +1 -0
- package/dist/chunk-PIDTEQ3U.js +105 -0
- package/dist/chunk-PIDTEQ3U.js.map +1 -0
- package/dist/chunk-PX3YSS5C.js +32 -0
- package/dist/chunk-PX3YSS5C.js.map +1 -0
- package/dist/chunk-PZQVSDLH.js +61 -0
- package/dist/chunk-PZQVSDLH.js.map +1 -0
- package/dist/chunk-QIRXYKCE.js +113 -0
- package/dist/chunk-QIRXYKCE.js.map +1 -0
- package/dist/chunk-QO55FN2Y.js +20 -0
- package/dist/chunk-QO55FN2Y.js.map +1 -0
- package/dist/chunk-QQFJMRIB.js +45 -0
- package/dist/chunk-QQFJMRIB.js.map +1 -0
- package/dist/chunk-S3HKPI7I.js +218 -0
- package/dist/chunk-S3HKPI7I.js.map +1 -0
- package/dist/{chunk-WXMGNX2Y.js → chunk-S3PCZ7NV.js} +2 -2
- package/dist/chunk-S3PCZ7NV.js.map +1 -0
- package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
- package/dist/chunk-SRSJO5QE.js.map +1 -0
- package/dist/chunk-U5IFGQCD.js +117 -0
- package/dist/chunk-U5IFGQCD.js.map +1 -0
- package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
- package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
- package/dist/chunk-ZK2G3VR5.js +35 -0
- package/dist/chunk-ZK2G3VR5.js.map +1 -0
- package/dist/chunk-ZVI5YST2.js +41 -0
- package/dist/chunk-ZVI5YST2.js.map +1 -0
- package/dist/collapsible-panel.d.ts +2 -2
- package/dist/collapsible-panel.js +47 -6
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/device-class.js +33 -5
- package/dist/device-class.js.map +1 -1
- package/dist/disclosure-arrow.js +1 -1
- package/dist/empty-state.d.ts +1 -1
- package/dist/empty-state.js +1 -1
- package/dist/floating-toolbar.d.ts +1 -1
- package/dist/floating-toolbar.js +1 -1
- package/dist/index.js +25 -25
- package/dist/list-action-row.d.ts +1 -1
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.d.ts +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-inset-control.d.ts +1 -1
- package/dist/list-inset-control.js +1 -1
- package/dist/list-inset-text.d.ts +1 -1
- package/dist/list-inset-text.js +1 -1
- package/dist/list-item.d.ts +1 -1
- package/dist/list-item.js +1 -1
- package/dist/loading-spinner.js +1 -1
- package/dist/nav-stack.d.ts +1 -1
- package/dist/nav-stack.js +1 -1
- package/dist/panel-header.d.ts +1 -1
- package/dist/panel-header.js +1 -1
- package/dist/resizable-region.d.ts +1 -1
- package/dist/resizable-region.js +1 -1
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +1 -1
- package/dist/select.js +1 -1
- package/dist/skeleton.d.ts +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/split-view.d.ts +1 -1
- package/dist/split-view.js +70 -10
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.d.ts +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog.css +26 -0
- package/dist/tab-bar.d.ts +1 -1
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +1 -1
- package/dist/tab-scaffold.js +43 -8
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +1 -1
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +1 -1
- package/dist/toolbar.js +1 -1
- package/dist/value-table.d.ts +2 -2
- package/dist/value-table.js +1 -1
- package/dist/wire-catalog.d.ts +31 -2
- package/dist/wire-catalog.js +203 -8
- package/dist/wire-catalog.js.map +1 -1
- package/dist/wire-nav-stack.js +22 -7
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-resizable-regions.d.ts +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.js +52 -41
- package/dist/wire-sidebar.js.map +1 -1
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.js +9 -4
- package/dist/wire-tab-scaffold.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +1 -1
- package/dist/workbench.js +56 -9
- package/dist/workbench.js.map +1 -1
- package/docs/app-layouts.md +7 -7
- package/docs/catalog.md +108 -24
- package/docs/collapsible-panel.md +38 -11
- package/docs/component-contract.md +30 -2
- package/docs/component-selection.md +71 -63
- package/docs/design/templates.md +1 -1
- package/docs/design-philosophy.md +5 -5
- package/docs/device-class.md +8 -8
- package/docs/examples/command-palette-adapter.tsx +67 -17
- package/docs/examples/component-catalog-extension.json +45 -0
- package/docs/layout.md +19 -19
- package/docs/nav-stack.md +15 -6
- package/docs/recipes.md +4 -4
- package/docs/split-view.md +3 -3
- package/docs/tab-scaffold.md +16 -7
- package/docs/webawesome-theme.md +8 -8
- package/docs/workbench.md +18 -5
- package/llms.txt +20 -14
- package/package.json +6 -4
- package/ux-demo/recipes/app-shell.tsx +206 -10
- package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
- package/ux-demo/recipes/compact-toolbar.tsx +114 -12
- package/ux-demo/recipes/composer-form.tsx +106 -18
- package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
- package/ux-demo/recipes/list-workspace-states.tsx +165 -8
- package/ux-demo/recipes/loaders.ts +5 -2
- package/ux-demo/recipes/loading-inspector.tsx +124 -41
- package/ux-demo/recipes/mount-recipe.ts +27 -6
- package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
- package/ux-demo/recipes/navigation-stack.tsx +61 -19
- package/ux-demo/recipes/types.ts +3 -1
- package/ux-demo/recipes/workspace-header.tsx +57 -2
- package/dist/chunk-2Z5TWHKQ.js +0 -34
- package/dist/chunk-2Z5TWHKQ.js.map +0 -1
- package/dist/chunk-37T3VNIZ.js +0 -19
- package/dist/chunk-37T3VNIZ.js.map +0 -1
- package/dist/chunk-55DATV3A.js +0 -10
- package/dist/chunk-55DATV3A.js.map +0 -1
- package/dist/chunk-5ATCDSQS.js +0 -42
- package/dist/chunk-5ATCDSQS.js.map +0 -1
- package/dist/chunk-5E2HVJ33.js +0 -10
- package/dist/chunk-5E2HVJ33.js.map +0 -1
- package/dist/chunk-6NLRBSVX.js.map +0 -1
- package/dist/chunk-7BR3DO2A.js.map +0 -1
- package/dist/chunk-7JB7VPRI.js +0 -34
- package/dist/chunk-7JB7VPRI.js.map +0 -1
- package/dist/chunk-7LDCTU7D.js +0 -134
- package/dist/chunk-7LDCTU7D.js.map +0 -1
- package/dist/chunk-AHQI52QO.js.map +0 -1
- package/dist/chunk-AQNKJRFH.js +0 -79
- package/dist/chunk-AQNKJRFH.js.map +0 -1
- package/dist/chunk-AVZHJN6C.js +0 -14
- package/dist/chunk-AVZHJN6C.js.map +0 -1
- package/dist/chunk-B24C4A6T.js +0 -22
- package/dist/chunk-B24C4A6T.js.map +0 -1
- package/dist/chunk-CH4SQ6H5.js +0 -39
- package/dist/chunk-CH4SQ6H5.js.map +0 -1
- package/dist/chunk-DAFFDO33.js +0 -14
- package/dist/chunk-DAFFDO33.js.map +0 -1
- package/dist/chunk-I7INRJO4.js +0 -17
- package/dist/chunk-I7INRJO4.js.map +0 -1
- package/dist/chunk-L4OKFDJY.js +0 -24
- package/dist/chunk-L4OKFDJY.js.map +0 -1
- package/dist/chunk-M5JRI6MN.js +0 -27
- package/dist/chunk-M5JRI6MN.js.map +0 -1
- package/dist/chunk-RNPGFQ33.js +0 -10
- package/dist/chunk-RNPGFQ33.js.map +0 -1
- package/dist/chunk-VPCTCUED.js +0 -10
- package/dist/chunk-VPCTCUED.js.map +0 -1
- package/dist/chunk-WRTAQ7BZ.js +0 -23
- package/dist/chunk-WRTAQ7BZ.js.map +0 -1
- package/dist/chunk-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-WXMGNX2Y.js.map +0 -1
- package/dist/chunk-XXMOARTH.js.map +0 -1
- package/dist/chunk-YGN7OMLT.js.map +0 -1
- package/dist/chunk-ZQQ6P4SV.js +0 -20
- package/dist/chunk-ZQQ6P4SV.js.map +0 -1
package/docs/catalog.md
CHANGED
|
@@ -7,7 +7,7 @@ you get the same shell without rebuilding it. Like the app layouts, it is a
|
|
|
7
7
|
subpath-only, tree-shakeable module that adds nothing to the main barrel.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @kerfjs/ui
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
- `Catalog(props)` returns the shell as `SafeHtml` (a `<main class="kui-catalog">`).
|
|
@@ -17,20 +17,20 @@ npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is neede
|
|
|
17
17
|
- `wireCatalog(root, options)` wires the interactions (sidebar selection, the
|
|
18
18
|
related-entry popup menu, and the collapse/theme toggles) with one delegated
|
|
19
19
|
listener set and returns a disposer; it can also mirror the active id into the
|
|
20
|
-
URL.
|
|
20
|
+
URL and reveal the active sidebar row after a controlled render.
|
|
21
21
|
|
|
22
22
|
## What you supply
|
|
23
23
|
|
|
24
24
|
- **`sections`** — category-grouped entries: `{ category, entries: [{ id, name,
|
|
25
|
-
|
|
25
|
+
description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
|
|
26
26
|
`ListItem` under a `ListHeader` for its category. Short `tags` render as quiet
|
|
27
27
|
trailing pills for decision metadata such as `Discouraged`.
|
|
28
28
|
- **`content`** — the rendered preview for the active entry. Keep a map of `id →
|
|
29
|
-
|
|
29
|
+
() => SafeHtml` in your app and call `renderers[active]()` in your render.
|
|
30
30
|
- **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header.
|
|
31
31
|
- **`secondarySections`** — an optional secondary "ecosystem" group shown below the
|
|
32
32
|
primary sections with a quieter treatment: `{ label, sections, collapsible?,
|
|
33
|
-
|
|
33
|
+
expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
|
|
34
34
|
`expanded` (the app owns it; wire it with `wireCatalog`'s `onToggleSecondary`).
|
|
35
35
|
- Optional slots: `headerActions` (extra header controls), `sidebarFooter` (extra
|
|
36
36
|
sidebar content), and `status` (a footer status line).
|
|
@@ -47,7 +47,7 @@ instead of hand-rolled example markup, so labels, notes, and left-edge alignment
|
|
|
47
47
|
stay consistent:
|
|
48
48
|
|
|
49
49
|
```tsx
|
|
50
|
-
import { CatalogExample, CatalogExampleStack } from
|
|
50
|
+
import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
|
|
51
51
|
|
|
52
52
|
const buttonPreview = (
|
|
53
53
|
<CatalogExampleStack label="Button variants">
|
|
@@ -72,26 +72,99 @@ content-item/composition that already owns its geometry. The inset is published
|
|
|
72
72
|
the `--kui-catalog-example-align` custom property so a debug overlay can exclude it
|
|
73
73
|
from a specimen's measured margin.
|
|
74
74
|
|
|
75
|
+
## Geometry inspection
|
|
76
|
+
|
|
77
|
+
Pass `geometryOverlay` to `Catalog` when individual component previews should
|
|
78
|
+
show otherwise-invisible geometry. The controlled boolean draws a dashed outer
|
|
79
|
+
bound around specimens with transparent backgrounds and devtools-style orange
|
|
80
|
+
bands over positive margins. Keep the prop present while switching entries so
|
|
81
|
+
`wireCatalog` can reuse one overlay layer; set it to `true` for focused component
|
|
82
|
+
previews and `false` for full compositions whose child geometry should remain
|
|
83
|
+
unmarked.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Catalog
|
|
87
|
+
{...props}
|
|
88
|
+
geometryOverlay={activeEntry.kind === "component"}
|
|
89
|
+
content={renderers[active.value]()}
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Call `wireCatalogGeometryOverlay(root)` after the first render to synchronize the
|
|
94
|
+
opt-in layer across rerenders, theme changes, resizes, and scrolling, and retain
|
|
95
|
+
its disposer alongside `wireCatalog`'s. `CatalogExample` labels and notes are
|
|
96
|
+
excluded; its `align` inset is also subtracted so alignment scaffolding is not
|
|
97
|
+
reported as intrinsic component margin. Put
|
|
98
|
+
`data-catalog-geometry-overlay-skip` on a preview subtree that is intentionally
|
|
99
|
+
explanatory chrome rather than a specimen.
|
|
100
|
+
|
|
101
|
+
Use the overlay together with machine-readable geometry ownership metadata; the
|
|
102
|
+
overlay verifies what is rendered, while metadata tells people and AI tools
|
|
103
|
+
whether the component, its parent, or its children are responsible for margin,
|
|
104
|
+
border, and padding. Catalogs for downstream components should conform to the
|
|
105
|
+
[`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
|
|
106
|
+
contract and can start from the checked
|
|
107
|
+
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
108
|
+
example; provide those entries beside Kerf's shipped catalog to AI tools.
|
|
109
|
+
|
|
110
|
+
## Selection reveal
|
|
111
|
+
|
|
112
|
+
Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
|
|
113
|
+
`onSelect` updates controlled state, the helper waits one animation frame, finds
|
|
114
|
+
the exact matching `data-item-id`, and scrolls it into view without changing
|
|
115
|
+
focus. A newer selection or disposal cancels the pending reveal. The default
|
|
116
|
+
media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
|
|
117
|
+
layouts keep their existing scroll position.
|
|
118
|
+
|
|
119
|
+
Pass an options object instead of `true` to customize `block`, `inline`,
|
|
120
|
+
`behavior`, or `media`; `media: false` deliberately enables the behavior at all
|
|
121
|
+
sizes. For an initial deep link that did not come through `wireCatalog`, call
|
|
122
|
+
`revealCatalogEntry(app, initialId, { block: "center" })` after the first mount.
|
|
123
|
+
|
|
75
124
|
## Complete example
|
|
76
125
|
|
|
77
126
|
```tsx
|
|
78
|
-
import { mount, signal } from
|
|
79
|
-
import { Catalog, type CatalogSection } from
|
|
80
|
-
import {
|
|
81
|
-
|
|
127
|
+
import { mount, signal } from "kerfjs";
|
|
128
|
+
import { Catalog, type CatalogSection } from "@kerfjs/ui/catalog";
|
|
129
|
+
import {
|
|
130
|
+
revealCatalogEntry,
|
|
131
|
+
wireCatalog,
|
|
132
|
+
wireCatalogGeometryOverlay,
|
|
133
|
+
} from "@kerfjs/ui/wire-catalog";
|
|
134
|
+
import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
|
|
82
135
|
|
|
83
136
|
// 1. Describe your components once.
|
|
84
137
|
const sections: CatalogSection[] = [
|
|
85
138
|
{
|
|
86
|
-
category:
|
|
139
|
+
category: "Controls",
|
|
87
140
|
entries: [
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
141
|
+
{
|
|
142
|
+
id: "button",
|
|
143
|
+
name: "Button",
|
|
144
|
+
description: "A pressable control.",
|
|
145
|
+
resources: [
|
|
146
|
+
{
|
|
147
|
+
label: "Source",
|
|
148
|
+
href: "/src/button.tsx",
|
|
149
|
+
detail: "src/button.tsx",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "field",
|
|
155
|
+
name: "Field",
|
|
156
|
+
description: "A labeled input.",
|
|
157
|
+
tags: ["Discouraged"],
|
|
158
|
+
related: [{ id: "button", name: "Button", group: "Used with" }],
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
category: "Feedback",
|
|
164
|
+
entries: [
|
|
165
|
+
{ id: "toast", name: "Toast", description: "A transient message." },
|
|
92
166
|
],
|
|
93
167
|
},
|
|
94
|
-
{ category: 'Feedback', entries: [{ id: 'toast', name: 'Toast', description: 'A transient message.' }] },
|
|
95
168
|
];
|
|
96
169
|
|
|
97
170
|
// 2. One preview render per entry id.
|
|
@@ -102,32 +175,43 @@ const renderers: Record<string, () => ReturnType<typeof Button>> = {
|
|
|
102
175
|
};
|
|
103
176
|
|
|
104
177
|
// 3. App-owned state (domain: which entry; transient: collapsed; global: theme).
|
|
105
|
-
const initial =
|
|
178
|
+
const initial =
|
|
179
|
+
new URLSearchParams(location.search).get("c") ?? sections[0].entries[0].id;
|
|
106
180
|
const active = signal(initial);
|
|
107
181
|
const collapsed = signal(false);
|
|
108
|
-
const theme = signal<
|
|
182
|
+
const theme = signal<"light" | "dark">("light");
|
|
109
183
|
|
|
110
|
-
const app = document.getElementById(
|
|
184
|
+
const app = document.getElementById("app")!;
|
|
111
185
|
mount(app, () => (
|
|
112
186
|
<Catalog
|
|
113
|
-
brand={{ title:
|
|
187
|
+
brand={{ title: "Acme UI", subtitle: "Design system" }}
|
|
114
188
|
sections={sections}
|
|
115
189
|
active={active.value}
|
|
116
190
|
content={renderers[active.value]?.() ?? <></>}
|
|
117
191
|
collapsed={collapsed.value}
|
|
118
192
|
theme={theme.value}
|
|
193
|
+
geometryOverlay={true}
|
|
119
194
|
/>
|
|
120
195
|
));
|
|
121
196
|
|
|
122
197
|
wireCatalog(app, {
|
|
123
|
-
onSelect: (id) => {
|
|
124
|
-
|
|
198
|
+
onSelect: (id) => {
|
|
199
|
+
active.value = id;
|
|
200
|
+
},
|
|
201
|
+
onToggleSidebar: () => {
|
|
202
|
+
collapsed.value = !collapsed.value;
|
|
203
|
+
},
|
|
125
204
|
onToggleTheme: () => {
|
|
126
|
-
theme.value = theme.value ===
|
|
205
|
+
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
127
206
|
document.documentElement.dataset.theme = theme.value; // apply your theme however you like
|
|
128
207
|
},
|
|
129
|
-
urlParam:
|
|
208
|
+
urlParam: "c", // mirror the active id into ?c=<id>
|
|
209
|
+
revealSelection: true, // reveal long desktop sidebars without moving focus
|
|
130
210
|
});
|
|
211
|
+
wireCatalogGeometryOverlay(app);
|
|
212
|
+
|
|
213
|
+
// Optional for an initial deep link whose row may start outside the viewport.
|
|
214
|
+
revealCatalogEntry(app, initial, { block: "center" });
|
|
131
215
|
```
|
|
132
216
|
|
|
133
217
|
## Ownership boundary
|
|
@@ -7,7 +7,7 @@ a full shell so you can dock one panel wherever your layout needs it. They are
|
|
|
7
7
|
subpath-only, tree-shakeable modules that add nothing to the main barrel.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @kerfjs/ui
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.css`.
|
|
@@ -46,33 +46,60 @@ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.
|
|
|
46
46
|
## Example
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
|
-
import { signal, mount } from
|
|
50
|
-
import { deviceClass } from
|
|
51
|
-
import {
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
import { signal, mount } from "kerfjs";
|
|
50
|
+
import { deviceClass } from "@kerfjs/ui/device-class";
|
|
51
|
+
import {
|
|
52
|
+
CollapsiblePanel,
|
|
53
|
+
CollapsiblePanelToggle,
|
|
54
|
+
} from "@kerfjs/ui/collapsible-panel";
|
|
55
|
+
import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
|
|
56
|
+
import "@kerfjs/ui/collapsible-panel.css";
|
|
54
57
|
|
|
55
58
|
const navCollapsed = signal(false);
|
|
56
59
|
const device = deviceClass();
|
|
57
60
|
|
|
58
|
-
const app = document.querySelector(
|
|
61
|
+
const app = document.querySelector("#app")!;
|
|
59
62
|
mount(app, () => (
|
|
60
63
|
<div class="layout">
|
|
61
|
-
<CollapsiblePanel
|
|
64
|
+
<CollapsiblePanel
|
|
65
|
+
id="nav"
|
|
66
|
+
side="left"
|
|
67
|
+
collapsed={navCollapsed.value}
|
|
68
|
+
label="Navigator"
|
|
69
|
+
>
|
|
62
70
|
<header>
|
|
63
|
-
<CollapsiblePanelToggle
|
|
71
|
+
<CollapsiblePanelToggle
|
|
72
|
+
side="left"
|
|
73
|
+
collapsed={navCollapsed.value}
|
|
74
|
+
action="toggle-nav"
|
|
75
|
+
panelId="nav"
|
|
76
|
+
/>
|
|
64
77
|
</header>
|
|
65
78
|
{/* nav items */}
|
|
66
79
|
</CollapsiblePanel>
|
|
67
80
|
<main>
|
|
68
|
-
{navCollapsed.value &&
|
|
81
|
+
{navCollapsed.value && (
|
|
82
|
+
<CollapsiblePanelToggle
|
|
83
|
+
side="left"
|
|
84
|
+
collapsed
|
|
85
|
+
action="toggle-nav"
|
|
86
|
+
label="Show navigator"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
69
89
|
{/* content */}
|
|
70
90
|
</main>
|
|
71
91
|
</div>
|
|
72
92
|
));
|
|
73
93
|
|
|
74
94
|
const stop = wireSidebar(app, {
|
|
75
|
-
panels: [
|
|
95
|
+
panels: [
|
|
96
|
+
{
|
|
97
|
+
id: "nav",
|
|
98
|
+
collapsed: navCollapsed,
|
|
99
|
+
toggleAction: "toggle-nav",
|
|
100
|
+
storageKey: "app.nav-collapsed",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
76
103
|
deviceClass: device,
|
|
77
104
|
});
|
|
78
105
|
```
|
|
@@ -20,6 +20,34 @@ remains authored rather than generated.
|
|
|
20
20
|
|
|
21
21
|
## Ownership boundaries
|
|
22
22
|
|
|
23
|
+
### Geometry ownership metadata
|
|
24
|
+
|
|
25
|
+
The canonical [`component-catalog.json`](../ai/component-catalog.json) records
|
|
26
|
+
the margin, border, and padding owner for every component and composition under
|
|
27
|
+
`geometry`. Read it before adding a wrapper or local spacing rule:
|
|
28
|
+
|
|
29
|
+
- `self` means the entry's public visual contract supplies that geometry.
|
|
30
|
+
- `parent` means the embedding layout must supply it.
|
|
31
|
+
- `child` means composed descendants supply it; the entry's outer shell does not.
|
|
32
|
+
- `none` means that dimension is intentionally absent from the standard contract.
|
|
33
|
+
- `conditional` means a variant, part, or configuration changes ownership; the
|
|
34
|
+
accompanying `notes` explain the boundary.
|
|
35
|
+
|
|
36
|
+
The values describe public ownership, including themed shadow parts, rather
|
|
37
|
+
than merely asking whether a CSS declaration appears on the host element.
|
|
38
|
+
Recipes are exempt because they arrange entries whose individual ownership is
|
|
39
|
+
already cataloged.
|
|
40
|
+
|
|
41
|
+
Downstream packages and applications should publish the same contract for their
|
|
42
|
+
reusable visual components. Use
|
|
43
|
+
[`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
|
|
44
|
+
and copy the structure of the checked
|
|
45
|
+
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
46
|
+
example. Keep app and Kerf entries as package-qualified inputs, then let people
|
|
47
|
+
or AI tools search their combined entries and compare `geometry` before adding
|
|
48
|
+
wrappers or insets. Do not add app-owned entries to Kerf's canonical catalog or
|
|
49
|
+
claim Kerf delivery paths for them.
|
|
50
|
+
|
|
23
51
|
- Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
|
|
24
52
|
- Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
|
|
25
53
|
- Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
|
|
@@ -166,14 +194,14 @@ render, hands in its own via `collapsible.signals`, drives it through
|
|
|
166
194
|
UI is consistent by default without every app reinventing it.
|
|
167
195
|
|
|
168
196
|
`wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
|
|
169
|
-
helpers. Its collapse behavior was
|
|
197
|
+
helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
|
|
170
198
|
transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
|
|
171
199
|
apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
|
|
172
200
|
app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
|
|
173
201
|
`NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
|
|
174
202
|
and tab order are data, a `ResizableRegion`'s committed size and a
|
|
175
203
|
`Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
|
|
176
|
-
Each helper already owns only the
|
|
204
|
+
Each helper already owns only the _ephemeral mechanics_ around that state —
|
|
177
205
|
`wireNavStack` the push/pop animation, `wireTabBars` the overflow autoscroll and
|
|
178
206
|
drag preview, `wireResizableRegions` the live drag preview — and reports committed
|
|
179
207
|
changes through callbacks. A `ListHeader` `toggle` disclosure's `expanded` is
|