@poodle64/ui 2026.9.16 → 2026.9.18
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 +124 -24
- package/dist/components/ui/app-shell/app-shell.svelte +2 -2
- package/dist/components/ui/app-shell/padding.d.ts +20 -0
- package/dist/components/ui/app-shell/padding.js +20 -0
- package/dist/components/ui/fact-grid/fact-grid.svelte +53 -0
- package/dist/components/ui/fact-grid/fact-grid.svelte.d.ts +18 -0
- package/dist/components/ui/fact-grid/index.d.ts +3 -0
- package/dist/components/ui/fact-grid/index.js +2 -0
- package/dist/components/ui/info-tip/info-tip.svelte +18 -1
- package/dist/components/ui/page-canvas/index.d.ts +3 -0
- package/dist/components/ui/page-canvas/index.js +2 -0
- package/dist/components/ui/page-canvas/page-canvas.svelte +374 -0
- package/dist/components/ui/page-canvas/page-canvas.svelte.d.ts +23 -0
- package/dist/components/ui/page-canvas/types.d.ts +35 -0
- package/dist/components/ui/page-canvas/types.js +8 -0
- package/dist/components/ui/panel/panel.svelte +97 -5
- package/dist/components/ui/panel/panel.svelte.d.ts +36 -1
- package/dist/components/ui/settings-shell/index.d.ts +2 -0
- package/dist/components/ui/settings-shell/index.js +2 -0
- package/dist/components/ui/settings-shell/settings-shell.svelte +111 -0
- package/dist/components/ui/settings-shell/settings-shell.svelte.d.ts +78 -0
- package/dist/styles.css +68 -13
- package/package.json +1 -1
- package/registry/component-map.json +40 -5
- package/registry/component-map.md +19 -2
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The settings destination: a grouped section list beside the section you are
|
|
4
|
+
* reading, inside AppShell's content area.
|
|
5
|
+
*
|
|
6
|
+
* Two PANES, not a panel and not a drawer. The distinction is load-bearing:
|
|
7
|
+
* a panel overlays and closes, and this package carries a standing directive
|
|
8
|
+
* against side panels for work you are actually doing. These panes divide and
|
|
9
|
+
* persist, each scrolling on its own, so a long settings page never pushes the
|
|
10
|
+
* section list out of reach.
|
|
11
|
+
*
|
|
12
|
+
* The list renders through AppNav rather than a row renderer of its own, and
|
|
13
|
+
* that is the whole reason this component is small. AppNav already owns the
|
|
14
|
+
* group eyebrow, the active row's leading edge and tint and weight, the
|
|
15
|
+
* `aria-current="page"`, the segment-safe prefix match, truncation, and the
|
|
16
|
+
* density ramp — and it is exported in its own right precisely so a navigation
|
|
17
|
+
* list that belongs in a PAGE is built from the same affordance rather than
|
|
18
|
+
* hand-copied. The estate has already paid for the alternative once: one app's
|
|
19
|
+
* inner nav had reimplemented the active indicator independently of its own
|
|
20
|
+
* top bar. A second row renderer here would re-commit that drift one
|
|
21
|
+
* destination down, on the one surface every app is about to adopt at once.
|
|
22
|
+
*
|
|
23
|
+
* Grouping is by WHOSE setting it is — the person's, then the workspace's —
|
|
24
|
+
* because that is the only split a reader can predict: "yours" changes what
|
|
25
|
+
* you see, "this company" changes what everyone sees. A group whose `items`
|
|
26
|
+
* is empty renders nothing (`toGroups` drops it), so an app with no personal
|
|
27
|
+
* sections yet can leave the group in its list rather than stubbing a row that
|
|
28
|
+
* leads nowhere.
|
|
29
|
+
*
|
|
30
|
+
* One label per row, nothing beneath it. A description long enough to be worth
|
|
31
|
+
* reading does not fit a 240px column: every one of them truncated to an
|
|
32
|
+
* ellipsis and told the reader less than the label already did (measured
|
|
33
|
+
* 16/09/2026 in a consumer, removed there the same day). The page introduces
|
|
34
|
+
* its own sections — that is what Panel's `description` is for.
|
|
35
|
+
*
|
|
36
|
+
* No "Settings" heading. The shell's bar names the section it is on as of
|
|
37
|
+
* 2026.9.16, so a heading here is the same word twice, one above the other.
|
|
38
|
+
*
|
|
39
|
+
* BELOW md the list STACKS above the content and the page scrolls as one.
|
|
40
|
+
* A horizontally scrollable strip of the same rows was the obvious
|
|
41
|
+
* alternative and loses on the requirement it was meant to serve — every
|
|
42
|
+
* section reachable in one tap. A strip fits about two and a half rows at
|
|
43
|
+
* 375px, so the rest are behind a sideways swipe with no scrollbar to
|
|
44
|
+
* advertise them: a swipe THEN a tap, where a stack is a scroll the reader is
|
|
45
|
+
* already doing. It also has to drop the group labels for width, which throws
|
|
46
|
+
* away the one split the reader could predict, and it needs a second row
|
|
47
|
+
* renderer in a horizontal mode — the drift this component exists to avoid.
|
|
48
|
+
*
|
|
49
|
+
* USE IT WITH `padded={false}` on AppShell. The list's border and tint have to
|
|
50
|
+
* reach the content area's edges or the two panes read as a floating card, and
|
|
51
|
+
* this component pads its own content pane to the shell's own rhythm
|
|
52
|
+
* (`CONTENT_PADDING`) so the settings destination is not the one route whose
|
|
53
|
+
* text sits somewhere else.
|
|
54
|
+
*/
|
|
55
|
+
import type { Snippet } from 'svelte';
|
|
56
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
57
|
+
import AppNav from '../app-shell/app-nav.svelte';
|
|
58
|
+
import { CONTENT_PADDING } from '../app-shell/padding.js';
|
|
59
|
+
import type { NavSource } from '../app-shell/types.js';
|
|
60
|
+
import { cn, type WithElementRef } from '../../../utils.js';
|
|
61
|
+
|
|
62
|
+
let {
|
|
63
|
+
sections,
|
|
64
|
+
currentPath,
|
|
65
|
+
listLabel = 'Settings sections',
|
|
66
|
+
children,
|
|
67
|
+
ref = $bindable(null),
|
|
68
|
+
class: className,
|
|
69
|
+
...restProps
|
|
70
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
|
71
|
+
/**
|
|
72
|
+
* The sections, as the package's own navigation vocabulary: groups of
|
|
73
|
+
* `NavItem` (`{ heading, items }`), bare items, or a mix. The same type the
|
|
74
|
+
* rail takes, so a row is the same row everywhere in the household and an
|
|
75
|
+
* app types its settings list against something it already has.
|
|
76
|
+
*
|
|
77
|
+
* A row at the settings ROOT (`/settings`) needs `exact: true`, or its
|
|
78
|
+
* prefix match keeps it lit on every section beneath it alongside the
|
|
79
|
+
* section's own row.
|
|
80
|
+
*/
|
|
81
|
+
sections: NavSource;
|
|
82
|
+
/** The active path. Apps pass `page.url.pathname`, as they do to AppShell. */
|
|
83
|
+
currentPath?: string;
|
|
84
|
+
/** The list's accessible name. */
|
|
85
|
+
listLabel?: string;
|
|
86
|
+
children: Snippet;
|
|
87
|
+
} = $props();
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<div bind:this={ref} class={cn('ds-settings', className)} {...restProps}>
|
|
91
|
+
<!--
|
|
92
|
+
Width, tint and rule as utilities rather than in styles.css, because AppNav
|
|
93
|
+
declares `flex-1` on this same element: a base-layer `flex: none` loses to
|
|
94
|
+
a utility by layer, whatever order it is written in, while a utility passed
|
|
95
|
+
through `cn()` is resolved by tailwind-merge — `flex-none` and `flex-1` are
|
|
96
|
+
one group, so the last one wins and it is this one.
|
|
97
|
+
|
|
98
|
+
15rem (240px) is measured, not picked: it is the width at which a
|
|
99
|
+
two-word section label ("Overhead suppliers", "Delegation limits") sits on
|
|
100
|
+
one line beside its icon at comfortable density.
|
|
101
|
+
-->
|
|
102
|
+
<AppNav
|
|
103
|
+
nav={sections}
|
|
104
|
+
{currentPath}
|
|
105
|
+
label={listLabel}
|
|
106
|
+
class="border-border bg-muted/30 flex-none border-b md:w-60 md:border-b-0 md:border-e"
|
|
107
|
+
/>
|
|
108
|
+
<div class={cn('ds-settings-pane', CONTENT_PADDING)}>
|
|
109
|
+
{@render children()}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings destination: a grouped section list beside the section you are
|
|
3
|
+
* reading, inside AppShell's content area.
|
|
4
|
+
*
|
|
5
|
+
* Two PANES, not a panel and not a drawer. The distinction is load-bearing:
|
|
6
|
+
* a panel overlays and closes, and this package carries a standing directive
|
|
7
|
+
* against side panels for work you are actually doing. These panes divide and
|
|
8
|
+
* persist, each scrolling on its own, so a long settings page never pushes the
|
|
9
|
+
* section list out of reach.
|
|
10
|
+
*
|
|
11
|
+
* The list renders through AppNav rather than a row renderer of its own, and
|
|
12
|
+
* that is the whole reason this component is small. AppNav already owns the
|
|
13
|
+
* group eyebrow, the active row's leading edge and tint and weight, the
|
|
14
|
+
* `aria-current="page"`, the segment-safe prefix match, truncation, and the
|
|
15
|
+
* density ramp — and it is exported in its own right precisely so a navigation
|
|
16
|
+
* list that belongs in a PAGE is built from the same affordance rather than
|
|
17
|
+
* hand-copied. The estate has already paid for the alternative once: one app's
|
|
18
|
+
* inner nav had reimplemented the active indicator independently of its own
|
|
19
|
+
* top bar. A second row renderer here would re-commit that drift one
|
|
20
|
+
* destination down, on the one surface every app is about to adopt at once.
|
|
21
|
+
*
|
|
22
|
+
* Grouping is by WHOSE setting it is — the person's, then the workspace's —
|
|
23
|
+
* because that is the only split a reader can predict: "yours" changes what
|
|
24
|
+
* you see, "this company" changes what everyone sees. A group whose `items`
|
|
25
|
+
* is empty renders nothing (`toGroups` drops it), so an app with no personal
|
|
26
|
+
* sections yet can leave the group in its list rather than stubbing a row that
|
|
27
|
+
* leads nowhere.
|
|
28
|
+
*
|
|
29
|
+
* One label per row, nothing beneath it. A description long enough to be worth
|
|
30
|
+
* reading does not fit a 240px column: every one of them truncated to an
|
|
31
|
+
* ellipsis and told the reader less than the label already did (measured
|
|
32
|
+
* 16/09/2026 in a consumer, removed there the same day). The page introduces
|
|
33
|
+
* its own sections — that is what Panel's `description` is for.
|
|
34
|
+
*
|
|
35
|
+
* No "Settings" heading. The shell's bar names the section it is on as of
|
|
36
|
+
* 2026.9.16, so a heading here is the same word twice, one above the other.
|
|
37
|
+
*
|
|
38
|
+
* BELOW md the list STACKS above the content and the page scrolls as one.
|
|
39
|
+
* A horizontally scrollable strip of the same rows was the obvious
|
|
40
|
+
* alternative and loses on the requirement it was meant to serve — every
|
|
41
|
+
* section reachable in one tap. A strip fits about two and a half rows at
|
|
42
|
+
* 375px, so the rest are behind a sideways swipe with no scrollbar to
|
|
43
|
+
* advertise them: a swipe THEN a tap, where a stack is a scroll the reader is
|
|
44
|
+
* already doing. It also has to drop the group labels for width, which throws
|
|
45
|
+
* away the one split the reader could predict, and it needs a second row
|
|
46
|
+
* renderer in a horizontal mode — the drift this component exists to avoid.
|
|
47
|
+
*
|
|
48
|
+
* USE IT WITH `padded={false}` on AppShell. The list's border and tint have to
|
|
49
|
+
* reach the content area's edges or the two panes read as a floating card, and
|
|
50
|
+
* this component pads its own content pane to the shell's own rhythm
|
|
51
|
+
* (`CONTENT_PADDING`) so the settings destination is not the one route whose
|
|
52
|
+
* text sits somewhere else.
|
|
53
|
+
*/
|
|
54
|
+
import type { Snippet } from 'svelte';
|
|
55
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
56
|
+
import type { NavSource } from '../app-shell/types.js';
|
|
57
|
+
import { type WithElementRef } from '../../../utils.js';
|
|
58
|
+
type $$ComponentProps = WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
|
59
|
+
/**
|
|
60
|
+
* The sections, as the package's own navigation vocabulary: groups of
|
|
61
|
+
* `NavItem` (`{ heading, items }`), bare items, or a mix. The same type the
|
|
62
|
+
* rail takes, so a row is the same row everywhere in the household and an
|
|
63
|
+
* app types its settings list against something it already has.
|
|
64
|
+
*
|
|
65
|
+
* A row at the settings ROOT (`/settings`) needs `exact: true`, or its
|
|
66
|
+
* prefix match keeps it lit on every section beneath it alongside the
|
|
67
|
+
* section's own row.
|
|
68
|
+
*/
|
|
69
|
+
sections: NavSource;
|
|
70
|
+
/** The active path. Apps pass `page.url.pathname`, as they do to AppShell. */
|
|
71
|
+
currentPath?: string;
|
|
72
|
+
/** The list's accessible name. */
|
|
73
|
+
listLabel?: string;
|
|
74
|
+
children: Snippet;
|
|
75
|
+
};
|
|
76
|
+
declare const SettingsShell: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
77
|
+
type SettingsShell = ReturnType<typeof SettingsShell>;
|
|
78
|
+
export default SettingsShell;
|
package/dist/styles.css
CHANGED
|
@@ -770,27 +770,24 @@
|
|
|
770
770
|
color: var(--ds-nav-ink);
|
|
771
771
|
}
|
|
772
772
|
|
|
773
|
-
/* The disclosed rows. The indent is measured against the parent's own
|
|
774
|
-
geometry rather than picked: .ds-nav-item pads 0.625rem and the icon is
|
|
775
|
-
1.125rem, so the icon's centre line sits at 1.1875rem — where the guide
|
|
776
|
-
border goes — and 0.625rem of padding here plus the child's own 0.625rem
|
|
777
|
-
lands its label at 2.5rem, exactly under the parent's. The border is a real
|
|
778
|
-
affordance at fifteen rows, not decoration: it is what makes the run
|
|
779
|
-
scannable as one section. */
|
|
780
773
|
/* The disclosed rows carry no guide rule. Indentation and the children's own
|
|
781
774
|
icons already read as one group, and a line down the side is a second
|
|
782
|
-
signal for a grouping that was never ambiguous.
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
775
|
+
signal for a grouping that was never ambiguous. 8f39b02 removed the rule
|
|
776
|
+
but not the 1px it was contributing to the indent (design-system#38): the
|
|
777
|
+
border sat between this margin and this padding, so the child label
|
|
778
|
+
landed 1px further right than the padding alone now gives it. Restored
|
|
779
|
+
here instead, so the geometry — not a rule nobody can see — still carries
|
|
780
|
+
it: `.ds-nav-item` pads 0.625rem and the icon is 1.125rem, so 1.1875rem
|
|
781
|
+
(margin) + 0.625rem plus the former border's 1px (this padding) +
|
|
782
|
+
0.625rem (the child's own padding) lands its label at 2.5rem, exactly
|
|
783
|
+
under the parent's. */
|
|
787
784
|
.ds-nav-children {
|
|
788
785
|
display: flex;
|
|
789
786
|
flex-direction: column;
|
|
790
787
|
gap: 0.125rem;
|
|
791
788
|
margin-top: 0.125rem;
|
|
792
789
|
margin-left: 1.1875rem;
|
|
793
|
-
padding-left: 0.625rem;
|
|
790
|
+
padding-left: calc(0.625rem + 1px);
|
|
794
791
|
}
|
|
795
792
|
|
|
796
793
|
/* The rail's foot: where you configure the app, and which build you are on.
|
|
@@ -881,6 +878,64 @@
|
|
|
881
878
|
padding-inline: 0;
|
|
882
879
|
}
|
|
883
880
|
|
|
881
|
+
/* The settings destination's two panes (SettingsShell).
|
|
882
|
+
|
|
883
|
+
Only the SHAPE is here; the list's width, tint and rule are utilities on the
|
|
884
|
+
AppNav element itself, because AppNav declares `flex-1` there and a
|
|
885
|
+
base-layer `flex: none` would lose to it by layer order rather than by
|
|
886
|
+
position — the cascade trap recorded against `.ds-nav`'s ink. What is left is
|
|
887
|
+
the axis swap and the scroll bounding, which nothing else declares.
|
|
888
|
+
|
|
889
|
+
48rem is Tailwind's own --breakpoint-md, written literally for the reason
|
|
890
|
+
`.ds-shell-rail` writes it literally: a media query cannot read a custom
|
|
891
|
+
property, so an app that retunes md retunes this with it. */
|
|
892
|
+
.ds-settings {
|
|
893
|
+
display: flex;
|
|
894
|
+
flex-direction: column;
|
|
895
|
+
flex: 1 1 auto;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/* `min-height: 0` is what buys the two independent scrollers, and it is
|
|
899
|
+
deliberately NOT set below md. A flex item is floored at its content height
|
|
900
|
+
unless it is allowed to shrink past it, so without this neither pane ever
|
|
901
|
+
scrolls and the whole content area does instead. Below md that floor is
|
|
902
|
+
exactly what is wanted: the list stacks above the content and the page
|
|
903
|
+
scrolls as one, which is the only sane arrangement on a phone — two nested
|
|
904
|
+
scrollers at 375px is the sideways-scroll hazard this package already carries
|
|
905
|
+
a gate for. */
|
|
906
|
+
@media (min-width: 48rem) {
|
|
907
|
+
.ds-settings {
|
|
908
|
+
flex-direction: row;
|
|
909
|
+
min-height: 0;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.ds-settings-pane {
|
|
914
|
+
flex: 1 1 auto;
|
|
915
|
+
min-width: 0;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/* The pane stacks its sections itself, so a settings page writes a run of
|
|
919
|
+
Panels and no wrapper — the framing markup this surface exists to stop a
|
|
920
|
+
consumer writing. 1.25rem is the package's existing section rhythm, the same
|
|
921
|
+
gap `.ds-dialog-section` puts between adjacent sections.
|
|
922
|
+
|
|
923
|
+
A margin rule rather than `display: flex; gap`, because at md and up this box
|
|
924
|
+
is a height-bounded scroller: as a flex column its children carry
|
|
925
|
+
`flex-shrink: 1` and get squashed toward min-content before the scrollbar is
|
|
926
|
+
ever reached, so a tall section would compress instead of scrolling. Block
|
|
927
|
+
flow overflows, which is what a scroller wants. */
|
|
928
|
+
.ds-settings-pane > * + * {
|
|
929
|
+
margin-top: 1.25rem;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
@media (min-width: 48rem) {
|
|
933
|
+
.ds-settings-pane {
|
|
934
|
+
min-height: 0;
|
|
935
|
+
overflow-y: auto;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
884
939
|
/* A hard ceiling on the whole shell, in the one unit that names the actual
|
|
885
940
|
drawn surface rather than a percentage of an ancestor. `.ds-shell` is
|
|
886
941
|
already `flex` with `overflow-hidden` in the component markup, which
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/ui",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.18",
|
|
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,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"package": "@poodle64/ui",
|
|
4
|
-
"version": "2026.9.
|
|
4
|
+
"version": "2026.9.18",
|
|
5
5
|
"generatedBy": "scripts/generate-registry.mjs",
|
|
6
6
|
"source": "scripts/situations.json + package source (DO NOT EDIT the outputs by hand)",
|
|
7
|
-
"componentCount":
|
|
8
|
-
"situationCount":
|
|
7
|
+
"componentCount": 60,
|
|
8
|
+
"situationCount": 17
|
|
9
9
|
},
|
|
10
10
|
"situations": [
|
|
11
11
|
{
|
|
@@ -33,6 +33,13 @@
|
|
|
33
33
|
"import": "@poodle64/ui/stat-list",
|
|
34
34
|
"props": "items, title?, info?",
|
|
35
35
|
"insteadOf": "a hand-built <dl> or a stack of label/value <div>s"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "FactGrid",
|
|
39
|
+
"dir": "fact-grid",
|
|
40
|
+
"import": "@poodle64/ui/fact-grid",
|
|
41
|
+
"props": "facts",
|
|
42
|
+
"insteadOf": "the same figures inside a Panel or DetailPanel that already supplies the card — StatList's own border would nest a box in a box; FactGrid is the borderless, wrapping-grid answer for that spot"
|
|
36
43
|
}
|
|
37
44
|
]
|
|
38
45
|
},
|
|
@@ -150,8 +157,8 @@
|
|
|
150
157
|
"name": "Panel",
|
|
151
158
|
"dir": "panel",
|
|
152
159
|
"import": "@poodle64/ui/panel",
|
|
153
|
-
"props": "children, title?, subtitle?, icon?, action?, pad?",
|
|
154
|
-
"insteadOf": "raw Card + CardHeader + CardTitle assembled by hand on every route"
|
|
160
|
+
"props": "children, title?, subtitle?, description?, icon?, action?, footer?, tone?, pad?",
|
|
161
|
+
"insteadOf": "raw Card + CardHeader + CardTitle assembled by hand on every route; also the settings section — there is no separate SettingsSection, `description` wraps, `footer` carries Save/Cancel, `tone=\"destructive\"` is the danger zone"
|
|
155
162
|
}
|
|
156
163
|
]
|
|
157
164
|
},
|
|
@@ -274,6 +281,20 @@
|
|
|
274
281
|
}
|
|
275
282
|
]
|
|
276
283
|
},
|
|
284
|
+
{
|
|
285
|
+
"key": "document-evidence",
|
|
286
|
+
"title": "A document a machine read, with evidence outlined",
|
|
287
|
+
"description": "One page at a time, fitted to its pane both ways so an A4 statement and a tall receipt alike fill it, with what a reading points at outlined on the page — a hovered or clicked region with its label, a dashed 'where it looked' set, and a focus that zooms in and centres. The app supplies the page render (image, PDF canvas, anything) and the region coordinates as page fractions; this owns the fit/zoom/pan and the overlay only.",
|
|
288
|
+
"components": [
|
|
289
|
+
{
|
|
290
|
+
"name": "PageCanvas",
|
|
291
|
+
"dir": "page-canvas",
|
|
292
|
+
"import": "@poodle64/ui/page-canvas",
|
|
293
|
+
"props": "pageCount, page, currentPage?, regions?, lookedRegions?, activeRegionId?, focusedRegionId?, zoom?",
|
|
294
|
+
"insteadOf": "a hand-built page viewer per app (godswood's DocumentViewer, pebblestone's PdfViewer) each re-deriving fit/zoom/pan, now with no way to outline what was read (#839)"
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
},
|
|
277
298
|
{
|
|
278
299
|
"key": "page-frame",
|
|
279
300
|
"title": "The frame every route shares",
|
|
@@ -323,6 +344,20 @@
|
|
|
323
344
|
}
|
|
324
345
|
]
|
|
325
346
|
},
|
|
347
|
+
{
|
|
348
|
+
"key": "settings-destination",
|
|
349
|
+
"title": "The settings destination",
|
|
350
|
+
"description": "The one settings surface: a grouped section list beside the section you are reading, inside AppShell’s content area (give AppShell padded={false}). Sections are grouped by whose setting it is — the person’s, then the workspace’s. The sections themselves are Panels.",
|
|
351
|
+
"components": [
|
|
352
|
+
{
|
|
353
|
+
"name": "SettingsShell",
|
|
354
|
+
"dir": "settings-shell",
|
|
355
|
+
"import": "@poodle64/ui/settings-shell",
|
|
356
|
+
"props": "sections, children, currentPath?, listLabel?",
|
|
357
|
+
"insteadOf": "a hand-built two-pane settings layout, or a card of tab links in a grid, per app"
|
|
358
|
+
}
|
|
359
|
+
]
|
|
360
|
+
},
|
|
326
361
|
{
|
|
327
362
|
"key": "dialog-and-overlay",
|
|
328
363
|
"title": "A dialog or overlay",
|
|
@@ -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.18`. 60 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
|
|
|
@@ -23,6 +23,7 @@ _A block of plain figures read at a glance — an 'at a glance' strip — where
|
|
|
23
23
|
| Component | Import | Key props | Reach for it instead of |
|
|
24
24
|
| --- | --- | --- | --- |
|
|
25
25
|
| `StatList` | `@poodle64/ui/stat-list` | `items, title?, info?` | a hand-built <dl> or a stack of label/value <div>s |
|
|
26
|
+
| `FactGrid` | `@poodle64/ui/fact-grid` | `facts` | the same figures inside a Panel or DetailPanel that already supplies the card — StatList's own border would nest a box in a box; FactGrid is the borderless, wrapping-grid answer for that spot |
|
|
26
27
|
|
|
27
28
|
## Comparing magnitudes across rows
|
|
28
29
|
|
|
@@ -81,7 +82,7 @@ _The generic titled card every route reaches for — an optional header with ico
|
|
|
81
82
|
|
|
82
83
|
| Component | Import | Key props | Reach for it instead of |
|
|
83
84
|
| --- | --- | --- | --- |
|
|
84
|
-
| `Panel` | `@poodle64/ui/panel` | `children, title?, subtitle?, icon?, action?, pad?` | raw Card + CardHeader + CardTitle assembled by hand on every route |
|
|
85
|
+
| `Panel` | `@poodle64/ui/panel` | `children, title?, subtitle?, description?, icon?, action?, footer?, tone?, pad?` | raw Card + CardHeader + CardTitle assembled by hand on every route; also the settings section — there is no separate SettingsSection, `description` wraps, `footer` carries Save/Cancel, `tone="destructive"` is the danger zone |
|
|
85
86
|
|
|
86
87
|
## A form the app itself describes
|
|
87
88
|
|
|
@@ -118,6 +119,14 @@ _The estate's shared document-library surfaces — a faceted catalogue, one coll
|
|
|
118
119
|
| `DocumentDetail` | `@poodle64/ui/document-detail` | `document, loading?, error?, collectionHref?, onOpenCollection?, actions?, children?` | a bespoke document page: identity dl, locations, tags and memberships hand-laid-out per app |
|
|
119
120
|
| `SearchResults` | `@poodle64/ui/search-results` | `results, query?, total?, loading?, error?, resultHref?, onOpen?, onRetry?` | a hand-built ranked hit list minting its own highlight and relevance styling |
|
|
120
121
|
|
|
122
|
+
## A document a machine read, with evidence outlined
|
|
123
|
+
|
|
124
|
+
_One page at a time, fitted to its pane both ways so an A4 statement and a tall receipt alike fill it, with what a reading points at outlined on the page — a hovered or clicked region with its label, a dashed 'where it looked' set, and a focus that zooms in and centres. The app supplies the page render (image, PDF canvas, anything) and the region coordinates as page fractions; this owns the fit/zoom/pan and the overlay only._
|
|
125
|
+
|
|
126
|
+
| Component | Import | Key props | Reach for it instead of |
|
|
127
|
+
| --- | --- | --- | --- |
|
|
128
|
+
| `PageCanvas` | `@poodle64/ui/page-canvas` | `pageCount, page, currentPage?, regions?, lookedRegions?, activeRegionId?, focusedRegionId?, zoom?` | a hand-built page viewer per app (godswood's DocumentViewer, pebblestone's PdfViewer) each re-deriving fit/zoom/pan, now with no way to outline what was read (#839) |
|
|
129
|
+
|
|
121
130
|
## The frame every route shares
|
|
122
131
|
|
|
123
132
|
_The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column._
|
|
@@ -131,6 +140,14 @@ _The standing chrome — the app shell and its nav, the one page-title treatment
|
|
|
131
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 |
|
|
132
141
|
| `AppIdentity` | `@poodle64/ui/app-identity` | `user, workspace?, role?, entitlements?, onSignOut, onAccountSettings?, onSwitchTheme?` | a hand-rolled account/avatar dropdown wired to raw DropdownMenu.* + Avatar.* |
|
|
133
142
|
|
|
143
|
+
## The settings destination
|
|
144
|
+
|
|
145
|
+
_The one settings surface: a grouped section list beside the section you are reading, inside AppShell’s content area (give AppShell padded={false}). Sections are grouped by whose setting it is — the person’s, then the workspace’s. The sections themselves are Panels._
|
|
146
|
+
|
|
147
|
+
| Component | Import | Key props | Reach for it instead of |
|
|
148
|
+
| --- | --- | --- | --- |
|
|
149
|
+
| `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 |
|
|
150
|
+
|
|
134
151
|
## A dialog or overlay
|
|
135
152
|
|
|
136
153
|
_A modal task and the pieces around it — the dialog frame, a section within its body, a ⌘K palette, an inline (i) hint._
|