@kerfjs/ui 5.0.0-beta.8 → 5.0.0-beta.9
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 +20 -20
- package/ai/component-catalog.json +123 -80
- package/ai/public-api-signatures-v1.md +181 -26
- package/ai/skill.md +12 -12
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/browser/list-action-row.js +4 -0
- package/dist/browser/{menu-header.js → list-header.js} +2 -2
- package/dist/browser/{menu-item.js → list-item.js} +2 -2
- package/dist/catalog.d.ts +119 -0
- package/dist/catalog.js +128 -0
- package/dist/catalog.js.map +1 -0
- package/dist/{chunk-BNQ2YDV4.js → chunk-2TS7ZSYX.js} +2 -2
- package/dist/{chunk-BNQ2YDV4.js.map → chunk-2TS7ZSYX.js.map} +1 -1
- package/dist/{chunk-PONXLV5U.js → chunk-37T3VNIZ.js} +1 -1
- package/dist/{chunk-PONXLV5U.js.map → chunk-37T3VNIZ.js.map} +1 -1
- package/dist/{chunk-3YD6ZNRA.js → chunk-7JB7VPRI.js} +8 -8
- package/dist/{chunk-3YD6ZNRA.js.map → chunk-7JB7VPRI.js.map} +1 -1
- package/dist/{chunk-YKUUZPED.js → chunk-L4OKFDJY.js} +7 -7
- package/dist/{chunk-YKUUZPED.js.map → chunk-L4OKFDJY.js.map} +1 -1
- package/dist/{chunk-H6URQGJI.js → chunk-W5EQ3HYQ.js} +11 -11
- package/dist/{chunk-H6URQGJI.js.map → chunk-W5EQ3HYQ.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +10 -10
- package/dist/{menu-action-row.d.ts → list-action-row.d.ts} +7 -7
- package/dist/list-action-row.js +5 -0
- package/dist/list-action-row.js.map +1 -0
- package/dist/{menu-header.d.ts → list-header.d.ts} +9 -9
- package/dist/list-header.js +7 -0
- package/dist/list-header.js.map +1 -0
- package/dist/{menu-item.d.ts → list-item.d.ts} +5 -5
- package/dist/list-item.js +5 -0
- package/dist/list-item.js.map +1 -0
- package/dist/panel-header.js +1 -1
- package/dist/split-view.d.ts +1 -1
- package/dist/split-view.js.map +1 -1
- package/dist/styles/catalog.css +482 -0
- package/dist/styles/list-action-row.css +160 -0
- package/dist/styles/{menu-header.css → list-header.css} +33 -33
- package/dist/styles/list-item.css +123 -0
- package/dist/styles/styles.css +4 -3
- package/dist/value-table.js +1 -1
- package/dist/wire-catalog.d.ts +26 -0
- package/dist/wire-catalog.js +50 -0
- package/dist/wire-catalog.js.map +1 -0
- package/docs/accessibility.md +5 -5
- package/docs/catalog.md +154 -0
- package/docs/component-contract.md +12 -12
- package/docs/component-selection.md +12 -11
- package/docs/design-philosophy.md +4 -4
- package/docs/layout.md +8 -8
- package/docs/recipes.md +17 -5
- package/docs/split-view.md +1 -1
- package/docs/ux-demo.md +5 -5
- package/llms.txt +12 -8
- package/package.json +25 -16
- package/ux-demo/recipes/app-shell.tsx +3 -3
- package/ux-demo/recipes/list-detail-dialog.tsx +45 -0
- package/ux-demo/recipes/loaders.ts +4 -2
- package/ux-demo/recipes/loading-inspector.tsx +79 -0
- package/ux-demo/recipes/navigation-sidebar.tsx +5 -5
- package/ux-demo/recipes/navigation-stack.tsx +4 -4
- package/ux-demo/recipes/recipes.css +13 -7
- package/dist/browser/menu-action-row.js +0 -4
- package/dist/menu-action-row.js +0 -5
- package/dist/menu-action-row.js.map +0 -1
- package/dist/menu-header.js +0 -7
- package/dist/menu-header.js.map +0 -1
- package/dist/menu-item.js +0 -5
- package/dist/menu-item.js.map +0 -1
- package/dist/styles/menu-action-row.css +0 -160
- package/dist/styles/menu-item.css +0 -123
- package/ux-demo/recipes/master-detail-dialog.tsx +0 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kerfjs/ui",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.9",
|
|
4
4
|
"description": "Accessible, composable UI primitives for kerf applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,20 +64,20 @@
|
|
|
64
64
|
"browser": "./dist/browser/toolbar-text.js",
|
|
65
65
|
"import": "./dist/toolbar-text.js"
|
|
66
66
|
},
|
|
67
|
-
"./
|
|
68
|
-
"types": "./dist/
|
|
69
|
-
"browser": "./dist/browser/
|
|
70
|
-
"import": "./dist/
|
|
67
|
+
"./list-action-row": {
|
|
68
|
+
"types": "./dist/list-action-row.d.ts",
|
|
69
|
+
"browser": "./dist/browser/list-action-row.js",
|
|
70
|
+
"import": "./dist/list-action-row.js"
|
|
71
71
|
},
|
|
72
|
-
"./
|
|
73
|
-
"types": "./dist/
|
|
74
|
-
"browser": "./dist/browser/
|
|
75
|
-
"import": "./dist/
|
|
72
|
+
"./list-item": {
|
|
73
|
+
"types": "./dist/list-item.d.ts",
|
|
74
|
+
"browser": "./dist/browser/list-item.js",
|
|
75
|
+
"import": "./dist/list-item.js"
|
|
76
76
|
},
|
|
77
|
-
"./
|
|
78
|
-
"types": "./dist/
|
|
79
|
-
"browser": "./dist/browser/
|
|
80
|
-
"import": "./dist/
|
|
77
|
+
"./list-header": {
|
|
78
|
+
"types": "./dist/list-header.d.ts",
|
|
79
|
+
"browser": "./dist/browser/list-header.js",
|
|
80
|
+
"import": "./dist/list-header.js"
|
|
81
81
|
},
|
|
82
82
|
"./resizable-region": {
|
|
83
83
|
"types": "./dist/resizable-region.d.ts",
|
|
@@ -97,6 +97,14 @@
|
|
|
97
97
|
"browser": "./dist/browser/app-tab.js",
|
|
98
98
|
"import": "./dist/app-tab.js"
|
|
99
99
|
},
|
|
100
|
+
"./catalog": {
|
|
101
|
+
"types": "./dist/catalog.d.ts",
|
|
102
|
+
"import": "./dist/catalog.js"
|
|
103
|
+
},
|
|
104
|
+
"./wire-catalog": {
|
|
105
|
+
"types": "./dist/wire-catalog.d.ts",
|
|
106
|
+
"import": "./dist/wire-catalog.js"
|
|
107
|
+
},
|
|
100
108
|
"./tab-bar": {
|
|
101
109
|
"types": "./dist/tab-bar.d.ts",
|
|
102
110
|
"browser": "./dist/browser/tab-bar.js",
|
|
@@ -200,11 +208,12 @@
|
|
|
200
208
|
"./toolbar.css": "./dist/styles/toolbar.css",
|
|
201
209
|
"./toolbar-control-group.css": "./dist/styles/toolbar-control-group.css",
|
|
202
210
|
"./toolbar-text.css": "./dist/styles/toolbar-text.css",
|
|
203
|
-
"./
|
|
204
|
-
"./
|
|
205
|
-
"./
|
|
211
|
+
"./list-action-row.css": "./dist/styles/list-action-row.css",
|
|
212
|
+
"./list-item.css": "./dist/styles/list-item.css",
|
|
213
|
+
"./list-header.css": "./dist/styles/list-header.css",
|
|
206
214
|
"./resizable-region.css": "./dist/styles/resizable-region.css",
|
|
207
215
|
"./app-tab.css": "./dist/styles/app-tab.css",
|
|
216
|
+
"./catalog.css": "./dist/styles/catalog.css",
|
|
208
217
|
"./tab-bar.css": "./dist/styles/tab-bar.css",
|
|
209
218
|
"./nav-stack.css": "./dist/styles/nav-stack.css",
|
|
210
219
|
"./split-view.css": "./dist/styles/split-view.css",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import '@kerfjs/ui/layout.css';
|
|
2
2
|
import './recipes.css';
|
|
3
3
|
|
|
4
|
+
import { ListHeader } from '@kerfjs/ui/list-header';
|
|
5
|
+
import { ListItem } from '@kerfjs/ui/list-item';
|
|
4
6
|
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
5
|
-
import { MenuHeader } from '@kerfjs/ui/menu-header';
|
|
6
|
-
import { MenuItem } from '@kerfjs/ui/menu-item';
|
|
7
7
|
import { PanelHeader } from '@kerfjs/ui/panel-header';
|
|
8
8
|
import { ResizableRegion } from '@kerfjs/ui/resizable-region';
|
|
9
9
|
import { Toolbar } from '@kerfjs/ui/toolbar';
|
|
@@ -23,7 +23,7 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
23
23
|
const render = () => <section class="kui-recipe recipe-shell kui-recipe__surface kui-layout" data-recipe="recipe-app-shell" data-responsive-pane={responsivePane.value}>
|
|
24
24
|
<Toolbar label="Atlas workspace" leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Atlas" /></ToolbarControlGroup>} trailing={<div class="recipe-shell__toolbar-actions"><ToolbarControlGroup className="recipe-shell__responsive-controls" appearance="borderless" label="Workspace panes"><button type="button" aria-label="Show navigation" aria-controls="recipe-shell-navigation" aria-pressed={String(responsivePane.value === 'navigation')} data-action="recipe-action" data-recipe-command="show-navigation"><LucideIcon icon={Folder} name="folder" /></button><button type="button" aria-label="Show content" aria-controls="recipe-shell-content" aria-pressed={String(responsivePane.value === 'content')} data-action="recipe-action" data-recipe-command="show-content"><LucideIcon icon={Inbox} name="inbox" /></button><button type="button" aria-label="Show inspector" aria-controls="recipe-shell-inspector" aria-pressed={String(responsivePane.value === 'inspector')} data-action="recipe-action" data-recipe-command="show-inspector"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup><ToolbarControlGroup appearance="borderless" label="Workspace controls"><button type="button" aria-label="Notifications" data-action="recipe-action" data-recipe-command="notify"><LucideIcon icon={Bell} name="bell" /></button><button type="button" aria-label="Settings" data-action="recipe-action" data-recipe-command="settings"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup></div>} />
|
|
25
25
|
<div class="recipe-shell__body">
|
|
26
|
-
<div id="recipe-shell-navigation" class="recipe-shell__nav-region"><ResizableRegion id="recipe-navigation" label="Navigation" size={navigationSize.value} min={180} max={320}><aside class="recipe-shell__nav kui-pane"><
|
|
26
|
+
<div id="recipe-shell-navigation" class="recipe-shell__nav-region"><ResizableRegion id="recipe-navigation" label="Navigation" size={navigationSize.value} min={180} max={320}><aside class="recipe-shell__nav kui-pane"><ListHeader label="Workspace" /><nav class="recipe-shell__nav-list kui-pane__content kui-content" aria-label="Workspace"><section><ListItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} selected={selected.value === 'inbox'} /><ListItem action="recipe-action" itemId="projects" label="Projects with a deliberately wrapping title" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'projects'} multiline /></section></nav></aside></ResizableRegion></div>
|
|
27
27
|
<main id="recipe-shell-content" class="recipe-shell__main kui-pane"><PanelHeader title={selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'} titleId="recipe-shell-main-title" actions={<button class="kui-recipe__button" data-primary="true" type="button" data-action="recipe-action" data-recipe-command="new">New task</button>} /><div class="recipe-shell__main-body kui-pane__content kui-content"><p class="kui-recipe__ownership kui-content-item">Recipe owns pane geometry and one scroll owner per pane. The app owns routing, data, pane visibility, sizes, and persistence.</p><div class="recipe-shell__cards">{['Release accessibility audit', 'Prepare tablet navigation', 'Review stale-data states', 'Confirm package boundaries'].map((title) => <article class="recipe-shell__card kui-content-item"><strong>{title}</strong><p class="kui-recipe__muted">Assigned to the interface systems team · due this week</p></article>)}</div></div></main>
|
|
28
28
|
<div id="recipe-shell-inspector" class="recipe-shell__inspector-region"><ResizableRegion id="recipe-inspector" label="Inspector" size={inspectorSize.value} min={200} max={360} edge="start"><aside class="recipe-shell__inspector kui-pane"><PanelHeader title="Inspector" titleId="recipe-shell-inspector-title" /><div class="recipe-shell__inspector-body kui-pane__content kui-content"><ValueTable label="Selected task"><ValueTableRow label="Status" value="In review" /><ValueTableRow label="Owner" value="Mara Chen" /><ValueTableRow label="Priority" value="High" /></ValueTable></div></aside></ResizableRegion></div>
|
|
29
29
|
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import '@kerfjs/ui/layout.css';
|
|
2
|
+
import '@kerfjs/ui/webawesome.css';
|
|
3
|
+
import '@awesome.me/webawesome/dist/components/button/button.js';
|
|
4
|
+
import '@awesome.me/webawesome/dist/components/dialog/dialog.js';
|
|
5
|
+
import './recipes.css';
|
|
6
|
+
|
|
7
|
+
import { ListHeader } from '@kerfjs/ui/list-header';
|
|
8
|
+
import { ListItem } from '@kerfjs/ui/list-item';
|
|
9
|
+
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
10
|
+
import { PanelHeader } from '@kerfjs/ui/panel-header';
|
|
11
|
+
import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
|
|
12
|
+
import { signal } from 'kerfjs';
|
|
13
|
+
import { FileText } from 'lucide';
|
|
14
|
+
|
|
15
|
+
import type { RecipeFactory } from './types.js';
|
|
16
|
+
|
|
17
|
+
const records = {
|
|
18
|
+
alpha: { name: 'Northstar migration', owner: 'Mara Chen', state: 'In review', updated: 'Today, 09:42' },
|
|
19
|
+
beta: { name: 'Tablet navigation', owner: 'Sam Rivera', state: 'Ready', updated: 'Yesterday, 16:20' },
|
|
20
|
+
gamma: { name: 'Contrast audit', owner: 'Inez Okafor', state: 'Blocked', updated: 'Monday, 11:05' },
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
interface RecipeDialogElement extends HTMLElement {
|
|
24
|
+
open: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const createRecipe: RecipeFactory = (announce) => {
|
|
28
|
+
const selected = signal<keyof typeof records>('alpha');
|
|
29
|
+
const open = { value: false };
|
|
30
|
+
const render = () => <section class="kui-recipe kui-content" data-recipe="recipe-list-detail-dialog"><button class="kui-recipe__button kui-content-item" data-primary="true" type="button" data-action="recipe-action" data-recipe-command="open">Open project details</button><p class="kui-recipe__ownership kui-content-item">Web Awesome owns modal focus and dismissal. The recipe owns header/body/list-detail anatomy; the app owns open state, selection, and policy.</p><wa-dialog class="recipe-dialog" label="Project details" without-header open={open.value}><div class="recipe-dialog__pane kui-pane"><div class="recipe-list-detail kui-pane__content"><nav class="recipe-list-detail__list kui-pane" aria-label="Projects"><ListHeader label="Recent projects" /><div class="kui-pane__content kui-content"><section>{Object.entries(records).map(([id, record]) => <ListItem action="recipe-action" itemId={id} label={record.name} selected={selected.value === id} multiline />)}</section></div></nav><section class="recipe-list-detail__detail kui-pane"><PanelHeader title="Project details" titleId="recipe-dialog-title" summary="Compare delivery state without leaving the workspace." summaryId="recipe-dialog-summary" icon={<LucideIcon icon={FileText} name="file-text" />} actions={<wa-button appearance="plain" data-action="recipe-action" data-recipe-command="close">Close</wa-button>} /><div class="recipe-list-detail__body kui-pane__content kui-content" aria-live="polite"><h3 class="recipe-list-detail__title kui-recipe__pane-title kui-content-item">{records[selected.value].name}</h3><ValueTable label="Project details"><ValueTableRow label="Owner" value={records[selected.value].owner} /><ValueTableRow label="Status" value={records[selected.value].state} /><ValueTableRow label="Updated" value={records[selected.value].updated} /></ValueTable><div class="recipe-list-detail__actions kui-control-cluster"><wa-button appearance="outlined" data-action="recipe-action" data-recipe-command="archive">Archive</wa-button><wa-button variant="brand" appearance="accent" data-action="recipe-action" data-recipe-command="open-record">Open project</wa-button></div></div></section></div></div></wa-dialog></section>;
|
|
31
|
+
return {
|
|
32
|
+
render,
|
|
33
|
+
action(command, element) {
|
|
34
|
+
const id = element.dataset.itemId;
|
|
35
|
+
if (id && id in records) { selected.value = id as keyof typeof records; announce(`Selected ${records[selected.value].name}`); return; }
|
|
36
|
+
const dialog = element.closest('[data-recipe]')?.querySelector<RecipeDialogElement>('wa-dialog');
|
|
37
|
+
if (command === 'open' && dialog) { element.focus(); open.value = true; dialog.open = true; return; }
|
|
38
|
+
if (command === 'close' && dialog) { dialog.open = false; return; }
|
|
39
|
+
announce(`${command} requested`);
|
|
40
|
+
},
|
|
41
|
+
afterHide() {
|
|
42
|
+
open.value = false;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -4,11 +4,12 @@ export const recipeIds = [
|
|
|
4
4
|
'recipe-app-shell',
|
|
5
5
|
'recipe-navigation-sidebar',
|
|
6
6
|
'recipe-workspace-header',
|
|
7
|
-
'recipe-
|
|
7
|
+
'recipe-list-detail-dialog',
|
|
8
8
|
'recipe-composer-form',
|
|
9
9
|
'recipe-list-workspace-states',
|
|
10
10
|
'recipe-compact-toolbar',
|
|
11
11
|
'recipe-navigation-stack',
|
|
12
|
+
'recipe-loading-inspector',
|
|
12
13
|
] as const;
|
|
13
14
|
|
|
14
15
|
export type RecipeId = typeof recipeIds[number];
|
|
@@ -17,11 +18,12 @@ export const recipeLoaders: Record<RecipeId, () => Promise<{ createRecipe: Recip
|
|
|
17
18
|
'recipe-app-shell': () => import('./app-shell.js'),
|
|
18
19
|
'recipe-navigation-sidebar': () => import('./navigation-sidebar.js'),
|
|
19
20
|
'recipe-workspace-header': () => import('./workspace-header.js'),
|
|
20
|
-
'recipe-
|
|
21
|
+
'recipe-list-detail-dialog': () => import('./list-detail-dialog.js'),
|
|
21
22
|
'recipe-composer-form': () => import('./composer-form.js'),
|
|
22
23
|
'recipe-list-workspace-states': () => import('./list-workspace-states.js'),
|
|
23
24
|
'recipe-compact-toolbar': () => import('./compact-toolbar.js'),
|
|
24
25
|
'recipe-navigation-stack': () => import('./navigation-stack.js'),
|
|
26
|
+
'recipe-loading-inspector': () => import('./loading-inspector.js'),
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export function isRecipeId(value: string): value is RecipeId {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import '@kerfjs/ui/layout.css';
|
|
2
|
+
import '@kerfjs/ui/select/register';
|
|
3
|
+
import './recipes.css';
|
|
4
|
+
|
|
5
|
+
import { ListItem } from '@kerfjs/ui/list-item';
|
|
6
|
+
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
7
|
+
import { PanelHeader } from '@kerfjs/ui/panel-header';
|
|
8
|
+
import { SegmentedControl } from '@kerfjs/ui/segmented-control';
|
|
9
|
+
import { Select } from '@kerfjs/ui/select';
|
|
10
|
+
import { StateBanner } from '@kerfjs/ui/state-banner';
|
|
11
|
+
import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
|
|
12
|
+
import { signal } from 'kerfjs';
|
|
13
|
+
import { Columns3, FileText, Inbox, List } from 'lucide';
|
|
14
|
+
|
|
15
|
+
import type { RecipeFactory } from './types.js';
|
|
16
|
+
|
|
17
|
+
const icon = (node: Parameters<typeof LucideIcon>[0]['icon'], name: string) => <LucideIcon icon={node} name={name} />;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A record inspector whose per-record values load asynchronously. Every
|
|
21
|
+
* value-bearing component takes `placeholder` from one loading flag, so the same
|
|
22
|
+
* chrome renders a faithful loading state and then the populated record — no
|
|
23
|
+
* separate skeleton markup. The composition is the point; `Skeleton` is the
|
|
24
|
+
* primitive it is built on (see the Skeleton component demo).
|
|
25
|
+
*/
|
|
26
|
+
export const createRecipe: RecipeFactory = (announce) => {
|
|
27
|
+
const loading = signal(true);
|
|
28
|
+
const render = () => {
|
|
29
|
+
const p = loading.value;
|
|
30
|
+
return <section class="kui-recipe recipe-inspector kui-recipe__surface kui-pane" data-recipe="recipe-loading-inspector" data-inspector-loading={String(p)}>
|
|
31
|
+
<PanelHeader
|
|
32
|
+
title="Ticket · KF-2048"
|
|
33
|
+
titleId="recipe-inspector-title"
|
|
34
|
+
summary="Restore keyboard focus after a dialog closes"
|
|
35
|
+
summaryId="recipe-inspector-summary"
|
|
36
|
+
icon={icon(FileText, 'file-text')}
|
|
37
|
+
placeholder={p}
|
|
38
|
+
actions={<button class="kui-recipe__button" data-primary="true" type="button" data-action="recipe-action" data-recipe-command="toggle">{p ? 'Show loaded' : 'Show loading'}</button>}
|
|
39
|
+
/>
|
|
40
|
+
<div class="recipe-inspector__body kui-pane__content kui-content">
|
|
41
|
+
<section>
|
|
42
|
+
<ValueTable label="Ticket details">
|
|
43
|
+
<ValueTableRow label="Status" value="In review" icon={icon(Inbox, 'inbox')} placeholder={p} />
|
|
44
|
+
<ValueTableRow label="Owner" value="Mara Lopez" placeholder={p} />
|
|
45
|
+
<ValueTableRow label="Priority" value="High" placeholder={p} />
|
|
46
|
+
<ValueTableRow label="Updated" value="2 hours ago" placeholder={p} />
|
|
47
|
+
</ValueTable>
|
|
48
|
+
</section>
|
|
49
|
+
<Select name="recipe-inspector-status" value="review" label="Status" placeholder={p} choices={[
|
|
50
|
+
{ value: 'review', label: 'In review' },
|
|
51
|
+
{ value: 'ready', label: 'Ready' },
|
|
52
|
+
{ value: 'done', label: 'Done' },
|
|
53
|
+
]} />
|
|
54
|
+
<SegmentedControl id="recipe-inspector-view" label="Inspector view" value="details" appearance="toolbar" shape="pill" size="small" placeholder={p} choices={[
|
|
55
|
+
{ value: 'details', label: 'Details', content: icon(List, 'list') },
|
|
56
|
+
{ value: 'activity', label: 'Activity', content: icon(Columns3, 'columns-3') },
|
|
57
|
+
{ value: 'files', label: 'Files', content: icon(FileText, 'file-text') },
|
|
58
|
+
]} />
|
|
59
|
+
<section>
|
|
60
|
+
<ListItem action="recipe-action" itemId="reassign" label="Reassign ticket" icon={icon(Inbox, 'inbox')} placeholder={p} />
|
|
61
|
+
<ListItem action="recipe-action" itemId="watch" label="Watch for changes" placeholder={p} />
|
|
62
|
+
</section>
|
|
63
|
+
{p
|
|
64
|
+
? <StateBanner tone="info" title="" detail="" icon={icon(FileText, 'file-text')} placeholder />
|
|
65
|
+
: <StateBanner tone="success" title="Up to date" detail="All checks passed on the latest revision." icon={icon(FileText, 'file-text')} />}
|
|
66
|
+
<p class="kui-recipe__ownership kui-content-item">Each value-bearing component's <code>placeholder</code> prop renders skeletons in its value slots while loading, so the recipe composes a faithful loading inspector from real chrome. The app owns the loading lifecycle and which values are still unknown.</p>
|
|
67
|
+
</div>
|
|
68
|
+
</section>;
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
render,
|
|
72
|
+
action(command) {
|
|
73
|
+
if (command === 'toggle') {
|
|
74
|
+
loading.value = !loading.value;
|
|
75
|
+
announce(loading.value ? 'Inspector is loading' : 'Inspector loaded');
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import '@kerfjs/ui/layout.css';
|
|
2
2
|
import './recipes.css';
|
|
3
3
|
|
|
4
|
+
import { ListHeader } from '@kerfjs/ui/list-header';
|
|
5
|
+
import { ListItem } from '@kerfjs/ui/list-item';
|
|
4
6
|
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
5
|
-
import { MenuHeader } from '@kerfjs/ui/menu-header';
|
|
6
|
-
import { MenuItem } from '@kerfjs/ui/menu-item';
|
|
7
7
|
import { Toolbar } from '@kerfjs/ui/toolbar';
|
|
8
8
|
import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
|
|
9
9
|
import { signal } from 'kerfjs';
|
|
@@ -16,11 +16,11 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
16
16
|
const expanded = signal(true);
|
|
17
17
|
const render = () => <aside class="kui-recipe recipe-sidebar kui-recipe__surface kui-pane" data-recipe="recipe-navigation-sidebar">
|
|
18
18
|
<div class="recipe-sidebar__body kui-pane__content kui-content">
|
|
19
|
-
<section><
|
|
20
|
-
<section><
|
|
19
|
+
<section><ListHeader label="Workspace" count={3} countLabel="3 workspaces" action="recipe-action" actionLabel="Add workspace item" actionIcon={<LucideIcon icon={Plus} name="plus" />} /><ListItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} trailing={<span>12</span>} selected={selected.value === 'inbox'} /><ListItem action="recipe-action" itemId="shared" label="Shared with me" icon={<LucideIcon icon={Users} name="users" />} selected={selected.value === 'shared'} /><ListItem action="recipe-action" itemId="drafts" label="Drafts without an icon" selected={selected.value === 'drafts'} /></section>
|
|
20
|
+
<section><ListHeader label="Projects" toggle action="recipe-action" expanded={expanded.value} />{expanded.value && <><ListItem action="recipe-action" itemId="design" label="Design system rollout across desktop and tablet" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'design'} multiline /><ListItem action="recipe-action" itemId="archive" label="Archived" disabled title="Available to administrators" /><div class="kui-content-item"><strong>Quarterly goal</strong><p class="kui-recipe__muted">Ship accessible navigation patterns to every workspace.</p></div></>}</section>
|
|
21
21
|
<p class="kui-recipe__ownership kui-content-item">The recipe owns pane, content-item, and toolbar geometry. The app owns routes, permissions, labels, and disclosure state.</p>
|
|
22
22
|
</div>
|
|
23
23
|
<footer class="recipe-sidebar__footer kui-pane__footer"><Toolbar label="Sidebar actions" divider={false} leading={<ToolbarControlGroup appearance="borderless" single><button type="button" aria-label="Add project" data-action="recipe-action" data-recipe-command="add"><LucideIcon icon={Plus} name="plus" /></button></ToolbarControlGroup>} trailing={<ToolbarControlGroup appearance="borderless" single><button type="button" aria-label="Workspace settings" data-action="recipe-action" data-recipe-command="settings"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup>} /></footer>
|
|
24
24
|
</aside>;
|
|
25
|
-
return { render, action(command, element) { if (element.matches('.kui-
|
|
25
|
+
return { render, action(command, element) { if (element.matches('.kui-list-header__toggle')) { expanded.value = !expanded.value; announce(expanded.value ? 'Projects expanded' : 'Projects collapsed'); return; } const id = element.dataset.itemId; if (id) selected.value = id; announce(id ? `Selected ${id}` : `${command} requested`); } };
|
|
26
26
|
};
|
|
@@ -3,9 +3,9 @@ import '@kerfjs/ui/nav-stack.css';
|
|
|
3
3
|
import '@kerfjs/ui/toolbar-text.css';
|
|
4
4
|
import './recipes.css';
|
|
5
5
|
|
|
6
|
+
import { ListHeader } from '@kerfjs/ui/list-header';
|
|
7
|
+
import { ListItem } from '@kerfjs/ui/list-item';
|
|
6
8
|
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
7
|
-
import { MenuHeader } from '@kerfjs/ui/menu-header';
|
|
8
|
-
import { MenuItem } from '@kerfjs/ui/menu-item';
|
|
9
9
|
import { NavStack, type NavStackView } from '@kerfjs/ui/nav-stack';
|
|
10
10
|
import { signal } from 'kerfjs';
|
|
11
11
|
import { ChevronRight, FileText, Folder } from 'lucide';
|
|
@@ -30,8 +30,8 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
30
30
|
title: 'Library',
|
|
31
31
|
content: <div class="recipe-navstack__view kui-pane__content kui-content">
|
|
32
32
|
<section>
|
|
33
|
-
<
|
|
34
|
-
{ITEMS.map((item) => <
|
|
33
|
+
<ListHeader label="Components" />
|
|
34
|
+
{ITEMS.map((item) => <ListItem action="recipe-action" itemId={item.id} label={item.label} icon={<LucideIcon icon={Folder} name="folder" />} trailing={<LucideIcon icon={ChevronRight} name="chevron-right" />} />)}
|
|
35
35
|
</section>
|
|
36
36
|
<p class="kui-recipe__ownership kui-content-item">The recipe owns the stack as a signal of views and pushes/pops it; `NavStack` renders the stack and `wireNavStack` slides the content and settles the chrome. The app owns selection, data, and routing.</p>
|
|
37
37
|
</div>,
|
|
@@ -114,23 +114,25 @@
|
|
|
114
114
|
margin: 0;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
.recipe-
|
|
117
|
+
.recipe-list-detail {
|
|
118
118
|
display: grid;
|
|
119
119
|
width: 100%;
|
|
120
120
|
min-height: 25rem;
|
|
121
121
|
grid-template-columns: minmax(13rem, 0.8fr) minmax(18rem, 1.4fr);
|
|
122
122
|
}
|
|
123
|
-
.recipe-
|
|
123
|
+
.recipe-list-detail__list {
|
|
124
124
|
border-inline-end: 1px solid var(--kui-color-border);
|
|
125
125
|
}
|
|
126
|
-
.recipe-
|
|
126
|
+
.recipe-list-detail__detail {
|
|
127
127
|
min-width: 0;
|
|
128
|
+
}
|
|
129
|
+
.recipe-list-detail__body {
|
|
128
130
|
padding-block: var(--kui-layout-item-gap);
|
|
129
131
|
}
|
|
130
|
-
.recipe-
|
|
132
|
+
.recipe-list-detail__title {
|
|
131
133
|
margin-inline: var(--kui-layout-inline-margin, remify(8px));
|
|
132
134
|
}
|
|
133
|
-
.recipe-
|
|
135
|
+
.recipe-list-detail__actions {
|
|
134
136
|
margin-inline: var(--kui-layout-inline-margin, remify(8px));
|
|
135
137
|
}
|
|
136
138
|
.recipe-dialog {
|
|
@@ -256,10 +258,10 @@
|
|
|
256
258
|
.recipe-compact-toolbar__guide {
|
|
257
259
|
grid-template-columns: 1fr 1fr;
|
|
258
260
|
}
|
|
259
|
-
.recipe-
|
|
261
|
+
.recipe-list-detail {
|
|
260
262
|
grid-template-columns: 1fr;
|
|
261
263
|
}
|
|
262
|
-
.recipe-
|
|
264
|
+
.recipe-list-detail__list {
|
|
263
265
|
border-inline-end: 0;
|
|
264
266
|
border-block-end: 1px solid var(--kui-color-border);
|
|
265
267
|
}
|
|
@@ -353,3 +355,7 @@
|
|
|
353
355
|
little block breathing room, not an inline pad that would double-inset them. */
|
|
354
356
|
padding-block: var(--kui-space-2xs);
|
|
355
357
|
}
|
|
358
|
+
|
|
359
|
+
.recipe-inspector {
|
|
360
|
+
width: min(remify(560px), 100%);
|
|
361
|
+
}
|
package/dist/menu-action-row.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"menu-action-row.js"}
|
package/dist/menu-header.js
DELETED
package/dist/menu-header.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"menu-header.js"}
|
package/dist/menu-item.js
DELETED
package/dist/menu-item.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"menu-item.js"}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
.kui-menu-action-row {
|
|
2
|
-
--kui-menu-action-row-color: var(--kui-color-neutral-on-normal);
|
|
3
|
-
--kui-menu-action-row-border: transparent;
|
|
4
|
-
--kui-menu-action-row-background: transparent;
|
|
5
|
-
--kui-menu-action-row-hover-border: var(--kui-color-neutral-border-normal);
|
|
6
|
-
--kui-menu-action-row-hover-background: var(--kui-color-neutral-fill-quiet);
|
|
7
|
-
--kui-menu-action-row-selected-color: var(--kui-color-brand-on-quiet);
|
|
8
|
-
--kui-menu-action-row-selected-border: var(--kui-color-brand-border-quiet);
|
|
9
|
-
--kui-menu-action-row-selected-background: var(--kui-color-brand-fill-normal);
|
|
10
|
-
--kui-menu-action-row-trailing-hover-background: var(
|
|
11
|
-
--kui-color-neutral-fill-normal
|
|
12
|
-
);
|
|
13
|
-
display: flex;
|
|
14
|
-
width: calc(100% - (2 * var(--kui-layout-inline-margin, 0.5rem)));
|
|
15
|
-
min-height: 2.75rem;
|
|
16
|
-
margin-inline: var(--kui-layout-inline-margin, 0.5rem);
|
|
17
|
-
border-radius: var(--kui-layout-rounded-radius, calc(1px + 0.6875rem));
|
|
18
|
-
color: var(--kui-menu-action-row-color);
|
|
19
|
-
background: var(--kui-menu-action-row-background);
|
|
20
|
-
box-shadow: inset 0 0 0 1px var(--kui-menu-action-row-border);
|
|
21
|
-
align-items: stretch;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.kui-menu-action-row:has(.kui-menu-action-row__primary:hover) {
|
|
25
|
-
--kui-menu-action-row-border: var(--kui-menu-action-row-hover-border);
|
|
26
|
-
--kui-menu-action-row-background: var(--kui-menu-action-row-hover-background);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.kui-menu-action-row[data-selected="true"],
|
|
30
|
-
.kui-menu-action-row[data-pressed="true"] {
|
|
31
|
-
--kui-menu-action-row-border: var(--kui-menu-action-row-selected-border);
|
|
32
|
-
--kui-menu-action-row-background: var(
|
|
33
|
-
--kui-menu-action-row-selected-background
|
|
34
|
-
);
|
|
35
|
-
color: var(--kui-menu-action-row-selected-color);
|
|
36
|
-
font-weight: 650;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.kui-menu-action-row:has(.kui-menu-action-row__primary:focus-visible) {
|
|
40
|
-
outline: var(--kui-focus-ring);
|
|
41
|
-
outline-offset: 1px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.kui-menu-action-row__primary,
|
|
45
|
-
.kui-menu-action-row__trailing-action {
|
|
46
|
-
height: auto !important;
|
|
47
|
-
min-height: 2.75rem;
|
|
48
|
-
padding: 0;
|
|
49
|
-
border: 0;
|
|
50
|
-
appearance: none;
|
|
51
|
-
color: inherit;
|
|
52
|
-
background: transparent;
|
|
53
|
-
cursor: pointer;
|
|
54
|
-
font: inherit;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.kui-menu-action-row__primary {
|
|
58
|
-
position: relative;
|
|
59
|
-
display: block;
|
|
60
|
-
flex: 1 1 auto;
|
|
61
|
-
min-width: 2.75rem;
|
|
62
|
-
padding: var(--kui-layout-item-padding, 0.5rem);
|
|
63
|
-
padding-inline-start: calc(1px + var(--kui-layout-item-padding, 0.5rem));
|
|
64
|
-
text-align: start;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.kui-menu-action-row__primary:focus-visible {
|
|
68
|
-
outline: none;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.kui-menu-action-row__primary:disabled,
|
|
72
|
-
.kui-menu-action-row__trailing-action:disabled {
|
|
73
|
-
cursor: not-allowed;
|
|
74
|
-
opacity: 0.58;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.kui-menu-action-row__icon {
|
|
78
|
-
position: absolute;
|
|
79
|
-
inset-block-start: 50%;
|
|
80
|
-
inset-inline-start: calc(1px + var(--kui-layout-item-padding, 0.5rem));
|
|
81
|
-
display: grid;
|
|
82
|
-
width: 1.5rem;
|
|
83
|
-
height: 1.5rem;
|
|
84
|
-
color: currentColor;
|
|
85
|
-
transform: translateY(-50%);
|
|
86
|
-
place-items: center;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.kui-menu-action-row__icon svg {
|
|
90
|
-
display: block;
|
|
91
|
-
width: 1.5rem;
|
|
92
|
-
height: 1.5rem;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.kui-menu-action-row__label {
|
|
96
|
-
display: grid;
|
|
97
|
-
min-width: 0;
|
|
98
|
-
min-height: calc(
|
|
99
|
-
2.75rem - (2 * var(--kui-layout-item-padding, 0.5rem))
|
|
100
|
-
);
|
|
101
|
-
overflow: hidden;
|
|
102
|
-
text-overflow: ellipsis;
|
|
103
|
-
white-space: nowrap;
|
|
104
|
-
align-items: center;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.kui-menu-action-row[data-has-icon="true"] .kui-menu-action-row__label {
|
|
108
|
-
margin-inline-start: calc(
|
|
109
|
-
1.5rem + var(--kui-layout-item-gap, 0.5rem)
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.kui-menu-action-row[data-multiline="true"] .kui-menu-action-row__label {
|
|
114
|
-
display: block;
|
|
115
|
-
overflow: visible;
|
|
116
|
-
overflow-wrap: anywhere;
|
|
117
|
-
white-space: normal;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.kui-menu-action-row[data-multiline="true"] .kui-menu-action-row__icon {
|
|
121
|
-
inset-block-start: calc(
|
|
122
|
-
var(--kui-layout-item-padding, 0.5rem) + (1lh / 2)
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.kui-menu-action-row__trailing-action {
|
|
127
|
-
display: grid;
|
|
128
|
-
width: 2.75rem;
|
|
129
|
-
min-width: 2.75rem;
|
|
130
|
-
border-radius: var(--kui-radius-pill);
|
|
131
|
-
place-items: center;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.kui-menu-action-row__trailing-action:hover:not(:disabled) {
|
|
135
|
-
background: var(--kui-menu-action-row-trailing-hover-background);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.kui-menu-action-row__trailing-action:focus-visible {
|
|
139
|
-
outline: var(--kui-focus-ring);
|
|
140
|
-
outline-offset: -2px;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.kui-menu-action-row__trailing-action > svg {
|
|
144
|
-
width: 1rem;
|
|
145
|
-
height: 1rem;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@media (forced-colors: active) {
|
|
149
|
-
.kui-menu-action-row {
|
|
150
|
-
--kui-menu-action-row-border: ButtonText;
|
|
151
|
-
outline: 1px solid ButtonText;
|
|
152
|
-
outline-offset: -1px;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.kui-menu-action-row[data-selected="true"],
|
|
156
|
-
.kui-menu-action-row[data-pressed="true"] {
|
|
157
|
-
--kui-menu-action-row-selected-color: HighlightText;
|
|
158
|
-
--kui-menu-action-row-selected-background: Highlight;
|
|
159
|
-
}
|
|
160
|
-
}
|