@kerfjs/ui 4.4.1 → 4.5.0-beta.1
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 +425 -42
- package/ai/application-ui-diagnostic-ids-v1.json +92 -0
- package/ai/application-ui-profile-sync.cjs +529 -0
- package/ai/application-ui-profile.d.mts +1 -0
- package/ai/application-ui-profile.d.ts +102 -0
- package/ai/application-ui-profile.defaults.json +41 -0
- package/ai/application-ui-profile.mjs +626 -0
- package/ai/application-ui-profile.schema.json +130 -0
- package/ai/catalog-authoring.json +25 -0
- package/ai/catalog-authoring.schema.json +61 -0
- package/ai/compile-time-contracts-v1.json +102 -0
- package/ai/compile-time-contracts-v1.schema.json +49 -0
- package/ai/component-catalog-extension-v2.d.ts +7 -0
- package/ai/component-catalog-extension-v2.schema.json +7 -0
- package/ai/component-catalog-extension.schema.json +79 -0
- package/ai/component-catalog-v2-overrides.json +319 -0
- package/ai/component-catalog-v2.d.ts +67 -0
- package/ai/component-catalog-v2.json +4675 -0
- package/ai/component-catalog-v2.schema.json +246 -0
- package/ai/component-catalog.json +5826 -0
- package/ai/component-catalog.schema.json +95 -0
- package/ai/public-api-signatures-v1.md +1719 -0
- package/ai/skill.md +129 -21
- package/ai/webawesome-jsx-signatures-v1.md +106 -0
- package/analyzer/cli.mjs +46 -0
- package/analyzer/index.d.mts +45 -0
- package/analyzer/index.mjs +737 -0
- package/analyzer/report.schema.json +66 -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/floating-toolbar.js +3 -0
- package/dist/browser/list-action-row.js +4 -0
- package/dist/browser/list-header.js +6 -0
- package/dist/browser/list-inset-control.js +3 -0
- package/dist/browser/list-inset-text.js +3 -0
- package/dist/browser/list-item.js +4 -0
- package/dist/browser/list.js +3 -0
- package/dist/browser/loading-spinner.js +2 -2
- package/dist/browser/lucide-icon.js +2 -2
- package/dist/browser/pane.js +3 -0
- 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/sunken-panel.js +3 -0
- 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-resources.d.ts +34 -0
- package/dist/catalog-resources.js +22 -0
- package/dist/catalog-resources.js.map +1 -0
- package/dist/catalog.d.ts +150 -0
- package/dist/catalog.js +377 -0
- package/dist/catalog.js.map +1 -0
- package/dist/chunk-5VTB2BKP.js +35 -0
- package/dist/chunk-5VTB2BKP.js.map +1 -0
- package/dist/chunk-6WTETUZ6.js +21 -0
- package/dist/chunk-6WTETUZ6.js.map +1 -0
- package/dist/chunk-7KEUJIIC.js +218 -0
- package/dist/chunk-7KEUJIIC.js.map +1 -0
- package/dist/chunk-7XRDUG4Y.js +58 -0
- package/dist/chunk-7XRDUG4Y.js.map +1 -0
- package/dist/chunk-B3A3IVO5.js +83 -0
- package/dist/chunk-B3A3IVO5.js.map +1 -0
- package/dist/chunk-DPDBVKNA.js +113 -0
- package/dist/chunk-DPDBVKNA.js.map +1 -0
- package/dist/chunk-FEAMR7S4.js +68 -0
- package/dist/chunk-FEAMR7S4.js.map +1 -0
- package/dist/chunk-FEY65TBV.js +15 -0
- package/dist/chunk-FEY65TBV.js.map +1 -0
- package/dist/{chunk-TRNBHPNW.js → chunk-IJBSZ4NX.js} +11 -8
- package/dist/chunk-IJBSZ4NX.js.map +1 -0
- package/dist/chunk-J5BFYY7Q.js +35 -0
- package/dist/chunk-J5BFYY7Q.js.map +1 -0
- package/dist/chunk-JRKSK2HX.js +79 -0
- package/dist/chunk-JRKSK2HX.js.map +1 -0
- package/dist/chunk-KWWR5VMS.js +45 -0
- package/dist/chunk-KWWR5VMS.js.map +1 -0
- package/dist/chunk-LPOMRC4C.js +25 -0
- package/dist/chunk-LPOMRC4C.js.map +1 -0
- package/dist/chunk-M7JBER5U.js +41 -0
- package/dist/chunk-M7JBER5U.js.map +1 -0
- package/dist/chunk-NENBUYX5.js +79 -0
- package/dist/chunk-NENBUYX5.js.map +1 -0
- package/dist/chunk-NWSEP5RT.js +117 -0
- package/dist/chunk-NWSEP5RT.js.map +1 -0
- package/dist/chunk-OJWWHFH3.js +426 -0
- package/dist/chunk-OJWWHFH3.js.map +1 -0
- package/dist/chunk-PSKN344N.js +114 -0
- package/dist/chunk-PSKN344N.js.map +1 -0
- package/dist/chunk-PX3YSS5C.js +32 -0
- package/dist/chunk-PX3YSS5C.js.map +1 -0
- package/dist/chunk-QO55FN2Y.js +20 -0
- package/dist/chunk-QO55FN2Y.js.map +1 -0
- package/dist/chunk-R7ZWUN64.js +38 -0
- package/dist/chunk-R7ZWUN64.js.map +1 -0
- package/dist/chunk-RKVQEH4J.js +61 -0
- package/dist/chunk-RKVQEH4J.js.map +1 -0
- package/dist/chunk-SRSJO5QE.js +40 -0
- package/dist/chunk-SRSJO5QE.js.map +1 -0
- package/dist/chunk-SYDMYBPG.js +31 -0
- package/dist/chunk-SYDMYBPG.js.map +1 -0
- package/dist/chunk-TB6DY7H5.js +25 -0
- package/dist/chunk-TB6DY7H5.js.map +1 -0
- package/dist/chunk-UFZKLBCF.js +108 -0
- package/dist/chunk-UFZKLBCF.js.map +1 -0
- package/dist/{chunk-MQIZAD4A.js → chunk-UKKSMZYT.js} +76 -22
- package/dist/chunk-UKKSMZYT.js.map +1 -0
- package/dist/chunk-W5L2JSBY.js +40 -0
- package/dist/chunk-W5L2JSBY.js.map +1 -0
- package/dist/chunk-Y45PPBDV.js +105 -0
- package/dist/chunk-Y45PPBDV.js.map +1 -0
- package/dist/chunk-Y6LYKFXY.js +43 -0
- package/dist/chunk-Y6LYKFXY.js.map +1 -0
- package/dist/collapsible-panel.d.ts +66 -0
- package/dist/collapsible-panel.js +63 -0
- package/dist/collapsible-panel.js.map +1 -0
- package/dist/device-class.d.ts +62 -0
- package/dist/device-class.js +103 -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/divider-sides-267FA7sY.d.ts +4 -0
- package/dist/empty-state.d.ts +1 -1
- package/dist/empty-state.js +1 -1
- package/dist/floating-toolbar.d.ts +28 -0
- package/dist/floating-toolbar.js +3 -0
- package/dist/floating-toolbar.js.map +1 -0
- package/dist/index.d.ts +20 -9
- package/dist/index.js +29 -18
- 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 +64 -0
- package/dist/list-header.js +7 -0
- package/dist/list-header.js.map +1 -0
- package/dist/list-inset-control.d.ts +16 -0
- package/dist/list-inset-control.js +3 -0
- package/dist/list-inset-control.js.map +1 -0
- package/dist/list-inset-text.d.ts +25 -0
- package/dist/list-inset-text.js +3 -0
- package/dist/list-inset-text.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/list.d.ts +19 -0
- package/dist/list.js +3 -0
- package/dist/list.js.map +1 -0
- package/dist/loading-spinner.js +1 -1
- 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/pane.d.ts +49 -0
- package/dist/pane.js +4 -0
- package/dist/pane.js.map +1 -0
- package/dist/panel-header.d.ts +42 -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 +8 -6
- 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 +15 -6
- 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 +81 -0
- package/dist/split-view.js.map +1 -0
- package/dist/state-banner.d.ts +8 -3
- package/dist/state-banner.js +2 -1
- package/dist/styles/app-tab.css +152 -0
- package/dist/styles/catalog.css +575 -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/floating-toolbar.css +38 -0
- package/dist/styles/foundation.css +362 -0
- package/dist/styles/layout.css +55 -0
- package/dist/styles/list-action-row.css +160 -0
- package/dist/styles/list-header.css +160 -0
- package/dist/styles/list-inset-control.css +7 -0
- package/dist/styles/list-inset-text.css +14 -0
- package/dist/styles/list-item.css +123 -0
- package/dist/styles/list.css +43 -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/pane.css +62 -0
- package/dist/styles/panel-header.css +64 -0
- package/dist/styles/resizable-region.css +111 -0
- package/{src → dist/styles}/segmented-control.css +58 -31
- package/dist/styles/select.css +140 -0
- package/dist/styles/skeleton.css +31 -0
- package/dist/styles/split-view.css +35 -0
- package/dist/styles/state-banner.css +198 -0
- package/dist/styles/styles.css +24 -0
- package/dist/styles/sunken-panel.css +22 -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 +225 -0
- package/dist/styles/toolbar-text.css +92 -0
- package/dist/styles/toolbar.css +97 -0
- package/dist/styles/value-table.css +75 -0
- package/dist/styles/webawesome.css +399 -0
- package/dist/styles/workbench.css +94 -0
- package/dist/sunken-panel.d.ts +19 -0
- package/dist/sunken-panel.js +3 -0
- package/dist/sunken-panel.js.map +1 -0
- package/dist/tab-bar.d.ts +10 -2
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +30 -0
- package/dist/tab-scaffold.js +51 -0
- package/dist/tab-scaffold.js.map +1 -0
- package/dist/token-search-field.d.ts +59 -0
- package/dist/token-search-field.js +4 -0
- package/dist/token-search-field.js.map +1 -0
- package/dist/toolbar-control-group.d.ts +11 -5
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +31 -4
- package/dist/toolbar-text.js +2 -1
- package/dist/toolbar.d.ts +5 -3
- package/dist/toolbar.js +1 -1
- package/dist/value-table.d.ts +13 -3
- 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 +57 -0
- package/dist/wire-catalog.js +302 -0
- package/dist/wire-catalog.js.map +1 -0
- package/dist/wire-nav-stack.d.ts +15 -0
- package/dist/wire-nav-stack.js +103 -0
- package/dist/wire-nav-stack.js.map +1 -0
- package/dist/wire-resizable-regions.d.ts +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.d.ts +43 -0
- package/dist/wire-sidebar.js +128 -0
- package/dist/wire-sidebar.js.map +1 -0
- package/dist/wire-tab-bars.d.ts +18 -2
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.d.ts +11 -0
- package/dist/wire-tab-scaffold.js +21 -0
- package/dist/wire-tab-scaffold.js.map +1 -0
- package/dist/wire-token-search-fields.d.ts +111 -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 +64 -0
- package/dist/workbench.js.map +1 -0
- package/docs/accessibility.md +124 -4
- package/docs/app-layouts.md +61 -0
- package/docs/catalog.md +364 -0
- package/docs/collapsible-panel.md +129 -0
- package/docs/component-contract.md +312 -5
- package/docs/component-selection.md +219 -0
- package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
- package/docs/design/templates/empty-state/filtered.svg +1 -0
- package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
- package/docs/design/templates/empty-state/with-action.svg +1 -0
- package/docs/design/templates/empty-state-dark.svg +1 -0
- package/docs/design/templates/empty-state.svg +1 -0
- package/docs/design/templates/list/compact-dark.svg +1 -0
- package/docs/design/templates/list/compact.svg +1 -0
- package/docs/design/templates/list/stack-dark.svg +1 -0
- package/docs/design/templates/list/stack.svg +1 -0
- package/docs/design/templates/list-action-row/default-dark.svg +1 -0
- package/docs/design/templates/list-action-row/default.svg +1 -0
- package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
- package/docs/design/templates/list-action-row/selected.svg +1 -0
- package/docs/design/templates/list-action-row-dark.svg +1 -0
- package/docs/design/templates/list-action-row.svg +1 -0
- package/docs/design/templates/list-dark.svg +1 -0
- package/docs/design/templates/list-header/action-dark.svg +1 -0
- package/docs/design/templates/list-header/action.svg +1 -0
- package/docs/design/templates/list-header/count-dark.svg +1 -0
- package/docs/design/templates/list-header/count.svg +1 -0
- package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
- package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
- package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
- package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
- package/docs/design/templates/list-header-dark.svg +1 -0
- package/docs/design/templates/list-header.svg +1 -0
- package/docs/design/templates/list-item/default-dark.svg +1 -0
- package/docs/design/templates/list-item/default.svg +1 -0
- package/docs/design/templates/list-item/multiline-dark.svg +1 -0
- package/docs/design/templates/list-item/multiline.svg +1 -0
- package/docs/design/templates/list-item/selected-dark.svg +1 -0
- package/docs/design/templates/list-item/selected.svg +1 -0
- package/docs/design/templates/list-item/trailing-dark.svg +1 -0
- package/docs/design/templates/list-item/trailing.svg +1 -0
- package/docs/design/templates/list-item-dark.svg +1 -0
- package/docs/design/templates/list-item.svg +1 -0
- package/docs/design/templates/list.svg +1 -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 +1 -0
- package/docs/design/templates/panel-header.svg +1 -0
- package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
- package/docs/design/templates/segmented-control/equal.svg +1 -0
- package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
- package/docs/design/templates/segmented-control/pill-small.svg +1 -0
- package/docs/design/templates/segmented-control-dark.svg +1 -0
- package/docs/design/templates/segmented-control.svg +1 -0
- package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
- package/docs/design/templates/skeleton/avatar.svg +1 -0
- package/docs/design/templates/skeleton/block-dark.svg +1 -0
- package/docs/design/templates/skeleton/block.svg +1 -0
- package/docs/design/templates/skeleton/lines-dark.svg +1 -0
- package/docs/design/templates/skeleton/lines.svg +1 -0
- package/docs/design/templates/skeleton-dark.svg +1 -0
- package/docs/design/templates/skeleton.svg +1 -0
- package/docs/design/templates/state-banner/danger-dark.svg +1 -0
- package/docs/design/templates/state-banner/danger.svg +1 -0
- package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
- package/docs/design/templates/state-banner/info-badge.svg +1 -0
- package/docs/design/templates/state-banner/info-dark.svg +1 -0
- package/docs/design/templates/state-banner/info.svg +1 -0
- package/docs/design/templates/state-banner/success-dark.svg +1 -0
- package/docs/design/templates/state-banner/success.svg +1 -0
- package/docs/design/templates/state-banner/warning-dark.svg +1 -0
- package/docs/design/templates/state-banner/warning.svg +1 -0
- package/docs/design/templates/state-banner-dark.svg +1 -0
- package/docs/design/templates/state-banner.svg +1 -0
- package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
- package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
- package/docs/design/templates/tab-bar-dark.svg +1 -0
- package/docs/design/templates/tab-bar.svg +1 -0
- package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
- package/docs/design/templates/token-search-field/empty.svg +1 -0
- package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
- package/docs/design/templates/token-search-field/tokens.svg +1 -0
- package/docs/design/templates/token-search-field-dark.svg +1 -0
- package/docs/design/templates/token-search-field.svg +1 -0
- package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
- package/docs/design/templates/toolbar/title-and-actions.svg +1 -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 +1 -0
- package/docs/design/templates/toolbar-control-group.svg +1 -0
- package/docs/design/templates/toolbar-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/default.svg +1 -0
- package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/large.svg +1 -0
- package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/small.svg +1 -0
- package/docs/design/templates/toolbar-text-dark.svg +1 -0
- package/docs/design/templates/toolbar-text.svg +1 -0
- package/docs/design/templates/toolbar.svg +1 -0
- package/docs/design/templates/value-table/details-dark.svg +1 -0
- package/docs/design/templates/value-table/details.svg +1 -0
- package/docs/design/templates/value-table-dark.svg +1 -0
- package/docs/design/templates/value-table.svg +1 -0
- package/docs/design/templates.md +130 -0
- package/docs/design-philosophy.md +59 -1
- package/docs/device-class.md +54 -0
- package/docs/examples/application-ui-profile.json +60 -0
- package/docs/examples/command-palette-adapter.tsx +120 -0
- package/docs/examples/component-catalog-extension-v2.json +81 -0
- package/docs/examples/component-catalog-extension.json +45 -0
- package/docs/layout.md +150 -0
- package/docs/nav-stack.md +56 -0
- package/docs/recipes.md +167 -0
- package/docs/split-view.md +49 -0
- package/docs/sunken-panel.md +53 -0
- package/docs/tab-scaffold.md +50 -0
- package/docs/type-contracts.md +102 -0
- package/docs/ui-analyzer.md +86 -0
- package/docs/ui-doctor.md +84 -0
- package/docs/ui-evaluator.md +122 -0
- package/docs/ux-demo.md +23 -9
- package/docs/webawesome-theme.md +87 -7
- package/docs/workbench.md +73 -0
- package/doctor/cli.mjs +115 -0
- package/doctor/config.schema.json +68 -0
- package/doctor/eslint-diagnostics.d.mts +8 -0
- package/doctor/eslint-diagnostics.mjs +7 -0
- package/doctor/index.d.mts +116 -0
- package/doctor/index.mjs +1257 -0
- package/doctor/report.schema.json +118 -0
- package/evaluator/cli.mjs +89 -0
- package/evaluator/index.d.mts +90 -0
- package/evaluator/index.mjs +1138 -0
- package/evaluator/report.schema.json +152 -0
- package/llms.txt +197 -14
- package/package.json +228 -46
- package/traversal-exclusions.mjs +27 -0
- package/ux-demo/recipes/app-shell.tsx +228 -0
- package/ux-demo/recipes/collapsible-sidebar.tsx +223 -0
- package/ux-demo/recipes/compact-toolbar.tsx +145 -0
- package/ux-demo/recipes/composer-form.tsx +151 -0
- package/ux-demo/recipes/list-detail-dialog.tsx +181 -0
- package/ux-demo/recipes/list-workspace-states.tsx +183 -0
- package/ux-demo/recipes/loaders.ts +36 -0
- package/ux-demo/recipes/loading-inspector.tsx +164 -0
- package/ux-demo/recipes/mount-recipe.ts +64 -0
- package/ux-demo/recipes/navigation-sidebar.tsx +137 -0
- package/ux-demo/recipes/navigation-stack.tsx +118 -0
- package/ux-demo/recipes/recipes.css +448 -0
- package/ux-demo/recipes/types.ts +19 -0
- package/ux-demo/recipes/workspace-header.tsx +67 -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 +0 -27
- package/dist/chunk-3O7ZEUW2.js.map +0 -1
- package/dist/chunk-4HHCYSGT.js +0 -79
- package/dist/chunk-4HHCYSGT.js.map +0 -1
- package/dist/chunk-55DATV3A.js +0 -10
- package/dist/chunk-55DATV3A.js.map +0 -1
- package/dist/chunk-723YNJBH.js +0 -17
- package/dist/chunk-723YNJBH.js.map +0 -1
- package/dist/chunk-DAFFDO33.js +0 -14
- package/dist/chunk-DAFFDO33.js.map +0 -1
- package/dist/chunk-GY5WH7TO.js +0 -10
- package/dist/chunk-GY5WH7TO.js.map +0 -1
- package/dist/chunk-MBR3TYNL.js +0 -14
- package/dist/chunk-MBR3TYNL.js.map +0 -1
- package/dist/chunk-MFKMHK5W.js +0 -10
- package/dist/chunk-MFKMHK5W.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-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-WY4CAEPG.js +0 -14
- package/dist/chunk-WY4CAEPG.js.map +0 -1
- package/dist/dialog-header.d.ts +0 -14
- package/dist/dialog-header.js +0 -3
- package/dist/dialog-header.js.map +0 -1
- package/dist/menu-header.d.ts +0 -15
- package/dist/menu-header.js +0 -3
- package/dist/menu-header.js.map +0 -1
- package/dist/menu-item.d.ts +0 -22
- package/dist/menu-item.js +0 -3
- package/dist/menu-item.js.map +0 -1
- package/dist/page-header.d.ts +0 -9
- package/dist/page-header.js +0 -3
- package/dist/page-header.js.map +0 -1
- package/src/app-tab.css +0 -20
- package/src/dialog-header.css +0 -9
- package/src/empty-state.css +0 -6
- package/src/foundation.css +0 -106
- package/src/loading-spinner.css +0 -4
- package/src/lucide-icon.css +0 -1
- package/src/menu-header.css +0 -13
- package/src/menu-item.css +0 -12
- package/src/page-header.css +0 -3
- package/src/resizable-region.css +0 -19
- package/src/select.css +0 -18
- package/src/state-banner.css +0 -31
- package/src/styles.css +0 -18
- package/src/tab-bar.css +0 -4
- package/src/toolbar-control-group.css +0 -25
- package/src/toolbar-text.css +0 -4
- package/src/toolbar.css +0 -9
- package/src/value-table.css +0 -5
- package/src/webawesome.css +0 -178
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Choosing an app layout
|
|
2
|
+
|
|
3
|
+
`@kerfjs/ui` ships four opt-in, tree-shakeable whole-screen layouts plus the
|
|
4
|
+
[`device-class`](device-class.md) signal that drives their responsive behavior.
|
|
5
|
+
This guide maps a **data + interaction + device** situation to the layout to
|
|
6
|
+
reach for, and states the device-class threshold at which the presentation
|
|
7
|
+
changes. The layouts:
|
|
8
|
+
|
|
9
|
+
- [`NavStack`](nav-stack.md) — push/pop navigation (a single pane is a one-entry stack).
|
|
10
|
+
- [`SplitView`](split-view.md) — list-detail (two panes, collapsing to a stack).
|
|
11
|
+
- [`Workbench`](workbench.md) — the Xcode-like collapsible rails + drawer.
|
|
12
|
+
- [`TabScaffold`](tab-scaffold.md) — the iOS bottom tab bar (each tab a stack).
|
|
13
|
+
- [`CollapsiblePanel` + `wireSidebar`](collapsible-panel.md) — a standalone
|
|
14
|
+
collapsible rail or bottom drawer (with `CollapsiblePanelToggle`), outside a full
|
|
15
|
+
shell: the standard collapse animation, icon convention, and `wireSidebar`
|
|
16
|
+
semantics (focus, compact overlay, persistence).
|
|
17
|
+
|
|
18
|
+
Derive responsiveness from `deviceClass()`: `compact` (a handset or portrait
|
|
19
|
+
tablet) means "one pane at a time"; `atLeast('tablet')` / `atLeast('desktop')`
|
|
20
|
+
gate the roomier presentations.
|
|
21
|
+
|
|
22
|
+
## Decision matrix
|
|
23
|
+
|
|
24
|
+
| Situation | Layout | Device threshold |
|
|
25
|
+
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| Simple app, a few flat sections | `NavStack` with one entry (single pane); add `TabScaffold` for 2–5 co-equal sections on handset | `TabScaffold` on `compact`; promote its tabs to a `Workbench` rail / sidebar `atLeast('desktop')` |
|
|
27
|
+
| Drill-down browsing (list → item → sub-item) | `NavStack`; upgrade to `SplitView` once list + detail fit together | `SplitView` two-pane `atLeast('tablet')` landscape / non-`compact`; `NavStack` form on `compact` |
|
|
28
|
+
| Two related panes, selecting on the left updates the right | `SplitView` | two panes when not `compact`; collapses to `NavStack` (list → detail) on `compact` |
|
|
29
|
+
| Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench` | full three-panel `atLeast('desktop')`; on smaller classes present the rails via `NavStack` / overlay drawers, not a shrunken shell |
|
|
30
|
+
| Mobile app with 2–5 top-level destinations, each its own drill-down | `TabScaffold`, each tab a `NavStack` | bottom bar on `compact`; promote to a rail / sidebar `atLeast('desktop')` |
|
|
31
|
+
|
|
32
|
+
### Worked examples
|
|
33
|
+
|
|
34
|
+
- **Settings screen (simple):** one `NavStack` entry per screen; push a subpage
|
|
35
|
+
on tap. No `SplitView`/`Workbench` — it is a single flow.
|
|
36
|
+
- **Mail (drill-down + two-pane):** `SplitView` with `list={<ThreadList/>}` and
|
|
37
|
+
`detail={<Message/>}`, `compact={device.value.compact}`,
|
|
38
|
+
`detailActive={selected != null}`. On desktop both panes show with a resizable
|
|
39
|
+
separator; on a phone it is a `NavStack` (threads → message, back clears the
|
|
40
|
+
selection).
|
|
41
|
+
- **IDE (complex tool):** `Workbench` with a left navigator rail, a right
|
|
42
|
+
inspector rail, and a bottom console drawer, each `collapsed` bound to a
|
|
43
|
+
signal. Only offer this `atLeast('desktop')`.
|
|
44
|
+
- **Social app (tabbed):** `TabScaffold` with Home / Search / Profile tabs, each
|
|
45
|
+
`content` a `NavStack`. On a tablet/desktop, render the same sections as a
|
|
46
|
+
`Workbench` left rail instead of a bottom bar.
|
|
47
|
+
|
|
48
|
+
## Dialogs
|
|
49
|
+
|
|
50
|
+
Pick the dialog's inner layout by the same complexity axis, then apply the device
|
|
51
|
+
class to how it is presented (compose with [`overlay`](../../docs/19-native-overlay-backing.md)):
|
|
52
|
+
|
|
53
|
+
- **desktop:** an inline dialog — a `SplitView` two-pane body, or a `NavStack`
|
|
54
|
+
for a wizard.
|
|
55
|
+
- **portrait tablet / handset:** present a `SplitView`/complex dialog as a
|
|
56
|
+
full-screen modal (its `compact` `NavStack` form).
|
|
57
|
+
- **landscape tablet:** a large partial-cover modal (does not need to go full
|
|
58
|
+
screen).
|
|
59
|
+
|
|
60
|
+
A `NavStack` works as a dialog body at every size — a wizard pushes and pops its
|
|
61
|
+
steps with cross-faded chrome.
|
package/docs/catalog.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Catalog — a reusable component-gallery shell
|
|
2
|
+
|
|
3
|
+
`@kerfjs/ui/catalog` is an opt-in, whole-screen shell for building a **component
|
|
4
|
+
catalog** — the collapsible category sidebar + titled preview stage + resources
|
|
5
|
+
footer that the kerf UI catalog itself uses. Point it at your own components and
|
|
6
|
+
you get the same shell without rebuilding it. Like the app layouts, it is a
|
|
7
|
+
subpath-only, tree-shakeable module that adds nothing to the main barrel.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `Catalog(props)` returns the shell as `SafeHtml` (a `<main class="kui-catalog">`).
|
|
14
|
+
It is **controlled and stateless**: your app owns the `active`, `collapsed`, and
|
|
15
|
+
`theme` signals and computes the preview `content` from `active` in its own
|
|
16
|
+
`mount()` render.
|
|
17
|
+
- `wireCatalog(root, options)` wires the interactions (sidebar selection, the
|
|
18
|
+
related-entry popup menu, and the collapse/theme toggles) with one delegated
|
|
19
|
+
listener set and returns a disposer; it can also mirror the active id into the
|
|
20
|
+
URL and reveal the active sidebar row after a controlled render.
|
|
21
|
+
|
|
22
|
+
## What you supply
|
|
23
|
+
|
|
24
|
+
- **`sections`** — category-grouped entries: `{ category, entries: [{ id, name,
|
|
25
|
+
description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
|
|
26
|
+
`ListItem` under a `ListHeader` for its category. Short `tags` render as quiet
|
|
27
|
+
trailing pills for decision metadata such as `Discouraged`.
|
|
28
|
+
- **`content`** — the rendered preview for the active entry. Keep a map of `id →
|
|
29
|
+
() => SafeHtml` in your app and call `renderers[active]()` in your render.
|
|
30
|
+
- **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header.
|
|
31
|
+
- **`secondarySections`** — an optional secondary "ecosystem" group shown below the
|
|
32
|
+
primary sections with a quieter treatment: `{ label, sections, collapsible?,
|
|
33
|
+
expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
|
|
34
|
+
`expanded` (the app owns it; wire it with `wireCatalog`'s `onToggleSecondary`).
|
|
35
|
+
- Optional slots: `headerActions` (extra header controls), `sidebarFooter` (extra
|
|
36
|
+
sidebar content), and `status` (a footer status line).
|
|
37
|
+
|
|
38
|
+
Per-entry `resources` render as "open in new tab" links in the footer, and
|
|
39
|
+
`related` renders a "Related entries" popup menu — a `single` `ToolbarControlGroup`
|
|
40
|
+
around a `wa-dropdown` (grouped by each entry's `group`), so register its elements
|
|
41
|
+
with `@kerfjs/ui/select/register` when you use it.
|
|
42
|
+
|
|
43
|
+
## Catalog demo authoring contract
|
|
44
|
+
|
|
45
|
+
This section is the single authoritative contract for tools and people that
|
|
46
|
+
author Catalog previews. The machine-readable discovery entry is
|
|
47
|
+
[`catalog-authoring.json`](../ai/catalog-authoring.json); exact props remain in
|
|
48
|
+
[`public-api-signatures-v1.md`](../ai/public-api-signatures-v1.md#kerfjsuicatalog).
|
|
49
|
+
The component catalog deliberately does not duplicate these rules: it describes
|
|
50
|
+
which component to choose, while this contract describes how to present the
|
|
51
|
+
chosen component.
|
|
52
|
+
|
|
53
|
+
### Choose the demo mode
|
|
54
|
+
|
|
55
|
+
Classify every entry before rendering it:
|
|
56
|
+
|
|
57
|
+
| Entry kind | Preview purpose | Geometry overlay |
|
|
58
|
+
| --------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
59
|
+
| Focused component | Show one public component, its meaningful variants, and adverse states. | `true`; the overlay inspects each selected specimen. |
|
|
60
|
+
| Composition or recipe | Show several components cooperating as one product surface. | `false`; child geometry remains unmarked so the composition can be read as a whole. |
|
|
61
|
+
|
|
62
|
+
Keep `geometryOverlay` present and compute it from the active entry. Do not make
|
|
63
|
+
it a permanent catalog-wide `true`, and do not give individual specimens their
|
|
64
|
+
own overlay implementation.
|
|
65
|
+
|
|
66
|
+
### Required structure
|
|
67
|
+
|
|
68
|
+
- `Catalog` is the one shell. The app owns active-entry state and passes one
|
|
69
|
+
active preview through `content`.
|
|
70
|
+
- `CatalogExampleStack` is the group for one preview's rows. Put route/test
|
|
71
|
+
metadata such as `data-demo` on its rendered root through `rootAttributes`.
|
|
72
|
+
Pass `label` when the stack needs an accessible name: the rendered `section`
|
|
73
|
+
is then exposed as a named region. An unlabeled stack remains an ordinary
|
|
74
|
+
grouping rather than adding an unnecessary landmark.
|
|
75
|
+
- `CatalogExample` is one row: optional generated `ListHeader` label, optional
|
|
76
|
+
generated note, then one specimen or one intentionally coupled specimen
|
|
77
|
+
cluster. Use one row per variant/state; do not hand-author the helper's private
|
|
78
|
+
classes.
|
|
79
|
+
- The specimen is an immediate child of `CatalogExample`. A focused component
|
|
80
|
+
row should place the component root there, without a decorative card or
|
|
81
|
+
spacing wrapper. A composition row may place the composition root there.
|
|
82
|
+
|
|
83
|
+
Use `align="glyph"` for a bare glyph/text specimen, `align="inline-control"`
|
|
84
|
+
for a control whose own inline padding contributes about 8px, and `align="none"`
|
|
85
|
+
(the default) for a content item or composition that owns its geometry.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
|
|
89
|
+
|
|
90
|
+
const buttonPreview = (
|
|
91
|
+
<CatalogExampleStack
|
|
92
|
+
label="Button variants"
|
|
93
|
+
rootAttributes={{ "data-demo": "button" }}
|
|
94
|
+
>
|
|
95
|
+
<CatalogExample label="Icon" note="A bare glyph." align="glyph">
|
|
96
|
+
<LucideIcon icon={Plus} name="plus" />
|
|
97
|
+
</CatalogExample>
|
|
98
|
+
<CatalogExample label="Control" align="inline-control">
|
|
99
|
+
<SegmentedControl id="view" label="View" value="list" choices={choices} />
|
|
100
|
+
</CatalogExample>
|
|
101
|
+
<CatalogExample
|
|
102
|
+
label="Authoring note"
|
|
103
|
+
note="Explanatory chrome is not a specimen."
|
|
104
|
+
rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
|
|
105
|
+
>
|
|
106
|
+
<p>Use the public helper contract.</p>
|
|
107
|
+
</CatalogExample>
|
|
108
|
+
</CatalogExampleStack>
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The overlay selects every immediate child of a `CatalogExample`; the helper
|
|
113
|
+
marks its generated label and note so they are excluded automatically. It does
|
|
114
|
+
not recursively promote a nested child to be the specimen. Outside an example
|
|
115
|
+
row, it selects only top-level
|
|
116
|
+
`[data-component]` roots in the canvas and ignores nested component descendants.
|
|
117
|
+
These rules keep a row's label/group scaffolding out of the measurement and make
|
|
118
|
+
the authored nesting determine exactly what is inspected.
|
|
119
|
+
|
|
120
|
+
### Metadata ownership
|
|
121
|
+
|
|
122
|
+
Use `rootAttributes` on either helper for authoring metadata such as `data-demo`
|
|
123
|
+
or `data-catalog-geometry-overlay-skip`; the metadata lands on that helper's
|
|
124
|
+
rendered root. The slot accepts only `data-*` strings. Structural
|
|
125
|
+
`data-catalog-example`, `data-catalog-example-stack`, and `data-align` semantics
|
|
126
|
+
remain helper-owned and are rejected case-insensitively at runtime, including
|
|
127
|
+
from structurally widened or JavaScript objects. Do not copy the helpers'
|
|
128
|
+
private `kui-catalog-*` classes into preview markup.
|
|
129
|
+
|
|
130
|
+
The app owns entry ids, `kind`, routing, sources, relationships, and test hooks.
|
|
131
|
+
The helpers own their structural markers, label/note anatomy, alignment marker,
|
|
132
|
+
and private classes. Component metadata such as margin/border/padding ownership
|
|
133
|
+
lives in `component-catalog.json`; do not infer or overwrite it from overlay
|
|
134
|
+
pixels.
|
|
135
|
+
|
|
136
|
+
### Geometry overlay and legend
|
|
137
|
+
|
|
138
|
+
Pass the conditional `geometryOverlay` boolean to `Catalog`, then call
|
|
139
|
+
`wireCatalogGeometryOverlay(root)` once after the first render and retain its
|
|
140
|
+
disposer alongside `wireCatalog`'s.
|
|
141
|
+
|
|
142
|
+
| Overlay mark | Meaning | It is not |
|
|
143
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
144
|
+
| Orange translucent band | A positive computed margin on the selected specimen, after subtracting the helper's alignment inset. | Padding, gap, or empty content. Zero and negative margins are not drawn. |
|
|
145
|
+
| Accent solid edge | The selected specimen's computed border widths and radius, except `none`/`hidden` styles. | A focus ring, outline, child border, or metadata ownership claim. |
|
|
146
|
+
| Quiet dashed outline | The border-box bound of a transparent selected specimen with no visible border. | A real CSS border, padding edge, or proof that the specimen owns size. |
|
|
147
|
+
|
|
148
|
+
The overlay recomputes from live rendered styles when specimens resize, preview
|
|
149
|
+
markup changes, theme/root attributes change, or loaded/inline stylesheets
|
|
150
|
+
change. Component CSS is therefore the source of truth; do not duplicate margin
|
|
151
|
+
or border values in demo data. The overlay does not visualize padding, gaps,
|
|
152
|
+
negative/zero margins, scroll overflow, hit targets, or nested descendants.
|
|
153
|
+
Inspect computed styles and the machine-readable `geometry` metadata for those
|
|
154
|
+
facts. Opaque borderless specimens do not receive the dashed bound.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<Catalog
|
|
158
|
+
{...props}
|
|
159
|
+
geometryOverlay={activeEntry.kind === "component"}
|
|
160
|
+
content={renderers[active.value]()}
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Put `rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}` on a
|
|
165
|
+
`CatalogExample` or `CatalogExampleStack` only when that whole subtree is
|
|
166
|
+
explanatory chrome rather than a specimen. The marker excludes the marked root
|
|
167
|
+
and every descendant from selection; it does not merely hide one band. It is
|
|
168
|
+
normally unnecessary in a composition because the active entry already sets the
|
|
169
|
+
global overlay to `false`.
|
|
170
|
+
|
|
171
|
+
Use the overlay together with machine-readable geometry ownership metadata; the
|
|
172
|
+
overlay verifies what is rendered, while metadata tells people and AI tools
|
|
173
|
+
whether the component, its parent, or its children are responsible for margin,
|
|
174
|
+
border, and padding. Catalogs for downstream components should conform to the
|
|
175
|
+
[`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
|
|
176
|
+
contract and can start from the checked
|
|
177
|
+
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
178
|
+
example; provide those entries beside Kerf's shipped catalog to AI tools.
|
|
179
|
+
|
|
180
|
+
### Automated conformance and reviewed exceptions
|
|
181
|
+
|
|
182
|
+
Run `npm run check:demo-conformance` after changing a first-party demo, its
|
|
183
|
+
catalog kind, or the shell's overlay logic. The TypeScript-AST gate verifies
|
|
184
|
+
facts that source can prove without guessing at rendered intent:
|
|
185
|
+
|
|
186
|
+
- focused component routes import and use `CatalogExampleStack` and
|
|
187
|
+
`CatalogExample` from the public package;
|
|
188
|
+
- focused route metadata uses the helpers' `rootAttributes` slot, example rows
|
|
189
|
+
are not empty, and composition routes do not add redundant skip markers;
|
|
190
|
+
- every `@kerfjs/ui` import is a published package export and relative imports
|
|
191
|
+
do not reach into `ui/src`;
|
|
192
|
+
- demo JSX does not copy private `kui-catalog-*` structural classes; and
|
|
193
|
+
- the shell derives both geometry-overlay enablement and documented demo mode
|
|
194
|
+
from the active entry's source and kind.
|
|
195
|
+
|
|
196
|
+
The gate deliberately does not infer component ownership from arbitrary class
|
|
197
|
+
names, margins, borders, or nested descendants. Runtime selection and geometry
|
|
198
|
+
remain the browser suite's job.
|
|
199
|
+
|
|
200
|
+
A focused route may bypass the two public layout helpers only when the route's
|
|
201
|
+
stage geometry is itself the reviewed specimen. Add the narrow waiver to
|
|
202
|
+
[`catalog-conformance-exceptions.json`](../ux-demo/catalog-conformance-exceptions.json)
|
|
203
|
+
with the exact route, source file, stable diagnostic ids, a substantive reason,
|
|
204
|
+
and the reviewing `KF-*` ticket. Only helper/metadata rules are waivable;
|
|
205
|
+
private imports, private markup, empty examples, composition overlay drift, and
|
|
206
|
+
shell-mode drift always fail. The gate rejects duplicate, malformed, unused,
|
|
207
|
+
and stale exceptions, so delete a waiver when its route adopts the standard
|
|
208
|
+
helpers.
|
|
209
|
+
|
|
210
|
+
## Selection reveal
|
|
211
|
+
|
|
212
|
+
Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
|
|
213
|
+
`onSelect` updates controlled state, the helper waits one animation frame, finds
|
|
214
|
+
the exact matching `data-item-id`, and scrolls it into view without changing
|
|
215
|
+
focus. A newer selection or disposal cancels the pending reveal. The default
|
|
216
|
+
media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
|
|
217
|
+
layouts keep their existing scroll position.
|
|
218
|
+
|
|
219
|
+
Pass an options object instead of `true` to customize `block`, `inline`,
|
|
220
|
+
`behavior`, or `media`; `media: false` deliberately enables the behavior at all
|
|
221
|
+
sizes. For an initial deep link that did not come through `wireCatalog`, call
|
|
222
|
+
`revealCatalogEntry(app, initialId, { block: "center" })` after the first mount.
|
|
223
|
+
|
|
224
|
+
## Complete example
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { mount, signal, type SafeHtml } from "kerfjs";
|
|
228
|
+
import {
|
|
229
|
+
Catalog,
|
|
230
|
+
CatalogExample,
|
|
231
|
+
CatalogExampleStack,
|
|
232
|
+
type CatalogSection,
|
|
233
|
+
} from "@kerfjs/ui/catalog";
|
|
234
|
+
import {
|
|
235
|
+
revealCatalogEntry,
|
|
236
|
+
wireCatalog,
|
|
237
|
+
wireCatalogGeometryOverlay,
|
|
238
|
+
} from "@kerfjs/ui/wire-catalog";
|
|
239
|
+
import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
|
|
240
|
+
|
|
241
|
+
type DemoKind = "component" | "composition";
|
|
242
|
+
type DemoEntry = CatalogSection["entries"][number] & { kind: DemoKind };
|
|
243
|
+
|
|
244
|
+
// 1. Describe selection and overlay mode once.
|
|
245
|
+
const entries: DemoEntry[] = [
|
|
246
|
+
{
|
|
247
|
+
id: "button",
|
|
248
|
+
name: "Button",
|
|
249
|
+
kind: "component",
|
|
250
|
+
description: "A pressable control.",
|
|
251
|
+
resources: [
|
|
252
|
+
{ label: "Source", href: "/src/button.tsx", detail: "src/button.tsx" },
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "profile-form",
|
|
257
|
+
name: "Profile form",
|
|
258
|
+
kind: "composition",
|
|
259
|
+
description: "A labeled field and save action working together.",
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
const sections: CatalogSection[] = [
|
|
263
|
+
{
|
|
264
|
+
category: "Examples",
|
|
265
|
+
entries: entries.map(({ kind: _kind, ...entry }) => entry),
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
// 2. Every preview uses one public group and public example rows.
|
|
270
|
+
const renderers: Record<string, () => SafeHtml> = {
|
|
271
|
+
button: () => (
|
|
272
|
+
<CatalogExampleStack
|
|
273
|
+
label="Button states"
|
|
274
|
+
rootAttributes={{ "data-demo": "button" }}
|
|
275
|
+
>
|
|
276
|
+
<CatalogExample label="Default" align="inline-control">
|
|
277
|
+
<Button label="Save" />
|
|
278
|
+
</CatalogExample>
|
|
279
|
+
<CatalogExample
|
|
280
|
+
label="Authoring note"
|
|
281
|
+
note="This explanatory row is deliberately excluded from inspection."
|
|
282
|
+
rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
|
|
283
|
+
>
|
|
284
|
+
<p>The application owns product copy and actions.</p>
|
|
285
|
+
</CatalogExample>
|
|
286
|
+
</CatalogExampleStack>
|
|
287
|
+
),
|
|
288
|
+
"profile-form": () => (
|
|
289
|
+
<CatalogExampleStack
|
|
290
|
+
label="Profile form composition"
|
|
291
|
+
rootAttributes={{ "data-demo": "profile-form" }}
|
|
292
|
+
>
|
|
293
|
+
<CatalogExample label="Complete composition">
|
|
294
|
+
<ProfileForm />
|
|
295
|
+
</CatalogExample>
|
|
296
|
+
</CatalogExampleStack>
|
|
297
|
+
),
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// 3. App-owned state (domain: which entry; transient: collapsed; global: theme).
|
|
301
|
+
const initial =
|
|
302
|
+
new URLSearchParams(location.search).get("c") ?? sections[0].entries[0].id;
|
|
303
|
+
const active = signal(initial);
|
|
304
|
+
const collapsed = signal(false);
|
|
305
|
+
const theme = signal<"light" | "dark">("light");
|
|
306
|
+
const activeEntry = () => entries.find(({ id }) => id === active.value) ?? entries[0];
|
|
307
|
+
|
|
308
|
+
const app = document.getElementById("app")!;
|
|
309
|
+
mount(app, () => (
|
|
310
|
+
<Catalog
|
|
311
|
+
brand={{ title: "Acme UI", subtitle: "Design system" }}
|
|
312
|
+
sections={sections}
|
|
313
|
+
active={active.value}
|
|
314
|
+
content={renderers[active.value]?.() ?? <></>}
|
|
315
|
+
collapsed={collapsed.value}
|
|
316
|
+
theme={theme.value}
|
|
317
|
+
geometryOverlay={activeEntry().kind === "component"}
|
|
318
|
+
/>
|
|
319
|
+
));
|
|
320
|
+
|
|
321
|
+
wireCatalog(app, {
|
|
322
|
+
onSelect: (id) => {
|
|
323
|
+
active.value = id;
|
|
324
|
+
},
|
|
325
|
+
onToggleSidebar: () => {
|
|
326
|
+
collapsed.value = !collapsed.value;
|
|
327
|
+
},
|
|
328
|
+
onToggleTheme: () => {
|
|
329
|
+
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
330
|
+
document.documentElement.dataset.theme = theme.value; // apply your theme however you like
|
|
331
|
+
},
|
|
332
|
+
urlParam: "c", // mirror the active id into ?c=<id>
|
|
333
|
+
revealSelection: true, // reveal long desktop sidebars without moving focus
|
|
334
|
+
});
|
|
335
|
+
wireCatalogGeometryOverlay(app);
|
|
336
|
+
|
|
337
|
+
// Optional for an initial deep link whose row may start outside the viewport.
|
|
338
|
+
revealCatalogEntry(app, initial, { block: "center" });
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Ownership boundary
|
|
342
|
+
|
|
343
|
+
`Catalog` renders the shell; the app owns everything stateful:
|
|
344
|
+
|
|
345
|
+
- **`active`** is domain state (which entry is shown) — the app's signal, updated in
|
|
346
|
+
`onSelect`, read to compute `content`.
|
|
347
|
+
- **`collapsed`** is transient UI — the app's signal, flipped in `onToggleSidebar`.
|
|
348
|
+
- **`theme`** is a global preference — the app's signal; `wireCatalog` only reports
|
|
349
|
+
the toggle, the app applies the theme (the shell reads `theme` to show the toggle's
|
|
350
|
+
opposite-state label). Omit `theme` to hide the toggle entirely.
|
|
351
|
+
|
|
352
|
+
## Custom action names
|
|
353
|
+
|
|
354
|
+
The shell emits `data-action="catalog-select"` (sidebar items),
|
|
355
|
+
`catalog-toggle-sidebar`, and `catalog-toggle-theme`. Override them with
|
|
356
|
+
`selectAction` / `toggleSidebarAction` / `toggleThemeAction` on `Catalog` (and the
|
|
357
|
+
matching options on `wireCatalog`) if they collide with your own action table.
|
|
358
|
+
|
|
359
|
+
## CSS
|
|
360
|
+
|
|
361
|
+
`Catalog` composes public primitives (`Toolbar`, `ListHeader`, `ListItem`, `Select`,
|
|
362
|
+
…). Import `@kerfjs/ui/styles.css` for the whole layer, or `@kerfjs/ui/catalog.css`
|
|
363
|
+
plus each composed primitive's CSS. The shell is theme-aware and responsive: it
|
|
364
|
+
stacks the sidebar above the detail below ~832px and hides it when collapsed.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# CollapsiblePanel + wireSidebar — reusable sidebar / drawer support
|
|
2
|
+
|
|
3
|
+
`@kerfjs/ui/collapsible-panel` and `@kerfjs/ui/wire-sidebar` are the standalone,
|
|
4
|
+
reusable pieces for an app's own **side rails** and **bottom drawers** — the same
|
|
5
|
+
collapse semantics the `Workbench` shell and the catalog sidebar use, but outside
|
|
6
|
+
a full shell so you can dock one panel wherever your layout needs it. They are
|
|
7
|
+
subpath-only, tree-shakeable modules that add nothing to the main barrel.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.css`.
|
|
14
|
+
|
|
15
|
+
## The pieces
|
|
16
|
+
|
|
17
|
+
- **`CollapsiblePanel({ id, side, collapsed?, size?, label?, children })`** — the
|
|
18
|
+
docked panel: a `'left'` / `'right'` rail or a `'bottom'` drawer. It owns only
|
|
19
|
+
presentation. Collapsing snaps the panel's own size to zero in one reflow while
|
|
20
|
+
the fixed-size content slides out via `transform` (composited, clipped) — never
|
|
21
|
+
a per-frame width/height animation. The app owns the `collapsed` signal; `size`
|
|
22
|
+
overrides the CSS default width/height.
|
|
23
|
+
- **`CollapsiblePanelToggle({ side, collapsed, action, panelId?, label? })`** and
|
|
24
|
+
**`collapsiblePanelToggleIcon(side, collapsed)`** — the standard toggle
|
|
25
|
+
affordance and its icon convention, so every sidebar reads the same: `PanelLeft*`
|
|
26
|
+
for a left rail, `PanelRight*` for a right rail, `PanelBottom*` for a bottom
|
|
27
|
+
drawer — the `Close` glyph while open, the `Open` glyph while collapsed. Placement
|
|
28
|
+
is the app's: put a collapse toggle in the panel's own header and an expand toggle
|
|
29
|
+
somewhere always-visible (a toolbar) so it is reachable while collapsed.
|
|
30
|
+
- **`wireSidebar(root, { panels, deviceClass?, storage? })`** — the interaction
|
|
31
|
+
semantics. Each `panels` entry is `{ id, collapsed, toggleAction, storageKey? }`.
|
|
32
|
+
It:
|
|
33
|
+
- **toggles** the panel's `collapsed` signal when any `[data-action=toggleAction]`
|
|
34
|
+
button is clicked, and remembers the trigger;
|
|
35
|
+
- **manages focus** — moves focus into the panel when it opens, and restores it
|
|
36
|
+
to the trigger when it closes;
|
|
37
|
+
- **presents a compact overlay** when `deviceClass.compact` is true (pass a
|
|
38
|
+
`deviceClass()` signal): the open panel floats over the content with a
|
|
39
|
+
dismissable backdrop, Escape and backdrop-click collapse it, and Tab is trapped
|
|
40
|
+
within the panel (the ARIA dialog pattern);
|
|
41
|
+
- **persists** the collapsed state to `storage` (default `localStorage`) under
|
|
42
|
+
`storageKey`, seeding the signal on wire-up.
|
|
43
|
+
|
|
44
|
+
Returns a disposer. Retain it and call it on teardown.
|
|
45
|
+
|
|
46
|
+
## Example
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { signal, mount } from "kerfjs";
|
|
50
|
+
import { deviceClass } from "@kerfjs/ui/device-class";
|
|
51
|
+
import {
|
|
52
|
+
CollapsiblePanel,
|
|
53
|
+
CollapsiblePanelToggle,
|
|
54
|
+
} from "@kerfjs/ui/collapsible-panel";
|
|
55
|
+
import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
|
|
56
|
+
import "@kerfjs/ui/collapsible-panel.css";
|
|
57
|
+
|
|
58
|
+
const navCollapsed = signal(false);
|
|
59
|
+
const device = deviceClass();
|
|
60
|
+
|
|
61
|
+
const app = document.querySelector("#app")!;
|
|
62
|
+
mount(app, () => (
|
|
63
|
+
<div class="layout">
|
|
64
|
+
<CollapsiblePanel
|
|
65
|
+
id="nav"
|
|
66
|
+
side="left"
|
|
67
|
+
collapsed={navCollapsed.value}
|
|
68
|
+
label="Navigator"
|
|
69
|
+
>
|
|
70
|
+
<header>
|
|
71
|
+
<CollapsiblePanelToggle
|
|
72
|
+
side="left"
|
|
73
|
+
collapsed={navCollapsed.value}
|
|
74
|
+
action="toggle-nav"
|
|
75
|
+
panelId="nav"
|
|
76
|
+
/>
|
|
77
|
+
</header>
|
|
78
|
+
{/* nav items */}
|
|
79
|
+
</CollapsiblePanel>
|
|
80
|
+
<main>
|
|
81
|
+
{navCollapsed.value && (
|
|
82
|
+
<CollapsiblePanelToggle
|
|
83
|
+
side="left"
|
|
84
|
+
collapsed
|
|
85
|
+
action="toggle-nav"
|
|
86
|
+
label="Show navigator"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
{/* content */}
|
|
90
|
+
</main>
|
|
91
|
+
</div>
|
|
92
|
+
));
|
|
93
|
+
|
|
94
|
+
const stop = wireSidebar(app, {
|
|
95
|
+
panels: [
|
|
96
|
+
{
|
|
97
|
+
id: "nav",
|
|
98
|
+
collapsed: navCollapsed,
|
|
99
|
+
toggleAction: "toggle-nav",
|
|
100
|
+
storageKey: "app.nav-collapsed",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
deviceClass: device,
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## When to use which
|
|
108
|
+
|
|
109
|
+
- One or two independent rails / a drawer you place yourself → **`CollapsiblePanel` +
|
|
110
|
+
`wireSidebar`**.
|
|
111
|
+
- A whole Xcode-like workspace (left rail + right rail + bottom drawer + work area
|
|
112
|
+
in one shell) → **[`Workbench`](workbench.md)**, which owns the layout and the same
|
|
113
|
+
collapse animation.
|
|
114
|
+
- Drag-to-resize a panel → compose **[`ResizableRegion`](../src/resizable-region.tsx)**
|
|
115
|
+
/ `wireResizableRegions`; the app owns the size signal.
|
|
116
|
+
|
|
117
|
+
The app still owns everything domain-specific — which panels exist, their order,
|
|
118
|
+
sizes, content, and any per-project persistence — exactly as with the other layouts.
|
|
119
|
+
|
|
120
|
+
## Recipe and coverage
|
|
121
|
+
|
|
122
|
+
The catalog ships a runnable **Collapsible sidebar** recipe — a left rail and a
|
|
123
|
+
bottom drawer with the standard toggles, the compact overlay, and per-panel
|
|
124
|
+
persistence: [open it](../ux-demo/?component=recipe-collapsible-sidebar) or read
|
|
125
|
+
[`recipes.md`](recipes.md#collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx).
|
|
126
|
+
It is covered end-to-end across Chromium, Firefox, and WebKit by
|
|
127
|
+
`tests/browser/collapsible-sidebar-recipe.spec.ts` (collapse/expand, focus
|
|
128
|
+
move/restore, the compact overlay + Escape/backdrop dismiss, and the Tab trap),
|
|
129
|
+
alongside the component/wire unit tests.
|