@poodle64/ui 2026.9.19 → 2026.9.20
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 +58 -0
- package/dist/components/ui/list-shell/index.d.ts +2 -0
- package/dist/components/ui/list-shell/index.js +2 -0
- package/dist/components/ui/list-shell/list-shell.svelte +149 -0
- package/dist/components/ui/list-shell/list-shell.svelte.d.ts +103 -0
- package/package.json +1 -1
- package/registry/component-map.json +10 -3
- package/registry/component-map.md +3 -2
package/README.md
CHANGED
|
@@ -402,6 +402,64 @@ all three serve any route:
|
|
|
402
402
|
truncates. Carry one or the other. A page writes a run of `Panel`s and no
|
|
403
403
|
wrapper: the pane stacks them on the package's section rhythm itself.
|
|
404
404
|
|
|
405
|
+
### A working list beside a page's own content
|
|
406
|
+
|
|
407
|
+
AppShell has no second-column slot, on purpose: a module's own **pages**
|
|
408
|
+
belong in the rail (`NavItem.children`), never a hand-rolled column beside it.
|
|
409
|
+
A module's **data** (a queue, an inbox, a working list of things waiting on
|
|
410
|
+
the user) is a different situation, not navigation, so it belongs in the page
|
|
411
|
+
beside the content it serves, exactly as the settings destination does.
|
|
412
|
+
`ListShell` is that shape: a foldable list, in the rail's own chrome, that
|
|
413
|
+
vanishes entirely when closed and hands the freed width back to the page.
|
|
414
|
+
|
|
415
|
+
```svelte
|
|
416
|
+
<script lang="ts">
|
|
417
|
+
import AppShell from '@poodle64/ui/app-shell';
|
|
418
|
+
import ListShell from '@poodle64/ui/list-shell';
|
|
419
|
+
|
|
420
|
+
// Bind it to whatever already persists a user preference for this module:
|
|
421
|
+
// a module-scoped `.svelte.ts` store backed by localStorage, or the app's
|
|
422
|
+
// own settings. ListShell holds no storage of its own.
|
|
423
|
+
let listOpen = $state(true);
|
|
424
|
+
</script>
|
|
425
|
+
|
|
426
|
+
<!-- padded={false}: the list has to reach the content area's edge, flush
|
|
427
|
+
against the rail, or it reads as a floating card. -->
|
|
428
|
+
<AppShell {nav} currentPath={page.url.pathname} padded={false}>
|
|
429
|
+
<div class="flex min-h-0 flex-1">
|
|
430
|
+
<ListShell title="Waiting on you" bind:open={listOpen}>
|
|
431
|
+
{#snippet actions()}
|
|
432
|
+
<Button size="icon" variant="outline" aria-label="Add documents"><Plus /></Button>
|
|
433
|
+
{/snippet}
|
|
434
|
+
<!-- tabs, group headings, rows: entirely this module's own -->
|
|
435
|
+
</ListShell>
|
|
436
|
+
<div class="min-w-0 flex-1">
|
|
437
|
+
{#if !listOpen}
|
|
438
|
+
<!-- The reopen control is this module's own markup, at whatever
|
|
439
|
+
position it chooses; ListShell never places it. -->
|
|
440
|
+
<Button onclick={() => (listOpen = true)} aria-expanded={listOpen}>
|
|
441
|
+
Waiting on you <Badge>{waitingCount}</Badge>
|
|
442
|
+
</Button>
|
|
443
|
+
{/if}
|
|
444
|
+
<!-- the module's own content -->
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
</AppShell>
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
| Prop | Purpose |
|
|
451
|
+
| ---------- | ----------------------------------------------------------------------------- |
|
|
452
|
+
| `title` | The list's heading, in the shell's display face. |
|
|
453
|
+
| `open` | Bindable. Not persisted by this component; bind it to what already persists a preference for this module. |
|
|
454
|
+
| `label` | The landmark's accessible name. Defaults to `title`. |
|
|
455
|
+
| `actions` | Leading header actions beside the title: an "Add" button, a filter. |
|
|
456
|
+
|
|
457
|
+
Width is fixed at 25rem (400px) and not a prop, the same anti-drift discipline
|
|
458
|
+
`SettingsShell` applies to its own 240px: one width, chosen once, rather than
|
|
459
|
+
every module guessing its own. Below `md` this never renders, at either state,
|
|
460
|
+
since a 400px column has nowhere to go beside a rail that is already a drawer
|
|
461
|
+
there; a module's phone view is its own route.
|
|
462
|
+
|
|
405
463
|
### The content measure
|
|
406
464
|
|
|
407
465
|
How wide the page body may get, chosen once in the layout from a scale of four:
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ListShell — a module's working list, folded away and brought back by the
|
|
4
|
+
* app that owns it, beside the content it serves.
|
|
5
|
+
*
|
|
6
|
+
* AppShell carries no second-column slot for this on purpose: `sidebar` was
|
|
7
|
+
* such a slot until 2026.8.11, and what it produced was two apps rendering a
|
|
8
|
+
* module's own PAGES beside the rail while the other seven rendered them
|
|
9
|
+
* inside it — the shell shape differing per app, and in one app per module.
|
|
10
|
+
* Operator ruling, 21/08/2026: no app supports an additional sidebar; a
|
|
11
|
+
* section's own pages roll out beneath it in the rail (`NavItem.children`).
|
|
12
|
+
* That ruling is about NAVIGATION. A working list is not navigation — it is
|
|
13
|
+
* data the module owns (a queue of things waiting on the user), and
|
|
14
|
+
* `20-sveltekit-frontend.md` already answers where that belongs: "a column
|
|
15
|
+
* that is not NavItem-shaped … belongs in the page beside the content it
|
|
16
|
+
* serves." `ContextColumn` is the household's other proof this is a real,
|
|
17
|
+
* sanctioned shape — a page-level flanking `<aside>`, not a shell slot.
|
|
18
|
+
*
|
|
19
|
+
* So this is a companion to AppShell, exactly as `SettingsShell` is: the
|
|
20
|
+
* shell provides ONE chrome (the rail, the bar, the content frame), and any
|
|
21
|
+
* module reaches for this to add a foldable list beside its own content,
|
|
22
|
+
* inside AppShell's content area. Every module gets the same list, not a
|
|
23
|
+
* hand-rolled column per module — which is the actual thing the 21/08/2026
|
|
24
|
+
* ruling exists to prevent, still true one level down.
|
|
25
|
+
*
|
|
26
|
+
* USE IT WITH `padded={false}` on AppShell, exactly as `SettingsShell`
|
|
27
|
+
* requires: the list has to reach the content area's edge — flush against
|
|
28
|
+
* the rail, with no gap — or it reads as a floating card rather than chrome.
|
|
29
|
+
* It paints itself in the rail's OWN chrome tokens (`bg-shell`,
|
|
30
|
+
* `text-shell-foreground`) for the same reason: a list that is chrome reads
|
|
31
|
+
* as chrome, not as a panel someone built inside the page.
|
|
32
|
+
*
|
|
33
|
+
* Width is fixed at 25rem (400px), measured off the operator's approved
|
|
34
|
+
* mockup (radar-hooves/godswood#839, Focus-List.dc.html) rather than picked,
|
|
35
|
+
* and not exposed as a prop — the same anti-drift discipline `SettingsShell`
|
|
36
|
+
* applies to its own 240px: one width, chosen once, or ten modules each
|
|
37
|
+
* guess their own.
|
|
38
|
+
*
|
|
39
|
+
* Open/closed is the CALLER's state (`open`, bindable), the same way
|
|
40
|
+
* AppShell's own `collapsed` is: bind it to persist the choice across
|
|
41
|
+
* reloads (a module-scoped `$state` in a `.svelte.ts` file, synced to
|
|
42
|
+
* `localStorage` — `svelte-basics.md` §shared state — or the app's own
|
|
43
|
+
* settings store). This component holds no storage of its own, on purpose:
|
|
44
|
+
* a module that already persists user preferences server-side should not
|
|
45
|
+
* gain a second, disagreeing mechanism here.
|
|
46
|
+
*
|
|
47
|
+
* When `open` is false this renders NOTHING — no placeholder, no rail — so
|
|
48
|
+
* the content beside it gets the freed width back, exactly like the
|
|
49
|
+
* operator's mockup (Focus-Run.dc.html). Reopening it is deliberately NOT
|
|
50
|
+
* this component's job: the fold toggle only exists while the list is open
|
|
51
|
+
* (inside its own header, where the mockup puts it); the reopen affordance
|
|
52
|
+
* — a "Waiting on you 338" pill, a bare icon, wherever the module wants it —
|
|
53
|
+
* is the CALLER's own markup, driving the same bound `open`. Fixing that
|
|
54
|
+
* button's position here would be exactly the one-shape-per-module drift
|
|
55
|
+
* this file exists to end; the module's own page already earned that
|
|
56
|
+
* decision by owning the content beside this list. A caller's reopen
|
|
57
|
+
* control should carry `aria-expanded={open}` itself, so the two controls
|
|
58
|
+
* (this one, and the caller's) read as one continuous toggle to a screen
|
|
59
|
+
* reader even though only one is ever in the document at a time:
|
|
60
|
+
*
|
|
61
|
+
* <button onclick={() => (open = true)} aria-expanded={open}>
|
|
62
|
+
* Waiting on you <Badge>{waitingCount}</Badge>
|
|
63
|
+
* </button>
|
|
64
|
+
*
|
|
65
|
+
* Below `md` this never renders, at either state: the rail is already a
|
|
66
|
+
* drawer there, and a 400px column has nowhere to go beside it. Godswood's
|
|
67
|
+
* own answer is a dedicated phone route for the same list (Phone.dc.html);
|
|
68
|
+
* that choice is the app's, not this component's — it only gets out of the
|
|
69
|
+
* way so the app's own mobile route can render undisturbed.
|
|
70
|
+
*
|
|
71
|
+
* The header is chrome (title, the fold toggle, and an optional leading
|
|
72
|
+
* `actions` snippet for a module-specific control — an "Add" button in the
|
|
73
|
+
* mockup). Everything below it — tabs, grouped headings, the rows
|
|
74
|
+
* themselves — is the module's own `children`, exactly as `SettingsShell`
|
|
75
|
+
* hands its list body to the caller rather than describing a row shape of
|
|
76
|
+
* its own. A queue is not the only thing this list will ever hold.
|
|
77
|
+
*/
|
|
78
|
+
import type { Snippet } from 'svelte';
|
|
79
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
80
|
+
import { cn, type WithElementRef } from '../../../utils.js';
|
|
81
|
+
import PanelLeftClose from '@lucide/svelte/icons/panel-left-close';
|
|
82
|
+
|
|
83
|
+
let {
|
|
84
|
+
title,
|
|
85
|
+
label = title,
|
|
86
|
+
open = $bindable(true),
|
|
87
|
+
actions,
|
|
88
|
+
children,
|
|
89
|
+
ref = $bindable(null),
|
|
90
|
+
class: className,
|
|
91
|
+
...restProps
|
|
92
|
+
}: WithElementRef<HTMLAttributes<HTMLElement>> & {
|
|
93
|
+
/** The list's heading, in the shell's display face. */
|
|
94
|
+
title: string;
|
|
95
|
+
/**
|
|
96
|
+
* The landmark's accessible name. Defaults to `title` — the visible
|
|
97
|
+
* heading already says what this is, so a module only sets `label`
|
|
98
|
+
* separately when the heading text would not stand alone as a name
|
|
99
|
+
* (WCAG 2.4.1), e.g. a heading that leads with a live count.
|
|
100
|
+
*/
|
|
101
|
+
label?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Whether the list is open. Bindable, and NOT persisted by this
|
|
104
|
+
* component — bind it to whatever the module already uses to persist a
|
|
105
|
+
* user preference across a reload.
|
|
106
|
+
*/
|
|
107
|
+
open?: boolean;
|
|
108
|
+
/** Leading header actions beside the title — an "Add" button, a filter. */
|
|
109
|
+
actions?: Snippet;
|
|
110
|
+
/** The list body: tabs, group headings, rows — entirely the module's own. */
|
|
111
|
+
children: Snippet;
|
|
112
|
+
} = $props();
|
|
113
|
+
|
|
114
|
+
const listId = $props.id();
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
{#if open}
|
|
118
|
+
<aside
|
|
119
|
+
bind:this={ref}
|
|
120
|
+
id={listId}
|
|
121
|
+
aria-label={label}
|
|
122
|
+
class={cn(
|
|
123
|
+
'bg-shell text-shell-foreground border-border hidden w-[25rem] flex-none flex-col border-e md:flex',
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
{...restProps}
|
|
127
|
+
>
|
|
128
|
+
<div class="flex flex-none items-center gap-2 px-5 pt-5 pb-3">
|
|
129
|
+
<h2 class="font-display text-display flex-1 truncate font-semibold tracking-tight">
|
|
130
|
+
{title}
|
|
131
|
+
</h2>
|
|
132
|
+
{#if actions}{@render actions()}{/if}
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
onclick={() => (open = false)}
|
|
136
|
+
aria-expanded="true"
|
|
137
|
+
aria-controls={listId}
|
|
138
|
+
aria-label={`Fold ${label} away`}
|
|
139
|
+
title={`Fold ${label} away`}
|
|
140
|
+
class="text-shell-muted-foreground hover:text-shell-foreground border-border grid size-9 flex-none place-items-center rounded-md border transition-colors"
|
|
141
|
+
>
|
|
142
|
+
<PanelLeftClose class="size-4" />
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
|
|
146
|
+
{@render children()}
|
|
147
|
+
</div>
|
|
148
|
+
</aside>
|
|
149
|
+
{/if}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListShell — a module's working list, folded away and brought back by the
|
|
3
|
+
* app that owns it, beside the content it serves.
|
|
4
|
+
*
|
|
5
|
+
* AppShell carries no second-column slot for this on purpose: `sidebar` was
|
|
6
|
+
* such a slot until 2026.8.11, and what it produced was two apps rendering a
|
|
7
|
+
* module's own PAGES beside the rail while the other seven rendered them
|
|
8
|
+
* inside it — the shell shape differing per app, and in one app per module.
|
|
9
|
+
* Operator ruling, 21/08/2026: no app supports an additional sidebar; a
|
|
10
|
+
* section's own pages roll out beneath it in the rail (`NavItem.children`).
|
|
11
|
+
* That ruling is about NAVIGATION. A working list is not navigation — it is
|
|
12
|
+
* data the module owns (a queue of things waiting on the user), and
|
|
13
|
+
* `20-sveltekit-frontend.md` already answers where that belongs: "a column
|
|
14
|
+
* that is not NavItem-shaped … belongs in the page beside the content it
|
|
15
|
+
* serves." `ContextColumn` is the household's other proof this is a real,
|
|
16
|
+
* sanctioned shape — a page-level flanking `<aside>`, not a shell slot.
|
|
17
|
+
*
|
|
18
|
+
* So this is a companion to AppShell, exactly as `SettingsShell` is: the
|
|
19
|
+
* shell provides ONE chrome (the rail, the bar, the content frame), and any
|
|
20
|
+
* module reaches for this to add a foldable list beside its own content,
|
|
21
|
+
* inside AppShell's content area. Every module gets the same list, not a
|
|
22
|
+
* hand-rolled column per module — which is the actual thing the 21/08/2026
|
|
23
|
+
* ruling exists to prevent, still true one level down.
|
|
24
|
+
*
|
|
25
|
+
* USE IT WITH `padded={false}` on AppShell, exactly as `SettingsShell`
|
|
26
|
+
* requires: the list has to reach the content area's edge — flush against
|
|
27
|
+
* the rail, with no gap — or it reads as a floating card rather than chrome.
|
|
28
|
+
* It paints itself in the rail's OWN chrome tokens (`bg-shell`,
|
|
29
|
+
* `text-shell-foreground`) for the same reason: a list that is chrome reads
|
|
30
|
+
* as chrome, not as a panel someone built inside the page.
|
|
31
|
+
*
|
|
32
|
+
* Width is fixed at 25rem (400px), measured off the operator's approved
|
|
33
|
+
* mockup (radar-hooves/godswood#839, Focus-List.dc.html) rather than picked,
|
|
34
|
+
* and not exposed as a prop — the same anti-drift discipline `SettingsShell`
|
|
35
|
+
* applies to its own 240px: one width, chosen once, or ten modules each
|
|
36
|
+
* guess their own.
|
|
37
|
+
*
|
|
38
|
+
* Open/closed is the CALLER's state (`open`, bindable), the same way
|
|
39
|
+
* AppShell's own `collapsed` is: bind it to persist the choice across
|
|
40
|
+
* reloads (a module-scoped `$state` in a `.svelte.ts` file, synced to
|
|
41
|
+
* `localStorage` — `svelte-basics.md` §shared state — or the app's own
|
|
42
|
+
* settings store). This component holds no storage of its own, on purpose:
|
|
43
|
+
* a module that already persists user preferences server-side should not
|
|
44
|
+
* gain a second, disagreeing mechanism here.
|
|
45
|
+
*
|
|
46
|
+
* When `open` is false this renders NOTHING — no placeholder, no rail — so
|
|
47
|
+
* the content beside it gets the freed width back, exactly like the
|
|
48
|
+
* operator's mockup (Focus-Run.dc.html). Reopening it is deliberately NOT
|
|
49
|
+
* this component's job: the fold toggle only exists while the list is open
|
|
50
|
+
* (inside its own header, where the mockup puts it); the reopen affordance
|
|
51
|
+
* — a "Waiting on you 338" pill, a bare icon, wherever the module wants it —
|
|
52
|
+
* is the CALLER's own markup, driving the same bound `open`. Fixing that
|
|
53
|
+
* button's position here would be exactly the one-shape-per-module drift
|
|
54
|
+
* this file exists to end; the module's own page already earned that
|
|
55
|
+
* decision by owning the content beside this list. A caller's reopen
|
|
56
|
+
* control should carry `aria-expanded={open}` itself, so the two controls
|
|
57
|
+
* (this one, and the caller's) read as one continuous toggle to a screen
|
|
58
|
+
* reader even though only one is ever in the document at a time:
|
|
59
|
+
*
|
|
60
|
+
* <button onclick={() => (open = true)} aria-expanded={open}>
|
|
61
|
+
* Waiting on you <Badge>{waitingCount}</Badge>
|
|
62
|
+
* </button>
|
|
63
|
+
*
|
|
64
|
+
* Below `md` this never renders, at either state: the rail is already a
|
|
65
|
+
* drawer there, and a 400px column has nowhere to go beside it. Godswood's
|
|
66
|
+
* own answer is a dedicated phone route for the same list (Phone.dc.html);
|
|
67
|
+
* that choice is the app's, not this component's — it only gets out of the
|
|
68
|
+
* way so the app's own mobile route can render undisturbed.
|
|
69
|
+
*
|
|
70
|
+
* The header is chrome (title, the fold toggle, and an optional leading
|
|
71
|
+
* `actions` snippet for a module-specific control — an "Add" button in the
|
|
72
|
+
* mockup). Everything below it — tabs, grouped headings, the rows
|
|
73
|
+
* themselves — is the module's own `children`, exactly as `SettingsShell`
|
|
74
|
+
* hands its list body to the caller rather than describing a row shape of
|
|
75
|
+
* its own. A queue is not the only thing this list will ever hold.
|
|
76
|
+
*/
|
|
77
|
+
import type { Snippet } from 'svelte';
|
|
78
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
79
|
+
import { type WithElementRef } from '../../../utils.js';
|
|
80
|
+
type $$ComponentProps = WithElementRef<HTMLAttributes<HTMLElement>> & {
|
|
81
|
+
/** The list's heading, in the shell's display face. */
|
|
82
|
+
title: string;
|
|
83
|
+
/**
|
|
84
|
+
* The landmark's accessible name. Defaults to `title` — the visible
|
|
85
|
+
* heading already says what this is, so a module only sets `label`
|
|
86
|
+
* separately when the heading text would not stand alone as a name
|
|
87
|
+
* (WCAG 2.4.1), e.g. a heading that leads with a live count.
|
|
88
|
+
*/
|
|
89
|
+
label?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Whether the list is open. Bindable, and NOT persisted by this
|
|
92
|
+
* component — bind it to whatever the module already uses to persist a
|
|
93
|
+
* user preference across a reload.
|
|
94
|
+
*/
|
|
95
|
+
open?: boolean;
|
|
96
|
+
/** Leading header actions beside the title — an "Add" button, a filter. */
|
|
97
|
+
actions?: Snippet;
|
|
98
|
+
/** The list body: tabs, group headings, rows — entirely the module's own. */
|
|
99
|
+
children: Snippet;
|
|
100
|
+
};
|
|
101
|
+
declare const ListShell: import("svelte").Component<$$ComponentProps, {}, "open" | "ref">;
|
|
102
|
+
type ListShell = ReturnType<typeof ListShell>;
|
|
103
|
+
export default ListShell;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/ui",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.20",
|
|
4
4
|
"description": "Household shared component layer: shadcn-svelte primitives (bits-ui) plus the composed page chrome every app builds its routes from, restyled by each app's @poodle64/design-tokens alias layer. One fix reaches every app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"package": "@poodle64/ui",
|
|
4
|
-
"version": "2026.9.
|
|
4
|
+
"version": "2026.9.20",
|
|
5
5
|
"generatedBy": "scripts/generate-registry.mjs",
|
|
6
6
|
"source": "scripts/situations.json + package source (DO NOT EDIT the outputs by hand)",
|
|
7
|
-
"componentCount":
|
|
7
|
+
"componentCount": 61,
|
|
8
8
|
"situationCount": 17
|
|
9
9
|
},
|
|
10
10
|
"situations": [
|
|
@@ -298,7 +298,7 @@
|
|
|
298
298
|
{
|
|
299
299
|
"key": "page-frame",
|
|
300
300
|
"title": "The frame every route shares",
|
|
301
|
-
"description": "The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column.",
|
|
301
|
+
"description": "The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column, a foldable working list beside the page's own content.",
|
|
302
302
|
"components": [
|
|
303
303
|
{
|
|
304
304
|
"name": "AppShell",
|
|
@@ -335,6 +335,13 @@
|
|
|
335
335
|
"props": "stats, statsTitle?, statsInfo?, detail?, ariaLabel?",
|
|
336
336
|
"insteadOf": "a hand-built right-hand <aside> with a stat card and a detail pane"
|
|
337
337
|
},
|
|
338
|
+
{
|
|
339
|
+
"name": "ListShell",
|
|
340
|
+
"dir": "list-shell",
|
|
341
|
+
"import": "@poodle64/ui/list-shell",
|
|
342
|
+
"props": "title, children, label?, open?, actions?",
|
|
343
|
+
"insteadOf": "a module hand-rolling its own foldable second column beside the rail — a queue, an inbox, a working list"
|
|
344
|
+
},
|
|
338
345
|
{
|
|
339
346
|
"name": "AppIdentity",
|
|
340
347
|
"dir": "app-identity",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @poodle64/ui — situation → component map
|
|
2
2
|
|
|
3
3
|
<!-- GENERATED by scripts/generate-registry.mjs from scripts/situations.json + package source. DO NOT EDIT. -->
|
|
4
|
-
Generated from `@poodle64/ui@2026.9.
|
|
4
|
+
Generated from `@poodle64/ui@2026.9.20`. 61 components, 17 situations.
|
|
5
5
|
|
|
6
6
|
**Read this before writing a `<div>`.** Find the SITUATION you are in below, then compose the component named for it — do not hand-build it from raw `Card` or utility classes. Import is `import { Name } from '<import path>'`. Props marked `?` are optional. This map is the retrieval step the [`frontend-design` skill] makes mandatory; the [CHI 2026 study] measured composing-from-a-registry at 95% design-system compliance against 71% for writing the CSS from a prose style guide.
|
|
7
7
|
|
|
@@ -129,7 +129,7 @@ _One page at a time, fitted to its pane both ways so an A4 statement and a tall
|
|
|
129
129
|
|
|
130
130
|
## The frame every route shares
|
|
131
131
|
|
|
132
|
-
_The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column._
|
|
132
|
+
_The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column, a foldable working list beside the page's own content._
|
|
133
133
|
|
|
134
134
|
| Component | Import | Key props | Reach for it instead of |
|
|
135
135
|
| --- | --- | --- | --- |
|
|
@@ -138,6 +138,7 @@ _The standing chrome — the app shell and its nav, the one page-title treatment
|
|
|
138
138
|
| `Segmented` | `@poodle64/ui/segmented` | `value, options, label, size?` | a hand-built row of aria-pressed buttons for a small fixed choice |
|
|
139
139
|
| `PageHeader` | `@poodle64/ui/page-header` | `title?, eyebrow?, breadcrumbs?, icon?, subtitle?, info?, meta?, actions?` | a hand-written <h1> and title bar (the drift gate fails this) |
|
|
140
140
|
| `ContextColumn` | `@poodle64/ui/context-column` | `stats, statsTitle?, statsInfo?, detail?, ariaLabel?` | a hand-built right-hand <aside> with a stat card and a detail pane |
|
|
141
|
+
| `ListShell` | `@poodle64/ui/list-shell` | `title, children, label?, open?, actions?` | a module hand-rolling its own foldable second column beside the rail — a queue, an inbox, a working list |
|
|
141
142
|
| `AppIdentity` | `@poodle64/ui/app-identity` | `user, workspace?, role?, entitlements?, onSignOut, onAccountSettings?, onSwitchTheme?` | a hand-rolled account/avatar dropdown wired to raw DropdownMenu.* + Avatar.* |
|
|
142
143
|
|
|
143
144
|
## The settings destination
|