@poodle64/ui 2026.10.5 → 2026.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/components/ui/about/about.svelte +46 -0
- package/dist/components/ui/about/about.svelte.d.ts +18 -0
- package/dist/components/ui/about/index.d.ts +2 -0
- package/dist/components/ui/about/index.js +2 -0
- package/dist/components/ui/ledger/ledger.svelte +4 -11
- package/dist/components/ui/ledger/ledger.svelte.d.ts +1 -1
- package/dist/components/ui/ledger/look.d.ts +9 -0
- package/dist/components/ui/ledger/look.js +11 -0
- package/dist/components/ui/record-list/record-list.svelte +4 -11
- package/dist/components/ui/record-list/record-list.svelte.d.ts +1 -1
- package/dist/components/ui/sonner/sonner.svelte +16 -1
- package/package.json +1 -1
- package/registry/component-map.json +9 -2
- package/registry/component-map.md +2 -1
package/README.md
CHANGED
|
@@ -96,6 +96,7 @@ rather than rebuilding it:
|
|
|
96
96
|
| `record-switcher` | The open record's whole name as the top bar's control, its menu finding, grouping and picturing every record, and the record's pages as tabs. |
|
|
97
97
|
| `document-pane` | One document beside what it belongs to: titled, closable, its page fitted, with paging and zoom (a `PageCanvas` in a `Panel`). |
|
|
98
98
|
| `detail-panel` | The entity-detail surface: header with icon/eyebrow/title/`StatusBadge`/close, scrollable body, footer of actions. |
|
|
99
|
+
| `about` | About content for a page or dialogue: `name`, `version`, optional `description`/`brand`/`links`, and explicitly selected non-sensitive `diagnostics`. Never pass settings or environment objects. `writeClipboard` accepts the host clipboard API directly; otherwise the browser API is used. Mount the shared Toaster once for copy success/failure. |
|
|
99
100
|
| `settings-shell` | The settings destination: a grouped section list of `NavItem` rows beside the section you are reading, both scrolling independently from `md`, stacked below it. Render it inside `AppShell` with `padded={false}`. |
|
|
100
101
|
| `context-column` | The persistent right-hand column. A list page's: a standing `StatList` plus an optional detail that flows in on select. A record page's (`title`, `children`): the one opened item, closable, scrolling inside itself, and its `documentPane` beside it once the page's row has room. Below `xl` (1280px) the same content opens from a floating trigger instead of disappearing. See [A record's page](#a-records-page). |
|
|
101
102
|
| `app-dialog` | The dialogue frame: titled header, scrollable body, footer action bar, five sizes (`xs`…`xl`), and an `onOpenChange` for the dismissals the caller did not drive. |
|
|
@@ -899,7 +900,7 @@ no module rebuilds them and a need one module finds becomes a toggle for all.
|
|
|
899
900
|
| `selected`, `bulkActions` | Ticking exists only with `bulkActions`, whose snippet gets the ticked rows in the bar that replaces the toolbar. |
|
|
900
901
|
| `open`, `editor` | Opening exists only with `editor`: the row lifts out of its card as its own surface, holding the snippet. Its second argument carries the row's `template` and each column's grid line, so fields can sit under their columns. |
|
|
901
902
|
| `originActions`, `onAttachment` | Split, join and undo under a synthetic row's statement lines; the paperclip as a button. |
|
|
902
|
-
| `noun`, `empty`, `footer`, `layout` | "line" for a printed layer; the empty state; "Show more" after the last group; a forced `phone`/`narrow`/`wide` where the ledger's own width (below
|
|
903
|
+
| `noun`, `empty`, `footer`, `layout` | "line" for a printed layer; the empty state; "Show more" after the last group; a forced `phone`/`narrow`/`wide` where the ledger's own width (below 480px, from 1200px) should not decide. |
|
|
903
904
|
|
|
904
905
|
Settled here, the same in every module, and not props:
|
|
905
906
|
|
|
@@ -985,7 +986,7 @@ stays for a dense grid of data; a module's own records are a RecordList.
|
|
|
985
986
|
| `noun`, `empty`, `footer`, `layout` | As the Ledger's. |
|
|
986
987
|
|
|
987
988
|
A sort orders rows inside their groups, largest first (words from A), then
|
|
988
|
-
reversed, then the list's own order. On a phone (below
|
|
989
|
+
reversed, then the list's own order. On a phone (below 480px of list) a row is its thumbnail, title
|
|
989
990
|
and lines, with the first end-aligned column's value beside the title. Measured
|
|
990
991
|
in a real engine at 390, 1440 and 3360px (`harness/drive.mjs`, `?surface=records`).
|
|
991
992
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { toast } from 'svelte-sonner';
|
|
4
|
+
import Panel from '../panel/panel.svelte';
|
|
5
|
+
import FactGrid from '../fact-grid/fact-grid.svelte';
|
|
6
|
+
import Button from '../button/button.svelte';
|
|
7
|
+
|
|
8
|
+
let { name, version, description, brand, links = [], diagnostics = {},
|
|
9
|
+
writeClipboard = (text: string) => navigator.clipboard.writeText(text) }: {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
brand?: Snippet;
|
|
14
|
+
links?: readonly { label: string; href: string }[];
|
|
15
|
+
/** Explicit non-sensitive fields, never a settings/environment object. */
|
|
16
|
+
diagnostics?: Record<string, string>;
|
|
17
|
+
/** Pass the host's clipboard API directly where the browser API is unavailable. */
|
|
18
|
+
writeClipboard?: (text: string) => Promise<void>;
|
|
19
|
+
} = $props();
|
|
20
|
+
const facts = $derived([
|
|
21
|
+
{ key: 'version', label: 'Version', value: version },
|
|
22
|
+
...Object.entries(diagnostics).map(([label, value]) => ({ key: `diagnostic:${label}`, label, value }))
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
async function copyDiagnostics() {
|
|
26
|
+
try {
|
|
27
|
+
await writeClipboard(`${name}\n${facts.map((fact) => `${fact.label}: ${fact.value}`).join('\n')}`);
|
|
28
|
+
toast.success('Diagnostics copied');
|
|
29
|
+
} catch {
|
|
30
|
+
toast.error('Could not copy diagnostics', { description: 'Clipboard access is unavailable.' });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<Panel title={name} {description}>
|
|
36
|
+
<div class="flex flex-col gap-4">
|
|
37
|
+
{#if brand}{@render brand()}{/if}
|
|
38
|
+
<FactGrid {facts} />
|
|
39
|
+
<div class="flex flex-wrap gap-2">
|
|
40
|
+
{#each links as link}
|
|
41
|
+
<Button variant="outline" href={link.href} target="_blank" rel="noopener noreferrer">{link.label}</Button>
|
|
42
|
+
{/each}
|
|
43
|
+
<Button variant="outline" onclick={copyDiagnostics}>Copy diagnostics</Button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</Panel>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
brand?: Snippet;
|
|
7
|
+
links?: readonly {
|
|
8
|
+
label: string;
|
|
9
|
+
href: string;
|
|
10
|
+
}[];
|
|
11
|
+
/** Explicit non-sensitive fields, never a settings/environment object. */
|
|
12
|
+
diagnostics?: Record<string, string>;
|
|
13
|
+
/** Pass the host's clipboard API directly where the browser API is unavailable. */
|
|
14
|
+
writeClipboard?: (text: string) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
declare const About: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
|
+
type About = ReturnType<typeof About>;
|
|
18
|
+
export default About;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* It fills its parent's height and scrolls its own rows, so the head card
|
|
10
10
|
* stays at the top: give it a bounded parent (`flex-1 min-h-0` in a
|
|
11
|
-
* full-height page). Width picks the layout: a phone's list below
|
|
11
|
+
* full-height page). Width picks the layout: a phone's list below 480px, the
|
|
12
12
|
* wide column widths from 1200px.
|
|
13
13
|
*/
|
|
14
14
|
import type { Snippet } from 'svelte';
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
ROW_TEXT,
|
|
43
43
|
RULE,
|
|
44
44
|
THROUGH,
|
|
45
|
-
groupSpacing
|
|
45
|
+
groupSpacing,
|
|
46
|
+
listLayout
|
|
46
47
|
} from './look.js';
|
|
47
48
|
import { AU_LOCALE } from '../../../format.js';
|
|
48
49
|
import { cn } from '../../../utils.js';
|
|
@@ -212,15 +213,7 @@
|
|
|
212
213
|
/** The bars above the list end where its cards do: clear of its scroll bar's gutter. */
|
|
213
214
|
const edge = $derived(`calc(0.5rem + ${Math.max(0, listOuter - listInner)}px)`);
|
|
214
215
|
const mode = $derived(
|
|
215
|
-
layout !== 'auto'
|
|
216
|
-
? layout
|
|
217
|
-
: width === 0
|
|
218
|
-
? 'narrow'
|
|
219
|
-
: width < 600
|
|
220
|
-
? 'phone'
|
|
221
|
-
: width >= 1200
|
|
222
|
-
? 'wide'
|
|
223
|
-
: 'narrow'
|
|
216
|
+
layout !== 'auto' ? layout : listLayout(width)
|
|
224
217
|
);
|
|
225
218
|
const WIDE = $derived(mode === 'wide');
|
|
226
219
|
const PHONE = $derived(mode === 'phone');
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* It fills its parent's height and scrolls its own rows, so the head card
|
|
9
9
|
* stays at the top: give it a bounded parent (`flex-1 min-h-0` in a
|
|
10
|
-
* full-height page). Width picks the layout: a phone's list below
|
|
10
|
+
* full-height page). Width picks the layout: a phone's list below 480px, the
|
|
11
11
|
* wide column widths from 1200px.
|
|
12
12
|
*/
|
|
13
13
|
import type { Snippet } from 'svelte';
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
* the RecordList. A head card, row cards with hairlines between rows, and each
|
|
4
4
|
* group's label on the page ground.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* The layout a list's own width picks: a phone's list below 480px, the wide
|
|
8
|
+
* tracks from 1200px, the narrow tracks between. The list's width, not the
|
|
9
|
+
* window's: beside a ContextColumn at 1280px with the rail open it is 588px,
|
|
10
|
+
* which is a laptop's list, not a phone's.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PHONE_BELOW = 480;
|
|
13
|
+
export declare const WIDE_FROM = 1200;
|
|
14
|
+
export declare function listLayout(width: number): 'phone' | 'narrow' | 'wide';
|
|
6
15
|
export declare const CELL = "min-w-0 truncate px-2";
|
|
7
16
|
export declare const END = "pr-5 text-right";
|
|
8
17
|
/** The row's height rides the package's density ramp: 3.5rem, 3rem compact. */
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
* the RecordList. A head card, row cards with hairlines between rows, and each
|
|
4
4
|
* group's label on the page ground.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* The layout a list's own width picks: a phone's list below 480px, the wide
|
|
8
|
+
* tracks from 1200px, the narrow tracks between. The list's width, not the
|
|
9
|
+
* window's: beside a ContextColumn at 1280px with the rail open it is 588px,
|
|
10
|
+
* which is a laptop's list, not a phone's.
|
|
11
|
+
*/
|
|
12
|
+
export const PHONE_BELOW = 480;
|
|
13
|
+
export const WIDE_FROM = 1200;
|
|
14
|
+
export function listLayout(width) {
|
|
15
|
+
return width === 0 ? 'narrow' : width < PHONE_BELOW ? 'phone' : width >= WIDE_FROM ? 'wide' : 'narrow';
|
|
16
|
+
}
|
|
6
17
|
export const CELL = 'min-w-0 truncate px-2';
|
|
7
18
|
export const END = 'pr-5 text-right';
|
|
8
19
|
/** The row's height rides the package's density ramp: 3.5rem, 3rem compact. */
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* It fills its parent's height and scrolls its own rows, so the head card
|
|
15
15
|
* stays at the top: give it a bounded parent (`flex-1 min-h-0`). Width picks
|
|
16
|
-
* the layout, as the Ledger's does: a phone's list below
|
|
16
|
+
* the layout, as the Ledger's does: a phone's list below 480px, the wide
|
|
17
17
|
* tracks and the `'wide'` columns from 1200px. On a phone a row is its
|
|
18
18
|
* thumbnail, title and lines, with the first end-aligned column's value
|
|
19
19
|
* beside the title.
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
ROW_TEXT,
|
|
39
39
|
RULE,
|
|
40
40
|
THROUGH,
|
|
41
|
-
groupSpacing
|
|
41
|
+
groupSpacing,
|
|
42
|
+
listLayout
|
|
42
43
|
} from '../ledger/look.js';
|
|
43
44
|
import ListToolbar from '../list-toolbar/list-toolbar.svelte';
|
|
44
45
|
import type { ListAction, ListIconAction } from '../list-toolbar/types.js';
|
|
@@ -146,15 +147,7 @@
|
|
|
146
147
|
/** The bar ends where the cards do: clear of the scroll bar's gutter. */
|
|
147
148
|
const edge = $derived(`calc(0.5rem + ${Math.max(0, listOuter - listInner)}px)`);
|
|
148
149
|
const mode = $derived(
|
|
149
|
-
layout !== 'auto'
|
|
150
|
-
? layout
|
|
151
|
-
: width === 0
|
|
152
|
-
? 'narrow'
|
|
153
|
-
: width < 600
|
|
154
|
-
? 'phone'
|
|
155
|
-
: width >= 1200
|
|
156
|
-
? 'wide'
|
|
157
|
-
: 'narrow'
|
|
150
|
+
layout !== 'auto' ? layout : listLayout(width)
|
|
158
151
|
);
|
|
159
152
|
const WIDE = $derived(mode === 'wide');
|
|
160
153
|
const PHONE = $derived(mode === 'phone');
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* It fills its parent's height and scrolls its own rows, so the head card
|
|
14
14
|
* stays at the top: give it a bounded parent (`flex-1 min-h-0`). Width picks
|
|
15
|
-
* the layout, as the Ledger's does: a phone's list below
|
|
15
|
+
* the layout, as the Ledger's does: a phone's list below 480px, the wide
|
|
16
16
|
* tracks and the `'wide'` columns from 1200px. On a phone a row is its
|
|
17
17
|
* thumbnail, title and lines, with the first end-aligned column's value
|
|
18
18
|
* beside the title.
|
|
@@ -7,11 +7,26 @@
|
|
|
7
7
|
import InfoIcon from '@lucide/svelte/icons/info';
|
|
8
8
|
import TriangleAlertIcon from '@lucide/svelte/icons/triangle-alert';
|
|
9
9
|
|
|
10
|
-
let { ...restProps }: SonnerProps = $props();
|
|
10
|
+
let { toastOptions = {}, ...restProps }: SonnerProps = $props();
|
|
11
|
+
const options = $derived({
|
|
12
|
+
...toastOptions,
|
|
13
|
+
style: 'font-family: var(--ds-font-body, inherit); font-size: var(--ds-text-sm, 0.875rem); ' + (toastOptions.style ?? ''),
|
|
14
|
+
classes: {
|
|
15
|
+
icon: 'size-4!',
|
|
16
|
+
content: 'min-w-0 max-h-[calc(100dvh-6rem)] overflow-y-auto',
|
|
17
|
+
title: 'whitespace-pre-wrap [overflow-wrap:anywhere]',
|
|
18
|
+
description: 'whitespace-pre-wrap [overflow-wrap:anywhere] text-inherit!',
|
|
19
|
+
...toastOptions.classes
|
|
20
|
+
}
|
|
21
|
+
});
|
|
11
22
|
</script>
|
|
12
23
|
|
|
13
24
|
<Sonner
|
|
14
25
|
theme={mode.current}
|
|
26
|
+
position="top-right"
|
|
27
|
+
closeButton
|
|
28
|
+
richColors={false}
|
|
29
|
+
toastOptions={options}
|
|
15
30
|
class="toaster group"
|
|
16
31
|
style="--normal-bg: var(--popover, var(--ds-color-surface-3)); --normal-text: var(--popover-foreground, var(--ds-color-foreground)); --normal-border: var(--border);"
|
|
17
32
|
{...restProps}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/ui",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.7",
|
|
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.10.
|
|
4
|
+
"version": "2026.10.7",
|
|
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": 67,
|
|
8
8
|
"situationCount": 18
|
|
9
9
|
},
|
|
10
10
|
"situations": [
|
|
@@ -404,6 +404,13 @@
|
|
|
404
404
|
"import": "@poodle64/ui/settings-shell",
|
|
405
405
|
"props": "sections, children, currentPath?, listLabel?",
|
|
406
406
|
"insteadOf": "a hand-built two-pane settings layout, or a card of tab links in a grid, per app"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"name": "About",
|
|
410
|
+
"dir": "about",
|
|
411
|
+
"import": "@poodle64/ui/about",
|
|
412
|
+
"props": "name, version, description?, brand?, links?, diagnostics?, writeClipboard?",
|
|
413
|
+
"insteadOf": "a per-app About page or dialogue with duplicated version, links and diagnostic copying"
|
|
407
414
|
}
|
|
408
415
|
]
|
|
409
416
|
},
|
|
@@ -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.10.
|
|
4
|
+
Generated from `@poodle64/ui@2026.10.7`. 67 components, 18 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
|
|
|
@@ -160,6 +160,7 @@ _The one settings surface: a grouped section list beside the section you are rea
|
|
|
160
160
|
| Component | Import | Key props | Reach for it instead of |
|
|
161
161
|
| --- | --- | --- | --- |
|
|
162
162
|
| `SettingsShell` | `@poodle64/ui/settings-shell` | `sections, children, currentPath?, listLabel?` | a hand-built two-pane settings layout, or a card of tab links in a grid, per app |
|
|
163
|
+
| `About` | `@poodle64/ui/about` | `name, version, description?, brand?, links?, diagnostics?, writeClipboard?` | a per-app About page or dialogue with duplicated version, links and diagnostic copying |
|
|
163
164
|
|
|
164
165
|
## A dialog or overlay
|
|
165
166
|
|