@kerfjs/ui 5.0.0-beta.4 → 5.0.0-beta.7

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.
Files changed (156) hide show
  1. package/README.md +9 -8
  2. package/ai/component-catalog.json +379 -174
  3. package/ai/public-api-signatures-v1.md +387 -33
  4. package/ai/skill.md +15 -7
  5. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  6. package/dist/app-tab.d.ts +3 -1
  7. package/dist/app-tab.js +2 -1
  8. package/dist/browser/app-tab.js +1 -0
  9. package/dist/browser/menu-action-row.js +1 -0
  10. package/dist/browser/menu-header.js +1 -0
  11. package/dist/browser/menu-item.js +1 -0
  12. package/dist/browser/panel-header.js +7 -0
  13. package/dist/browser/segmented-control.js +1 -0
  14. package/dist/browser/select.js +1 -0
  15. package/dist/browser/skeleton.js +3 -0
  16. package/dist/browser/state-banner.js +1 -0
  17. package/dist/browser/toolbar-text.js +1 -0
  18. package/dist/browser/value-table.js +1 -0
  19. package/dist/chunk-2RJBFNB6.js +172 -0
  20. package/dist/chunk-2RJBFNB6.js.map +1 -0
  21. package/dist/{chunk-XZXRLY5Q.js → chunk-2Z5TWHKQ.js} +7 -6
  22. package/dist/chunk-2Z5TWHKQ.js.map +1 -0
  23. package/dist/{chunk-EGHL5S4P.js → chunk-3YD6ZNRA.js} +8 -7
  24. package/dist/chunk-3YD6ZNRA.js.map +1 -0
  25. package/dist/chunk-B24C4A6T.js +22 -0
  26. package/dist/chunk-B24C4A6T.js.map +1 -0
  27. package/dist/{chunk-NPPD2A6O.js → chunk-H6URQGJI.js} +8 -6
  28. package/dist/chunk-H6URQGJI.js.map +1 -0
  29. package/dist/chunk-I7INRJO4.js +17 -0
  30. package/dist/chunk-I7INRJO4.js.map +1 -0
  31. package/dist/chunk-KJYXOTG3.js +10 -0
  32. package/dist/chunk-KJYXOTG3.js.map +1 -0
  33. package/dist/{chunk-CTQKI2XL.js → chunk-LC3YSAZC.js} +14 -3
  34. package/dist/chunk-LC3YSAZC.js.map +1 -0
  35. package/dist/chunk-N3SUXLLS.js +23 -0
  36. package/dist/chunk-N3SUXLLS.js.map +1 -0
  37. package/dist/{chunk-R6JHHBKQ.js → chunk-PONXLV5U.js} +5 -5
  38. package/dist/chunk-PONXLV5U.js.map +1 -0
  39. package/dist/chunk-TZNCCM5H.js +20 -0
  40. package/dist/chunk-TZNCCM5H.js.map +1 -0
  41. package/dist/{chunk-TRNBHPNW.js → chunk-YGN7OMLT.js} +11 -8
  42. package/dist/chunk-YGN7OMLT.js.map +1 -0
  43. package/dist/chunk-YKUUZPED.js +24 -0
  44. package/dist/chunk-YKUUZPED.js.map +1 -0
  45. package/dist/device-class.d.ts +62 -0
  46. package/dist/device-class.js +75 -0
  47. package/dist/device-class.js.map +1 -0
  48. package/dist/index.d.ts +3 -3
  49. package/dist/index.js +15 -15
  50. package/dist/menu-action-row.d.ts +3 -1
  51. package/dist/menu-action-row.js +2 -1
  52. package/dist/menu-header.d.ts +3 -1
  53. package/dist/menu-header.js +2 -1
  54. package/dist/menu-item.d.ts +3 -1
  55. package/dist/menu-item.js +2 -1
  56. package/dist/nav-stack.d.ts +38 -0
  57. package/dist/nav-stack.js +5 -0
  58. package/dist/nav-stack.js.map +1 -0
  59. package/dist/panel-header.d.ts +28 -0
  60. package/dist/panel-header.js +7 -0
  61. package/dist/panel-header.js.map +1 -0
  62. package/dist/segmented-control.d.ts +3 -1
  63. package/dist/segmented-control.js +2 -1
  64. package/dist/select.d.ts +5 -2
  65. package/dist/select.js +2 -1
  66. package/dist/skeleton.d.ts +24 -0
  67. package/dist/skeleton.js +3 -0
  68. package/dist/skeleton.js.map +1 -0
  69. package/dist/split-view.d.ts +42 -0
  70. package/dist/split-view.js +21 -0
  71. package/dist/split-view.js.map +1 -0
  72. package/dist/state-banner.d.ts +3 -1
  73. package/dist/state-banner.js +2 -1
  74. package/dist/styles/foundation.css +21 -0
  75. package/dist/styles/menu-header.css +19 -2
  76. package/dist/styles/nav-stack.css +112 -0
  77. package/dist/styles/panel-header.css +64 -0
  78. package/dist/styles/select.css +42 -0
  79. package/dist/styles/skeleton.css +31 -0
  80. package/dist/styles/split-view.css +35 -0
  81. package/dist/styles/styles.css +2 -2
  82. package/dist/styles/tab-scaffold.css +84 -0
  83. package/dist/styles/toolbar-control-group.css +52 -2
  84. package/dist/styles/toolbar-text.css +11 -0
  85. package/dist/styles/toolbar.css +9 -0
  86. package/dist/styles/workbench.css +94 -0
  87. package/dist/tab-scaffold.d.ts +30 -0
  88. package/dist/tab-scaffold.js +16 -0
  89. package/dist/tab-scaffold.js.map +1 -0
  90. package/dist/toolbar-text.d.ts +6 -2
  91. package/dist/toolbar-text.js +2 -1
  92. package/dist/value-table.d.ts +3 -1
  93. package/dist/value-table.js +2 -1
  94. package/dist/wire-nav-stack.d.ts +15 -0
  95. package/dist/wire-nav-stack.js +88 -0
  96. package/dist/wire-nav-stack.js.map +1 -0
  97. package/dist/wire-tab-scaffold.d.ts +11 -0
  98. package/dist/wire-tab-scaffold.js +16 -0
  99. package/dist/wire-tab-scaffold.js.map +1 -0
  100. package/dist/wire-token-search-fields.d.ts +46 -4
  101. package/dist/wire-token-search-fields.js +1 -1
  102. package/dist/workbench.d.ts +33 -0
  103. package/dist/workbench.js +17 -0
  104. package/dist/workbench.js.map +1 -0
  105. package/docs/accessibility.md +10 -10
  106. package/docs/app-layouts.md +57 -0
  107. package/docs/component-contract.md +46 -9
  108. package/docs/component-selection.md +57 -7
  109. package/docs/design-philosophy.md +23 -1
  110. package/docs/device-class.md +54 -0
  111. package/docs/layout.md +33 -8
  112. package/docs/nav-stack.md +47 -0
  113. package/docs/recipes.md +17 -5
  114. package/docs/split-view.md +49 -0
  115. package/docs/tab-scaffold.md +41 -0
  116. package/docs/ux-demo.md +3 -3
  117. package/docs/workbench.md +47 -0
  118. package/llms.txt +116 -43
  119. package/package.json +43 -11
  120. package/ux-demo/recipes/app-shell.tsx +3 -3
  121. package/ux-demo/recipes/composer-form.tsx +2 -2
  122. package/ux-demo/recipes/list-workspace-states.tsx +2 -2
  123. package/ux-demo/recipes/loaders.ts +2 -0
  124. package/ux-demo/recipes/master-detail-dialog.tsx +2 -2
  125. package/ux-demo/recipes/mount-recipe.ts +3 -0
  126. package/ux-demo/recipes/navigation-stack.tsx +76 -0
  127. package/ux-demo/recipes/recipes.css +19 -8
  128. package/ux-demo/recipes/workspace-header.tsx +2 -2
  129. package/dist/browser/dialog-header.js +0 -5
  130. package/dist/browser/page-header.js +0 -3
  131. package/dist/chunk-2PES33HS.js +0 -13
  132. package/dist/chunk-2PES33HS.js.map +0 -1
  133. package/dist/chunk-CTQKI2XL.js.map +0 -1
  134. package/dist/chunk-EGHL5S4P.js.map +0 -1
  135. package/dist/chunk-GY5WH7TO.js +0 -10
  136. package/dist/chunk-GY5WH7TO.js.map +0 -1
  137. package/dist/chunk-H5AGGVU5.js +0 -75
  138. package/dist/chunk-H5AGGVU5.js.map +0 -1
  139. package/dist/chunk-K3G72I6D.js +0 -24
  140. package/dist/chunk-K3G72I6D.js.map +0 -1
  141. package/dist/chunk-NPPD2A6O.js.map +0 -1
  142. package/dist/chunk-R6JHHBKQ.js.map +0 -1
  143. package/dist/chunk-S3TPM2LP.js +0 -23
  144. package/dist/chunk-S3TPM2LP.js.map +0 -1
  145. package/dist/chunk-TRNBHPNW.js.map +0 -1
  146. package/dist/chunk-VHW5YNIB.js +0 -17
  147. package/dist/chunk-VHW5YNIB.js.map +0 -1
  148. package/dist/chunk-XZXRLY5Q.js.map +0 -1
  149. package/dist/dialog-header.d.ts +0 -15
  150. package/dist/dialog-header.js +0 -5
  151. package/dist/dialog-header.js.map +0 -1
  152. package/dist/page-header.d.ts +0 -9
  153. package/dist/page-header.js +0 -3
  154. package/dist/page-header.js.map +0 -1
  155. package/dist/styles/dialog-header.css +0 -87
  156. package/dist/styles/page-header.css +0 -33
@@ -1,3 +1,4 @@
1
- export { SegmentedControl } from './chunk-TRNBHPNW.js';
1
+ export { SegmentedControl } from './chunk-YGN7OMLT.js';
2
+
2
3
 
3
4
  //# sourceMappingURL=segmented-control.js.map
package/dist/select.d.ts CHANGED
@@ -18,11 +18,14 @@ interface SelectProps<Value extends string = string> {
18
18
  ariaLabel?: string;
19
19
  choices: readonly SelectChoice<Value>[];
20
20
  className?: string;
21
- placeholder?: string;
21
+ /** Empty-value hint text shown in the closed control (the native select placeholder). */
22
+ placeholderText?: string;
22
23
  disabled?: boolean;
23
24
  fitMenu?: boolean;
24
25
  renderSelected?: (choice: SelectChoice<Value>) => SafeHtml;
26
+ /** Render as an unanimated loading skeleton: the label above a static, empty control box. */
27
+ placeholder?: boolean;
25
28
  }
26
- declare function Select<Value extends string>({ name, value, label, ariaLabel, choices, className, placeholder, disabled, fitMenu, renderSelected }: SelectProps<Value>): SafeHtml;
29
+ declare function Select<Value extends string>({ name, value, label, ariaLabel, choices, className, placeholderText, disabled, fitMenu, renderSelected, placeholder }: SelectProps<Value>): SafeHtml;
27
30
 
28
31
  export { Select, type SelectChoice, type SelectProps };
package/dist/select.js CHANGED
@@ -1,4 +1,5 @@
1
- export { Select } from './chunk-CTQKI2XL.js';
1
+ export { Select } from './chunk-LC3YSAZC.js';
2
+
2
3
 
3
4
 
4
5
  //# sourceMappingURL=select.js.map
@@ -0,0 +1,24 @@
1
+ import * as kerfjs from 'kerfjs';
2
+
3
+ interface SkeletonProps {
4
+ /** Width as any CSS length (e.g. `remify(120px)`, `60%`). Defaults to filling its slot. */
5
+ width?: string;
6
+ /** Height as any CSS length. Defaults to a single text line. */
7
+ height?: string;
8
+ /** Corner radius override (a CSS length). Defaults to the small radius token. */
9
+ radius?: string;
10
+ /** Render this many stacked lines (the last one shorter), for multi-line text. */
11
+ lines?: number;
12
+ /** Accessible label. Omit to keep the block decorative (`aria-hidden`). */
13
+ label?: string;
14
+ className?: string;
15
+ }
16
+ /**
17
+ * A subtle, deliberately **unanimated** loading placeholder block. Use it for a
18
+ * value slot whose content is not yet known, on its own or via a component's
19
+ * `placeholder` prop. Decorative by default (`aria-hidden`); pass `label` to
20
+ * announce it. Sizes to its slot unless `width`/`height` are given.
21
+ */
22
+ declare function Skeleton({ width, height, radius, lines, label, className }: SkeletonProps): kerfjs.SafeHtml;
23
+
24
+ export { Skeleton, type SkeletonProps };
@@ -0,0 +1,3 @@
1
+ export { Skeleton } from './chunk-B24C4A6T.js';
2
+
3
+ //# sourceMappingURL=skeleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"skeleton.js"}
@@ -0,0 +1,42 @@
1
+ import { SafeHtml } from 'kerfjs';
2
+
3
+ interface SplitViewResizable {
4
+ size: number;
5
+ min: number;
6
+ max: number;
7
+ }
8
+ interface SplitViewProps {
9
+ id: string;
10
+ label: string;
11
+ /** The list (primary) pane. */
12
+ list: SafeHtml;
13
+ /** The detail (secondary) pane. */
14
+ detail: SafeHtml;
15
+ /**
16
+ * Compact ("one pane at a time") classes — a handset or portrait tablet.
17
+ * Derive from `deviceClass().value.compact`. When true the split collapses to
18
+ * a `NavStack`: the list is the root and the detail is pushed over it.
19
+ */
20
+ compact?: boolean;
21
+ /** In compact mode, whether the detail is currently pushed over the list. */
22
+ detailActive?: boolean;
23
+ /** Title/label for the list (compact NavStack root + region label). */
24
+ listTitle?: string;
25
+ /** Title/label for the detail (compact NavStack pushed view + region label). */
26
+ detailTitle?: string;
27
+ /** Back label for the compact NavStack (default "Back"). */
28
+ backLabel?: string;
29
+ /** A resizable separator on roomy classes (min/max px). Omit for a fixed split. */
30
+ resizable?: SplitViewResizable;
31
+ className?: string;
32
+ }
33
+ /**
34
+ * A list-detail (master-detail) split. On roomy classes it shows both panes side
35
+ * by side with an optional resizable separator; on compact classes it collapses
36
+ * to a `NavStack` (list → detail). See `docs/23-app-layouts.md` §3.2. Compose the
37
+ * resizable wiring with `wireResizableRegions` and the compact back with
38
+ * `wireNavStack`.
39
+ */
40
+ declare function SplitView({ id, label, list, detail, compact, detailActive, listTitle, detailTitle, backLabel, resizable, className }: SplitViewProps): SafeHtml;
41
+
42
+ export { SplitView, type SplitViewProps, type SplitViewResizable };
@@ -0,0 +1,21 @@
1
+ import { NavStack } from './chunk-N3SUXLLS.js';
2
+
3
+ import { ResizableRegion } from './chunk-M5JRI6MN.js';
4
+
5
+ import { jsx, jsxs } from 'kerfjs/jsx-runtime';
6
+
7
+ function SplitView({ id, label, list, detail, compact = false, detailActive = false, listTitle = "", detailTitle = "", backLabel = "Back", resizable, className = "" }) {
8
+ if (compact) {
9
+ const views = detailActive ? [{ key: "list", title: listTitle, content: list }, { key: "detail", title: detailTitle, content: detail }] : [{ key: "list", title: listTitle, content: list }];
10
+ return /* @__PURE__ */ jsx("div", { class: `kui-split-view kui-split-view--compact ${className}`.trim(), id, "data-component": "split-view", "data-split-mode": "compact", children: /* @__PURE__ */ jsx(NavStack, { id: `${id}-stack`, label, views, backLabel }) });
11
+ }
12
+ const listPane = resizable ? /* @__PURE__ */ jsx(ResizableRegion, { id: `${id}-list`, label: listTitle || "List", size: resizable.size, min: resizable.min, max: resizable.max, children: /* @__PURE__ */ jsx("div", { class: "kui-split-view__list kui-pane", "data-split-list": true, children: list }) }) : /* @__PURE__ */ jsx("div", { class: "kui-split-view__list kui-pane", "data-split-list": true, children: list });
13
+ return /* @__PURE__ */ jsxs("div", { class: `kui-split-view ${className}`.trim(), id, "data-component": "split-view", "data-split-mode": "split", "aria-label": label, children: [
14
+ listPane,
15
+ /* @__PURE__ */ jsx("div", { class: "kui-split-view__detail kui-pane", "data-split-detail": true, "aria-label": detailTitle || void 0, children: detail })
16
+ ] });
17
+ }
18
+
19
+ export { SplitView };
20
+
21
+ //# sourceMappingURL=split-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/split-view.tsx"],"names":[],"mappings":";;;;;;AA4CO,SAAS,UAAU,EAAE,EAAA,EAAI,OAAO,IAAA,EAAM,MAAA,EAAQ,UAAU,KAAA,EAAO,YAAA,GAAe,OAAO,SAAA,GAAY,EAAA,EAAI,cAAc,EAAA,EAAI,SAAA,GAAY,QAAQ,SAAA,EAAW,SAAA,GAAY,IAAG,EAAmB;AAC7L,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,KAAA,GAAQ,YAAA,GACV,CAAC,EAAE,GAAA,EAAK,MAAA,EAAQ,KAAA,EAAO,SAAA,EAAW,OAAA,EAAS,IAAA,EAAK,EAAG,EAAE,GAAA,EAAK,QAAA,EAAU,KAAA,EAAO,WAAA,EAAa,OAAA,EAAS,MAAA,EAAQ,CAAA,GACzG,CAAC,EAAE,GAAA,EAAK,MAAA,EAAQ,KAAA,EAAO,SAAA,EAAW,OAAA,EAAS,IAAA,EAAM,CAAA;AACrD,IAAA,uBAAO,GAAA,CAAC,SAAI,KAAA,EAAO,CAAA,uCAAA,EAA0C,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK,EAAG,EAAA,EAAQ,gBAAA,EAAe,YAAA,EAAa,mBAAgB,SAAA,EACnI,QAAA,kBAAA,GAAA,CAAC,YAAS,EAAA,EAAI,CAAA,EAAG,EAAE,CAAA,MAAA,CAAA,EAAU,KAAA,EAAc,KAAA,EAAc,SAAA,EAAsB,CAAA,EACjF,CAAA;AAAA,EACF;AACA,EAAA,MAAM,QAAA,GAAW,SAAA,mBACb,GAAA,CAAC,eAAA,EAAA,EAAgB,IAAI,CAAA,EAAG,EAAE,CAAA,KAAA,CAAA,EAAS,KAAA,EAAO,aAAa,MAAA,EAAQ,IAAA,EAAM,SAAA,CAAU,IAAA,EAAM,KAAK,SAAA,CAAU,GAAA,EAAK,GAAA,EAAK,SAAA,CAAU,KACtH,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,+BAAA,EAAgC,mBAAe,IAAA,EAAE,QAAA,EAAA,IAAA,EAAK,CAAA,EACnE,CAAA,uBACC,KAAA,EAAA,EAAI,KAAA,EAAM,+BAAA,EAAgC,iBAAA,EAAe,MAAE,QAAA,EAAA,IAAA,EAAK,CAAA;AACrE,EAAA,uBAAO,IAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,CAAA,eAAA,EAAkB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK,EAAG,EAAA,EAAQ,gBAAA,EAAe,YAAA,EAAa,iBAAA,EAAgB,OAAA,EAAQ,cAAY,KAAA,EAC9H,QAAA,EAAA;AAAA,IAAA,QAAA;AAAA,oBACD,GAAA,CAAC,SAAI,KAAA,EAAM,iCAAA,EAAkC,qBAAiB,IAAA,EAAC,YAAA,EAAY,WAAA,IAAe,MAAA,EAAY,QAAA,EAAA,MAAA,EAAO;AAAA,GAAA,EAC/G,CAAA;AACF","file":"split-view.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport { NavStack } from './nav-stack.js';\nimport { ResizableRegion } from './resizable-region.js';\n\nexport interface SplitViewResizable {\n size: number;\n min: number;\n max: number;\n}\n\nexport interface SplitViewProps {\n id: string;\n label: string;\n /** The list (primary) pane. */\n list: SafeHtml;\n /** The detail (secondary) pane. */\n detail: SafeHtml;\n /**\n * Compact (\"one pane at a time\") classes — a handset or portrait tablet.\n * Derive from `deviceClass().value.compact`. When true the split collapses to\n * a `NavStack`: the list is the root and the detail is pushed over it.\n */\n compact?: boolean;\n /** In compact mode, whether the detail is currently pushed over the list. */\n detailActive?: boolean;\n /** Title/label for the list (compact NavStack root + region label). */\n listTitle?: string;\n /** Title/label for the detail (compact NavStack pushed view + region label). */\n detailTitle?: string;\n /** Back label for the compact NavStack (default \"Back\"). */\n backLabel?: string;\n /** A resizable separator on roomy classes (min/max px). Omit for a fixed split. */\n resizable?: SplitViewResizable;\n className?: string;\n}\n\n/**\n * A list-detail (master-detail) split. On roomy classes it shows both panes side\n * by side with an optional resizable separator; on compact classes it collapses\n * to a `NavStack` (list → detail). See `docs/23-app-layouts.md` §3.2. Compose the\n * resizable wiring with `wireResizableRegions` and the compact back with\n * `wireNavStack`.\n */\nexport function SplitView({ id, label, list, detail, compact = false, detailActive = false, listTitle = '', detailTitle = '', backLabel = 'Back', resizable, className = '' }: SplitViewProps) {\n if (compact) {\n const views = detailActive\n ? [{ key: 'list', title: listTitle, content: list }, { key: 'detail', title: detailTitle, content: detail }]\n : [{ key: 'list', title: listTitle, content: list }];\n return <div class={`kui-split-view kui-split-view--compact ${className}`.trim()} id={id} data-component=\"split-view\" data-split-mode=\"compact\">\n <NavStack id={`${id}-stack`} label={label} views={views} backLabel={backLabel} />\n </div>;\n }\n const listPane = resizable\n ? <ResizableRegion id={`${id}-list`} label={listTitle || 'List'} size={resizable.size} min={resizable.min} max={resizable.max}>\n <div class=\"kui-split-view__list kui-pane\" data-split-list>{list}</div>\n </ResizableRegion>\n : <div class=\"kui-split-view__list kui-pane\" data-split-list>{list}</div>;\n return <div class={`kui-split-view ${className}`.trim()} id={id} data-component=\"split-view\" data-split-mode=\"split\" aria-label={label}>\n {listPane}\n <div class=\"kui-split-view__detail kui-pane\" data-split-detail aria-label={detailTitle || undefined}>{detail}</div>\n </div>;\n}\n"]}
@@ -9,7 +9,9 @@ interface StateBannerProps {
9
9
  tone?: StateBannerTone;
10
10
  urgency?: 'status' | 'alert';
11
11
  className?: string;
12
+ /** Render the title and detail as unanimated loading skeletons, keeping the icon and tone. */
13
+ placeholder?: boolean;
12
14
  }
13
- declare function StateBanner({ title, detail, icon, action, tone, urgency, className }: StateBannerProps): SafeHtml;
15
+ declare function StateBanner({ title, detail, icon, action, tone, urgency, className, placeholder }: StateBannerProps): SafeHtml;
14
16
 
15
17
  export { StateBanner, type StateBannerProps, type StateBannerTone };
@@ -1,3 +1,4 @@
1
- export { StateBanner } from './chunk-VHW5YNIB.js';
1
+ export { StateBanner } from './chunk-I7INRJO4.js';
2
+
2
3
 
3
4
  //# sourceMappingURL=state-banner.js.map
@@ -24,12 +24,33 @@
24
24
  --kui-font-l: var(--wa-font-size-l, 20px);
25
25
  --kui-font-2xl: var(--wa-font-size-2xl, 32px);
26
26
  --kui-font-weight-semibold: var(--wa-font-weight-semibold, 650);
27
+ /* Official spacing scale (see docs/layout.md "Spacing scale"). Five canonical
28
+ steps, each expressing one relationship — pick by how connected the elements
29
+ are, not by eye:
30
+ 0px --kui-space-none no separation (elements read as one unit)
31
+ 4px --kui-space-2xs very minor — connected, but readability/aesthetics
32
+ demand a hair of air
33
+ 8px --kui-space-xs standard — between elements within a group
34
+ 16px --kui-space-m minor — between homogeneous groups
35
+ 24px --kui-space-l major — between heterogeneous groups
36
+ --kui-space-s (12px) and --kui-space-xl (32px) are off the canonical rhythm;
37
+ reach for them only as deliberate exceptions, not as default steps. All
38
+ scalable values are remify-authored against the fixed 16px baseline. */
39
+ --kui-space-none: 0;
27
40
  --kui-space-2xs: var(--wa-space-2xs, 0.25rem);
28
41
  --kui-space-xs: var(--wa-space-xs, 0.5rem);
29
42
  --kui-space-s: var(--wa-space-s, 0.75rem);
30
43
  --kui-space-m: var(--wa-space-m, 1rem);
31
44
  --kui-space-l: var(--wa-space-l, 1.5rem);
32
45
  --kui-space-xl: var(--wa-space-xl, 2rem);
46
+ /* Device-class breakpoints, mirrored from @kerfjs/ui/device-class's
47
+ DEFAULT_BREAKPOINTS so CSS media queries and the reactive deviceClass()
48
+ signal read the same minimum widths. Authored as literal px (a media-query
49
+ boundary is a physical width, not a scalable length). */
50
+ --kui-bp-mobile: 360px;
51
+ --kui-bp-tablet: 720px;
52
+ --kui-bp-desktop: 1024px;
53
+ --kui-bp-xl-desktop: 1440px;
33
54
  --kui-layout-inline-margin: var(--kui-space-xs);
34
55
  --kui-layout-item-border-width: 1px;
35
56
  --kui-layout-item-padding: var(--kui-space-xs);
@@ -93,10 +93,23 @@
93
93
  }
94
94
 
95
95
  .kui-menu-header__action {
96
+ /* A fitted square: the icon size plus its padding and 1px border, instead of a
97
+ forced 44px box that left an oversized halo around the ~18px icon and, via the
98
+ parent's align-items: stretch, a vertically stretched oval. `align-self:
99
+ center` keeps it centered in the header's 44px row without stretching. Sizing
100
+ off the icon variable keeps it square and correct at any icon size. */
101
+ --_kui-menu-header-action-size: calc(
102
+ var(--_kui-menu-header-action-icon-size) +
103
+ (2 * var(--kui-layout-item-padding, 0.5rem)) + 2px
104
+ );
96
105
  appearance: none;
97
- width: 2.75rem;
98
- height: 2.75rem;
106
+ display: inline-flex;
107
+ width: var(--_kui-menu-header-action-size);
108
+ height: var(--_kui-menu-header-action-size);
99
109
  padding: var(--kui-layout-item-padding, 0.5rem);
110
+ align-items: center;
111
+ justify-content: center;
112
+ align-self: center;
100
113
  cursor: pointer;
101
114
  flex: none;
102
115
  }
@@ -138,6 +151,10 @@
138
151
 
139
152
  .kui-menu-header__action > svg,
140
153
  .kui-menu-header__action-layer > svg {
154
+ /* min-block-size: 0 overrides the flex item's automatic minimum, which would
155
+ otherwise resolve to the icon SVG's intrinsic 24px viewBox height and stretch
156
+ the button past its used icon size. */
141
157
  width: var(--_kui-menu-header-action-icon-size);
142
158
  height: var(--_kui-menu-header-action-icon-size);
159
+ min-block-size: 0;
143
160
  }
@@ -0,0 +1,112 @@
1
+ .kui-nav-stack {
2
+ display: flex;
3
+ min-width: 0;
4
+ min-height: 0;
5
+ height: 100%;
6
+ overflow: hidden;
7
+ flex-direction: column;
8
+ background: var(--kui-color-surface);
9
+ }
10
+
11
+ .kui-nav-stack__chrome {
12
+ display: flex;
13
+ min-width: 0;
14
+ min-height: calc(2px + 2.625rem);
15
+ padding-inline: var(--kui-space-xs);
16
+ border-block-end: 1px solid var(--kui-color-border);
17
+ align-items: center;
18
+ gap: var(--kui-space-xs);
19
+ background: var(--kui-color-surface);
20
+ }
21
+
22
+ .kui-nav-stack__lead {
23
+ display: flex;
24
+ flex: 1;
25
+ min-width: 0;
26
+ align-items: center;
27
+ gap: var(--kui-space-2xs);
28
+ }
29
+
30
+ .kui-nav-stack__back {
31
+ display: grid;
32
+ width: 2.5rem;
33
+ height: 2.5rem;
34
+ padding: 0;
35
+ border: 1px solid transparent;
36
+ border-radius: var(--kui-layout-rounded-radius, calc(1px + 0.6875rem));
37
+ color: inherit;
38
+ background: transparent;
39
+ cursor: pointer;
40
+ place-items: center;
41
+ }
42
+
43
+ .kui-nav-stack__back:hover {
44
+ border-color: var(--kui-color-border);
45
+ background: var(--kui-color-neutral-fill-normal);
46
+ }
47
+
48
+ .kui-nav-stack__back:focus-visible {
49
+ outline: var(--kui-focus-ring);
50
+ outline-offset: 1px;
51
+ }
52
+
53
+ .kui-nav-stack__back-icon {
54
+ display: block;
55
+ }
56
+
57
+ /* The title is ToolbarText (which owns its font, height, and ellipsis); it just
58
+ needs to flex to fill the lead zone beside the back control. */
59
+ .kui-nav-stack__title {
60
+ flex: 1 1 auto;
61
+ min-width: 0;
62
+ }
63
+
64
+ .kui-nav-stack__actions {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--kui-space-2xs);
68
+ }
69
+
70
+ .kui-nav-stack__viewport {
71
+ position: relative;
72
+ flex: 1;
73
+ min-height: 0;
74
+ overflow: hidden;
75
+ }
76
+
77
+ .kui-nav-stack__view {
78
+ position: absolute;
79
+ overflow: auto;
80
+ background: var(--kui-color-surface);
81
+ transition: transform 200ms ease;
82
+ inset: 0;
83
+ }
84
+
85
+ /* Only the active view (and any view mid-exit) is shown; lower entries stay
86
+ mounted for state/focus but hidden behind it. */
87
+ .kui-nav-stack__view[data-nav-active="false"] {
88
+ visibility: hidden;
89
+ }
90
+
91
+ .kui-nav-stack__view[data-nav-exiting="true"] {
92
+ z-index: 1;
93
+ visibility: visible;
94
+ }
95
+
96
+ /* Push: the incoming view starts off the trailing edge and slides to rest.
97
+ Pop: the outgoing view starts at rest and slides off the trailing edge. */
98
+ .kui-nav-stack__view--entering,
99
+ .kui-nav-stack__view--exiting {
100
+ transform: translateX(100%);
101
+ }
102
+
103
+ .kui-nav-stack__bottom {
104
+ border-block-start: 1px solid var(--kui-color-border);
105
+ background: var(--kui-color-surface);
106
+ }
107
+
108
+ @media (prefers-reduced-motion: reduce) {
109
+ .kui-nav-stack__view {
110
+ transition: none;
111
+ }
112
+ }
@@ -0,0 +1,64 @@
1
+ .kui-panel-header {
2
+ display: grid;
3
+ width: 100%;
4
+ gap: 0;
5
+ }
6
+
7
+ /* The only chrome PanelHeader adds to the plain Toolbar: the icon group is a
8
+ normal (bordered) control group given a brand fill, with its border color set
9
+ to match the fill so it reads as a solid filled tile rather than a bordered
10
+ pill. Both are the group's own theming variables — no Toolbar style is
11
+ overridden. */
12
+ .kui-panel-header__icon.kui-toolbar-control-group {
13
+ --kui-toolbar-control-background: var(
14
+ --kui-panel-header-icon-background,
15
+ var(--kui-color-brand-fill-quiet)
16
+ );
17
+ --kui-toolbar-control-border: var(
18
+ --kui-panel-header-icon-border,
19
+ var(--kui-color-brand-fill-quiet)
20
+ );
21
+ color: var(
22
+ --kui-panel-header-icon-foreground,
23
+ var(--kui-color-brand-on-quiet)
24
+ );
25
+ /* The icon is a raw svg child (not a button), so the group's default
26
+ flex-start alignment would pin it to the left of the filled tile. Center it
27
+ on both axes inside the circle. */
28
+ justify-content: center;
29
+ }
30
+
31
+ .kui-panel-header__icon svg {
32
+ display: block;
33
+ width: 1.375rem;
34
+ height: 1.375rem;
35
+ }
36
+
37
+ /* The subtitle sits on its own row, its text left-aligned with the title text
38
+ (past the toolbar padding, the optional icon group + leading gap, and the
39
+ title's own inline padding). */
40
+ .kui-panel-header__summary {
41
+ min-width: 0;
42
+ margin: 0;
43
+ overflow: hidden;
44
+ padding-inline-start: var(--kui-panel-header-title-inset);
45
+ color: var(--kui-color-text-quiet);
46
+ text-overflow: ellipsis;
47
+ white-space: nowrap;
48
+ }
49
+
50
+ .kui-panel-header[data-has-icon="false"] {
51
+ --kui-panel-header-title-inset: calc(
52
+ var(--kui-toolbar-gap, 0.5rem) +
53
+ var(--kui-layout-item-padding, 0.5rem)
54
+ );
55
+ }
56
+
57
+ .kui-panel-header[data-has-icon="true"] {
58
+ --kui-panel-header-title-inset: calc(
59
+ var(--kui-toolbar-gap, 0.5rem) +
60
+ var(--kui-toolbar-group-size, calc(2px + 2.625rem)) +
61
+ var(--kui-toolbar-gap, 0.5rem) +
62
+ var(--kui-layout-item-padding, 0.5rem)
63
+ );
64
+ }
@@ -94,3 +94,45 @@
94
94
  letter-spacing: 0.04em;
95
95
  text-transform: uppercase;
96
96
  }
97
+
98
+ /* Loading placeholder: a static box that mirrors the closed combobox (label
99
+ above, a value skeleton and a dormant chevron inside a 44px control box).
100
+ Rendered instead of the interactive wa-select, so it needs no registration. */
101
+ .kui-select--placeholder {
102
+ display: block;
103
+ width: 100%;
104
+ }
105
+ .kui-select__placeholder-label {
106
+ display: block;
107
+ margin-block-end: 0.25rem;
108
+ color: var(--kui-color-text);
109
+ font-size: var(--kui-font-s);
110
+ line-height: 1.4;
111
+ }
112
+ .kui-select__placeholder-box {
113
+ display: flex;
114
+ min-height: 2.75rem;
115
+ align-items: center;
116
+ justify-content: space-between;
117
+ gap: 0.5rem;
118
+ padding-inline: var(--kui-layout-item-padding, 0.5rem);
119
+ border: 1px solid var(--kui-color-border);
120
+ border-radius: var(--kui-layout-rounded-radius, calc(1px + 0.6875rem));
121
+ background: var(--kui-color-surface);
122
+ }
123
+ .kui-select__placeholder-box .kui-skeleton {
124
+ max-width: 10rem;
125
+ }
126
+ .kui-select__placeholder-chevron {
127
+ display: inline-flex;
128
+ width: 1rem;
129
+ height: 1rem;
130
+ flex: none;
131
+ color: var(--kui-color-text-quiet);
132
+ transform: scale(var(--kui-disclosure-icon-scale, 0.85));
133
+ }
134
+ .kui-select__placeholder-chevron svg {
135
+ display: block;
136
+ width: 100%;
137
+ height: 100%;
138
+ }
@@ -0,0 +1,31 @@
1
+ .kui-skeleton {
2
+ --kui-skeleton-radius: var(--kui-radius-s);
3
+
4
+ display: inline-block;
5
+ width: 100%;
6
+ max-width: 100%;
7
+ height: 1em;
8
+ min-width: 1.5rem;
9
+ border-radius: var(--kui-skeleton-radius);
10
+ /* Deliberately unanimated: a flat, subtle fill that reads as "not yet loaded"
11
+ without drawing the eye or implying progress. */
12
+ background: color-mix(in srgb, var(--kui-color-text) 12%, transparent);
13
+ vertical-align: middle;
14
+ }
15
+
16
+ .kui-skeleton-lines {
17
+ display: flex;
18
+ width: 100%;
19
+ flex-direction: column;
20
+ gap: var(--kui-space-2xs);
21
+ }
22
+
23
+ .kui-skeleton-lines .kui-skeleton {
24
+ height: 0.8em;
25
+ }
26
+
27
+ @media (forced-colors: active) {
28
+ .kui-skeleton {
29
+ background: GrayText;
30
+ }
31
+ }
@@ -0,0 +1,35 @@
1
+ .kui-split-view {
2
+ display: flex;
3
+ min-width: 0;
4
+ min-height: 0;
5
+ height: 100%;
6
+ overflow: hidden;
7
+ background: var(--kui-color-surface);
8
+ }
9
+
10
+ /* The list pane; when not resizable it takes an intrinsic column and the detail
11
+ grows to fill. A resizable list is sized by ResizableRegion instead. */
12
+ .kui-split-view__list {
13
+ min-width: 0;
14
+ min-height: 0;
15
+ flex: 0 0 auto;
16
+ width: var(--kui-split-view-list-width, 20rem);
17
+ border-inline-end: 1px solid var(--kui-color-border);
18
+ }
19
+
20
+ .kui-split-view > .kui-resizable-region .kui-split-view__list {
21
+ width: 100%;
22
+ border-inline-end: 0;
23
+ }
24
+
25
+ .kui-split-view__detail {
26
+ flex: 1;
27
+ min-width: 0;
28
+ min-height: 0;
29
+ }
30
+
31
+ /* Compact mode is a full-bleed NavStack. */
32
+ .kui-split-view--compact {
33
+ display: block;
34
+ height: 100%;
35
+ }
@@ -11,12 +11,12 @@
11
11
  @import "./resizable-region.css";
12
12
  @import "./app-tab.css";
13
13
  @import "./tab-bar.css";
14
- @import "./page-header.css";
15
14
  @import "./loading-spinner.css";
15
+ @import "./skeleton.css";
16
16
  @import "./select.css";
17
17
  @import "./segmented-control.css";
18
18
  @import "./token-search-field.css";
19
19
  @import "./state-banner.css";
20
20
  @import "./empty-state.css";
21
- @import "./dialog-header.css";
21
+ @import "./panel-header.css";
22
22
  @import "./value-table.css";
@@ -0,0 +1,84 @@
1
+ .kui-tab-scaffold {
2
+ display: flex;
3
+ min-width: 0;
4
+ min-height: 0;
5
+ height: 100%;
6
+ overflow: hidden;
7
+ flex-direction: column;
8
+ background: var(--kui-color-surface);
9
+ }
10
+
11
+ .kui-tab-scaffold__scenes {
12
+ position: relative;
13
+ flex: 1;
14
+ min-height: 0;
15
+ overflow: hidden;
16
+ }
17
+
18
+ /* Every tab stays mounted (its NavStack keeps its stack and scroll); only the
19
+ active scene is shown. */
20
+ .kui-tab-scaffold__scene {
21
+ position: absolute;
22
+ overflow: auto;
23
+ inset: 0;
24
+ }
25
+
26
+ .kui-tab-scaffold__scene[data-active="false"] {
27
+ visibility: hidden;
28
+ }
29
+
30
+ .kui-tab-scaffold__bar {
31
+ display: flex;
32
+ flex: 0 0 auto;
33
+ padding-block-start: var(--kui-space-2xs);
34
+ /* Respect the home-indicator safe area on iOS-class devices. */
35
+ padding-block-end: calc(
36
+ var(--kui-space-2xs) + env(safe-area-inset-bottom, 0px)
37
+ );
38
+ border-block-start: 1px solid var(--kui-color-border);
39
+ background: var(--kui-color-surface);
40
+ }
41
+
42
+ .kui-tab-scaffold__tab {
43
+ display: flex;
44
+ flex: 1 1 0;
45
+ min-width: 0;
46
+ min-height: 2.75rem;
47
+ padding: var(--kui-space-2xs) var(--kui-space-xs);
48
+ border: 0;
49
+ color: var(--kui-color-text-quiet);
50
+ background: transparent;
51
+ cursor: pointer;
52
+ font: inherit;
53
+ flex-direction: column;
54
+ align-items: center;
55
+ justify-content: center;
56
+ gap: 0.125rem;
57
+ }
58
+
59
+ .kui-tab-scaffold__tab[aria-selected="true"] {
60
+ color: var(--kui-color-accent-text);
61
+ }
62
+
63
+ .kui-tab-scaffold__tab:focus-visible {
64
+ outline: var(--kui-focus-ring);
65
+ outline-offset: -2px;
66
+ }
67
+
68
+ .kui-tab-scaffold__tab-icon {
69
+ display: grid;
70
+ place-items: center;
71
+ }
72
+
73
+ .kui-tab-scaffold__tab-icon svg {
74
+ width: 1.375rem;
75
+ height: 1.375rem;
76
+ }
77
+
78
+ .kui-tab-scaffold__tab-label {
79
+ max-width: 100%;
80
+ overflow: hidden;
81
+ font-size: var(--kui-font-2xs);
82
+ text-overflow: ellipsis;
83
+ white-space: nowrap;
84
+ }