@kerfjs/ui 4.4.1 → 5.0.0-beta.3
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 +231 -5
- package/ai/component-catalog.json +4467 -0
- package/ai/component-catalog.schema.json +79 -0
- package/ai/public-api-signatures-v1.md +619 -0
- package/ai/skill.md +64 -11
- package/ai/webawesome-jsx-signatures-v1.md +106 -0
- package/dist/app-tab.d.ts +12 -2
- package/dist/app-tab.js +2 -1
- package/dist/browser/app-tab.js +2 -2
- package/dist/browser/dialog-header.js +4 -2
- package/dist/browser/disclosure-arrow.js +4 -0
- package/dist/browser/empty-state.js +3 -3
- package/dist/browser/loading-spinner.js +2 -2
- package/dist/browser/lucide-icon.js +2 -2
- package/dist/browser/menu-action-row.js +3 -0
- package/dist/browser/menu-header.js +4 -2
- package/dist/browser/menu-item.js +2 -2
- package/dist/browser/page-header.js +2 -2
- package/dist/browser/resizable-region.js +2 -2
- package/dist/browser/segmented-control.js +2 -2
- package/dist/browser/select.js +3 -3
- package/dist/browser/state-banner.js +2 -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 +2 -2
- package/dist/browser/toolbar.js +2 -2
- package/dist/browser/value-table.js +2 -2
- 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-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-NHXX4JI5.js → chunk-CTQKI2XL.js} +3 -3
- package/dist/chunk-CTQKI2XL.js.map +1 -0
- package/dist/chunk-EGHL5S4P.js +33 -0
- package/dist/chunk-EGHL5S4P.js.map +1 -0
- package/dist/chunk-H5AGGVU5.js +75 -0
- package/dist/chunk-H5AGGVU5.js.map +1 -0
- package/dist/chunk-K3G72I6D.js +24 -0
- package/dist/chunk-K3G72I6D.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-NPPD2A6O.js +40 -0
- package/dist/chunk-NPPD2A6O.js.map +1 -0
- package/dist/chunk-R6JHHBKQ.js +19 -0
- package/dist/chunk-R6JHHBKQ.js.map +1 -0
- package/dist/chunk-S3TPM2LP.js +23 -0
- package/dist/chunk-S3TPM2LP.js.map +1 -0
- package/dist/{chunk-T5XNMVOI.js → chunk-XZXRLY5Q.js} +14 -5
- package/dist/chunk-XZXRLY5Q.js.map +1 -0
- package/dist/dialog-header.d.ts +3 -2
- package/dist/dialog-header.js +3 -1
- 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 +5 -1
- package/dist/index.js +16 -11
- package/dist/menu-action-row.d.ts +50 -0
- package/dist/menu-action-row.js +4 -0
- package/dist/menu-action-row.js.map +1 -0
- package/dist/menu-header.d.ts +28 -2
- package/dist/menu-header.js +4 -1
- package/dist/menu-item.d.ts +10 -1
- package/dist/menu-item.js +2 -1
- package/dist/resizable-region.d.ts +3 -1
- package/dist/resizable-region.js +1 -1
- package/dist/select.js +1 -1
- package/dist/styles/app-tab.css +152 -0
- package/dist/styles/dialog-header.css +87 -0
- package/dist/styles/disclosure-arrow.css +23 -0
- package/dist/styles/empty-state.css +47 -0
- package/dist/styles/foundation.css +326 -0
- package/dist/styles/layout.css +73 -0
- package/dist/styles/loading-spinner.css +21 -0
- package/dist/styles/lucide-icon.css +6 -0
- package/dist/styles/menu-action-row.css +154 -0
- package/dist/styles/menu-header.css +143 -0
- package/dist/styles/menu-item.css +118 -0
- package/dist/styles/page-header.css +26 -0
- package/dist/styles/resizable-region.css +111 -0
- package/{src → dist/styles}/segmented-control.css +62 -31
- package/dist/styles/select.css +96 -0
- package/dist/styles/state-banner.css +173 -0
- package/dist/styles/styles.css +22 -0
- package/dist/styles/tab-bar.css +45 -0
- package/dist/styles/token-search-field.css +281 -0
- package/dist/styles/toolbar-control-group.css +161 -0
- package/dist/styles/toolbar-text.css +29 -0
- package/dist/styles/toolbar.css +52 -0
- package/dist/styles/value-table.css +75 -0
- package/{src → dist/styles}/webawesome.css +119 -29
- 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/value-table.d.ts +10 -2
- package/dist/value-table.js +1 -1
- package/dist/webawesome.d.ts +100 -0
- package/dist/webawesome.js +3 -0
- package/dist/webawesome.js.map +1 -0
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-token-search-fields.d.ts +11 -0
- package/dist/wire-token-search-fields.js +5 -0
- package/dist/wire-token-search-fields.js.map +1 -0
- package/docs/accessibility.md +60 -3
- package/docs/component-contract.md +128 -3
- package/docs/component-selection.md +133 -0
- package/docs/design-philosophy.md +38 -0
- package/docs/examples/command-palette-adapter.tsx +70 -0
- package/docs/layout.md +95 -0
- package/docs/recipes.md +116 -0
- package/docs/ux-demo.md +17 -5
- package/docs/webawesome-theme.md +15 -0
- package/llms.txt +25 -2
- package/package.json +70 -27
- package/ux-demo/recipes/app-shell.tsx +32 -0
- package/ux-demo/recipes/compact-toolbar.tsx +43 -0
- package/ux-demo/recipes/composer-form.tsx +62 -0
- package/ux-demo/recipes/list-workspace-states.tsx +26 -0
- package/ux-demo/recipes/loaders.ts +27 -0
- package/ux-demo/recipes/master-detail-dialog.tsx +45 -0
- package/ux-demo/recipes/mount-recipe.ts +38 -0
- package/ux-demo/recipes/navigation-sidebar.tsx +26 -0
- package/ux-demo/recipes/recipes.css +328 -0
- package/ux-demo/recipes/types.ts +11 -0
- package/ux-demo/recipes/workspace-header.tsx +12 -0
- 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-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.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/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
|
@@ -2,13 +2,86 @@
|
|
|
2
2
|
|
|
3
3
|
`@kerfjs/ui` components are plain functions that return Kerf `SafeHtml`. They have no component instance or lifecycle.
|
|
4
4
|
|
|
5
|
+
## Selection boundary
|
|
6
|
+
|
|
7
|
+
Begin with the [need-first component matrix](./component-selection.md). Reuse a
|
|
8
|
+
primitive only when purpose, anatomy, state, and interaction match. Compose
|
|
9
|
+
public primitives for recurring layout; add a thin application adapter for
|
|
10
|
+
product state and policy; use custom markup when the semantic contract differs.
|
|
11
|
+
Recurring cross-product custom patterns belong in an upstream component or
|
|
12
|
+
recipe request, not in duplicated markup or selectors for undocumented anatomy.
|
|
13
|
+
|
|
14
|
+
The shipped [`component-catalog.json`](../ai/component-catalog.json) is the
|
|
15
|
+
canonical machine-readable inventory. Its facts are projected into the UX
|
|
16
|
+
catalog deterministically; package checks compare them with runtime exports,
|
|
17
|
+
package delivery paths, Web Awesome's installed manifest, AI guidance, and
|
|
18
|
+
documentation links. Nuanced prose in this contract and the selection guide
|
|
19
|
+
remains authored rather than generated.
|
|
20
|
+
|
|
5
21
|
## Ownership boundaries
|
|
6
22
|
|
|
7
23
|
- Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
|
|
8
24
|
- Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
|
|
9
25
|
- Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
|
|
10
26
|
- A reusable component never owns per-instance mutable module state.
|
|
11
|
-
- Consumers style through `--kui-*` semantic tokens and public component classes. Foundation tokens provide opinionated neutral, brand/info, success, warning, and danger fill/border/foreground roles. Stateful components expose local override variables;
|
|
27
|
+
- Consumers style through `--kui-*` semantic tokens and public component classes. Foundation tokens provide opinionated neutral, brand/info, success, warning, and danger fill/border/foreground roles. Stateful components expose local override variables; prefer an equivalent prop or token before writing a selector.
|
|
28
|
+
|
|
29
|
+
`MenuItem.rootAttributes`, `MenuActionRow.rootAttributes`,
|
|
30
|
+
`MenuHeader.rootAttributes`, and `AppTab.rootAttributes` accept
|
|
31
|
+
application-owned `data-*` metadata without
|
|
32
|
+
adding product fields to the shared API. A `MenuActionRow` trailing action uses
|
|
33
|
+
`trailingActionAttributes`, and a `MenuHeader` action/disclosure uses
|
|
34
|
+
`triggerAttributes`, for `data-*`, native popover target/action attributes, and
|
|
35
|
+
the corresponding `aria-controls`/`aria-haspopup` relationship. These slots do
|
|
36
|
+
not accept roles or component-owned action, selection, disclosure, accessible
|
|
37
|
+
name, disabled, or icon fields. Extension objects are filtered at runtime,
|
|
38
|
+
including case-insensitive rejection of protected `data-*` names, and the
|
|
39
|
+
component writes its protected attributes after the accepted metadata. Typed,
|
|
40
|
+
structurally widened, and JavaScript callers therefore cannot replace its
|
|
41
|
+
contract.
|
|
42
|
+
|
|
43
|
+
`AppTab.closeIcon` and `ResizableRegion.handleIcon` replace dormant decorative
|
|
44
|
+
glyph content only. They must not contain controls or interactive roles. The
|
|
45
|
+
component continues to own the named close button or focusable separator, and
|
|
46
|
+
`wireTabBars()` / `wireResizableRegions()` continue to own transient behavior
|
|
47
|
+
and disposal.
|
|
48
|
+
|
|
49
|
+
`MenuItem.trailing`, `MenuActionRow.label`, `MenuActionRow.icon`, and
|
|
50
|
+
`MenuActionRow.trailingActionIcon` are dormant SafeHtml slots. They must not
|
|
51
|
+
contain controls. When a row needs an independently interactive trailing
|
|
52
|
+
region, `MenuActionRow` owns the noninteractive root and the two sibling native
|
|
53
|
+
buttons; the application owns their delegated behavior and controlled state.
|
|
54
|
+
|
|
55
|
+
### Public CSS anatomy
|
|
56
|
+
|
|
57
|
+
The `publicClasses` array on each entry in
|
|
58
|
+
[`component-catalog.json`](../ai/component-catalog.json) is the exact supported
|
|
59
|
+
anatomy boundary. A scoped selector may join documented public classes, such as
|
|
60
|
+
`.workspace .kui-toolbar .kui-toolbar__trailing`, when composition-specific
|
|
61
|
+
layout cannot be expressed by a prop or token. A class being public does not
|
|
62
|
+
make copied component markup an invocation or transfer state and accessibility
|
|
63
|
+
ownership to the application.
|
|
64
|
+
|
|
65
|
+
Do not select a component's descendant by element name, id, attribute alone, or
|
|
66
|
+
an unlisted implementation class. Selectors such as `.kui-state-banner span`,
|
|
67
|
+
`.kui-menu-item [data-state]`, and `.kui-menu-item .local-label` depend on
|
|
68
|
+
private structure. If no prop, token, or cataloged class expresses a recurring
|
|
69
|
+
need, request a supported hook instead of inferring one from rendered markup.
|
|
70
|
+
|
|
71
|
+
`DisclosureArrow` uses an 18px root-scaled visual by default. Override
|
|
72
|
+
`--kui-disclosure-arrow-size` on the component or its containing scope when a
|
|
73
|
+
consumer needs another size. Kerf `Select` uses a separate Web Awesome expand
|
|
74
|
+
glyph contract, `--kui-disclosure-icon-scale: .5`; changing one contract does
|
|
75
|
+
not implicitly change the other. Direction changes take the shortest rotation
|
|
76
|
+
path; a 180-degree closed-to-open tie uses counterclockwise rotation.
|
|
77
|
+
|
|
78
|
+
In `MenuHeader` toggle mode, omitting `actionIcon` composes the production
|
|
79
|
+
`DisclosureArrow` and derives its visual direction from `expanded`. The app
|
|
80
|
+
must update that controlled state and reveal or hide real content. Passing a
|
|
81
|
+
custom `actionIcon` replaces the default rather than layering or rotating both.
|
|
82
|
+
The root fills the available inline width after its standard margins. A
|
|
83
|
+
separate action stays at the logical end in its 44px target while the visible
|
|
84
|
+
glyph defaults to 18px through `--kui-menu-header-action-icon-size`.
|
|
12
85
|
|
|
13
86
|
`StateBanner` exposes instance-level `--kui-state-banner-background`,
|
|
14
87
|
`--kui-state-banner-border`, `--kui-state-banner-foreground`,
|
|
@@ -27,11 +100,47 @@ separator with a compact grip that appears on hover or keyboard focus. Override
|
|
|
27
100
|
`--kui-resizable-region-handle-active-color` on a region when its containing
|
|
28
101
|
surface needs a different contrast level.
|
|
29
102
|
|
|
103
|
+
`@kerfjs/ui/layout.css` defines one structural model for sidebars, main areas,
|
|
104
|
+
inspectors, and dialogs. `.kui-pane` is unpadded and contains an optional
|
|
105
|
+
toolbar, one scrolling `.kui-pane__content`, and an optional footer.
|
|
106
|
+
`.kui-content` gives major children 24px vertical separation.
|
|
107
|
+
`.kui-content-item` gives one child 8px inline margin, a real 1px border,
|
|
108
|
+
8px padding, and 12px corners; border and background may be transparent without
|
|
109
|
+
changing geometry. `.kui-content-item--pill` selects the 22px radius.
|
|
110
|
+
|
|
111
|
+
`ToolbarControlGroup` is the unit of toolbar organization, even for dormant
|
|
112
|
+
text. Each group reserves `calc(2px + remify(42px))`, or 44px, with 8px between
|
|
113
|
+
groups and inside items. `MenuHeader` similarly separates its dormant title and
|
|
114
|
+
optional count or badge from its optional 44px action. Use the mutually
|
|
115
|
+
exclusive `count`/`countLabel` pair for non-negative safe-integer section
|
|
116
|
+
quantities; reserve `badge` for non-count `SafeHtml`. Do not concatenate counts
|
|
117
|
+
into the section label. Do not add padding to pane shells,
|
|
118
|
+
double child-owned geometry with wrapper insets, or create competing scroll
|
|
119
|
+
owners. The [layout contract](./layout.md) lists the public roles and tokens.
|
|
120
|
+
|
|
121
|
+
`DialogHeader` composes its icon and first title line inside a borderless
|
|
122
|
+
`ToolbarControlGroup` in a real top `Toolbar`. Direct `actions` children are
|
|
123
|
+
automatically placed in one contained `ToolbarControlGroup`; use the optional
|
|
124
|
+
localized `actionsLabel` when that group needs an accessible name. Existing
|
|
125
|
+
pre-grouped action content remains layout-compatible, but new callers should
|
|
126
|
+
pass the action children rather than groups. The optional summary is a separate
|
|
127
|
+
row aligned below the title, so it cannot pull the 24px icon or 34px circular
|
|
128
|
+
icon background out of vertical alignment with the 44px action row.
|
|
129
|
+
|
|
130
|
+
`ValueTable` composes typed `ValueTableRow` entries. A row owns its `dt`/`dd`
|
|
131
|
+
semantics and may receive a leading `SafeHtml` icon. Every row keeps 8px of
|
|
132
|
+
root-scaled padding above and below its content; the shared item-padding token
|
|
133
|
+
continues to own its inline inset. Separators follow the content they introduce:
|
|
134
|
+
8px from either edge for an iconless row, or 40px from the left edge (8px
|
|
135
|
+
padding + 24px icon + 8px gap) and 8px from the right edge for an icon-bearing
|
|
136
|
+
row. Applications own the values, formatting, and whether an icon is decorative
|
|
137
|
+
or meaningfully labeled.
|
|
138
|
+
|
|
30
139
|
## Imports and side effects
|
|
31
140
|
|
|
32
141
|
Every component has an explicit JS and CSS subpath. In CSS-aware browser builds,
|
|
33
142
|
the JS component subpath resolves to a generated wrapper that imports the
|
|
34
|
-
foundation, the component stylesheet
|
|
143
|
+
foundation, the compiled component stylesheet from `dist/styles`, and styles for its reachable UI
|
|
35
144
|
subcomponents. The source graph derives those transitive styles at build time;
|
|
36
145
|
an application root never maintains that list. Unused component subpaths and
|
|
37
146
|
their CSS remain unreachable. The root barrel and `@kerfjs/ui/unstyled` stay
|
|
@@ -45,19 +154,35 @@ when called and return disposers. CSS, the generated wrappers that make it
|
|
|
45
154
|
reachable, and the registration module are the package's only declared side
|
|
46
155
|
effects.
|
|
47
156
|
|
|
157
|
+
Package source styles express root-scaled geometry with `remify(<px>)`; the
|
|
158
|
+
build converts it against the 16px authoring baseline and exposes only ordinary
|
|
159
|
+
`rem` CSS. Literal pixels remain for intentional hairlines, and `em` remains
|
|
160
|
+
explicit when a value is relative to its component's current font size. The UX
|
|
161
|
+
catalog applies the same transform to source CSS under Vite so `npm run dev`
|
|
162
|
+
retains hot module replacement.
|
|
163
|
+
|
|
48
164
|
`@kerfjs/ui/webawesome.css` is a separate, optional CSS boundary. It imports
|
|
49
165
|
Web Awesome's public base stylesheet and overrides its semantic theme layer to
|
|
50
166
|
match the Kerf/Hot Sheet 2 system. It never imports component JavaScript.
|
|
167
|
+
`import type {} from '@kerfjs/ui/webawesome'` is the matching side-effect-free
|
|
168
|
+
Kerf JSX declaration boundary for every catalog-supported `wa-*` tag.
|
|
51
169
|
Consumers register only the individual Web Awesome modules they render and can
|
|
52
170
|
override `--wa-*` values after the theme import. The shared `--wa-*` values feed
|
|
53
171
|
both Web Awesome controls and Kerf's `--kui-*` foundation aliases, preventing a
|
|
54
172
|
second application palette.
|
|
55
173
|
|
|
174
|
+
`Select` owns its custom-element reconciliation seam. It gives each slotted
|
|
175
|
+
option icon a stable key and leaves the upgraded Web Awesome-owned slot subtree
|
|
176
|
+
untouched on later Kerf renders. Its custom selected slot is keyed by the
|
|
177
|
+
controlled value so changed selections replace that content. Applications
|
|
178
|
+
should pass ordinary `choices` and `renderSelected` output rather than adding
|
|
179
|
+
their own morph-control attributes.
|
|
180
|
+
|
|
56
181
|
`kerfjs` is a peer dependency and remains external in every build. Importing a toolbar must not bundle a second Kerf runtime, another UI component, Web Awesome registration, the UX catalog, or development tooling.
|
|
57
182
|
|
|
58
183
|
## Extracted versus application-specific
|
|
59
184
|
|
|
60
|
-
The package set is intentionally domain-neutral: icon rendering, toolbar primitives, controlled segmented choices, menu rows/headers, resizable regions and wiring, controlled reorderable tab bars, headers, loading, select, banners, empty states, dialog headers, and value
|
|
185
|
+
The package set is intentionally domain-neutral: icon rendering, toolbar primitives, controlled segmented choices, a controlled token-chip search field with DOM read/caret helpers, menu rows/headers, resizable regions and wiring, controlled reorderable tab bars, headers, loading, select, banners, empty states, dialog headers, and typed value-table rows.
|
|
61
186
|
|
|
62
187
|
Keep product adapters outside the package: connection-state maps, ticket empty-state copy, project/terminal/chat tab actions, saved pane sizes, provider or repository models, and application-specific palettes. An adapter may compose these primitives, map product state into their props, and override semantic CSS variables.
|
|
63
188
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Component selection and composition
|
|
2
|
+
|
|
3
|
+
Start with the interface need, not an export name or a screenshot resemblance.
|
|
4
|
+
Use the shipped [`component-catalog.json`](../ai/component-catalog.json) when a
|
|
5
|
+
tool needs exhaustive structured facts; this page remains the concise human
|
|
6
|
+
decision procedure.
|
|
7
|
+
|
|
8
|
+
1. Search the [Kerf UX catalog](./ux-demo.md) and the supported Web Awesome set.
|
|
9
|
+
2. Reuse a primitive when its purpose, anatomy, state, and interaction match.
|
|
10
|
+
3. Compose primitives for recurring layout. Prefer public props and tokens; when a composition needs a selector, target only classes listed in the catalog's `publicClasses` contract.
|
|
11
|
+
4. Add a thin application adapter for product copy, domain mapping, actions, routing, persistence, permissions, and transport.
|
|
12
|
+
5. Use custom markup only when the semantic contract differs. If the pattern recurs across products, open an upstream component or recipe request.
|
|
13
|
+
|
|
14
|
+
The application adapter is usually a plain function that maps domain state to
|
|
15
|
+
component props plus stable `data-action` values. It is not a fork of package
|
|
16
|
+
markup or CSS.
|
|
17
|
+
|
|
18
|
+
## Production recipes
|
|
19
|
+
|
|
20
|
+
Use the [complete recipe guide](./recipes.md) when several primitives form one
|
|
21
|
+
application boundary:
|
|
22
|
+
|
|
23
|
+
| Task | Stable catalog route |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| Desktop application shell | [Catalog](../ux-demo/) · `?component=recipe-app-shell` |
|
|
26
|
+
| Navigation sidebar | [Catalog](../ux-demo/) · `?component=recipe-navigation-sidebar` |
|
|
27
|
+
| Workspace header | [Catalog](../ux-demo/) · `?component=recipe-workspace-header` |
|
|
28
|
+
| Master-detail dialog | [Catalog](../ux-demo/) · `?component=recipe-master-detail-dialog` |
|
|
29
|
+
| Composer form | [Catalog](../ux-demo/) · `?component=recipe-composer-form` |
|
|
30
|
+
| List workspace states | [Catalog](../ux-demo/) · `?component=recipe-list-workspace-states` |
|
|
31
|
+
| Compact toolbar choices and actions | [Catalog](../ux-demo/) · `?component=recipe-compact-toolbar` |
|
|
32
|
+
|
|
33
|
+
Recipes use public production exports and show ownership boundaries; they are
|
|
34
|
+
copyable reference compositions, not new monolithic components.
|
|
35
|
+
|
|
36
|
+
## Missing recurring concepts
|
|
37
|
+
|
|
38
|
+
Kerf UI does not export a command-palette component. Do not invent a package
|
|
39
|
+
command-palette import.
|
|
40
|
+
The typed [application adapter example](./examples/command-palette-adapter.tsx)
|
|
41
|
+
imports `@kerfjs/ui/layout.css`, assigns one `.kui-layout` root and one surface
|
|
42
|
+
inset, and groups its related footer commands with `.kui-control-cluster` while
|
|
43
|
+
the application owns ranking, history, shortcut policy, focus policy,
|
|
44
|
+
availability, actions, and copy. If that concept recurs across products, open
|
|
45
|
+
an upstream component or recipe request.
|
|
46
|
+
|
|
47
|
+
## Problem-to-component matrix
|
|
48
|
+
|
|
49
|
+
| Interface need | Use when | Do not use when; nearest alternative | Required wiring | Application owns | Import | Recipe |
|
|
50
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
51
|
+
| Icon — `LucideIcon` | A decorative or explicitly labeled Lucide-compatible icon belongs in app UI. | Do not use an icon as the only name of an unfamiliar action; add visible or accessible text. Prefer it over Web Awesome `wa-icon`. | None. | Icon choice and meaningful label. | `@kerfjs/ui/lucide-icon` | [Accessibility](./accessibility.md#shared-rules) |
|
|
52
|
+
| Disclosure indicator — `DisclosureArrow` | A control needs one animated 18px root-scaled visual for open and closed state, including configurable directions or a replacement icon. | Do not use it as the interactive control or accessible name; place it inside the button or control that exposes expanded state. | Pass the controlled `open` state, render it inside the owning control, author replacement icon content facing right before transforms, and override `--kui-disclosure-arrow-size` only when another visual size is required. Direction changes use the shortest rotation path, with counterclockwise chosen for a 180-degree closed-to-open tie. | Open state, interaction, accessible name, size override, replacement glyph, direction choices, and shortest-path rotation. | `@kerfjs/ui/disclosure-arrow` | [Component ownership](./component-contract.md#ownership-boundaries) |
|
|
53
|
+
| Application toolbar — `Toolbar` | Leading identity, optional centered content, and trailing controls form one horizontal app bar. | Do not use it for a page title and one page action; use `PageHeader`. Do not use it as a dialog heading; use `DialogHeader`. | Compose `ToolbarText` and `ToolbarControlGroup` where their contracts fit. | Actions, command availability, responsive relocation, and state. | `@kerfjs/ui/toolbar` | [Toolbar composition](../README.md#component-subpaths) |
|
|
54
|
+
| Toolbar control cluster — `ToolbarControlGroup` | Related toolbar controls need contained, borderless, pressed, or single-control treatment. | Do not use it merely to align unrelated buttons; use toolbar slots or ordinary layout. Web Awesome `wa-button-group` is only for an exceptional grouped-action contract. | Delegate child actions; use `SegmentedControl` for an exclusive choice. | Actions, pressed/expanded state, and policy. | `@kerfjs/ui/toolbar-control-group` | [Component ownership](./component-contract.md#ownership-boundaries) |
|
|
55
|
+
| Toolbar identity text — `ToolbarText` | A toolbar needs large, default, or compact textual identity. | Do not substitute it for document heading semantics; use `PageHeader` or native headings. | None. | Text and responsive priority. | `@kerfjs/ui/toolbar-text` | [Toolbar composition](../README.md#component-subpaths) |
|
|
56
|
+
| Navigation row — `MenuItem` | A pane or navigation area needs a selectable, disabled, dormant-trailing, or multiline action row. | Do not put a control in `trailing`; use `MenuActionRow` when the trailing region must be independently interactive. Use an `<a>` for navigation that must retain link behavior, a native `<button>` for an ordinary action, or implement the complete ARIA menu widget. | Delegate its `data-action`; compose inside a `.kui-content` section. Put domain event/drop metadata in `rootAttributes` rather than adding wrapper markup. | Routing, selection, permissions, copy, action handling, and domain `data-*` values. | `@kerfjs/ui/menu-item` | [Pane geometry](../README.md#pane-and-content-geometry) |
|
|
57
|
+
| Navigation row with trailing action — `MenuActionRow` | A full-width row needs a selectable primary action and an independently focusable trailing action. | Use `MenuItem` when trailing content is dormant metadata. Do not put controls inside the row's `label`, `icon`, or `trailingActionIcon` SafeHtml slots. Do not use `AppTab` outside tablist semantics or `ToolbarControlGroup` outside a toolbar. | Delegate both action strings; update controlled selection and any popover/context-menu state in the app. | Routing, selection, both action policies, domain metadata, and popover/context-menu behavior. | `@kerfjs/ui/menu-action-row` | [Accessibility](./accessibility.md#menuactionrow) |
|
|
58
|
+
| Navigation section heading — `MenuHeader` | A menu section needs a full-width label, semantic count, non-count badge, logical-end action, or real disclosure state. | Do not concatenate counts into `label` or put numeric content in `badge`; use `count` with the localized full phrase in `countLabel`. Do not add a disclosure arrow to navigation that reveals nothing. Do not shrink its 44px action target to the 18px visual. Do not use it as a page or dialog title; use `PageHeader` or `DialogHeader`. | Delegate its optional action; the app controls expanded state and revealed content. Toggle mode supplies `DisclosureArrow` unless `actionIcon` replaces it. Use `triggerAttributes` only for domain `data-*` or a native popover relationship. | Section organization, valid count and localized count label, disclosure state and content, non-count badge content, popover target behavior, and policy. | `@kerfjs/ui/menu-header` | [Pane geometry](../README.md#pane-and-content-geometry) |
|
|
59
|
+
| Application layout composition | A sidebar, main area, inspector, or dialog needs shared toolbar/content/footer and child geometry. | Do not pad the pane shell, wrap child-owned geometry in competing insets, or invent unrelated centered measures. | Use `.kui-pane` and one `.kui-pane__content`; add `.kui-content` and `.kui-content-item` as needed. | Layout hierarchy, reading width, scroll ownership, and responsive relocation. | `@kerfjs/ui/layout.css` | [Pane anatomy](./layout.md#anatomy) |
|
|
60
|
+
| Menu composition | Navigation sections need full-size rows and the same content-item geometry as every other pane. | Do not add sidebar-specific wrapper padding, shrink targets to icon size, nest an interactive trailing control in `MenuItem`, or use a chevron on a row that does not disclose content. Use ordinary links for a different navigation contract. | Compose `MenuHeader`, `MenuItem`, and `MenuActionRow` in `.kui-content`; use `MenuHeader` toggle mode with real controlled content, and use `.kui-content-item` for other surfaces plus a pane footer for toolbar actions. | Information architecture, disclosure content and state, responsive drawer/shell behavior, and token overrides. | `@kerfjs/ui/layout.css` | [Pane geometry](../README.md#pane-and-content-geometry) |
|
|
61
|
+
| Resizable application pane — `ResizableRegion`, `clampRegionSize`, `resizeRegionFromPointer` | A controlled split pane needs the Kerf separator, collapse state, pointer plus keyboard resizing, or a product-specific decorative grip. | Do not use it for a static two-column layout; use CSS grid. Prefer it over Web Awesome `wa-split-panel` unless that component's distinct API is required. Keep `handleIcon` noninteractive. | Call `wireResizableRegions` from `@kerfjs/ui/wire-resizable-regions` once and retain its disposer. | Size signal, min/max policy, collapse policy, persistence, and optional decorative handle icon. | `@kerfjs/ui/resizable-region` | [ResizableRegion contract](./accessibility.md#resizableregion) |
|
|
62
|
+
| One application tab — `AppTab` | A controlled app tab needs selection, close, drag, leading/trailing anatomy, safe domain metadata, or a product-specific close glyph. | Do not render it alone or use it for a small settings choice; compose in `TabBar`, or use `SegmentedControl`. Keep `closeIcon` noninteractive. | Compose in `TabBar`; let `wireTabBars` manage interaction. Put only domain `data-*` values in `rootAttributes`. | Tab identity, order, selection, close policy, content, and domain metadata values. | `@kerfjs/ui/app-tab` | [Tabs contract](./accessibility.md#tabs) |
|
|
63
|
+
| Application tab strip — `TabBar`, `wireTabBars`, `reorderTabs` | Tabs switch page regions and may overflow, close, or reorder. | Do not use it for a compact local view toggle; use `SegmentedControl`. Do not use it for a long choice list; use `Select`. Prefer it over Web Awesome `wa-tab-group`, `wa-tab`, and `wa-tab-panel` for Kerf app tabs. | Call `wireTabBars` once, retain the disposer, and apply `onReorder` synchronously; `reorderTabs` is the default array helper. | Ordered tabs, selection, panels, routing, closing, and persistence. | `@kerfjs/ui/tab-bar` plus `@kerfjs/ui/wire-tab-bars` | [Tabs contract](./accessibility.md#tabs) |
|
|
64
|
+
| Page title and action — `PageHeader` | A page needs its primary heading and an optional trailing action. | Do not use it as persistent app chrome; use `Toolbar`. Do not use it inside a modal task; use `DialogHeader`. | Delegate an optional action normally. | Page title, action, and responsive placement. | `@kerfjs/ui/page-header` | [Header ownership](./component-contract.md#extracted-versus-application-specific) |
|
|
65
|
+
| Dialog title and summary — `DialogHeader` | A dialog needs a toolbar-aligned title, optional summary/id and icon, and grouped actions wired to the dialog's ARIA references. | Do not use it as the page's `h1`; use `PageHeader`. It supplies header structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. | Connect title and optional summary ids to the dialog host, pass action children directly, localize `actionsLabel` when the group needs a name, and delegate actions. | Open state, focus lifecycle, dismissal, actions, labels, and copy. | `@kerfjs/ui/dialog-header` | [Header ownership](./component-contract.md#extracted-versus-application-specific) |
|
|
66
|
+
| Key/value facts — `ValueTable`, `ValueTableRow` | Read-only labels and values form a semantic definition list, optionally with a leading icon. | Do not use it for editable form fields or a row/column data grid; use native form or table semantics. | Compose typed `ValueTableRow` entries; pass `icon` when a 24px leading icon adds useful context. | Values, formatting, icon meaning, and empty/loading policy. | `@kerfjs/ui/value-table` | [Component ownership](./component-contract.md#ownership-boundaries) |
|
|
67
|
+
| Indeterminate activity — `LoadingSpinner` | A Kerf surface needs compact, labeled or decorative indeterminate progress. | Do not use it for known progress; use Web Awesome `wa-progress-bar` or `wa-progress-ring`. Direct Web Awesome UI may use `wa-spinner`; do not mix spinner systems within one surface. | None; pass a label when the spinner conveys status. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
|
|
68
|
+
| Value selection — `Select` | A controlled form value comes from a moderate or long choice list, possibly grouped or icon-bearing. | Do not use it for commands; use a real action menu. Do not use it for a small visible choice set; use `SegmentedControl`. Prefer it over direct `wa-select`, `wa-option`, or value-like `wa-dropdown`/`wa-dropdown-item` composition. | Import `@kerfjs/ui/select/register` once; listen for standard input/change events. | Controlled value, validation, choices, and domain mapping. | `@kerfjs/ui/select` | [Web Awesome integration](../README.md#web-awesome-theme) |
|
|
69
|
+
| Small exclusive choice — `SegmentedControl` | A few visible choices switch a compact view or setting, with toolbar, rounded, or pill presentation. | Do not use it for tabpanel semantics; use `TabBar`. Do not use it for many choices; use `Select`. Prefer it over `wa-button-group` when the controls select one value. | Delegate its action, read `data-segment-value`, update `value`, and rerender. | Controlled value, labels, action, and persistence. | `@kerfjs/ui/segmented-control` | [SegmentedControl contract](./accessibility.md#segmentedcontrol) |
|
|
70
|
+
| Structured search editor — `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields` | Free text and ordered, editable, removable filter tokens share one searchbox; enable `collapsible` when an empty, unfocused field should reduce to one iconic action, standalone or in a toolbar group. | Do not use it for ordinary text entry; use a native input or Web Awesome `wa-input`. Do not use it when filters belong in separate form controls. | Read DOM-owned text on input, empty `textContent` on clear, and use `placeTokenSearchCaret` after explicit controlled focus changes. Call `wireTokenSearchFields` from `@kerfjs/ui/wire-token-search-fields` once so Enter submits without adding a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. In `collapsible` mode, control `expanded`, transfer focus after activation, and clear the transient state after focus leaves. | Parsing, suggestions, tokens, query execution, results, announcements, and the transient focus/open signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
|
|
71
|
+
| Persistent inline status — `StateBanner` | A neutral, info, success, warning, or danger message belongs next to the affected work. | Do not use it for a no-content screen; use `EmptyState`. Do not use it for transient confirmation; use a toast. Web Awesome `wa-callout` is the ecosystem alternative for Web Awesome-owned content. | Delegate an optional action; choose alert urgency only for attention-requiring failure. | State mapping, message lifetime, retry/action behavior, and copy. | `@kerfjs/ui/state-banner` | [Feedback accessibility](./accessibility.md#shared-rules) |
|
|
72
|
+
| Empty or busy content area — `EmptyState` | A content region has no items, cannot proceed, or is loading and needs explanation plus an optional action. | Do not use it for an inline status update; use `StateBanner`. Do not use it for transient success; use `wa-toast`/`wa-toast-item` or the application's toast system. | Delegate its optional action; it composes `LoadingSpinner` when busy. | Empty/busy policy, recovery action, illustration, and copy. | `@kerfjs/ui/empty-state` | [Feedback ownership](./component-contract.md#extracted-versus-application-specific) |
|
|
73
|
+
|
|
74
|
+
## Ambiguous choices
|
|
75
|
+
|
|
76
|
+
- `Toolbar` is persistent app chrome; `PageHeader` identifies a page; `DialogHeader` labels a focused modal task.
|
|
77
|
+
- `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `Select` handles a longer value list.
|
|
78
|
+
- `StateBanner` persists beside affected work; `EmptyState` replaces absent content; `wa-callout` is contextual ecosystem content; `wa-toast` and `wa-toast-item` are transient and must not carry the only copy of important state.
|
|
79
|
+
- `ResizableRegion` is an interactive controlled pane. CSS grid is the right answer when columns do not need a user-operable separator.
|
|
80
|
+
- `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
|
|
81
|
+
|
|
82
|
+
## Correct composition and duplicated-markup trap
|
|
83
|
+
|
|
84
|
+
Correct: let the pane stay unpadded while its children own the shared 8/1/8
|
|
85
|
+
geometry and 44px targets.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<aside class="kui-pane">
|
|
89
|
+
<nav class="kui-pane__content kui-content">
|
|
90
|
+
<section>
|
|
91
|
+
<MenuHeader label="Workspace" />
|
|
92
|
+
<MenuItem action="open" label="Inbox" icon={inboxIcon} />
|
|
93
|
+
<MenuActionRow action="open-file" label="main.ts" trailingAction="file-actions" trailingActionLabel="Actions for main.ts" trailingActionIcon={moreIcon} />
|
|
94
|
+
</section>
|
|
95
|
+
<div class="kui-content-item">Workspace details</div>
|
|
96
|
+
</nav>
|
|
97
|
+
</aside>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Incorrect: duplicating component-like rows and compensating for nested padding
|
|
101
|
+
forks the package anatomy and spacing contract.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<aside class="sidebar padded">
|
|
105
|
+
<h2 class="menu-header-copy">Workspace</h2>
|
|
106
|
+
<button class="menu-row-copy padded">Inbox</button>
|
|
107
|
+
<div class="panel indented-with-negative-margin">Workspace details</div>
|
|
108
|
+
</aside>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Web Awesome overlap policy
|
|
112
|
+
|
|
113
|
+
Web Awesome catalog coverage means supported and themed, not preferred. Import
|
|
114
|
+
`@kerfjs/ui/webawesome` for Kerf JSX types, individual component modules for
|
|
115
|
+
registration, and the CSS-only `@kerfjs/ui/webawesome.css` theme.
|
|
116
|
+
|
|
117
|
+
| Web Awesome choice | Kerf decision |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `wa-button`, `wa-dropdown`, `wa-dropdown-item` | Use buttons and command menus for actions. Use `MenuItem` for a navigation row and `Select` when the user chooses a value. |
|
|
120
|
+
| `wa-button-group` | Use only for exceptional grouped actions; use `SegmentedControl` for one-of-many selection. |
|
|
121
|
+
| `wa-input`, `wa-tag` | Use for ordinary text and tags; use `TokenSearchField` only when text and ordered filter tokens form one editor. |
|
|
122
|
+
| `wa-select`, `wa-option` | Use `Select`, which owns Kerf spacing, controlled rendering, icon stability, and explicit registration. |
|
|
123
|
+
| `wa-tab-group`, `wa-tab`, `wa-tab-panel` | Use `TabBar`/`AppTab` for application tabs or `SegmentedControl` for compact local views. |
|
|
124
|
+
| `wa-icon` | Use `LucideIcon` in application UI. |
|
|
125
|
+
| `wa-split-panel` | Use `ResizableRegion` for Kerf application panes; retain Split Panel only when its distinct API is required. |
|
|
126
|
+
| `wa-spinner`, `wa-progress-bar`, `wa-progress-ring`, `wa-skeleton` | Use `LoadingSpinner` for compact Kerf indeterminate activity; choose the ecosystem component when its distinct progress or placeholder semantics fit. |
|
|
127
|
+
| `wa-callout`, `wa-toast`, `wa-toast-item` | Use `StateBanner` for persistent inline app status, `EmptyState` for absent content, and toasts only for transient feedback. |
|
|
128
|
+
| `wa-popup`, `wa-tooltip`, `wa-popover` | Prefer the high-level interaction whose semantics fit. Use Popup only when its low-level anchored positioning removes custom placement code. |
|
|
129
|
+
| `wa-tree`, `wa-tree-item`, `wa-animated-image`, `wa-comparison` | Use only for the specialized behavior named by the component. |
|
|
130
|
+
| `wa-zoomable-frame` | Avoid for application UI; keep embedded-media behavior application-owned. |
|
|
131
|
+
|
|
132
|
+
All other entries in the [Web Awesome theme contract](./webawesome-theme.md#coverage)
|
|
133
|
+
remain supported when their native semantic contract matches the product need.
|
|
@@ -24,6 +24,44 @@ Unrelated updates must not close controls, move focus, reset a draft, replace a
|
|
|
24
24
|
|
|
25
25
|
Establish one reading order and one dominant action per decision point. Group first with alignment, spacing, typography, and shared surfaces. A border, fill, badge, or nested card must communicate a real distinction.
|
|
26
26
|
|
|
27
|
+
### Panes share one child-owned geometry
|
|
28
|
+
|
|
29
|
+
Sidebars, main areas, inspectors, and dialogs use the same unpadded pane. Their
|
|
30
|
+
children own margin, border, background, padding, and radius, so a transparent
|
|
31
|
+
surface occupies exactly the same geometry as a visible one. Major content
|
|
32
|
+
groups use 24px vertical separation; the inside of an item and the gap between
|
|
33
|
+
toolbar groups use 8px. Rows and actions keep 44px targets.
|
|
34
|
+
|
|
35
|
+
Use the package composition so the geometry has one owner:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<aside class="kui-pane">
|
|
39
|
+
<nav class="kui-pane__content kui-content">
|
|
40
|
+
<section>
|
|
41
|
+
<MenuHeader label="Workspace" />
|
|
42
|
+
<MenuItem action="open" label="Inbox" icon={inboxIcon} />
|
|
43
|
+
<MenuItem action="open" label="Drafts" />
|
|
44
|
+
</section>
|
|
45
|
+
<div class="kui-content-item">Panel contents</div>
|
|
46
|
+
</nav>
|
|
47
|
+
</aside>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Do not pad the pane and then pad every wrapper. That duplicates the geometry
|
|
51
|
+
and makes transparent borders behave differently from visible ones:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<aside class="pane padded">
|
|
55
|
+
<section class="padded">
|
|
56
|
+
<MenuHeader label="Workspace" />
|
|
57
|
+
<div class="panel padded-again">Panel contents</div>
|
|
58
|
+
</section>
|
|
59
|
+
</aside>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Change the shared item tokens at a real composition boundary when a product
|
|
63
|
+
needs different geometry; do not compensate with one-off negative margins.
|
|
64
|
+
|
|
27
65
|
### Prefer directness
|
|
28
66
|
|
|
29
67
|
Use the shortest understandable, recoverable interaction. Avoid modes, dialogs, confirmations, and explicit saves when direct manipulation can safely express the same result. Low ceremony still requires a discoverable affordance, visible focus, and honest feedback.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import '@kerfjs/ui/layout.css';
|
|
2
|
+
|
|
3
|
+
import { delegate, mount, signal } from 'kerfjs';
|
|
4
|
+
import { delegateActions } from 'kerfjs/actions';
|
|
5
|
+
|
|
6
|
+
export interface AppCommand {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
keywords?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CommandPaletteCallbacks {
|
|
13
|
+
onClose(): void;
|
|
14
|
+
onRun(command: AppCommand): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Application-local example, not an @kerfjs/ui export. The application owns
|
|
19
|
+
* ranking, history, shortcuts, focus policy, command availability, and copy.
|
|
20
|
+
*/
|
|
21
|
+
export function mountCommandPaletteAdapter(
|
|
22
|
+
root: HTMLElement,
|
|
23
|
+
commands: AppCommand[],
|
|
24
|
+
{ onClose, onRun }: CommandPaletteCallbacks,
|
|
25
|
+
): () => void {
|
|
26
|
+
const query = signal('');
|
|
27
|
+
const render = () => {
|
|
28
|
+
const needle = query.value.trim().toLocaleLowerCase();
|
|
29
|
+
const matches = commands.filter((command) => !needle ||
|
|
30
|
+
[command.label, ...(command.keywords ?? [])].some((value) => value.toLocaleLowerCase().includes(needle)));
|
|
31
|
+
return <section class="app-command-palette kui-content" aria-label="Command palette">
|
|
32
|
+
<div class="kui-content-item"><label for="app-command-query">Search commands</label><input id="app-command-query" type="search" value={query.value} autocomplete="off" data-command-query /></div>
|
|
33
|
+
<p class="kui-content-item" aria-live="polite">{matches.length} matching commands</p>
|
|
34
|
+
<ul class="kui-content-item" aria-label="Matching commands">{matches.map((command) => <li><button type="button" data-action="run-command" data-command-id={command.id}>{command.label}</button></li>)}</ul>
|
|
35
|
+
<footer class="kui-control-cluster kui-content-item" aria-label="Command palette actions">
|
|
36
|
+
<button type="button" data-action="clear-query">Clear search</button>
|
|
37
|
+
<button type="button" data-action="close-palette">Close</button>
|
|
38
|
+
</footer>
|
|
39
|
+
</section>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const stopMount = mount(root, render);
|
|
43
|
+
const stopActions = delegateActions(root, 'click', {
|
|
44
|
+
'run-command': (_event, element) => {
|
|
45
|
+
const id = (element as HTMLElement).dataset.commandId;
|
|
46
|
+
const command = commands.find((candidate) => candidate.id === id);
|
|
47
|
+
if (command) onRun(command);
|
|
48
|
+
},
|
|
49
|
+
'clear-query': () => {
|
|
50
|
+
query.value = '';
|
|
51
|
+
root.querySelector<HTMLInputElement>('[data-command-query]')?.focus();
|
|
52
|
+
},
|
|
53
|
+
'close-palette': onClose,
|
|
54
|
+
});
|
|
55
|
+
const stopInput = delegate<HTMLInputElement>(root, 'input', '[data-command-query]', (_event, input) => {
|
|
56
|
+
query.value = input.value;
|
|
57
|
+
});
|
|
58
|
+
const stopEscape = delegate(root, 'keydown', '[data-command-query]', (event) => {
|
|
59
|
+
if ((event as KeyboardEvent).key === 'Escape') onClose();
|
|
60
|
+
});
|
|
61
|
+
let disposed = false;
|
|
62
|
+
return () => {
|
|
63
|
+
if (disposed) return;
|
|
64
|
+
disposed = true;
|
|
65
|
+
stopEscape();
|
|
66
|
+
stopInput();
|
|
67
|
+
stopActions();
|
|
68
|
+
stopMount();
|
|
69
|
+
};
|
|
70
|
+
}
|
package/docs/layout.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Pane and content layout
|
|
2
|
+
|
|
3
|
+
Import `@kerfjs/ui/layout.css` for the shared geometry used by sidebars, main
|
|
4
|
+
areas, inspectors, and dialogs. The vocabulary deliberately describes structure
|
|
5
|
+
rather than location: the same pane can be placed anywhere.
|
|
6
|
+
|
|
7
|
+
## Anatomy
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<aside class="kui-pane">
|
|
11
|
+
<div class="kui-pane__toolbar"><Toolbar label="Workspace" ... /></div>
|
|
12
|
+
<div class="kui-pane__content kui-content">
|
|
13
|
+
<section>...</section>
|
|
14
|
+
<section>...</section>
|
|
15
|
+
</div>
|
|
16
|
+
<footer class="kui-pane__footer"><Toolbar label="Actions" ... /></footer>
|
|
17
|
+
</aside>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`.kui-pane` has no padding. It reserves rows for an optional toolbar, one
|
|
21
|
+
scrolling content area, and an optional footer. A main area or dialog often
|
|
22
|
+
omits the footer; a navigation pane commonly uses all three. Fixed chrome stays
|
|
23
|
+
outside `.kui-pane__content`, which is the pane's scroll owner.
|
|
24
|
+
|
|
25
|
+
`.kui-content` is a vertical stack with a 24px gap between major children.
|
|
26
|
+
Sections may contain adjacent `MenuItem` rows without adding another major gap.
|
|
27
|
+
Ordinary surface-like children use `.kui-content-item` and own their complete
|
|
28
|
+
geometry:
|
|
29
|
+
|
|
30
|
+
- 8px inline margin from the pane edge
|
|
31
|
+
- 1px border, transparent by default
|
|
32
|
+
- 8px internal padding and 8px internal gap
|
|
33
|
+
- `calc(1px + remify(11px))`, or 12px, rounded corners
|
|
34
|
+
|
|
35
|
+
Use `.kui-content-item--pill` for the 22px pill radius expressed as
|
|
36
|
+
`calc(1px + remify(21px))`. Consumers can make a content item visible without
|
|
37
|
+
changing its geometry by setting `--kui-content-item-border` and
|
|
38
|
+
`--kui-content-item-background`.
|
|
39
|
+
|
|
40
|
+
A visible parent surface does not make every child another visible card. The
|
|
41
|
+
composer recipe keeps its form as the single surface and uses three transparent
|
|
42
|
+
content items for the introduction, fields, and footer. A conditional
|
|
43
|
+
`StateBanner` remains visibly distinct because it communicates semantic status.
|
|
44
|
+
|
|
45
|
+
## Public roles and tokens
|
|
46
|
+
|
|
47
|
+
| Need | Class | Token / default |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Unpadded toolbar/content/footer structure | `.kui-pane` | — |
|
|
50
|
+
| Scrolling pane content | `.kui-pane__content` | — |
|
|
51
|
+
| Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
|
|
52
|
+
| Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
|
|
53
|
+
| Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
|
|
54
|
+
| Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
|
|
55
|
+
| Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
|
|
56
|
+
| Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
|
|
57
|
+
|
|
58
|
+
The component layer applies the same contract to `Toolbar`, `MenuHeader`,
|
|
59
|
+
`MenuItem`, `PageHeader`, `DialogHeader`, `StateBanner`, `ValueTable`,
|
|
60
|
+
`ValueTableRow`, tabs, and form controls. A value-table row separator starts at
|
|
61
|
+
the row's 8px content inset, or at 40px when the row contains its 24px leading
|
|
62
|
+
icon and 8px gap, and always ends 8px from the right edge. Each value-table row
|
|
63
|
+
also keeps 8px of root-scaled block padding independently of its semantic inline
|
|
64
|
+
inset. Most interactive rows and toolbar groups are 44px tall.
|
|
65
|
+
Toolbar groups reserve a real 1px outer border around a 42px inner area, even
|
|
66
|
+
when their border and background are transparent.
|
|
67
|
+
`DialogHeader` applies the same rule internally: its icon/title identity is a
|
|
68
|
+
borderless group in a top toolbar, its direct action children are wrapped in a
|
|
69
|
+
contained group, and its optional subtitle is a separate row aligned below the
|
|
70
|
+
title. The icon visual is 24px inside a 34px circular background.
|
|
71
|
+
|
|
72
|
+
## Ownership rules
|
|
73
|
+
|
|
74
|
+
1. Do not pad a sidebar, main area, dialog, or `.kui-pane` shell. Children own
|
|
75
|
+
their own margin, border, background, padding, and radius.
|
|
76
|
+
2. Use 24px gaps for major vertical separation and 8px gaps inside an item or
|
|
77
|
+
between toolbar groups. Do not confuse the two relationships.
|
|
78
|
+
3. Wrap toolbar content in `ToolbarControlGroup`, including dormant text. A
|
|
79
|
+
transparent group still reserves the same 44px geometry as a visible group.
|
|
80
|
+
4. Keep one scrolling content owner per pane. Toolbar and footer siblings stay
|
|
81
|
+
fixed while the content scrolls.
|
|
82
|
+
5. A split item keeps dormant and interactive regions separate. For example,
|
|
83
|
+
`MenuHeader` renders its title/count-or-badge cluster separately from its optional
|
|
84
|
+
logical-end 44px action. The header fills the available inline width and its
|
|
85
|
+
action glyph defaults to 18px; disclosure mode makes the title cluster itself
|
|
86
|
+
the button and supplies the production `DisclosureArrow` unless `actionIcon`
|
|
87
|
+
replaces it.
|
|
88
|
+
`MenuActionRow` uses a noninteractive row root around sibling 44px primary
|
|
89
|
+
and trailing buttons. `MenuItem.trailing` remains dormant content.
|
|
90
|
+
6. Reading width, column placement, and responsive relocation remain application
|
|
91
|
+
decisions. The shared classes define local geometry, not the whole shell.
|
|
92
|
+
|
|
93
|
+
At narrow widths or 200% zoom, relocate or stack panes before shrinking targets.
|
|
94
|
+
The 8/1/8 item contract and 44px controls remain stable, so screenshots and
|
|
95
|
+
focus-order tests exercise the same model at every viewport.
|