@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
|
@@ -2,13 +2,89 @@
|
|
|
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
|
+
`ListItem.rootAttributes`, `ListActionRow.rootAttributes`,
|
|
30
|
+
`ListHeader.rootAttributes`, and `AppTab.rootAttributes` accept
|
|
31
|
+
application-owned `data-*` metadata without
|
|
32
|
+
adding product fields to the shared API. A `ListActionRow` trailing action uses
|
|
33
|
+
`trailingActionAttributes`, and a `ListHeader` 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
|
+
`ListItem.trailing`, `ListActionRow.label`, `ListActionRow.icon`, and
|
|
50
|
+
`ListActionRow.trailingActionIcon` are dormant SafeHtml slots. They must not
|
|
51
|
+
contain controls. When a row needs an independently interactive trailing
|
|
52
|
+
region, `ListActionRow` owns the noninteractive root and the two sibling native
|
|
53
|
+
buttons; the application owns their delegated behavior and controlled state.
|
|
54
|
+
Both row components align a leading icon with the first label line when
|
|
55
|
+
`multiline` allows the label to wrap; additional lines extend below that fixed
|
|
56
|
+
visual anchor.
|
|
57
|
+
|
|
58
|
+
### Public CSS anatomy
|
|
59
|
+
|
|
60
|
+
The `publicClasses` array on each entry in
|
|
61
|
+
[`component-catalog.json`](../ai/component-catalog.json) is the exact supported
|
|
62
|
+
anatomy boundary. A scoped selector may join documented public classes, such as
|
|
63
|
+
`.workspace .kui-toolbar .kui-toolbar__trailing`, when composition-specific
|
|
64
|
+
layout cannot be expressed by a prop or token. A class being public does not
|
|
65
|
+
make copied component markup an invocation or transfer state and accessibility
|
|
66
|
+
ownership to the application.
|
|
67
|
+
|
|
68
|
+
Do not select a component's descendant by element name, id, attribute alone, or
|
|
69
|
+
an unlisted implementation class. Selectors such as `.kui-state-banner span`,
|
|
70
|
+
`.kui-list-item [data-state]`, and `.kui-list-item .local-label` depend on
|
|
71
|
+
private structure. If no prop, token, or cataloged class expresses a recurring
|
|
72
|
+
need, request a supported hook instead of inferring one from rendered markup.
|
|
73
|
+
|
|
74
|
+
`DisclosureArrow` uses an 18px root-scaled visual by default. Override
|
|
75
|
+
`--kui-disclosure-arrow-size` on the component or its containing scope when a
|
|
76
|
+
consumer needs another size. Kerf `Select` uses a separate Web Awesome expand
|
|
77
|
+
glyph contract, `--kui-disclosure-icon-scale: .5`; changing one contract does
|
|
78
|
+
not implicitly change the other. Direction changes take the shortest rotation
|
|
79
|
+
path; a 180-degree closed-to-open tie uses counterclockwise rotation.
|
|
80
|
+
|
|
81
|
+
In `ListHeader` toggle mode, omitting `actionIcon` composes the production
|
|
82
|
+
`DisclosureArrow` and derives its visual direction from `expanded`. The app
|
|
83
|
+
must update that controlled state and reveal or hide real content. Passing a
|
|
84
|
+
custom `actionIcon` replaces the default rather than layering or rotating both.
|
|
85
|
+
The root fills the available inline width after its standard margins. A
|
|
86
|
+
separate action stays at the logical end in its 44px target while the visible
|
|
87
|
+
glyph defaults to 18px through `--kui-list-header-action-icon-size`.
|
|
12
88
|
|
|
13
89
|
`StateBanner` exposes instance-level `--kui-state-banner-background`,
|
|
14
90
|
`--kui-state-banner-border`, `--kui-state-banner-foreground`,
|
|
@@ -27,11 +103,47 @@ separator with a compact grip that appears on hover or keyboard focus. Override
|
|
|
27
103
|
`--kui-resizable-region-handle-active-color` on a region when its containing
|
|
28
104
|
surface needs a different contrast level.
|
|
29
105
|
|
|
106
|
+
`@kerfjs/ui/layout.css` defines one structural model for sidebars, main areas,
|
|
107
|
+
inspectors, and dialogs. `.kui-pane` is unpadded and contains an optional
|
|
108
|
+
toolbar, one scrolling `.kui-pane__content`, and an optional footer.
|
|
109
|
+
`.kui-content` gives major children 24px vertical separation.
|
|
110
|
+
`.kui-content-item` gives one child 8px inline margin, a real 1px border,
|
|
111
|
+
8px padding, and 12px corners; border and background may be transparent without
|
|
112
|
+
changing geometry. `.kui-content-item--pill` selects the 22px radius.
|
|
113
|
+
|
|
114
|
+
`ToolbarControlGroup` is the unit of toolbar organization, even for dormant
|
|
115
|
+
text. Each group reserves `calc(2px + remify(42px))`, or 44px, with 8px between
|
|
116
|
+
groups and inside items. `ListHeader` similarly separates its dormant title and
|
|
117
|
+
optional count or badge from its optional 44px action. Use the mutually
|
|
118
|
+
exclusive `count`/`countLabel` pair for non-negative safe-integer section
|
|
119
|
+
quantities; reserve `badge` for non-count `SafeHtml`. Do not concatenate counts
|
|
120
|
+
into the section label. Do not add padding to pane shells,
|
|
121
|
+
double child-owned geometry with wrapper insets, or create competing scroll
|
|
122
|
+
owners. The [layout contract](./layout.md) lists the public roles and tokens.
|
|
123
|
+
|
|
124
|
+
`PanelHeader` is a plain top `Toolbar` used as a panel, dialog, or page heading;
|
|
125
|
+
it overrides no Toolbar styles. The leading zone holds an optional icon (a normal
|
|
126
|
+
bordered `ToolbarControlGroup` given a brand fill with a matching border) and the
|
|
127
|
+
title as extra-large `ToolbarText`, and the app's `actions` go straight into the
|
|
128
|
+
trailing zone (typically as a `ToolbarControlGroup`). The icon group is omitted
|
|
129
|
+
when no icon is passed. The optional summary is a separate row aligned below the
|
|
130
|
+
title, so it cannot pull the icon group out of vertical alignment with the
|
|
131
|
+
title and action row.
|
|
132
|
+
|
|
133
|
+
`ValueTable` composes typed `ValueTableRow` entries. A row owns its `dt`/`dd`
|
|
134
|
+
semantics and may receive a leading `SafeHtml` icon. Every row keeps 8px of
|
|
135
|
+
root-scaled padding above and below its content; the shared item-padding token
|
|
136
|
+
continues to own its inline inset. Separators follow the content they introduce:
|
|
137
|
+
8px from either edge for an iconless row, or 40px from the left edge (8px
|
|
138
|
+
padding + 24px icon + 8px gap) and 8px from the right edge for an icon-bearing
|
|
139
|
+
row. Applications own the values, formatting, and whether an icon is decorative
|
|
140
|
+
or meaningfully labeled.
|
|
141
|
+
|
|
30
142
|
## Imports and side effects
|
|
31
143
|
|
|
32
144
|
Every component has an explicit JS and CSS subpath. In CSS-aware browser builds,
|
|
33
145
|
the JS component subpath resolves to a generated wrapper that imports the
|
|
34
|
-
foundation, the component stylesheet
|
|
146
|
+
foundation, the compiled component stylesheet from `dist/styles`, and styles for its reachable UI
|
|
35
147
|
subcomponents. The source graph derives those transitive styles at build time;
|
|
36
148
|
an application root never maintains that list. Unused component subpaths and
|
|
37
149
|
their CSS remain unreachable. The root barrel and `@kerfjs/ui/unstyled` stay
|
|
@@ -41,23 +153,76 @@ overrides load later in the cascade or set scoped `--kui-*` variables. JavaScrip
|
|
|
41
153
|
modules are pure except the browser style wrappers and `@kerfjs/ui/select/register`,
|
|
42
154
|
which registers exactly the Web Awesome elements used by `Select`. Eventful
|
|
43
155
|
helpers such as `wireResizableRegions` and `wireTabBars` attach listeners only
|
|
44
|
-
when called and return disposers.
|
|
156
|
+
when called and return disposers. `wireTokenSearchFields` goes one step further:
|
|
157
|
+
by default it also owns the collapsible field's transient expand/collapse/focus
|
|
158
|
+
(activate to reveal and focus, Escape or empty blur to collapse), holding that
|
|
159
|
+
state in a signal it exposes on the returned handle. An app reads that signal in
|
|
160
|
+
render, hands in its own via `collapsible.signals`, drives it through
|
|
161
|
+
`handle.open`/`handle.close`, or disables any individual behavior — so transient
|
|
162
|
+
UI is consistent by default without every app reinventing it.
|
|
163
|
+
|
|
164
|
+
`wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
|
|
165
|
+
helpers. Its collapse behavior was *rich and error-prone* — reveal, focus
|
|
166
|
+
transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
|
|
167
|
+
apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
|
|
168
|
+
app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
|
|
169
|
+
`NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
|
|
170
|
+
and tab order are data, a `ResizableRegion`'s committed size and a
|
|
171
|
+
`Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
|
|
172
|
+
Each helper already owns only the *ephemeral mechanics* around that state —
|
|
173
|
+
`wireNavStack` the push/pop animation, `wireTabBars` the overflow autoscroll and
|
|
174
|
+
drag preview, `wireResizableRegions` the live drag preview — and reports committed
|
|
175
|
+
changes through callbacks. A `ListHeader` `toggle` disclosure's `expanded` is
|
|
176
|
+
likewise app-owned: it is a one-line boolean the app already tracks and must read to
|
|
177
|
+
render the section body, so a managed helper would remove no real complexity. Reach
|
|
178
|
+
for a managed default only when the transient behavior is substantial enough that
|
|
179
|
+
hand-rolling it produces genuine, inconsistent variation.
|
|
180
|
+
|
|
181
|
+
CSS, the generated wrappers that make it
|
|
45
182
|
reachable, and the registration module are the package's only declared side
|
|
46
183
|
effects.
|
|
47
184
|
|
|
185
|
+
A value-bearing component's `placeholder` prop is a first-class loading mode: the
|
|
186
|
+
component renders its own real chrome (labels, icon and action affordances,
|
|
187
|
+
container geometry) while replacing each **value** slot with a subtle, deliberately
|
|
188
|
+
unanimated `Skeleton` block and disabling its own interactive controls
|
|
189
|
+
(`aria-busy`, dropped `data-action`, disabled buttons). Sizes and shapes stay
|
|
190
|
+
identical to the populated component, so a parent composes a faithful loading view —
|
|
191
|
+
an inspector, a detail pane — from placeholder children without hand-rebuilding
|
|
192
|
+
markup. The application still owns the loading lifecycle (when to pass `placeholder`)
|
|
193
|
+
and which slots are unknown; the standalone `Skeleton` covers custom slots. It is a
|
|
194
|
+
placeholder, not progress: use `LoadingSpinner` for known busy activity, and never
|
|
195
|
+
animate the skeleton.
|
|
196
|
+
|
|
197
|
+
Package source styles express root-scaled geometry with `remify(<px>)`; the
|
|
198
|
+
build converts it against the 16px authoring baseline and exposes only ordinary
|
|
199
|
+
`rem` CSS. Literal pixels remain for intentional hairlines, and `em` remains
|
|
200
|
+
explicit when a value is relative to its component's current font size. The UX
|
|
201
|
+
catalog applies the same transform to source CSS under Vite so `npm run dev`
|
|
202
|
+
retains hot module replacement.
|
|
203
|
+
|
|
48
204
|
`@kerfjs/ui/webawesome.css` is a separate, optional CSS boundary. It imports
|
|
49
205
|
Web Awesome's public base stylesheet and overrides its semantic theme layer to
|
|
50
206
|
match the Kerf/Hot Sheet 2 system. It never imports component JavaScript.
|
|
207
|
+
`import type {} from '@kerfjs/ui/webawesome'` is the matching side-effect-free
|
|
208
|
+
Kerf JSX declaration boundary for every catalog-supported `wa-*` tag.
|
|
51
209
|
Consumers register only the individual Web Awesome modules they render and can
|
|
52
210
|
override `--wa-*` values after the theme import. The shared `--wa-*` values feed
|
|
53
211
|
both Web Awesome controls and Kerf's `--kui-*` foundation aliases, preventing a
|
|
54
212
|
second application palette.
|
|
55
213
|
|
|
214
|
+
`Select` owns its custom-element reconciliation seam. It gives each slotted
|
|
215
|
+
option icon a stable key and leaves the upgraded Web Awesome-owned slot subtree
|
|
216
|
+
untouched on later Kerf renders. Its custom selected slot is keyed by the
|
|
217
|
+
controlled value so changed selections replace that content. Applications
|
|
218
|
+
should pass ordinary `choices` and `renderSelected` output rather than adding
|
|
219
|
+
their own morph-control attributes.
|
|
220
|
+
|
|
56
221
|
`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
222
|
|
|
58
223
|
## Extracted versus application-specific
|
|
59
224
|
|
|
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
|
|
225
|
+
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
226
|
|
|
62
227
|
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
228
|
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
**Don't fight the components.** The package is built to look right unstyled, so
|
|
19
|
+
custom CSS is the exception. Before adding `padding`, `margin`, `width`, `height`,
|
|
20
|
+
`border`, `background`, a wrapper card, or a decoration, check whether the
|
|
21
|
+
component, the pane, or the content-item already owns it — it almost always does,
|
|
22
|
+
and adding more usually double-insets or fights it. Trust component defaults and
|
|
23
|
+
fix the surrounding layout instead of overriding a control. See
|
|
24
|
+
[`design-philosophy.md`](./design-philosophy.md) "Reach for the primitive, not for
|
|
25
|
+
CSS".
|
|
26
|
+
|
|
27
|
+
## Production recipes
|
|
28
|
+
|
|
29
|
+
Use the [complete recipe guide](./recipes.md) when several primitives form one
|
|
30
|
+
application boundary:
|
|
31
|
+
|
|
32
|
+
| Task | Stable catalog route |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| Desktop application shell | [Catalog](../ux-demo/) · `?component=recipe-app-shell` |
|
|
35
|
+
| Navigation sidebar | [Catalog](../ux-demo/) · `?component=recipe-navigation-sidebar` |
|
|
36
|
+
| Workspace header | [Catalog](../ux-demo/) · `?component=recipe-workspace-header` |
|
|
37
|
+
| List-detail dialog | [Catalog](../ux-demo/) · `?component=recipe-list-detail-dialog` |
|
|
38
|
+
| Composer form | [Catalog](../ux-demo/) · `?component=recipe-composer-form` |
|
|
39
|
+
| List workspace states | [Catalog](../ux-demo/) · `?component=recipe-list-workspace-states` |
|
|
40
|
+
| Compact toolbar choices and actions | [Catalog](../ux-demo/) · `?component=recipe-compact-toolbar` |
|
|
41
|
+
| Navigation stack | [Catalog](../ux-demo/) · `?component=recipe-navigation-stack` |
|
|
42
|
+
| Loading inspector | [Catalog](../ux-demo/) · `?component=recipe-loading-inspector` |
|
|
43
|
+
| Collapsible sidebar | [Catalog](../ux-demo/) · `?component=recipe-collapsible-sidebar` |
|
|
44
|
+
|
|
45
|
+
Recipes use public production exports and show ownership boundaries; they are
|
|
46
|
+
copyable reference compositions, not new monolithic components.
|
|
47
|
+
|
|
48
|
+
## Missing recurring concepts
|
|
49
|
+
|
|
50
|
+
Kerf UI does not export a command-palette component. Do not invent a package
|
|
51
|
+
command-palette import.
|
|
52
|
+
The typed [application adapter example](./examples/command-palette-adapter.tsx)
|
|
53
|
+
imports `@kerfjs/ui/layout.css`, assigns one `.kui-layout` root and one surface
|
|
54
|
+
inset, and groups its related footer commands with `.kui-control-cluster` while
|
|
55
|
+
the application owns ranking, history, shortcut policy, focus policy,
|
|
56
|
+
availability, actions, and copy. If that concept recurs across products, open
|
|
57
|
+
an upstream component or recipe request.
|
|
58
|
+
|
|
59
|
+
## Problem-to-component matrix
|
|
60
|
+
|
|
61
|
+
| Interface need | Use when | Do not use when; nearest alternative | Required wiring | Application owns | Import | Recipe |
|
|
62
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
63
|
+
| 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) |
|
|
64
|
+
| 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) |
|
|
65
|
+
| Application toolbar — `Toolbar` | Leading identity, optional centered content, and trailing controls form one horizontal app bar. | Do not use it for a page, panel, or dialog heading; use `PanelHeader`. | Compose `ToolbarText` and `ToolbarControlGroup` where their contracts fit. | Actions, command availability, responsive relocation, and state. | `@kerfjs/ui/toolbar` | [Toolbar composition](../README.md#component-subpaths) |
|
|
66
|
+
| 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) |
|
|
67
|
+
| Toolbar identity text — `ToolbarText` | A toolbar needs extra-large (page/panel title), large, default, or compact textual identity. | Plain text is not a heading by default; when a title needs heading semantics pass `headingLevel` (or prefer `PanelHeader`, which forwards it). | Optional `headingLevel` for `role="heading"` + `aria-level`. | Text, heading level, and responsive priority. | `@kerfjs/ui/toolbar-text` | [Toolbar composition](../README.md#component-subpaths) |
|
|
68
|
+
| Navigation row — `ListItem` | A pane or navigation area needs a selectable, disabled, dormant-trailing, or multiline action row. | Do not put a control in `trailing`; use `ListActionRow` 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/list-item` | [Pane geometry](../README.md#pane-and-content-geometry) |
|
|
69
|
+
| Navigation row with trailing action — `ListActionRow` | A full-width row needs a selectable primary action and an independently focusable trailing action. | Use `ListItem` 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/list-action-row` | [Accessibility](./accessibility.md#listactionrow) |
|
|
70
|
+
| Navigation section heading — `ListHeader` | 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, panel, or dialog title; use `PanelHeader`. | 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/list-header` | [Pane geometry](../README.md#pane-and-content-geometry) |
|
|
71
|
+
| 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, invent unrelated centered measures, or leave an icon-only rail for a hidden pane. | Use `.kui-pane` and one `.kui-pane__content`; add `.kui-content` and `.kui-content-item` as needed. A visible pane owns collapse in its toolbar; move a hidden inline-start pane's restore control to the main toolbar leading edge and an inline-end pane's restore control to its trailing edge. | Layout hierarchy, reading width, scroll ownership, responsive relocation, and pane visibility state. | `@kerfjs/ui/layout.css` | [Pane anatomy](./layout.md#anatomy) |
|
|
72
|
+
| 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 `ListItem`, or use a chevron on a row that does not disclose content. Use ordinary links for a different navigation contract. | Compose `ListHeader`, `ListItem`, and `ListActionRow` in `.kui-content`; use `ListHeader` 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) |
|
|
73
|
+
| 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) |
|
|
74
|
+
| 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) |
|
|
75
|
+
| 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) |
|
|
76
|
+
| Panel, dialog, or page heading — `PanelHeader` | A panel, dialog, or page needs a heading with an extra-large title, an optional icon and subtitle, and trailing actions. | Do not use it as persistent app chrome; use `Toolbar`. It supplies header structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. | Connect the title id and any provided summary id to the dialog or panel host, pass the trailing controls (typically a `ToolbarControlGroup`), and delegate their actions. **For a page or view title set `headingLevel` (usually `1`)** so the title is a real heading landmark (`role="heading"` + `aria-level`) for screen-reader heading navigation; omit it for a dialog title, which is referenced via `aria-labelledby` to `titleId` and needs no heading. | Open state, focus lifecycle, dismissal, the trailing controls, labels, copy, and the heading level for page use. | `@kerfjs/ui/panel-header` | [Header ownership](./component-contract.md#extracted-versus-application-specific) |
|
|
77
|
+
| 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) |
|
|
78
|
+
| 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) |
|
|
79
|
+
| Loading placeholder — `Skeleton` + a component's `placeholder` prop | A value or a whole component is still loading and should hold its space as a subtle, unanimated block, keeping the layout stable — an inspector or detail view rendering its real chrome with per-record values absent. | Do not use it for known progress (use `LoadingSpinner`), do not animate it, and do not hand-rebuild a component's empty state — set `placeholder` on the component instead. Prefer it over `wa-skeleton`, which the pure-Kerf primitives avoid to stay Web-Awesome-free. | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `PanelHeader`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render skeletons in its value slots with interactivity disabled; use the standalone `Skeleton` for a custom slot. | Loading lifecycle, which slots are unknown, and announcing the loading region. | `@kerfjs/ui/skeleton` | [Accessibility](./accessibility.md#shared-rules) |
|
|
80
|
+
| 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) |
|
|
81
|
+
| 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) |
|
|
82
|
+
| 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 it also manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse); bind the field's `expanded` to the signal on the returned handle (`handle.expanded(id)`) or adopt your own via `collapsible.signals`, and opt out per behavior only when the app must own it. | Parsing, suggestions, tokens, query execution, results, announcements, and — only if overriding the default — the collapsible `expanded` signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
|
|
83
|
+
| 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) |
|
|
84
|
+
| 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) |
|
|
85
|
+
|
|
86
|
+
## Ambiguous choices
|
|
87
|
+
|
|
88
|
+
- `Toolbar` is persistent app chrome; `PanelHeader` heads a panel, dialog, or page.
|
|
89
|
+
- `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `Select` handles a longer value list.
|
|
90
|
+
- `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.
|
|
91
|
+
- `ResizableRegion` is an interactive controlled pane. CSS grid is the right answer when columns do not need a user-operable separator.
|
|
92
|
+
- `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
|
|
93
|
+
|
|
94
|
+
## Toolbar composition
|
|
95
|
+
|
|
96
|
+
A `Toolbar` has three zones — `leading`, `center`, and `trailing`. In almost
|
|
97
|
+
every case the only things that go **directly** in a zone are `ToolbarText`
|
|
98
|
+
(identity/title text) and `ToolbarControlGroup` (any control or cluster of
|
|
99
|
+
controls). Do not drop bare buttons, inputs, links, or arbitrary markup straight
|
|
100
|
+
into a zone; wrap controls in a `ToolbarControlGroup` so they get the shared
|
|
101
|
+
toolbar geometry, hover/pressed treatment, and grouping. `SegmentedControl`,
|
|
102
|
+
`Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live
|
|
103
|
+
**inside** a `ToolbarControlGroup`, not loose in the zone. `PanelHeader` is the
|
|
104
|
+
one wrapper that composes these for you as a panel/dialog/page heading.
|
|
105
|
+
|
|
106
|
+
Common toolbar patterns:
|
|
107
|
+
|
|
108
|
+
| Want | Put in the zone | Notes |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| Identity or title text | `<ToolbarText text="…" size="large" />` (or `xlarge` for a page/panel title) | Wrap in a `single` borderless group only when it must align with adjacent control pills |
|
|
111
|
+
| One or more icon/text buttons | `<ToolbarControlGroup>{buttons}</ToolbarControlGroup>` | Use `buttonAppearance="push"` for toggle buttons with `aria-pressed`; `single` for a lone control |
|
|
112
|
+
| An exclusive view switch | `<ToolbarControlGroup><SegmentedControl … /></ToolbarControlGroup>` | Not `TabBar`, which switches tabpanels |
|
|
113
|
+
| A value list | `<ToolbarControlGroup><Select … /></ToolbarControlGroup>` | Register `@kerfjs/ui/select/register` once |
|
|
114
|
+
| A collapsible search box | `<ToolbarControlGroup single><TokenSearchField collapsible … /></ToolbarControlGroup>` | The group animates the iconic ↔ expanded states; `wireTokenSearchFields` manages expand/collapse/focus by default |
|
|
115
|
+
|
|
116
|
+
A **popup menu in a toolbar** is a `single` `ToolbarControlGroup` wrapping a Web
|
|
117
|
+
Awesome `wa-dropdown`: its `slot="trigger"` `wa-button` is the toolbar button and
|
|
118
|
+
the `wa-dropdown-item`s are the menu. Keep the dropdown's managed light-DOM
|
|
119
|
+
children under `data-morph-skip-children` so kerf does not reconcile Web Awesome's
|
|
120
|
+
own DOM.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<ToolbarControlGroup single>
|
|
124
|
+
<wa-dropdown placement="bottom-start" data-morph-skip-children>
|
|
125
|
+
<wa-button slot="trigger" appearance="plain" with-caret aria-label="Sort">
|
|
126
|
+
<LucideIcon icon={ArrowDownAZ} name="arrow-down-a-z" />
|
|
127
|
+
</wa-button>
|
|
128
|
+
<wa-dropdown-item data-action="sort-recent">Recently updated</wa-dropdown-item>
|
|
129
|
+
<wa-dropdown-item data-action="sort-priority">Priority</wa-dropdown-item>
|
|
130
|
+
</wa-dropdown>
|
|
131
|
+
</ToolbarControlGroup>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Correct composition and duplicated-markup trap
|
|
135
|
+
|
|
136
|
+
Correct: let the pane stay unpadded while its children own the shared 8/1/8
|
|
137
|
+
geometry and 44px targets.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
<aside class="kui-pane">
|
|
141
|
+
<nav class="kui-pane__content kui-content">
|
|
142
|
+
<section>
|
|
143
|
+
<ListHeader label="Workspace" />
|
|
144
|
+
<ListItem action="open" label="Inbox" icon={inboxIcon} />
|
|
145
|
+
<ListActionRow action="open-file" label="main.ts" trailingAction="file-actions" trailingActionLabel="Actions for main.ts" trailingActionIcon={moreIcon} />
|
|
146
|
+
</section>
|
|
147
|
+
<div class="kui-content-item">Workspace details</div>
|
|
148
|
+
</nav>
|
|
149
|
+
</aside>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Incorrect: duplicating component-like rows and compensating for nested padding
|
|
153
|
+
forks the package anatomy and spacing contract.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<aside class="sidebar padded">
|
|
157
|
+
<h2 class="list-header-copy">Workspace</h2>
|
|
158
|
+
<button class="menu-row-copy padded">Inbox</button>
|
|
159
|
+
<div class="panel indented-with-negative-margin">Workspace details</div>
|
|
160
|
+
</aside>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Web Awesome overlap policy
|
|
164
|
+
|
|
165
|
+
Web Awesome catalog coverage means supported and themed, not preferred. Import
|
|
166
|
+
`@kerfjs/ui/webawesome` for Kerf JSX types, individual component modules for
|
|
167
|
+
registration, and the CSS-only `@kerfjs/ui/webawesome.css` theme.
|
|
168
|
+
|
|
169
|
+
| Web Awesome choice | Kerf decision |
|
|
170
|
+
| --- | --- |
|
|
171
|
+
| `wa-button`, `wa-dropdown`, `wa-dropdown-item` | Use buttons and command menus for actions. Use `ListItem` for a navigation row and `Select` when the user chooses a value. |
|
|
172
|
+
| `wa-button-group` | Use only for exceptional grouped actions; use `SegmentedControl` for one-of-many selection. |
|
|
173
|
+
| `wa-input`, `wa-tag` | Use for ordinary text and tags; use `TokenSearchField` only when text and ordered filter tokens form one editor. |
|
|
174
|
+
| `wa-select`, `wa-option` | Use `Select`, which owns Kerf spacing, controlled rendering, icon stability, and explicit registration. |
|
|
175
|
+
| `wa-tab-group`, `wa-tab`, `wa-tab-panel` | Use `TabBar`/`AppTab` for application tabs or `SegmentedControl` for compact local views. |
|
|
176
|
+
| `wa-icon` | Use `LucideIcon` in application UI. |
|
|
177
|
+
| `wa-split-panel` | Use `ResizableRegion` for Kerf application panes; retain Split Panel only when its distinct API is required. |
|
|
178
|
+
| `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. |
|
|
179
|
+
| `wa-callout`, `wa-toast`, `wa-toast-item` | Use `StateBanner` for persistent inline app status, `EmptyState` for absent content, and toasts only for transient feedback. |
|
|
180
|
+
| `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. |
|
|
181
|
+
| `wa-tree`, `wa-tree-item`, `wa-animated-image`, `wa-comparison` | Use only for the specialized behavior named by the component. |
|
|
182
|
+
| `wa-zoomable-frame` | Avoid for application UI; keep embedded-media behavior application-owned. |
|
|
183
|
+
|
|
184
|
+
All other entries in the [Web Awesome theme contract](./webawesome-theme.md#coverage)
|
|
185
|
+
remain supported when their native semantic contract matches the product need.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="551.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="551.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct5"><rect x="587.7" y="34" width="64.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="folder" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, "system-ui", "Segoe UI", sans-serif" style="overflow:hidden" width="22" height="22"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.7-.9l-.8-1.2A2 2 0 0 0 7.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Package details</text></g></g></g></g><rect x="587.7" y="34" width="64.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="588.5" y="34.5" width="63" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="600.69" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">Done</text></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="551.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="551.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct5"><rect x="587.7" y="34" width="64.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(235,245,255)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(235,245,255)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(30, 110, 244)"><svg data-lucide="folder" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, "system-ui", "Segoe UI", sans-serif" style="overflow:hidden" width="22" height="22"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.7-.9l-.8-1.2A2 2 0 0 0 7.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Package details</text></g></g></g></g><rect x="587.7" y="34" width="64.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="588.5" y="34.5" width="63" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="600.69" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(29,29,31)">Done</text></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="550.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="550.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct5"><rect x="586.7" y="34" width="65.3" height="32"/></clipPath><clipPath id="cp6"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct7"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="file-text" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, "system-ui", "Segoe UI", sans-serif" style="overflow:hidden" width="22" height="22"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2 2 0 0 1 1.7.7l3.6 3.6A2 2 0 0 1 20 8v12a2 2 0 0 1-2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M14 2v5a1 1 0 0 0 1 1h5" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M10 9H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 13H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 17H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Northstar migration</text></g></g></g></g><rect x="586.7" y="34" width="65.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="587.5" y="34.5" width="64" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="599.66" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">Open</text><g clip-path="url(#cp6)"><g clip-path="url(#ct7)"><text x="88" y="95" font-family="system-ui, "-apple-system", sans-serif" font-size="16" fill="rgb(174,174,178)">In review · updated today</text></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="550.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="550.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct5"><rect x="586.7" y="34" width="65.3" height="32"/></clipPath><clipPath id="cp6"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct7"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(235,245,255)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(235,245,255)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(30, 110, 244)"><svg data-lucide="file-text" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, "system-ui", "Segoe UI", sans-serif" style="overflow:hidden" width="22" height="22"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2 2 0 0 1 1.7.7l3.6 3.6A2 2 0 0 1 20 8v12a2 2 0 0 1-2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M14 2v5a1 1 0 0 0 1 1h5" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M10 9H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 13H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 17H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Northstar migration</text></g></g></g></g><rect x="586.7" y="34" width="65.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="587.5" y="34.5" width="64" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="599.66" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(29,29,31)">Open</text><g clip-path="url(#cp6)"><g clip-path="url(#ct7)"><text x="88" y="95" font-family="system-ui, "-apple-system", sans-serif" font-size="16" fill="rgb(110,110,115)">In review · updated today</text></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="557.5" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="557.5" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct4"><rect x="593.5" y="34" width="58.5" height="32"/></clipPath><clipPath id="cp5"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct6"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Workspace settings</text></g></g></g></g><rect x="593.5" y="34" width="58.5" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="594.5" y="34.5" width="57" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="606.53" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">New</text><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="36" y="95" font-family="system-ui, "-apple-system", sans-serif" font-size="16" fill="rgb(174,174,178)">Manage members, billing, and integrations.</text></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="557.5" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="557.5" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct4"><rect x="593.5" y="34" width="58.5" height="32"/></clipPath><clipPath id="cp5"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct6"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Workspace settings</text></g></g></g></g><rect x="593.5" y="34" width="58.5" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="594.5" y="34.5" width="57" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="606.53" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(29,29,31)">New</text><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="36" y="95" font-family="system-ui, "-apple-system", sans-serif" font-size="16" fill="rgb(110,110,115)">Manage members, billing, and integrations.</text></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="500.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="500.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct4"><rect x="536.7" y="34" width="115.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">UI foundations</text></g></g></g></g><rect x="536.7" y="34" width="115.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="537.5" y="34.5" width="114" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="549.7" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">New pattern</text></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="500.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="500.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct4"><rect x="536.7" y="34" width="115.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">UI foundations</text></g></g></g></g><rect x="536.7" y="34" width="115.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="537.5" y="34.5" width="114" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="549.7" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(29,29,31)">New pattern</text></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 104" width="720" height="104"><defs><clipPath id="cp0"><rect x="28" y="28" width="616" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="616" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="146.9" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="146.9" height="44"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Recent activity</text></g></g></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 104" width="720" height="104"><defs><clipPath id="cp0"><rect x="28" y="28" width="616" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="616" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="146.9" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="146.9" height="44"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Recent activity</text></g></g></g></g></svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="752" height="806" viewBox="0 0 752 806">
|
|
2
|
+
<rect width="752" height="806" fill="#1c1c1e"/>
|
|
3
|
+
<text x="16" y="30" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Icon, summary, and action</text>
|
|
4
|
+
<svg x="16" y="38" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="v0-cp0"><rect x="28" y="28" width="550.7" height="44"/></clipPath><clipPath id="v0-ov1"><rect x="28" y="28" width="550.7" height="44" rx="0"/></clipPath><clipPath id="v0-cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="v0-cp3"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="v0-ct4"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="v0-ct5"><rect x="586.7" y="34" width="65.3" height="32"/></clipPath><clipPath id="v0-cp6"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="v0-ct7"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#v0-cp0)"><g clip-path="url(#v0-ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#v0-cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="file-text" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, "system-ui", "Segoe UI", sans-serif" style="overflow:hidden" width="22" height="22"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2 2 0 0 1 1.7.7l3.6 3.6A2 2 0 0 1 20 8v12a2 2 0 0 1-2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M14 2v5a1 1 0 0 0 1 1h5" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M10 9H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 13H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 17H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#v0-cp3)"><g clip-path="url(#v0-ct4)"><text x="88" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Northstar migration</text></g></g></g></g><rect x="586.7" y="34" width="65.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="587.5" y="34.5" width="64" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="599.66" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">Open</text><g clip-path="url(#v0-cp6)"><g clip-path="url(#v0-ct7)"><text x="88" y="95" font-family="system-ui, "-apple-system", sans-serif" font-size="16" fill="rgb(174,174,178)">In review · updated today</text></g></g></svg>
|
|
5
|
+
<text x="16" y="204" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Icon and action, no summary</text>
|
|
6
|
+
<svg x="16" y="212" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="v1-cp0"><rect x="28" y="28" width="551.7" height="44"/></clipPath><clipPath id="v1-ov1"><rect x="28" y="28" width="551.7" height="44" rx="0"/></clipPath><clipPath id="v1-cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="v1-cp3"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="v1-ct4"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="v1-ct5"><rect x="587.7" y="34" width="64.3" height="32"/></clipPath></defs><g clip-path="url(#v1-cp0)"><g clip-path="url(#v1-ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#v1-cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="folder" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, "system-ui", "Segoe UI", sans-serif" style="overflow:hidden" width="22" height="22"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.7-.9l-.8-1.2A2 2 0 0 0 7.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#v1-cp3)"><g clip-path="url(#v1-ct4)"><text x="88" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Package details</text></g></g></g></g><rect x="587.7" y="34" width="64.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="588.5" y="34.5" width="63" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="600.69" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">Done</text></svg>
|
|
7
|
+
<text x="16" y="354" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">No icon, summary and action</text>
|
|
8
|
+
<svg x="16" y="362" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="v2-cp0"><rect x="28" y="28" width="557.5" height="44"/></clipPath><clipPath id="v2-ov1"><rect x="28" y="28" width="557.5" height="44" rx="0"/></clipPath><clipPath id="v2-cp2"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="v2-ct3"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="v2-ct4"><rect x="593.5" y="34" width="58.5" height="32"/></clipPath><clipPath id="v2-cp5"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="v2-ct6"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#v2-cp0)"><g clip-path="url(#v2-ov1)"><g clip-path="url(#v2-cp2)"><g clip-path="url(#v2-ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Workspace settings</text></g></g></g></g><rect x="593.5" y="34" width="58.5" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="594.5" y="34.5" width="57" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="606.53" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">New</text><g clip-path="url(#v2-cp5)"><g clip-path="url(#v2-ct6)"><text x="36" y="95" font-family="system-ui, "-apple-system", sans-serif" font-size="16" fill="rgb(174,174,178)">Manage members, billing, and integrations.</text></g></g></svg>
|
|
9
|
+
<text x="16" y="528" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Title only</text>
|
|
10
|
+
<svg x="16" y="536" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 104" width="720" height="104"><defs><clipPath id="v3-cp0"><rect x="28" y="28" width="616" height="44"/></clipPath><clipPath id="v3-ov1"><rect x="28" y="28" width="616" height="44" rx="0"/></clipPath><clipPath id="v3-cp2"><rect x="28" y="28" width="146.9" height="44"/></clipPath><clipPath id="v3-ct3"><rect x="28" y="28" width="146.9" height="44"/></clipPath></defs><g clip-path="url(#v3-cp0)"><g clip-path="url(#v3-ov1)"><g clip-path="url(#v3-cp2)"><g clip-path="url(#v3-ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Recent activity</text></g></g></g></g></svg>
|
|
11
|
+
<text x="16" y="670" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Page heading (h1) with action</text>
|
|
12
|
+
<svg x="16" y="678" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="v4-cp0"><rect x="28" y="28" width="500.7" height="44"/></clipPath><clipPath id="v4-ov1"><rect x="28" y="28" width="500.7" height="44" rx="0"/></clipPath><clipPath id="v4-cp2"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="v4-ct3"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="v4-ct4"><rect x="536.7" y="34" width="115.3" height="32"/></clipPath></defs><g clip-path="url(#v4-cp0)"><g clip-path="url(#v4-ov1)"><g clip-path="url(#v4-cp2)"><g clip-path="url(#v4-ct3)"><text x="36" y="57" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">UI foundations</text></g></g></g></g><rect x="536.7" y="34" width="115.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="537.5" y="34.5" width="114" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="549.7" y="56" font-family=""ui-sans-serif", "-apple-system", "system-ui", "Segoe UI", sans-serif" font-size="16" fill="rgb(245,245,247)">New pattern</text></svg>
|
|
13
|
+
</svg>
|