@poodle64/ui 2026.9.21 → 2026.9.23
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
CHANGED
|
@@ -1411,6 +1411,6 @@ non-zero and naming the field on any difference.
|
|
|
1411
1411
|
1. Change a component; bump `version` in `package.json` (CalVer).
|
|
1412
1412
|
2. `pnpm build`, which runs `svelte-package` then `publint` (package.json/exports sanity).
|
|
1413
1413
|
3. Commit, tag `ui-v<version>`, push the tag.
|
|
1414
|
-
4. `.github/workflows/publish.yaml` runs on that push and publishes via npm
|
|
1415
|
-
OIDC trusted publishing: `gh run list --workflow=publish.yaml`.
|
|
1414
|
+
4. `.github/workflows/publish-kit-typescript.yaml` (repo root) runs on that push and publishes via npm
|
|
1415
|
+
OIDC trusted publishing: `gh run list -R radar-hooves/app-factory --workflow=publish-kit-typescript.yaml`.
|
|
1416
1416
|
Confirm with `npm view @poodle64/ui version`.
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
// components/ui — it is app machinery the layout mounts once (alongside
|
|
25
25
|
// initBrowserTelemetry), not page chrome a route composes from, so it does
|
|
26
26
|
// not sit in the situation→component registry either.
|
|
27
|
+
// A page control that shares its corner sets `--ds-report-clearance` on the
|
|
28
|
+
// document to lift the trigger clear of it (the librarian composer does).
|
|
27
29
|
|
|
28
30
|
let {
|
|
29
31
|
/** Where the report POSTs. The app's own route, defaulting to the one every household app serves. */
|
|
@@ -105,7 +107,7 @@
|
|
|
105
107
|
<button
|
|
106
108
|
type="button"
|
|
107
109
|
onclick={() => (open = true)}
|
|
108
|
-
class="border-border bg-card text-muted-foreground hover:text-foreground ds-edge fixed right-4 bottom-[calc(1rem+env(safe-area-inset-bottom))] z-50 grid size-11 flex-none place-items-center rounded-full border transition-colors"
|
|
110
|
+
class="border-border bg-card text-muted-foreground hover:text-foreground ds-edge fixed right-4 bottom-[calc(1rem+var(--ds-report-clearance,0px)+env(safe-area-inset-bottom))] z-50 grid size-11 flex-none place-items-center rounded-full border transition-colors"
|
|
109
111
|
aria-label="Report a problem"
|
|
110
112
|
>
|
|
111
113
|
<MessageSquarePlus class="size-4.5" />
|
|
@@ -150,98 +150,115 @@
|
|
|
150
150
|
class="flex-1 rounded-none border-0"
|
|
151
151
|
/>
|
|
152
152
|
{:else}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
153
|
+
<!-- containerClass targets table.svelte's own scroll container (`data-slot=
|
|
154
|
+
table-container`), which otherwise carries only `overflow-x-auto` — and
|
|
155
|
+
`overflow-x` other than visible forces `overflow-y` to compute `auto`
|
|
156
|
+
too, making that div a scroll container in its own right even though it
|
|
157
|
+
never actually scrolls (it is always sized to its own content). A
|
|
158
|
+
`position: sticky` header's containing block is its NEAREST ancestor
|
|
159
|
+
scroll container, so with no override here the header (radar-hooves/
|
|
160
|
+
godswood, 30/09/2026) sticks to that inert box and never visibly pins,
|
|
161
|
+
in every engine, not only WebKit — confirmed with this repo's own
|
|
162
|
+
Playwright (chromium and webkit) driving the unmodified component.
|
|
163
|
+
Folding this component's own intended scroll region into that SAME box
|
|
164
|
+
(rather than wrapping a second one around it) makes it the one thing
|
|
165
|
+
that actually scrolls, so sticky has a real ancestor to pin against. -->
|
|
166
|
+
<TableRoot class="w-full" containerClass="min-h-0 flex-1 overflow-y-auto">
|
|
167
|
+
<!-- Sticky lives on each `<th>`, not on `<thead>`: WebKit has a long
|
|
168
|
+
history of not reliably keeping `position: sticky` on a table-section
|
|
169
|
+
box (`<thead>`) pinned, while sticky on the `<th>` cells — which
|
|
170
|
+
participate in table layout as ordinary boxes — sticks correctly. -->
|
|
171
|
+
<TableHeader>
|
|
172
|
+
{#each headerGroups as headerGroup (headerGroup.id)}
|
|
173
|
+
<TableRow class="border-0 hover:bg-transparent">
|
|
174
|
+
{#if selectable}
|
|
175
|
+
<TableHead
|
|
176
|
+
class="border-border bg-card sticky top-0 z-10 w-10 border-b px-3 py-1.5"
|
|
177
|
+
>
|
|
178
|
+
<Checkbox
|
|
179
|
+
checked={allSelected}
|
|
180
|
+
indeterminate={someSelected}
|
|
181
|
+
onCheckedChange={toggleAll}
|
|
182
|
+
aria-label="Select all rows"
|
|
183
|
+
/>
|
|
184
|
+
</TableHead>
|
|
185
|
+
{/if}
|
|
186
|
+
{#each headerGroup.headers as header (header.id)}
|
|
187
|
+
{@const meta = header.column.columnDef.meta}
|
|
188
|
+
<TableHead
|
|
189
|
+
class={cn(
|
|
190
|
+
'text-muted-foreground border-border bg-card sticky top-0 z-10 text-2xs tracking-eyebrow border-b px-3 py-1.5 text-left font-medium whitespace-nowrap uppercase',
|
|
191
|
+
meta?.class,
|
|
192
|
+
meta?.headClass
|
|
193
|
+
)}
|
|
194
|
+
>
|
|
195
|
+
{#if !header.isPlaceholder}
|
|
196
|
+
<FlexRender
|
|
197
|
+
content={header.column.columnDef.header}
|
|
198
|
+
context={header.getContext()}
|
|
165
199
|
/>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
'text-muted-foreground border-border text-2xs tracking-eyebrow border-b px-3 py-1.5 text-left font-medium whitespace-nowrap uppercase',
|
|
173
|
-
meta?.class,
|
|
174
|
-
meta?.headClass
|
|
175
|
-
)}
|
|
176
|
-
>
|
|
177
|
-
{#if !header.isPlaceholder}
|
|
178
|
-
<FlexRender
|
|
179
|
-
content={header.column.columnDef.header}
|
|
180
|
-
context={header.getContext()}
|
|
181
|
-
/>
|
|
182
|
-
{/if}
|
|
183
|
-
</TableHead>
|
|
184
|
-
{/each}
|
|
185
|
-
</TableRow>
|
|
186
|
-
{/each}
|
|
187
|
-
</TableHeader>
|
|
200
|
+
{/if}
|
|
201
|
+
</TableHead>
|
|
202
|
+
{/each}
|
|
203
|
+
</TableRow>
|
|
204
|
+
{/each}
|
|
205
|
+
</TableHeader>
|
|
188
206
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
207
|
+
<TableBody>
|
|
208
|
+
{#each rows as row (row.id)}
|
|
209
|
+
{@const originalRow = row.original}
|
|
210
|
+
{@const rowId = getRowId(originalRow)}
|
|
211
|
+
{@const isSelected = rowId === selectedId}
|
|
212
|
+
{@const isChecked = selectedSet.has(rowId)}
|
|
213
|
+
{@const extraStyle = getRowStyle ? getRowStyle(originalRow) : ''}
|
|
214
|
+
<TableRow
|
|
215
|
+
data-state={isSelected ? 'selected' : undefined}
|
|
216
|
+
class="border-border/60 border-b last:border-0 {onSelect
|
|
217
|
+
? 'cursor-pointer hover:bg-[color-mix(in_oklch,var(--foreground)_4%,transparent)]'
|
|
218
|
+
: ''} {isSelected ? 'bg-[color-mix(in_oklch,var(--primary)_10%,transparent)]' : ''}"
|
|
219
|
+
style="{extraStyle}{isSelected ? ' ' + selectedShadow : ''}"
|
|
220
|
+
onclick={onSelect ? () => onSelect(rowId) : undefined}
|
|
221
|
+
onkeydown={onSelect
|
|
222
|
+
? (e) => {
|
|
223
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
onSelect(rowId);
|
|
209
226
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
<Checkbox
|
|
226
|
-
checked={isChecked}
|
|
227
|
-
onCheckedChange={(v) => setRowSelected(rowId, v)}
|
|
228
|
-
aria-label="Select row"
|
|
229
|
-
/>
|
|
230
|
-
</div>
|
|
231
|
-
</TableCell>
|
|
232
|
-
{/if}
|
|
233
|
-
{#each row.getVisibleCells() as cell (cell.id)}
|
|
234
|
-
{@const meta = cell.column.columnDef.meta}
|
|
235
|
-
<TableCell
|
|
236
|
-
class={cn('px-3 py-1.5 text-sm whitespace-nowrap', meta?.class, meta?.cellClass)}
|
|
227
|
+
}
|
|
228
|
+
: undefined}
|
|
229
|
+
tabindex={onSelect ? 0 : undefined}
|
|
230
|
+
>
|
|
231
|
+
{#if selectable}
|
|
232
|
+
<TableCell class="w-10 px-3 py-1.5">
|
|
233
|
+
<!-- The wrapper ONLY stops the event reaching the row, so ticking a
|
|
234
|
+
checkbox never also opens the master-detail. The checkbox's own
|
|
235
|
+
onCheckedChange is the single source of the selection change —
|
|
236
|
+
handling the click here as well would toggle twice per click and
|
|
237
|
+
net out to no selection at all. -->
|
|
238
|
+
<div
|
|
239
|
+
role="presentation"
|
|
240
|
+
onclick={(e) => e.stopPropagation()}
|
|
241
|
+
onkeydown={(e) => e.stopPropagation()}
|
|
237
242
|
>
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
<Checkbox
|
|
244
|
+
checked={isChecked}
|
|
245
|
+
onCheckedChange={(v) => setRowSelected(rowId, v)}
|
|
246
|
+
aria-label="Select row"
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</TableCell>
|
|
250
|
+
{/if}
|
|
251
|
+
{#each row.getVisibleCells() as cell (cell.id)}
|
|
252
|
+
{@const meta = cell.column.columnDef.meta}
|
|
253
|
+
<TableCell
|
|
254
|
+
class={cn('px-3 py-1.5 text-sm whitespace-nowrap', meta?.class, meta?.cellClass)}
|
|
255
|
+
>
|
|
256
|
+
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
|
|
257
|
+
</TableCell>
|
|
258
|
+
{/each}
|
|
259
|
+
</TableRow>
|
|
260
|
+
{/each}
|
|
261
|
+
</TableBody>
|
|
262
|
+
</TableRoot>
|
|
246
263
|
{/if}
|
|
247
264
|
</div>
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/ui",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.23",
|
|
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",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/radar-hooves/
|
|
9
|
+
"url": "git+https://github.com/radar-hooves/app-factory.git",
|
|
10
|
+
"directory": "kits/typescript/packages/ui"
|
|
10
11
|
},
|
|
11
12
|
"publishConfig": {
|
|
12
13
|
"registry": "https://registry.npmjs.org",
|
|
13
14
|
"access": "public",
|
|
14
|
-
"provenance":
|
|
15
|
+
"provenance": true
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"bin",
|
|
@@ -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.23`. 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
|
|