@poodle64/ui 2026.9.5 → 2026.9.6
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.
|
@@ -30,7 +30,14 @@
|
|
|
30
30
|
* instead (operator ruling, 07/09/2026, master-project#291's org-of-one
|
|
31
31
|
* case). Wire it whenever the app has a members route, whether or not it
|
|
32
32
|
* also renders a switcher.
|
|
33
|
+
*
|
|
34
|
+
* `links` is the escape valve for everything the fixed entries have no room
|
|
35
|
+
* for — an Admin route, a support link — as a LIST, so every app's extra
|
|
36
|
+
* destinations render as the same row. Without it an app puts its own
|
|
37
|
+
* destination in AppShell's `actions` slot as an icon button (cadmus did,
|
|
38
|
+
* twice), and two apps do it two ways in the same bar.
|
|
33
39
|
*/
|
|
40
|
+
import type { Component } from 'svelte';
|
|
34
41
|
import { toggleMode } from 'mode-watcher';
|
|
35
42
|
import * as DropdownMenu from '../dropdown-menu/index.js';
|
|
36
43
|
import * as Avatar from '../avatar/index.js';
|
|
@@ -43,7 +50,8 @@
|
|
|
43
50
|
onSignOut,
|
|
44
51
|
onAccountSettings,
|
|
45
52
|
onManageMembers,
|
|
46
|
-
onSwitchTheme
|
|
53
|
+
onSwitchTheme,
|
|
54
|
+
links = []
|
|
47
55
|
}: {
|
|
48
56
|
user: { username: string; display_name: string | null; email: string | null };
|
|
49
57
|
/** The active workspace's name. Omit while none is chosen yet — the block hides rather than rendering empty. */
|
|
@@ -70,6 +78,22 @@
|
|
|
70
78
|
onManageMembers?: () => void;
|
|
71
79
|
/** Override the theme action. Defaults to mode-watcher's toggleMode, matching AppShell. */
|
|
72
80
|
onSwitchTheme?: () => void;
|
|
81
|
+
/**
|
|
82
|
+
* App destinations the fixed entries have no room for, rendered in their
|
|
83
|
+
* own group.
|
|
84
|
+
*
|
|
85
|
+
* A list of items, never a snippet: a snippet lets each app invent its own
|
|
86
|
+
* row shape, and two apps inventing two shapes in the same top bar is the
|
|
87
|
+
* drift this package exists to end. Cadmus is the measured case — its
|
|
88
|
+
* Admin entry had nowhere to go and became an icon button in AppShell's
|
|
89
|
+
* `actions` slot, and a second one (a quiet support link) was about to
|
|
90
|
+
* follow it there.
|
|
91
|
+
*
|
|
92
|
+
* No `href`: the callback shape matches every other action on this
|
|
93
|
+
* component, and a consumer routes with its own `goto`. The icon is
|
|
94
|
+
* optional — a label-only row stays aligned with any iconed row beside it.
|
|
95
|
+
*/
|
|
96
|
+
links?: { label: string; icon?: Component<{ class?: string }>; onSelect: () => void }[];
|
|
73
97
|
} = $props();
|
|
74
98
|
|
|
75
99
|
const shown = $derived(user.display_name ?? user.username);
|
|
@@ -110,6 +134,11 @@
|
|
|
110
134
|
user.display_name ? initialsOf(user.display_name) : handleInitials(user.username)
|
|
111
135
|
);
|
|
112
136
|
|
|
137
|
+
// Spacers only where the group actually has an icon to align to: a group of
|
|
138
|
+
// label-only rows sits flush with the fixed entries below it, exactly as it
|
|
139
|
+
// would if this prop had never existed.
|
|
140
|
+
const linksHaveIcons = $derived(links.some((l) => l.icon));
|
|
141
|
+
|
|
113
142
|
function switchTheme() {
|
|
114
143
|
if (onSwitchTheme) onSwitchTheme();
|
|
115
144
|
else toggleMode();
|
|
@@ -177,6 +206,26 @@
|
|
|
177
206
|
<DropdownMenu.Item onSelect={onManageMembers}>Members</DropdownMenu.Item>
|
|
178
207
|
{/if}
|
|
179
208
|
{/if}
|
|
209
|
+
{#if links.length}
|
|
210
|
+
<DropdownMenu.Separator />
|
|
211
|
+
<DropdownMenu.Group>
|
|
212
|
+
<!-- Keyed by position, not by label: nothing in the type makes a label
|
|
213
|
+
unique, and two apps' configs colliding on one would be a
|
|
214
|
+
duplicate-key crash rather than a duplicated row. This list is
|
|
215
|
+
app configuration, not data that reorders. -->
|
|
216
|
+
{#each links as link, i (i)}
|
|
217
|
+
{@const Icon = link.icon}
|
|
218
|
+
<DropdownMenu.Item onSelect={link.onSelect}>
|
|
219
|
+
{#if Icon}
|
|
220
|
+
<Icon class="size-4" />
|
|
221
|
+
{:else if linksHaveIcons}
|
|
222
|
+
<span class="size-4 flex-none" aria-hidden="true"></span>
|
|
223
|
+
{/if}
|
|
224
|
+
{link.label}
|
|
225
|
+
</DropdownMenu.Item>
|
|
226
|
+
{/each}
|
|
227
|
+
</DropdownMenu.Group>
|
|
228
|
+
{/if}
|
|
180
229
|
<DropdownMenu.Separator />
|
|
181
230
|
<DropdownMenu.Item onSelect={switchTheme}>Switch theme</DropdownMenu.Item>
|
|
182
231
|
<DropdownMenu.Item onSelect={onAccountSettings}>Account settings</DropdownMenu.Item>
|
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed-in user surface for AppShell's `identity` slot.
|
|
3
|
+
*
|
|
4
|
+
* Graduated from the shell lab (`packages/console/src/routes/lab/+page.svelte`,
|
|
5
|
+
* the `identity === 'avatar'` shape) — operator ruling 04/09/2026: avatar
|
|
6
|
+
* only at rest, no name, no chevron, and no prop reintroduces a variant.
|
|
7
|
+
* Opening it answers who is signed in, which workspace they act in, and the
|
|
8
|
+
* ways out — identity first, because a menu that opens on a list of actions
|
|
9
|
+
* makes you infer who you are from the avatar you just clicked.
|
|
10
|
+
*
|
|
11
|
+
* Prop names match the stamped template's auth store
|
|
12
|
+
* (`frontend/src/lib/auth.svelte.ts` — `User`, `Membership`) exactly, so a
|
|
13
|
+
* consumer wires this straight from `auth.user`, `auth.entitlements`,
|
|
14
|
+
* `auth.activeWorkspace` and `auth.activeRole` with no reshaping. A guessed
|
|
15
|
+
* shape needs rewriting the day it meets the real store, which is the whole
|
|
16
|
+
* reason this one didn't. `display_name` and `email` are both nullable
|
|
17
|
+
* there, and `activeWorkspace` is genuinely null until a caller with several
|
|
18
|
+
* memberships chooses one — this component is built against that, not a
|
|
19
|
+
* happy path.
|
|
20
|
+
*
|
|
21
|
+
* Sign-out and settings are the consuming app's concerns, never behaviour
|
|
22
|
+
* baked in here: `onSignOut` and `onAccountSettings` are callbacks. Theme
|
|
23
|
+
* follows AppShell's own `onToggleTheme` pattern — override it, or accept
|
|
24
|
+
* mode-watcher's `toggleMode` by default.
|
|
25
|
+
*
|
|
26
|
+
* `onManageMembers` exists for the caller a workspace switcher has nothing
|
|
27
|
+
* to show: `WorkspaceMenu` (the factory template) renders nothing in the
|
|
28
|
+
* bar for a single-member workspace, and this is where Members lands
|
|
29
|
+
* instead (operator ruling, 07/09/2026, master-project#291's org-of-one
|
|
30
|
+
* case). Wire it whenever the app has a members route, whether or not it
|
|
31
|
+
* also renders a switcher.
|
|
32
|
+
*
|
|
33
|
+
* `links` is the escape valve for everything the fixed entries have no room
|
|
34
|
+
* for — an Admin route, a support link — as a LIST, so every app's extra
|
|
35
|
+
* destinations render as the same row. Without it an app puts its own
|
|
36
|
+
* destination in AppShell's `actions` slot as an icon button (cadmus did,
|
|
37
|
+
* twice), and two apps do it two ways in the same bar.
|
|
38
|
+
*/
|
|
39
|
+
import type { Component } from 'svelte';
|
|
1
40
|
type $$ComponentProps = {
|
|
2
41
|
user: {
|
|
3
42
|
username: string;
|
|
@@ -28,7 +67,29 @@ type $$ComponentProps = {
|
|
|
28
67
|
onManageMembers?: () => void;
|
|
29
68
|
/** Override the theme action. Defaults to mode-watcher's toggleMode, matching AppShell. */
|
|
30
69
|
onSwitchTheme?: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* App destinations the fixed entries have no room for, rendered in their
|
|
72
|
+
* own group.
|
|
73
|
+
*
|
|
74
|
+
* A list of items, never a snippet: a snippet lets each app invent its own
|
|
75
|
+
* row shape, and two apps inventing two shapes in the same top bar is the
|
|
76
|
+
* drift this package exists to end. Cadmus is the measured case — its
|
|
77
|
+
* Admin entry had nowhere to go and became an icon button in AppShell's
|
|
78
|
+
* `actions` slot, and a second one (a quiet support link) was about to
|
|
79
|
+
* follow it there.
|
|
80
|
+
*
|
|
81
|
+
* No `href`: the callback shape matches every other action on this
|
|
82
|
+
* component, and a consumer routes with its own `goto`. The icon is
|
|
83
|
+
* optional — a label-only row stays aligned with any iconed row beside it.
|
|
84
|
+
*/
|
|
85
|
+
links?: {
|
|
86
|
+
label: string;
|
|
87
|
+
icon?: Component<{
|
|
88
|
+
class?: string;
|
|
89
|
+
}>;
|
|
90
|
+
onSelect: () => void;
|
|
91
|
+
}[];
|
|
31
92
|
};
|
|
32
|
-
declare const AppIdentity:
|
|
93
|
+
declare const AppIdentity: Component<$$ComponentProps, {}, "">;
|
|
33
94
|
type AppIdentity = ReturnType<typeof AppIdentity>;
|
|
34
95
|
export default AppIdentity;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/ui",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.6",
|
|
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,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.6`. 56 components, 15 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
|
|