@kerfjs/ui 5.0.0-beta.100 → 5.0.0-beta.101
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 -1
- package/ai/component-catalog.json +214 -18
- package/ai/component-composition.json +216 -8
- package/ai/components/README.md +2 -0
- package/ai/components/backdrop-image.md +84 -0
- package/ai/components/nav-stack.md +6 -1
- package/ai/components/pane.md +11 -1
- package/ai/components/recipe-media-detail.md +57 -0
- package/ai/components/surface-scaffold.md +13 -6
- package/ai/components/tab-navigator.md +6 -1
- package/ai/components/toolbar-control-group.md +1 -0
- package/ai/public-api-signatures-v1.md +356 -32
- package/ai/skill.md +53 -5
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/backdrop-image.d.ts +132 -0
- package/dist/backdrop-image.js +5 -0
- package/dist/backdrop-image.js.map +1 -0
- package/dist/browser/backdrop-image.js +3 -0
- package/dist/catalog-resources.d.ts +2 -2
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +10 -9
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-QIG4IGUP.js → chunk-2PXZBFEC.js} +3 -1
- package/dist/chunk-2PXZBFEC.js.map +1 -0
- package/dist/{chunk-T4WJ5VGP.js → chunk-3SKAEPPZ.js} +2 -2
- package/dist/{chunk-T4WJ5VGP.js.map → chunk-3SKAEPPZ.js.map} +1 -1
- package/dist/chunk-5NQF35VO.js +67 -0
- package/dist/chunk-5NQF35VO.js.map +1 -0
- package/dist/{chunk-CMETHPKR.js → chunk-6E2H2E5A.js} +29 -8
- package/dist/chunk-6E2H2E5A.js.map +1 -0
- package/dist/{chunk-PIVY32AE.js → chunk-BYFTHA4I.js} +16 -3
- package/dist/chunk-BYFTHA4I.js.map +1 -0
- package/dist/{chunk-HGV7J4T6.js → chunk-EKHDLBRP.js} +1 -1
- package/dist/chunk-EKHDLBRP.js.map +1 -0
- package/dist/{chunk-QROYYGWU.js → chunk-FNZQOUZ5.js} +2 -2
- package/dist/{chunk-QROYYGWU.js.map → chunk-FNZQOUZ5.js.map} +1 -1
- package/dist/{chunk-EOO6PYGF.js → chunk-IXLUZHGT.js} +4 -4
- package/dist/{chunk-EOO6PYGF.js.map → chunk-IXLUZHGT.js.map} +1 -1
- package/dist/{chunk-UMKV7YVQ.js → chunk-LPHU44S3.js} +5 -1
- package/dist/chunk-LPHU44S3.js.map +1 -0
- package/dist/{chunk-INI4WDWC.js → chunk-M5KZ26VV.js} +27 -4
- package/dist/chunk-M5KZ26VV.js.map +1 -0
- package/dist/{chunk-TLMMFQWM.js → chunk-NS762OVM.js} +3 -3
- package/dist/{chunk-TLMMFQWM.js.map → chunk-NS762OVM.js.map} +1 -1
- package/dist/{chunk-CXZXHZTR.js → chunk-OZ6X76G5.js} +34 -1
- package/dist/chunk-OZ6X76G5.js.map +1 -0
- package/dist/{chunk-YOA5W5ZH.js → chunk-PKL6QJKK.js} +31 -3
- package/dist/chunk-PKL6QJKK.js.map +1 -0
- package/dist/chunk-QTSMHH5J.js +85 -0
- package/dist/chunk-QTSMHH5J.js.map +1 -0
- package/dist/chunk-SCDWURBO.js +136 -0
- package/dist/chunk-SCDWURBO.js.map +1 -0
- package/dist/{chunk-RHD7RKGI.js → chunk-SIW64V65.js} +3 -3
- package/dist/{chunk-RHD7RKGI.js.map → chunk-SIW64V65.js.map} +1 -1
- package/dist/chunk-SQ5DH66U.js +37 -0
- package/dist/chunk-SQ5DH66U.js.map +1 -0
- package/dist/{chunk-PYBBYSZ3.js → chunk-YBWQR2OK.js} +5 -5
- package/dist/{chunk-PYBBYSZ3.js.map → chunk-YBWQR2OK.js.map} +1 -1
- package/dist/{chunk-3GLYIIFR.js → chunk-ZYU5WRC3.js} +2 -2
- package/dist/{chunk-3GLYIIFR.js.map → chunk-ZYU5WRC3.js.map} +1 -1
- package/dist/collapsible-panel.js +3 -2
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +24 -20
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-item.js +1 -1
- package/dist/{nav-stack-VXTumzKa.d.ts → nav-stack-CU_0dKtI.d.ts} +41 -5
- package/dist/nav-stack.d.ts +4 -4
- package/dist/nav-stack.js +2 -1
- package/dist/pane.d.ts +76 -4
- package/dist/pane.js +2 -1
- package/dist/select.js +1 -1
- package/dist/split-view.d.ts +4 -4
- package/dist/split-view.js +3 -2
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/backdrop-image.css +241 -0
- package/dist/styles/components/actions/toolbar/toolbar.css +9 -0
- package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +128 -0
- package/dist/styles/components/layout/pane/pane.css +121 -1
- package/dist/styles/components/navigation/nav-stack/nav-stack.css +207 -6
- package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +121 -3
- package/dist/styles/components/surfaces/backdrop-image/backdrop-image.css +241 -0
- package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +166 -2
- package/dist/styles/foundation.css +165 -8
- package/dist/styles/nav-stack.css +207 -6
- package/dist/styles/overlay-sheet.css +211 -0
- package/dist/styles/pane.css +121 -1
- package/dist/styles/styles.css +1 -0
- package/dist/styles/surface-scaffold.css +166 -2
- package/dist/styles/tab-navigator.css +121 -3
- package/dist/styles/toolbar-control-group.css +128 -0
- package/dist/styles/toolbar.css +9 -0
- package/dist/styles/webawesome.css +10 -0
- package/dist/surface-scaffold-register.d.ts +3 -2
- package/dist/surface-scaffold-register.js +113 -10
- package/dist/surface-scaffold-register.js.map +1 -1
- package/dist/surface-scaffold.d.ts +38 -5
- package/dist/surface-scaffold.js +1 -1
- package/dist/tab-navigator.d.ts +43 -3
- package/dist/tab-navigator.js +35 -6
- package/dist/tab-navigator.js.map +1 -1
- package/dist/toolbar-control-group.d.ts +13 -2
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +6 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +1 -0
- package/dist/wire-catalog.d.ts +2 -2
- package/dist/wire-catalog.js +1 -1
- package/dist/wire-nav-stack.js +29 -3
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-overlay-sheets.d.ts +20 -0
- package/dist/wire-overlay-sheets.js +4 -0
- package/dist/wire-overlay-sheets.js.map +1 -0
- package/dist/wire-scroll-dividers.js +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +2 -2
- package/dist/workbench.js +2 -1
- package/docs/accessibility.md +35 -0
- package/docs/app-layouts.md +13 -0
- package/docs/backdrop-image.md +128 -0
- package/docs/component-contract.md +2 -0
- package/docs/component-selection.md +11 -2
- package/docs/design/templates/toolbar-control-group/glass-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/glass.svg +1 -0
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group.svg +1 -1
- package/docs/design/templates.md +8 -0
- package/docs/layout.md +135 -5
- package/docs/manual-test-plan.md +29 -0
- package/docs/nav-stack.md +37 -1
- package/docs/recipes.md +24 -0
- package/docs/surface-scaffold.md +149 -3
- package/docs/tab-navigator.md +36 -0
- package/docs/ux-demo.md +11 -1
- package/llms.txt +42 -4
- package/package.json +13 -1
- package/ux-demo/recipes/loaders.ts +2 -0
- package/ux-demo/recipes/media-detail.tsx +272 -0
- package/dist/chunk-CMETHPKR.js.map +0 -1
- package/dist/chunk-CXZXHZTR.js.map +0 -1
- package/dist/chunk-HGV7J4T6.js.map +0 -1
- package/dist/chunk-INI4WDWC.js.map +0 -1
- package/dist/chunk-PIVY32AE.js.map +0 -1
- package/dist/chunk-QIG4IGUP.js.map +0 -1
- package/dist/chunk-UMKV7YVQ.js.map +0 -1
- package/dist/chunk-YOA5W5ZH.js.map +0 -1
package/docs/layout.md
CHANGED
|
@@ -106,6 +106,83 @@ The color uses `--kui-sunken-panel-background`, falling back to
|
|
|
106
106
|
in a nested layout so a translucent override paints once. The public color
|
|
107
107
|
override may be set at a Pane or layout boundary.
|
|
108
108
|
|
|
109
|
+
### Background colors
|
|
110
|
+
|
|
111
|
+
Set `background` when a pane needs a surface color of its own, such as a
|
|
112
|
+
media detail page painted in its artwork's color. It takes a typed color from
|
|
113
|
+
`@kerfjs/ui/css-values`: `uiColor('surface-raised')` for a theme token,
|
|
114
|
+
`fillColor('#1f2a44')` for an app-chosen literal, or `colorVar('--app-hue')`
|
|
115
|
+
for an app-owned custom property. The color paints the whole pane, header and
|
|
116
|
+
footer included, and replaces the appearance's fill: a sunken pane with a
|
|
117
|
+
background drops its lowered color and grain. Omitted, the pane keeps its
|
|
118
|
+
default.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { fillColor } from "@kerfjs/ui/css-values";
|
|
122
|
+
|
|
123
|
+
<Pane background={fillColor("#1f2a44")} header={<AlbumToolbar />}>
|
|
124
|
+
<AlbumTracks />
|
|
125
|
+
</Pane>;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`PaneConfig.background` forwards like `appearance`, so a Workbench `mainPane`
|
|
129
|
+
or panel `pane`, a CollapsiblePanel `pane`, or a `NavStackView` `pane` takes
|
|
130
|
+
it. `NavStack` and `TabNavigator` take their own `background` for the stack
|
|
131
|
+
or navigator surface and their docked bars, and a `TabNavigatorTab` takes one
|
|
132
|
+
for its scene. Overlay chrome keeps the translucent chrome material over any
|
|
133
|
+
of them.
|
|
134
|
+
|
|
135
|
+
The app owns contrast: keep text at AA over the color you choose. A dark
|
|
136
|
+
color needs dark-tone controls or a scoped tint for its content. In forced
|
|
137
|
+
colors every background paints `Canvas`.
|
|
138
|
+
|
|
139
|
+
### Tinted surfaces
|
|
140
|
+
|
|
141
|
+
Set `tint` when a whole region should take on a color, the way a media detail
|
|
142
|
+
page takes on its artwork's color: `tint={{ color, scheme }}`. `color` is a
|
|
143
|
+
`@kerfjs/ui/css-values` color, usually one the app already knows about its
|
|
144
|
+
artwork, and `scheme` says whether that color reads as a `'light'` or a
|
|
145
|
+
`'dark'` surface. Kerf does no color detection; the app declares the scheme.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { fillColor } from "@kerfjs/ui/css-values";
|
|
149
|
+
|
|
150
|
+
<Pane tint={{ color: fillColor(album.bottomColor), scheme: "dark" }} header={<AlbumToolbar />}>
|
|
151
|
+
<AlbumTracks />
|
|
152
|
+
</Pane>;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The pane paints its tinted surface, and everything inside it re-derives from
|
|
156
|
+
the color: the surface and raised surface, normal and quiet text, link and
|
|
157
|
+
brand text, and everything built on them (quiet fills, the lowered surface,
|
|
158
|
+
the chrome material behind overlay headers, popup rows). Each token's
|
|
159
|
+
lightness is clamped into a band that keeps text at WCAG AA over any color: a
|
|
160
|
+
light tint's surface stays at or above 90% OKLCH lightness under dark text,
|
|
161
|
+
and a dark tint's surface at or below 30% under light text. Chroma is capped,
|
|
162
|
+
so text stays near-neutral and the surface reads as a tint of the color. A
|
|
163
|
+
seed sweep across hue and lightness holds every text token at 4.5:1 or better
|
|
164
|
+
over the surface, raised surface, and quiet fills, with and without the Web
|
|
165
|
+
Awesome theme (`tests/browser/tone-contrast.spec.ts`).
|
|
166
|
+
|
|
167
|
+
The scope also takes the declared `color-scheme` and the Web Awesome
|
|
168
|
+
`wa-light` / `wa-dark` class, so Web Awesome components inside it re-derive
|
|
169
|
+
their own fills. Without relative color syntax, and under
|
|
170
|
+
`prefers-contrast: more`, the scope keeps the plain palette in its declared
|
|
171
|
+
scheme. `tint` and `background` combine: `background` paints the pane, and
|
|
172
|
+
`tint` still re-derives the tokens for its content.
|
|
173
|
+
|
|
174
|
+
Add `accent` (usually the artwork's most vivid color) to re-derive the brand
|
|
175
|
+
ramp too: `tint={{ color, scheme, accent }}`. Loud fills (primary buttons, the
|
|
176
|
+
current popup row, checked controls) and their text, brand text and links,
|
|
177
|
+
text on brand fills, and the focus ring all take the accent's hue. Lightness
|
|
178
|
+
is clamped so each pair keeps AA for any accent: in a light scope the fill
|
|
179
|
+
stays at or below 50% OKLCH lightness under white text and brand text at most
|
|
180
|
+
45%; in a dark scope the fill stays between 72% and 85% under near-black text
|
|
181
|
+
and brand text at least 80%.
|
|
182
|
+
|
|
183
|
+
`PaneConfig.tint` forwards like `background`. `NavStack`, `TabNavigator`, and
|
|
184
|
+
each `TabNavigatorTab` take `tint` for the stack, the navigator, or one scene.
|
|
185
|
+
|
|
109
186
|
Pass any combination of logical sides to `separators`: `block-start`,
|
|
110
187
|
`block-end`, `inline-start`, and `inline-end`. Every line is off by default and
|
|
111
188
|
each enabled side uses `--kui-pane-separator-width` (1px) and
|
|
@@ -205,8 +282,10 @@ The header paints the shared chrome material, `--kui-chrome-material` (the
|
|
|
205
282
|
surface at 80% opacity) behind `--kui-chrome-material-filter` (a blur and
|
|
206
283
|
saturation backdrop filter), so content passing beneath it reads as softened
|
|
207
284
|
color, not as competing text. The material becomes the opaque surface under
|
|
208
|
-
`prefers-reduced-transparency: reduce` or `prefers-contrast: more`,
|
|
209
|
-
the browser lacks `backdrop-filter
|
|
285
|
+
`prefers-reduced-transparency: reduce` or `prefers-contrast: more`, where
|
|
286
|
+
the browser lacks `backdrop-filter`, on a slow-updating display
|
|
287
|
+
(`update: slow`), and when the app sets `data-kui-effects="static"` on the
|
|
288
|
+
root element (see [Chrome material](accessibility.md#chrome-material)). In forced colors the header paints
|
|
210
289
|
`Canvas` with a `CanvasText` bottom edge.
|
|
211
290
|
|
|
212
291
|
`wireScrollDividers` measures each overlay header (its border-box height,
|
|
@@ -268,6 +347,54 @@ inline gutter instead of nesting them inside another padded content item. A
|
|
|
268
347
|
conditional `StateBanner` remains visibly distinct because it communicates
|
|
269
348
|
semantic status.
|
|
270
349
|
|
|
350
|
+
### Clear header
|
|
351
|
+
|
|
352
|
+
`headerPresentation="clear"` is an overlay header with no fill: the same
|
|
353
|
+
measuring, placement, and scroll clearance as `overlay`, but fully
|
|
354
|
+
transparent, for a header laid over imagery such as a `BackdropImage` hero.
|
|
355
|
+
The header is not what keeps its controls legible over the image: put them
|
|
356
|
+
in glass control groups (`ToolbarControlGroup material="glass"`), or give the
|
|
357
|
+
content a scrim. A clear header draws no scroll divider. Set `underChrome` on a
|
|
358
|
+
`BackdropImage` that opens the content to run its artwork up beneath the
|
|
359
|
+
header.
|
|
360
|
+
|
|
361
|
+
A clear header stays clear while content scrolls beneath it. To fade a
|
|
362
|
+
backing in gradually instead, the scroll-edge effect of iOS, set
|
|
363
|
+
`scrollEdge="material"` (the translucent chrome material) or
|
|
364
|
+
`scrollEdge="surface"` (the opaque surface). The backing tracks the scroll:
|
|
365
|
+
it is fully clear at rest and fully in once content has scrolled
|
|
366
|
+
`scrollEdgeDistance` pixels beneath the header (default 60), and out again as
|
|
367
|
+
you return to the top. Only its opacity changes; the blur itself never
|
|
368
|
+
animates. `wireScrollDividers` publishes the progress
|
|
369
|
+
(`--kui-scroll-edge-progress`, 0 to 1) from the scroll it already tracks, so
|
|
370
|
+
it works in every engine with no extra wiring; unwired, the header stays
|
|
371
|
+
clear.
|
|
372
|
+
|
|
373
|
+
```tsx
|
|
374
|
+
<Pane
|
|
375
|
+
label="Album"
|
|
376
|
+
headerPresentation="clear"
|
|
377
|
+
scrollEdge="material"
|
|
378
|
+
header={<Toolbar label="Album" leading={<BackGroup />} trailing={<AlbumActions />} />}
|
|
379
|
+
>
|
|
380
|
+
<BackdropImage src={art} alt="" fadeTo={uiColor("surface")} underChrome>
|
|
381
|
+
<Tracks />
|
|
382
|
+
</BackdropImage>
|
|
383
|
+
</Pane>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Clear chrome turns opaque where transparency would hurt: under
|
|
387
|
+
`prefers-reduced-transparency: reduce` or `prefers-contrast: more` it paints
|
|
388
|
+
the opaque material, and in forced colors it paints `Canvas` with a
|
|
389
|
+
`CanvasText` edge. Under the static-effects switch or on a slow-updating
|
|
390
|
+
display, the scroll edge snaps on while content lies beneath the bar instead
|
|
391
|
+
of tracking the scroll.
|
|
392
|
+
`NavStack` (`chromePresentation`, `bottomToolbarPresentation`) and
|
|
393
|
+
`TabNavigator` (`barPresentation`) take the same `clear` value and
|
|
394
|
+
`scrollEdge` and `scrollEdgeDistance` props; a bottom bar fades its backing
|
|
395
|
+
out over the last `scrollEdgeDistance` pixels, clearing once the content
|
|
396
|
+
reaches its end.
|
|
397
|
+
|
|
271
398
|
## Content items
|
|
272
399
|
|
|
273
400
|
Render an ordinary surface-like `.kui-content` child with `ContentItem` from
|
|
@@ -498,9 +625,12 @@ do not create line spacing.
|
|
|
498
625
|
`Text` is also the answer for a normal-color subsection title. `ToolbarText`
|
|
499
626
|
below `xlarge` (`large`, `default`, `small`) is deliberately quiet: it labels a
|
|
500
627
|
toolbar's identity — a pane name or a status — and must not compete with the
|
|
501
|
-
content and controls around it. Set `tone="dark"` on a loud
|
|
502
|
-
|
|
503
|
-
|
|
628
|
+
content and controls around it. Set `tone="dark"` on a loud-filled toolbar
|
|
629
|
+
surface (the inverse `--kui-color-neutral-fill-loud`) to use
|
|
630
|
+
`--kui-color-neutral-on-loud` at every size, matching dark control groups; it
|
|
631
|
+
inverts with the color scheme. Over a dark photo or scrim keep the default
|
|
632
|
+
tone inside a `BackdropImage` overlay, which takes the dark scheme under its
|
|
633
|
+
`scrim`. Default tone keeps existing identity and heading colors. A subsection title is
|
|
504
634
|
document content, not toolbar identity: use `Text` with the `h2`–`h6` variant
|
|
505
635
|
the outline calls for (inside a `List` with `controlInsets`, its text lines up
|
|
506
636
|
with content items). Use `ListHeader` when the section is a list or menu group
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Manual test plan
|
|
2
|
+
|
|
3
|
+
Checks that automation cannot make truthfully. Run the catalog with
|
|
4
|
+
`npm run dev:lan` and open the printed URL on the device (see
|
|
5
|
+
[`ux-demo.md`](ux-demo.md)). Record the device, OS, and browser versions and the
|
|
6
|
+
result on the tracking ticket.
|
|
7
|
+
|
|
8
|
+
## DialogSurface bottom sheet: virtual keyboard and safe areas
|
|
9
|
+
|
|
10
|
+
Route: `?component=surface-scaffold`, example "Resizable Bottom Sheet". Run on
|
|
11
|
+
an iPhone (Safari) and an Android phone (Chrome).
|
|
12
|
+
|
|
13
|
+
1. Open the sheet and focus the Message field. The keyboard pushes the sheet's
|
|
14
|
+
content above it, and the panel runs behind the keyboard with no gap.
|
|
15
|
+
2. Rotate the phone, and pinch-zoom while the keyboard is open. The content
|
|
16
|
+
stays clear of the keyboard; while zoomed, the measured overlap is 0 and the
|
|
17
|
+
page relies on `dvh`.
|
|
18
|
+
3. Dismiss the keyboard. The bottom padding returns to the home-indicator safe
|
|
19
|
+
area, and the sheet's square bottom edge meets the screen edge.
|
|
20
|
+
4. Drag the grabber, with and without the keyboard open. The sheet resizes
|
|
21
|
+
between its minimum height and the screen less the top safe area.
|
|
22
|
+
|
|
23
|
+
Playwright cannot raise a virtual keyboard; the overlap formula is unit-tested
|
|
24
|
+
(`tests/unit/dialog-bottom-sheet.test.ts`) and a simulated keyboard is covered
|
|
25
|
+
in `tests/browser/dialog-bottom-sheet.spec.ts`.
|
|
26
|
+
|
|
27
|
+
## Automated coverage summary
|
|
28
|
+
|
|
29
|
+
Nothing has moved from this plan into automation yet.
|
package/docs/nav-stack.md
CHANGED
|
@@ -150,7 +150,11 @@ focusable descendant, and finally the view container itself. Use
|
|
|
150
150
|
`data-nav-focus` for a heading or primary control when DOM order is not the best
|
|
151
151
|
initial reading position; it may use `tabindex="-1"` for programmatic focus.
|
|
152
152
|
When a remembered control was removed during the controlled rerender, the same
|
|
153
|
-
fallback order applies.
|
|
153
|
+
fallback order applies. While focus rests on the view container itself (it
|
|
154
|
+
had nothing focusable, such as an inert loading placeholder), a later
|
|
155
|
+
re-render that morphs away the temporary `tabindex` keeps it there, and once
|
|
156
|
+
content with a focus target arrives under the same key, focus moves to it;
|
|
157
|
+
focus the person moved elsewhere is never reclaimed. Disposing the helper stops future transitions and
|
|
154
158
|
cleans up any temporary fallback `tabindex` it added.
|
|
155
159
|
|
|
156
160
|
## Separators and scroll dividers
|
|
@@ -187,6 +191,23 @@ top Toolbar's own `dividerSides`.
|
|
|
187
191
|
<NavStack id="settings" label="Settings" views={views.value} chromeDividers="always" />
|
|
188
192
|
```
|
|
189
193
|
|
|
194
|
+
## Background colors
|
|
195
|
+
|
|
196
|
+
`background` paints the stack, its docked chrome and bottom toolbar, and every
|
|
197
|
+
view in an app-chosen color from `@kerfjs/ui/css-values` instead of the
|
|
198
|
+
surface. One view's own color goes in its `pane: { background }`, which paints
|
|
199
|
+
over the stack's. Overlay chrome keeps the chrome material. The app keeps text
|
|
200
|
+
at AA over the color; forced colors paint `Canvas`. See
|
|
201
|
+
[Background colors](layout.md#background-colors).
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
<NavStack id="music" label="Music" views={views.value} background={uiColor("surface-raised")} />
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`tint={{ color, scheme }}` re-derives the stack's surface and text tokens from
|
|
208
|
+
an app color instead, so its chrome, views, and content all take on the color
|
|
209
|
+
with text kept at AA. See [Tinted surfaces](layout.md#tinted-surfaces).
|
|
210
|
+
|
|
190
211
|
## Overlay chrome
|
|
191
212
|
|
|
192
213
|
`chromePresentation="overlay"` lays the top chrome over the active view's top
|
|
@@ -296,6 +317,21 @@ edge.
|
|
|
296
317
|
`SplitView`'s `compactStack.bottomToolbarPresentation` and a Workbench
|
|
297
318
|
navigation panel's `navStack.bottomToolbarPresentation` forward it too.
|
|
298
319
|
|
|
320
|
+
### Clear chrome
|
|
321
|
+
|
|
322
|
+
`chromePresentation="clear"` and `bottomToolbarPresentation="clear"` lay the
|
|
323
|
+
bars over the active view like `overlay`, with the same measuring and edge
|
|
324
|
+
insets, but fully transparent, for chrome over imagery. Keep their controls
|
|
325
|
+
in glass control groups (`ToolbarControlGroup material="glass"`); the stack's
|
|
326
|
+
own back control is one automatically under clear chrome. They stay
|
|
327
|
+
clear while content scrolls beneath them; with `scrollEdge="material"` (or
|
|
328
|
+
`"surface"`) a backing fades in gradually, opacity only, over
|
|
329
|
+
`scrollEdgeDistance` pixels of scroll (default 60): behind the top chrome as
|
|
330
|
+
the view's content scrolls beneath it, and behind the bottom toolbar until
|
|
331
|
+
the content nears its end. Clear bars draw no scroll
|
|
332
|
+
divider, turn opaque under reduced transparency or increased contrast, and
|
|
333
|
+
paint `Canvas` in forced colors. See [Clear header](layout.md#clear-header).
|
|
334
|
+
|
|
299
335
|
## Safe areas
|
|
300
336
|
|
|
301
337
|
The stack paints through a device's unsafe areas. Its chrome pads for the top
|
package/docs/recipes.md
CHANGED
|
@@ -200,6 +200,30 @@ ephemeral interaction. For a full three-pane shell use `Workbench` instead — s
|
|
|
200
200
|
[`app-layouts.md`](app-layouts.md). This recipe is covered end-to-end across
|
|
201
201
|
Chromium, Firefox, and WebKit by `tests/browser/collapsible-sidebar-recipe.spec.ts`.
|
|
202
202
|
|
|
203
|
+
## Media Detail
|
|
204
|
+
|
|
205
|
+
[Open the recipe](../ux-demo/?component=recipe-media-detail) · [TSX source](../ux-demo/recipes/media-detail.tsx)
|
|
206
|
+
|
|
207
|
+
An Apple-Music-style album page composed only from components. A `NavStack`
|
|
208
|
+
with `chromePresentation="clear"` and `scrollEdge="material"` lays its bar
|
|
209
|
+
over the album, fully transparent at rest; its back control and the album
|
|
210
|
+
actions are glass control groups (`ToolbarControlGroup material="glass"`), so
|
|
211
|
+
they stay legible over the artwork, and the chrome material fades in once the
|
|
212
|
+
songs scroll beneath the bar. The stack takes a scoped `tint` from the
|
|
213
|
+
artwork's bottom color with `scheme: 'dark'`, so the surface, text, quiet
|
|
214
|
+
text, and the chrome material all re-derive from it at AA. The album view's
|
|
215
|
+
Pane opens with a `BackdropImage` that runs `underChrome` up beneath the clear
|
|
216
|
+
bar, lags the scroll (`parallax="subtle"`), blurs progressively as it scrolls
|
|
217
|
+
away, and fades into the tinted surface (`fadeTo={uiColor('surface')}`) that
|
|
218
|
+
continues under the song list. Every effect has the components' own
|
|
219
|
+
fallbacks: static under reduced motion, the static-effects switch, or without
|
|
220
|
+
scroll-driven animations; opaque under reduced transparency and increased
|
|
221
|
+
contrast; system colors in forced colors. The app owns the artwork and the
|
|
222
|
+
two colors it already knows about it (the tint and its scheme), the stack,
|
|
223
|
+
playback, and the data; the recipe's `wire` installs `wireScrollDividers` for
|
|
224
|
+
the scroll-edge state. See [`app-layouts.md`](app-layouts.md) for the layout
|
|
225
|
+
and [`backdrop-image.md`](backdrop-image.md) for the image.
|
|
226
|
+
|
|
203
227
|
## Rules shared by every recipe
|
|
204
228
|
|
|
205
229
|
- Import `@kerfjs/ui/layout.css`; keep every pane unpadded and use exactly one
|
package/docs/surface-scaffold.md
CHANGED
|
@@ -8,10 +8,17 @@ inside `PopupSurface` also keeps keyboard item navigation made during its show
|
|
|
8
8
|
animation when Web Awesome completes its initial focus handoff.
|
|
9
9
|
|
|
10
10
|
Wrap one `wa-dialog` with `DialogSurface`. Choose `size` (`small`, `medium`, or
|
|
11
|
-
`large`), `presentation` (`modal`, `side-sheet`,
|
|
11
|
+
`large`), `presentation` (`modal`, `side-sheet`, `fullscreen`, or
|
|
12
|
+
`bottom-sheet`; see [Bottom sheets](#bottom-sheets)), and independent
|
|
12
13
|
`bodyInset` / `footerInset` (`none`, `compact`, or `comfortable`). The defaults
|
|
13
14
|
match Kerf's medium modal, 8px body, and 16px footer rhythm.
|
|
14
15
|
|
|
16
|
+
The panel corner is concentric with a pill `ToolbarControlGroup` (22px) resting
|
|
17
|
+
at the body inset: 30px for `compact` (and `none`) and 38px for `comfortable`,
|
|
18
|
+
so header and body controls nest into the curve. A side sheet uses the same
|
|
19
|
+
radius on its leading corners, and a bare `wa-dialog` or `PopoverDialogSurface`
|
|
20
|
+
takes the 30px default.
|
|
21
|
+
|
|
15
22
|
The modal size presets have preferred widths of 400px, 560px, and 800px,
|
|
16
23
|
respectively, before the viewport width cap. Set `preferredWidth` to a branded
|
|
17
24
|
`CssLength` when the desktop shell needs another width. It takes precedence over
|
|
@@ -46,7 +53,8 @@ clamped to the dynamic viewport height minus twice the gutter (16px per edge
|
|
|
46
53
|
when the gutter is omitted). Negative gutters/caps clamp to zero. Omitting
|
|
47
54
|
both options preserves Web Awesome's existing width and phone height caps.
|
|
48
55
|
These options apply only to `presentation="modal"`; side sheets and fullscreen
|
|
49
|
-
surfaces keep their own geometry
|
|
56
|
+
surfaces keep their own geometry, and a bottom sheet reads `height` and
|
|
57
|
+
`maxHeight` as described in [Bottom sheets](#bottom-sheets).
|
|
50
58
|
|
|
51
59
|
```tsx
|
|
52
60
|
import '@kerfjs/ui/surface-scaffold/register';
|
|
@@ -74,6 +82,144 @@ An application can configure device-class breakpoints when its phone policy
|
|
|
74
82
|
differs from the default. Prefer a typed cap such as `maxHeight={remify(640)}`
|
|
75
83
|
when the modal should stop growing before the viewport bound.
|
|
76
84
|
|
|
85
|
+
### Bottom sheets
|
|
86
|
+
|
|
87
|
+
`presentation="bottom-sheet"` attaches the dialog to the bottom edge of the
|
|
88
|
+
screen instead of centering it, the way a phone presents a compose or quick
|
|
89
|
+
edit sheet. The default `modal` presentation is unchanged.
|
|
90
|
+
|
|
91
|
+
- **Geometry.** The panel rests on the viewport's block-end edge. Its top
|
|
92
|
+
corners keep the dialog radius and its bottom corners are square. It is
|
|
93
|
+
full width on a phone and the `size` / `preferredWidth` width, centered, on
|
|
94
|
+
a wider screen.
|
|
95
|
+
- **Safe area and keyboard.** The panel runs to the edge, behind the home
|
|
96
|
+
indicator and any virtual keyboard, while a block-end padding keeps its
|
|
97
|
+
content above them. The padding is the largest of
|
|
98
|
+
`--kui-safe-area-block-end`, the keyboard overlap the register measures from
|
|
99
|
+
`visualViewport` (layout height minus the visual viewport's height and top
|
|
100
|
+
offset, ignored while pinch-zoomed), and the VirtualKeyboard API's
|
|
101
|
+
`env(keyboard-inset-height)`. A page whose viewport meta sets
|
|
102
|
+
`interactive-widget=resizes-content` shrinks the layout viewport instead, so
|
|
103
|
+
the measured overlap is zero and the sheet simply follows the dynamic
|
|
104
|
+
viewport. Kerf never sets `navigator.virtualKeyboard.overlaysContent`; an
|
|
105
|
+
application that opts into it gets the `env()` value for free.
|
|
106
|
+
- **Height.** `height`, `minHeight`, and `maxHeight` measure the visible
|
|
107
|
+
content height above that clearance. `height` is the opening height
|
|
108
|
+
(omit it to size to content), and `maxHeight` caps it. The panel never
|
|
109
|
+
exceeds the dynamic viewport minus the top safe area and one
|
|
110
|
+
`viewportGutter` (16px by default), so a tall keyboard shrinks the content
|
|
111
|
+
rather than pushing the sheet off the top.
|
|
112
|
+
- **Resizing.** `resizable` adds a grabber at the top edge: a focusable
|
|
113
|
+
horizontal `separator` named "Resize sheet" with `aria-valuemin`,
|
|
114
|
+
`aria-valuenow`, and `aria-valuemax` in visible px. Drag it, or use the
|
|
115
|
+
arrow keys (16px), Page Up/Down (64px), Home (`minHeight`, default 160px),
|
|
116
|
+
and End (the cap). The register injects the grabber into the dialog's
|
|
117
|
+
shadow panel, so it never disturbs `bodyLayout` children or a Kerf morph.
|
|
118
|
+
Each opening starts again at `height`.
|
|
119
|
+
- **Motion.** The sheet slides up from the bottom edge when it opens and back
|
|
120
|
+
down when it is dismissed. `enterMotion="standard"` or
|
|
121
|
+
`exitMotion="standard"` keeps Web Awesome's scale-and-fade for that
|
|
122
|
+
transition. The slide falls back to Web Awesome's motion under
|
|
123
|
+
`prefers-reduced-motion: reduce` and the document's static effects mode
|
|
124
|
+
(`:root[data-kui-effects="static"]`). A prevented close still pulses.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import '@kerfjs/ui/surface-scaffold/register';
|
|
128
|
+
import { remify } from '@kerfjs/ui/css-values';
|
|
129
|
+
import { DialogSurface } from '@kerfjs/ui/surface-scaffold';
|
|
130
|
+
|
|
131
|
+
<DialogSurface presentation="bottom-sheet" height={remify(360)} minHeight={remify(200)} resizable bodyInset="none">
|
|
132
|
+
<wa-dialog label="New Message" light-dismiss>
|
|
133
|
+
{/* Application-owned fields */}
|
|
134
|
+
</wa-dialog>
|
|
135
|
+
</DialogSurface>;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The resize grabber, keyboard clearance, and slide need the `/register`
|
|
139
|
+
entry. Without it the sheet keeps its bottom-edge geometry and safe-area
|
|
140
|
+
padding, shows no grabber, and opens with Web Awesome's own animation.
|
|
141
|
+
`preferredWidth`, `bodyLayout`, the floating header and footer, and the
|
|
142
|
+
insets compose with a bottom sheet as they do with a modal.
|
|
143
|
+
|
|
144
|
+
### Core dialogs as bottom sheets
|
|
145
|
+
|
|
146
|
+
The core `kerfjs/overlay` surfaces (`overlay()` and the `confirm`, `prompt`,
|
|
147
|
+
`form`, and `choice` promise dialogs) take `presentation: 'bottom-sheet'`, but
|
|
148
|
+
the core ships no CSS. Import `@kerfjs/ui/overlay-sheet.css` once to give
|
|
149
|
+
them this package's bottom-sheet geometry:
|
|
150
|
+
|
|
151
|
+
- the bottom edge, full width up to 560px and centered beyond;
|
|
152
|
+
- square bottom corners under the dialog radius;
|
|
153
|
+
- the 8px surface inset, padded at the block end by the larger of the safe
|
|
154
|
+
area and the `--kerf-keyboard-inset` the core publishes;
|
|
155
|
+
- a cap below the top safe area;
|
|
156
|
+
- a slide up as it opens.
|
|
157
|
+
|
|
158
|
+
Pass `exitClass: 'kui-overlay-sheet-exit'` (with an `exitDuration` cap such as
|
|
159
|
+
`400`) to slide it back down as it closes. The stylesheet covers the plain
|
|
160
|
+
host and `native: true`, honors reduced motion and the static-effects switch,
|
|
161
|
+
and paints `Canvas` in forced colors.
|
|
162
|
+
|
|
163
|
+
The core renders bare default markup, so compose the content from components
|
|
164
|
+
through the dialog's `render` slot and spread the wiring slots onto native
|
|
165
|
+
buttons:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import "@kerfjs/ui/overlay-sheet.css";
|
|
169
|
+
|
|
170
|
+
confirm("Discard this draft?", {
|
|
171
|
+
presentation: "bottom-sheet",
|
|
172
|
+
exitClass: "kui-overlay-sheet-exit",
|
|
173
|
+
exitDuration: 400,
|
|
174
|
+
render: ({ message, ok, cancel }) => (
|
|
175
|
+
<List gap="xs">
|
|
176
|
+
<Toolbar label="Discard Draft" leading={<ToolbarText text="Discard Draft" size="xlarge" headingLevel={2} />} />
|
|
177
|
+
<ListInsetText>{message}</ListInsetText>
|
|
178
|
+
<Row hAlign="right" controlInsets="rl">
|
|
179
|
+
<ToolbarControlGroup label="Discard Draft Actions" content="text">
|
|
180
|
+
<button type="button" {...cancel}>Keep Editing</button>
|
|
181
|
+
<button type="button" {...ok}>Discard</button>
|
|
182
|
+
</ToolbarControlGroup>
|
|
183
|
+
</Row>
|
|
184
|
+
</List>
|
|
185
|
+
),
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The wired controls can be native buttons, as here, or Web Awesome buttons.
|
|
190
|
+
With kerfjs 5.0 or later, the core holds focus on the sheet until a Web
|
|
191
|
+
Awesome OK button has rendered, then moves focus to it. On kerfjs 4.x, use
|
|
192
|
+
native buttons for the wired controls: the core's first focus reaches a Web
|
|
193
|
+
Awesome button before it has rendered and aborts the open.
|
|
194
|
+
|
|
195
|
+
A core sheet can also be resizable, like a `resizable` `DialogSurface` sheet.
|
|
196
|
+
Add the `kui-overlay-sheet-resizable` class next to the default wrapper class,
|
|
197
|
+
and call `wireOverlaySheets()` from `@kerfjs/ui/wire-overlay-sheets` once for
|
|
198
|
+
each container sheets open in (`document.body`, the core's default):
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import "@kerfjs/ui/overlay-sheet.css";
|
|
202
|
+
import { wireOverlaySheets } from "@kerfjs/ui/wire-overlay-sheets";
|
|
203
|
+
|
|
204
|
+
const stopSheets = wireOverlaySheets();
|
|
205
|
+
|
|
206
|
+
overlay(<UpNext />, {
|
|
207
|
+
className: "kerf-overlay kui-overlay-sheet-resizable",
|
|
208
|
+
presentation: "bottom-sheet",
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Each such sheet gets the same grabber at its top edge: a `role="separator"`
|
|
213
|
+
that resizes the visible height by drag, the arrow keys (16px), Page Up/Down
|
|
214
|
+
(64px), Home (the minimum), and End (the cap), and reports that height as its
|
|
215
|
+
ARIA values. The minimum is `--kui-overlay-sheet-min-height` (160px). The
|
|
216
|
+
sheet opens at its content height every time. The grabber sticks to the top
|
|
217
|
+
edge while the content scrolls, and stays the sheet's first child (and first
|
|
218
|
+
tab stop) across re-renders of the overlay's content. Compose the sheet's
|
|
219
|
+
content root as a block or column, since the grabber is its first child. The
|
|
220
|
+
wiring watches only the container's direct children, which is where the core
|
|
221
|
+
appends each overlay; call the disposer to remove every grabber.
|
|
222
|
+
|
|
77
223
|
### Filling the dialog
|
|
78
224
|
|
|
79
225
|
`maxHeight` caps a modal that sizes to its content. A layout that fills the
|
|
@@ -267,4 +413,4 @@ preserve `data-morph-skip-children` when its upgraded light-DOM items must retai
|
|
|
267
413
|
identity across Kerf rerenders. Import `@kerfjs/ui/popup-menu/register` once for
|
|
268
414
|
the focus handoff fix when wrapping a raw `wa-dropdown`.
|
|
269
415
|
|
|
270
|
-
Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog`, measures each surface's footer for a floating footer's body clearance, draws a floating header's divider while content is scrolled beneath it, and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. A host that is itself an explicit dialog (`role="dialog"` or `"alertdialog"` with its own name) keeps that name alone: the native panel is left unnamed, so one dialog carries the name. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
|
|
416
|
+
Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog`, measures each surface's footer for a floating footer's body clearance, draws a floating header's divider while content is scrolled beneath it, wires a bottom sheet's resize grabber, keyboard clearance, and slide, and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. A host that is itself an explicit dialog (`role="dialog"` or `"alertdialog"` with its own name) keeps that name alone: the native panel is left unnamed, so one dialog carries the name. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
|
package/docs/tab-navigator.md
CHANGED
|
@@ -169,6 +169,30 @@ so it never moves the bar or a tab.
|
|
|
169
169
|
the wiring reports content below. It does not suppress the sunken-surface
|
|
170
170
|
separator; use `separator="hidden"` for that.
|
|
171
171
|
|
|
172
|
+
## Background colors
|
|
173
|
+
|
|
174
|
+
`background` paints the navigator and its docked bar in an app-chosen color
|
|
175
|
+
from `@kerfjs/ui/css-values` (`uiColor(...)`, `fillColor(...)`, or
|
|
176
|
+
`colorVar(...)`) instead of the surface. A tab's own `background` paints its
|
|
177
|
+
scene and replaces that scene's `appearance`. The overlay bar keeps the
|
|
178
|
+
chrome material over either. Keep the bar's labels and the scene's text at AA
|
|
179
|
+
over the colors you choose; forced colors paint `Canvas`. See
|
|
180
|
+
[Background colors](layout.md#background-colors).
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
<TabNavigator
|
|
184
|
+
id="library"
|
|
185
|
+
label="Library"
|
|
186
|
+
active={active.value}
|
|
187
|
+
background={uiColor("surface-raised")}
|
|
188
|
+
tabs={[{ id: "now", label: "Now Playing", content: <NowPlaying />, background: fillColor("#2b1d3a") }]}
|
|
189
|
+
/>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`tint={{ color, scheme }}` on the navigator, or on one tab for its scene,
|
|
193
|
+
re-derives the surface and text tokens from an app color, keeping text at AA.
|
|
194
|
+
See [Tinted surfaces](layout.md#tinted-surfaces).
|
|
195
|
+
|
|
172
196
|
## Translucent bar
|
|
173
197
|
|
|
174
198
|
`barPresentation="overlay"` lays the tab bar over the active scene's bottom
|
|
@@ -206,6 +230,18 @@ shows only while more content lies below. Like the NavStack overlay bars, the
|
|
|
206
230
|
tab bar ends where a classic (space-taking) scrollbar's track begins, so the
|
|
207
231
|
active scene's thumb and buttons stay visible and usable.
|
|
208
232
|
|
|
233
|
+
### Clear bar
|
|
234
|
+
|
|
235
|
+
`barPresentation="clear"` lays the bar over the active scene like `overlay`
|
|
236
|
+
but fully transparent, for a bar over imagery. It stays
|
|
237
|
+
clear while content scrolls beneath it; with `scrollEdge="material"` (or
|
|
238
|
+
`"surface"`) a backing fades in (opacity only) while the scene continues
|
|
239
|
+
beneath the bar and fades out over the last `scrollEdgeDistance` pixels
|
|
240
|
+
(default 60) before its end. A clear bar draws
|
|
241
|
+
no scroll divider, turns opaque under reduced transparency or increased
|
|
242
|
+
contrast, and paints `Canvas` in forced colors. See
|
|
243
|
+
[Clear header](layout.md#clear-header).
|
|
244
|
+
|
|
209
245
|
## Tab badges
|
|
210
246
|
|
|
211
247
|
Give a tab a `badge` (a count or short string such as `3`, `"99+"`, or
|
package/docs/ux-demo.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# UX catalog contract
|
|
2
2
|
|
|
3
|
-
Run `npm run dev` from `ui
|
|
3
|
+
Run `npm run dev` from `ui/` (or `npm run dev:lan` from the repo root or `ui/`). The catalog is a development and review surface, not a second implementation. The development server reloads component source edits without a restart: component entries still load through their `dist/browser` wrappers, so each component's reachable CSS is exercised as consumers receive it, but the JavaScript those wrappers re-export (and every other `@kerfjs/ui` subpath) resolves to `src/`, and CSS resolves to `src/` as well. A component that gains a new CSS dependency needs a restart, because the wrapper listing its stylesheets is regenerated only by the build. Production demo builds, previews, and the browser tests use `dist/`. The development server allows repo-owned assets used by the catalog, and its production bundle uses relative asset URLs so the complete catalog remains intact below preview and proxy paths.
|
|
4
|
+
|
|
5
|
+
`npm run dev:lan` serves the same development catalog on every network
|
|
6
|
+
interface, for checks only a real device can make: a phone's virtual keyboard
|
|
7
|
+
and safe areas, touch dragging, and device rendering. It prints the catalog's
|
|
8
|
+
LAN and Tailscale URLs (opening the bottom-sheet route) and accepts Tailscale
|
|
9
|
+
MagicDNS (`*.ts.net`) and mDNS (`*.local`) host names; plain `npm run dev` stays
|
|
10
|
+
on `127.0.0.1`. The catalog page sets `viewport-fit=cover`, so on a notched
|
|
11
|
+
phone `env(safe-area-inset-*)` reports the real insets and the layouts' safe-area
|
|
12
|
+
model is visible. See [`manual-test-plan.md`](manual-test-plan.md) for the
|
|
13
|
+
device checks.
|
|
4
14
|
|
|
5
15
|
Every Playwright entry point builds current source before the preview server
|
|
6
16
|
starts. `npm run test:e2e -- tests/browser/row.spec.ts` and a direct focused
|
package/llms.txt
CHANGED
|
@@ -24,6 +24,28 @@ depth; avoid a wrapper or extra background that paints the same visual surface
|
|
|
24
24
|
twice. Check contrast in both color schemes over the actual backdrop. See
|
|
25
25
|
[contextual transparency](./docs/webawesome-theme.md#contextual-transparency).
|
|
26
26
|
|
|
27
|
+
For chrome laid over imagery, use the `clear` presentation (Pane
|
|
28
|
+
`headerPresentation`, NavStack `chromePresentation` /
|
|
29
|
+
`bottomToolbarPresentation`, TabNavigator `barPresentation`) with glass
|
|
30
|
+
control groups, and `scrollEdge="material"` (or `"surface"`) to fade a
|
|
31
|
+
backing in gradually over `scrollEdgeDistance` (default 60px) as content
|
|
32
|
+
scrolls beneath (a first `BackdropImage` with `underChrome` runs its artwork beneath the header); see [Clear header](./docs/layout.md#clear-header).
|
|
33
|
+
|
|
34
|
+
For a `kerfjs/overlay` dialog with `presentation: 'bottom-sheet'`, import
|
|
35
|
+
`@kerfjs/ui/overlay-sheet.css` once (and pass `exitClass: 'kui-overlay-sheet-exit'`
|
|
36
|
+
for the slide out; add the `kui-overlay-sheet-resizable` class and call
|
|
37
|
+
`wireOverlaySheets()` for a resize grabber); see [Core dialogs as bottom sheets](./docs/surface-scaffold.md#core-dialogs-as-bottom-sheets).
|
|
38
|
+
|
|
39
|
+
For an app-chosen surface color, set the typed `background` prop (a
|
|
40
|
+
`@kerfjs/ui/css-values` color) on `Pane` (also `PaneConfig`), `NavStack`,
|
|
41
|
+
`TabNavigator`, or a `TabNavigatorTab` instead of styling the component. It
|
|
42
|
+
replaces the default or sunken fill; overlay chrome keeps its material; forced
|
|
43
|
+
colors paint `Canvas`. Keep content at AA over the color. See
|
|
44
|
+
[Background colors](./docs/layout.md#background-colors).
|
|
45
|
+
For a region that takes on a color with text kept at AA automatically, set
|
|
46
|
+
`tint={{ color, scheme }}` (the app declares the scheme; no color detection; an optional `accent` re-derives the brand ramp at AA);
|
|
47
|
+
see [Tinted surfaces](./docs/layout.md#tinted-surfaces).
|
|
48
|
+
|
|
27
49
|
For arbitrary interactive help or popup content, use `openAnchoredSurface` or
|
|
28
50
|
`openAnchoredSurfaceAt` from `@kerfjs/ui/anchored-surface`; see
|
|
29
51
|
[AnchoredSurface](./docs/anchored-surface.md). The app owns content and actions.
|
|
@@ -40,8 +62,10 @@ the folder modules, and CSS only imports their owner stylesheets.
|
|
|
40
62
|
|
|
41
63
|
### Shared semantic tones
|
|
42
64
|
|
|
43
|
-
Use `ToolbarText tone="dark"` on a loud
|
|
44
|
-
match dark control groups
|
|
65
|
+
Use `ToolbarText tone="dark"` on a loud-filled toolbar surface (the inverse
|
|
66
|
+
`--kui-color-neutral-fill-loud`) to match dark control groups; it inverts with
|
|
67
|
+
the color scheme. Over a dark photo or scrim keep the default tone inside a
|
|
68
|
+
`BackdropImage` overlay, which takes the dark scheme under its `scrim`. It applies to read-only headings and actionable
|
|
45
69
|
titles at every size; default tone retains existing identity and heading colors.
|
|
46
70
|
|
|
47
71
|
`SemanticTone` is the shared color-role type for `Badge`, `Chip`, `StateBanner`,
|
|
@@ -476,6 +500,7 @@ headingLevel`, `TokenSearchField clearIcon`, `ContentItem frame="framed"` for a
|
|
|
476
500
|
Text supports `controlMargins` with canonical physical Sides for an exterior 8px control gutter; it preserves border/padding and avoids an isolated Row wrapper. Avoid stacking it with parent insets. Inline spans retain native inline margin behavior.
|
|
477
501
|
|
|
478
502
|
`ToolbarControlGroup.tileTone` selects `neutral`, `brand`, `success`, `warning`, or `danger` quiet fill, matching quiet border, and on-quiet foreground for a non-interactive icon tile. Compose a contained `single` group whose only child is a direct decorative LucideIcon (`aria-hidden="true"`). Omission preserves the normal group palette. The prop does not recolor interactive, mixed, multi-control, or borderless groups; keep real actions in controls with their existing focus/hover/pressed treatments.
|
|
503
|
+
`ToolbarControlGroup` `material="glass"` paints a group that floats over imagery or a clear (transparent) toolbar on the translucent chrome material (`--kui-chrome-material` with the blur/saturate `--kui-chrome-material-filter`), with a specular edge in place of the gray border and a soft shadow. Its controls take `--kui-color-text`, so they stay at AA over any content; a raised or outline selection paints an opaque surface pill. The fill turns opaque under reduced transparency, increased contrast, static effects (`update: slow` or `:root[data-kui-effects="static"]`), or without backdrop-filter support, and paints Canvas/CanvasText in forced colors. A `tone="dark"` glass group keeps its loud fill and gains the blur and edge; a borderless group has no surface and ignores the material. Keep the solid default on an opaque toolbar, and never add your own `backdrop-filter` or translucent background to a group.
|
|
479
504
|
`ListItem`, `ListItemLink`, and `ListActionRow` take `iconTile` to present their leading `icon` in a filled 32px rounded tile holding an 18px glyph, for settings or command rows that identify each entry by color. Pass a tone (`neutral`, `brand`, `success`, `warning`, or `danger`) for the same quiet fill, matching quiet border, and on-quiet foreground as `ToolbarControlGroup.tileTone`, or `{ color, foreground? }` for an application color: `color` is a `CssColor` (`uiColor()`, `colorVar()`, or `fillColor('#e5484d')`) and `foreground` a `CssForegroundColor`; without one, the glyph is black or white, whichever contrasts with the fill. The tile sits 6px clear of a standard 44px row (a compact row is at least 42px tall), moves the label to the tile's edge, and keeps its colors when the row is hovered or selected. Never wrap the icon in your own tile or resize the row's icon slot.
|
|
480
505
|
Toolbar zones accept `KerfUiContent` directly. Use `ToolbarText` for identity and `ToolbarControlGroup` for related controls that need its shared chrome; a bare control keeps its own styling and accessibility responsibilities.
|
|
481
506
|
Expanded search groups accept `expandedOverflow="visible"` to anchor app-owned suggestion/date/help surfaces below their relative root; collapsed/default groups retain existing clipping. Apps own surface placement/dismissal and use `data-token-search-keep-open` or `collapsible.keepOpenOn` for focus.
|
|
@@ -528,7 +553,8 @@ headingLevel`, `TokenSearchField clearIcon`, `ContentItem frame="framed"` for a
|
|
|
528
553
|
For a header that floats over its scrolling content, set
|
|
529
554
|
`headerPresentation="overlay"` on the `Pane` (or in `PaneConfig`). It paints
|
|
530
555
|
the translucent chrome material (`--kui-chrome-material`, opaque under
|
|
531
|
-
reduced transparency, increased contrast,
|
|
556
|
+
reduced transparency, increased contrast, forced colors, a slow-updating
|
|
557
|
+
display, and the app-set `data-kui-effects="static"` root switch). The same
|
|
532
558
|
`wireScrollDividers` call measures the header and publishes its height as
|
|
533
559
|
block-start scroll clearance, which the content clears automatically. Keep
|
|
534
560
|
overlay-header text in the normal text color.
|
|
@@ -755,6 +781,18 @@ created, input }`. Never use a hand-styled `<input list>` + `<datalist>`.
|
|
|
755
781
|
inverse chrome, place the chips in a dark appearance scope (`.wa-dark`)
|
|
756
782
|
instead of restyling them.
|
|
757
783
|
|
|
784
|
+
- **Open a scrolling view with `BackdropImage`.** Place it first in a Pane or
|
|
785
|
+
NavStack view's scrolling content: `parallax` (`subtle` or a 0 to 1 factor)
|
|
786
|
+
lags the image on a compositor-only scroll timeline, `blur` (`radius`,
|
|
787
|
+
`blurredSrc`, `dim`) cross-fades in a once-blurred copy as it scrolls away
|
|
788
|
+
(only opacity animates, never the filter), `fadeTo` (a `CssColor`)
|
|
789
|
+
dissolves it into a color that keeps painting under its `children`, and
|
|
790
|
+
`overlay` content sits over its block end with an optional `scrim`. Pass
|
|
791
|
+
`alt=""` for a decorative image. It is static under reduced motion,
|
|
792
|
+
`data-kui-effects="static"`, or without scroll-driven animations, and ends on
|
|
793
|
+
a hard edge (overlay on a solid `fadeTo` band) under reduced transparency. Never hand-roll scroll listeners or
|
|
794
|
+
`background-attachment: fixed`.
|
|
795
|
+
|
|
758
796
|
- **Use `Composer` for a chat or prompt composer.** `@kerfjs/ui/composer`
|
|
759
797
|
renders a raised frame (standard border, large radius, `shadow-l`) whose
|
|
760
798
|
border turns brand while focus is within, a borderless textarea that grows
|
|
@@ -844,7 +882,7 @@ details.
|
|
|
844
882
|
|
|
845
883
|
Select menus use native viewport shifting with 10px gutters: default menus grow to content (up to 20rem), while `fitMenu` matches trigger width and long labels wrap. Keep popup sizing and position repairs out of consumer CSS.
|
|
846
884
|
|
|
847
|
-
Key sibling `DialogSurface`s that render conditionally with `rootAttributes={{ "data-key": "…" }}` so a re-render morphs each dialog into itself; never wrap one in a keyed app `div`. Modal `DialogSurface` bounds use typed `viewportGutter` and `maxHeight` (`CssLength` or `"viewport"`); explicit caps stay within the dynamic viewport minus both gutters. Omitted props preserve Web Awesome defaults. Application render state chooses responsive values, and preview layout remains application-owned. `height` sets a definite bounded modal size and `bodyLayout="fill"` lets one child fill the body; `bodyLayout="fill-last"` keeps leading rows at their height above one filling last child, and `footerPresentation="floating"` (with a typed `footerOffset`) floats a chat composer over the body while the body reserves its measured height; give the transcript `scrollClearance="block-end"` (a `scrollable` `List` or a `Pane`) to let it scroll beneath the composer, with the last message still resting above it, through the published `--kui-scroll-clearance-block-end` context (overlaying chrome publishes `--kui-scroll-clearance-block-start` / `-end`; one scroller per covered edge opts in; a composed Pane opts in through `PaneConfig.scrollClearance` on a Workbench `mainPane` or panel `pane`, a `CollapsiblePanel` `pane`, or a `NavStack` view's `pane`). `headerPresentation="floating"` lays the title bar over the body on the translucent `--kui-chrome-material-raised` (opaque under reduced transparency, increased contrast, and forced colors); the register measures it and the body reserves it, and a `fill` body's only child with `scrollClearance="both"` scrolls beneath both the title bar and a floating composer through the published `--kui-scroll-clearance-block-start`. `PopoverDialogSurface` is the non-modal, light-dismiss counterpart on the native Popover API, with the same size props and an `embedded` in-flow presentation; `describedBy` sets its `aria-describedby` (usually a title `ToolbarText`'s `descriptionId`).
|
|
885
|
+
Key sibling `DialogSurface`s that render conditionally with `rootAttributes={{ "data-key": "…" }}` so a re-render morphs each dialog into itself; never wrap one in a keyed app `div`. Modal `DialogSurface` bounds use typed `viewportGutter` and `maxHeight` (`CssLength` or `"viewport"`); explicit caps stay within the dynamic viewport minus both gutters. Omitted props preserve Web Awesome defaults. Application render state chooses responsive values, and preview layout remains application-owned. `height` sets a definite bounded modal size and `bodyLayout="fill"` lets one child fill the body; `bodyLayout="fill-last"` keeps leading rows at their height above one filling last child, and `footerPresentation="floating"` (with a typed `footerOffset`) floats a chat composer over the body while the body reserves its measured height; give the transcript `scrollClearance="block-end"` (a `scrollable` `List` or a `Pane`) to let it scroll beneath the composer, with the last message still resting above it, through the published `--kui-scroll-clearance-block-end` context (overlaying chrome publishes `--kui-scroll-clearance-block-start` / `-end`; one scroller per covered edge opts in; a composed Pane opts in through `PaneConfig.scrollClearance` on a Workbench `mainPane` or panel `pane`, a `CollapsiblePanel` `pane`, or a `NavStack` view's `pane`). `headerPresentation="floating"` lays the title bar over the body on the translucent `--kui-chrome-material-raised` (opaque under reduced transparency, increased contrast, and forced colors); the register measures it and the body reserves it, and a `fill` body's only child with `scrollClearance="both"` scrolls beneath both the title bar and a floating composer through the published `--kui-scroll-clearance-block-start`. `presentation="bottom-sheet"` attaches the dialog to the bottom edge with square bottom corners, safe-area and virtual-keyboard clearance behind the panel, and a slide in/out (`enterMotion` / `exitMotion`); `resizable` adds a top grabber separator bounded by `minHeight` and `maxHeight`. `PopoverDialogSurface` is the non-modal, light-dismiss counterpart on the native Popover API, with the same size props and an `embedded` in-flow presentation; `describedBy` sets its `aria-describedby` (usually a title `ToolbarText`'s `descriptionId`).
|
|
848
886
|
|
|
849
887
|
Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog` and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. A host that is itself an explicit dialog (`role="dialog"` or `"alertdialog"` with its own name) keeps that name alone: the native panel is left unnamed, so one dialog carries the name. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
|
|
850
888
|
|