@usableapp/cardds 0.7.68 → 0.7.70
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/CHANGELOG.md +10 -0
- package/README.md +6 -0
- package/css/stack.css +44 -0
- package/css/stamp.css +3 -0
- package/dist/cardds.css +47 -0
- package/dist/scaffold/Screen.js +6 -4
- package/dist/sheets/SheetStack.d.ts +5 -2
- package/dist/sheets/SheetStack.js +7 -3
- package/dist/templates/Home.d.ts +5 -3
- package/dist/templates/Home.js +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
> For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
|
|
4
4
|
|
|
5
|
+
## 0.7.70 — 2026-10-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`Home rise="flood"` / `SheetStack closed rise="flood"`: an opened sheet floods the whole screen, over the top bar** (Lh 2026-10-08) — a tap on a sheet head brings that sheet up over the `AppBar` to fill the screen from its top edge to its bottom. The bar is covered, and hidden from the keyboard and a screen reader, while a sheet is open; it is back at once when the sheet folds. At the screen's top the open sheet's top corners are square; folded, they are round again. Closed, the home looks exactly as before: the bar on top, the `base` under it, the heads at the bottom. `<Home rise="flood" top={…} title="…" sheets={…} base={…} />`. Before: an open sheet always stopped under the top bar; that is still the default (`rise="under"`), so nothing to change in a screen that does not ask for it. Only for a `closed` stack in a `Screen fill` (`Home` always is); on any other stack it warns once and is ignored. In plain markup the class is `sheet-stack--flood`. On a phone running edge to edge the sheet stops at the status bar's line (`--safe-top`), it does not go under it. Gallery: screen · Home, the `Flood` story.
|
|
9
|
+
|
|
10
|
+
## 0.7.69 — 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **A `Stamp` in a `Centre` above a closed stack fits a short phone** (reported by timebank, 2026-10-07) — in `Home base={<Centre>…}` / `SheetStack base` the stamp is held by the HEIGHT of the room above the sheet heads as well as by the screen's width: its height is at most 45% of that room, so two lines of words fit under it. Before: sized from the width alone, at 320×568 the stamp ran up under the top bar and the words met the first sheet's head (a group 314px tall in a 253px room). A phone 640 tall or more looks as before. Nothing to change in a screen. Three lines of words under the stamp on a 320×568 phone with three sheets still run a little long: keep the line short.
|
|
14
|
+
|
|
5
15
|
## 0.7.68 — 2026-10-07
|
|
6
16
|
|
|
7
17
|
### Changed
|
package/README.md
CHANGED
|
@@ -188,6 +188,12 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
188
188
|
mark changes colour the same way. Right after a fold, while the
|
|
189
189
|
pile is still coming back, a tap is answered by where each head RESTS: the
|
|
190
190
|
head that will stand under the finger opens, whatever is passing there.
|
|
191
|
+
An open sheet stops under the top bar. `rise="flood"` on the `SheetStack` (or
|
|
192
|
+
`Home`) — `sheet-stack--flood` in markup — lets it rise over the bar and fill
|
|
193
|
+
the whole screen instead: the stack is then the whole screen, the bar a layer
|
|
194
|
+
under the sheets (hidden while one is open), nothing resizes. At the screen's
|
|
195
|
+
top the open sheet's top corners are square; folded, they are round again. It needs
|
|
196
|
+
`--closed` in a `Screen fill`.
|
|
191
197
|
- **Five sheets max.** Slots are declared per `:nth-child` in `stack.css`; a
|
|
192
198
|
sixth would render at slot 0, on top of the first.
|
|
193
199
|
- **Every head needs its own `.sheet-stack__back`** if the stack is `--tap`. It stays
|
package/css/stack.css
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
</div>
|
|
31
31
|
--closed (with --tap): nothing open on arrival — every sheet peeks at the
|
|
32
32
|
bottom, the headroom above is the base's (block below).
|
|
33
|
+
--flood (with --closed): an open sheet fills the whole screen, over the top bar.
|
|
33
34
|
Direct children only, five at most (slots are :nth-child below):
|
|
34
35
|
first = the back of the stack, last = the front. README "Sheet stack
|
|
35
36
|
markup". The SheetStack component opens and closes a --tap stack; without it the
|
|
@@ -285,6 +286,7 @@
|
|
|
285
286
|
overflow: hidden;
|
|
286
287
|
overscroll-behavior: none;
|
|
287
288
|
touch-action: none;
|
|
289
|
+
container-type: size; /* what stands in it may be sized by this room (cqh) — its width is the fill screen's own content width, so cqw reads the same as before */
|
|
288
290
|
}
|
|
289
291
|
/* a base that is ONE Page is a page's words (a title, a welcome, the news): it reads from the TOP, like any page —
|
|
290
292
|
loose things (a picture, a line) still sit against the peeks (Lh 2026-09-23, the home) */
|
|
@@ -294,6 +296,48 @@
|
|
|
294
296
|
.sheet-stack__base:has(> .screen__centre:only-child) { align-content: center; }
|
|
295
297
|
.sheet-stack__base > .screen__centre { padding-inline: 0; }
|
|
296
298
|
|
|
299
|
+
/* ============================================================
|
|
300
|
+
.sheet-stack--flood — a closed stack whose open sheet FLOODS the screen: it rises over the top bar and fills the whole screen
|
|
301
|
+
(`rise="flood"`), instead of stopping under it. Only for a --closed --tap stack in a .screen--fill.
|
|
302
|
+
|
|
303
|
+
A sheet is rigid: it cannot grow to cover the bar, it only moves by translate. So the stack is made the
|
|
304
|
+
whole screen instead, and every sheet is screen-height always. The bar leaves the column and becomes a
|
|
305
|
+
LAYER at the screen's top, under the sheets (they paint at --level 1-5, the bar at 0): closed, the peeks
|
|
306
|
+
still end at the stack's bottom, --_headroom unchanged; open, the same translate lands the sheet flush
|
|
307
|
+
at the stack's top, which is now the screen's top, and the bar is what it covers. Nothing resizes, no
|
|
308
|
+
new state, no script — Screen only measures the bar (--top-h; a bar is a floor, it grows with the text).
|
|
309
|
+
The screen's safe-top padding stays: a sheet stops at that line, never under the phone's status bar.
|
|
310
|
+
|
|
311
|
+
The base must not sit under the bar or take its taps: it starts below the bar (margin, not padding —
|
|
312
|
+
its box does not overlap the bar) and is shorter by the same amount, so its room is exactly what it was
|
|
313
|
+
under a bar in the column. That amount is the bar plus the breathing under it today (--sp-2 under an
|
|
314
|
+
appbar, the screen's gap under a topbar). Covered, the bar is also hidden (visibility), after the sheet
|
|
315
|
+
has covered it and at once when the sheet folds, so a keyboard and a screen reader do not reach it.
|
|
316
|
+
============================================================ */
|
|
317
|
+
.screen--fill:has(> .sheet-stack--flood) { --_bar-gap: var(--sp-2); }
|
|
318
|
+
.screen--fill:has(> .sheet-stack--flood):has(> .topbar:first-child) { --_bar-gap: var(--screen-gap); }
|
|
319
|
+
.screen--fill:has(> .sheet-stack--flood) > :is(.appbar, .topbar):first-child {
|
|
320
|
+
position: absolute;
|
|
321
|
+
inset: var(--safe-top) 0 auto;
|
|
322
|
+
max-width: none;
|
|
323
|
+
margin: 0;
|
|
324
|
+
z-index: 0; /* under every sheet (--level 1-5); the base clears it by margin */
|
|
325
|
+
}
|
|
326
|
+
.sheet-stack--flood { --_bar: calc(var(--top-h, var(--header-h)) + var(--_bar-gap)); }
|
|
327
|
+
.sheet-stack--flood > .sheet-stack__base {
|
|
328
|
+
margin-top: var(--_bar);
|
|
329
|
+
height: calc(var(--_headroom, 0px) - var(--_bar));
|
|
330
|
+
}
|
|
331
|
+
.screen--fill:has(> .sheet-stack--flood > .stack-card.is-open, > .sheet-stack--flood > :not(.stack-card) > .stack-card.is-open) > :is(.appbar, .topbar):first-child {
|
|
332
|
+
visibility: hidden;
|
|
333
|
+
transition: visibility 0s linear var(--motion-sheet); /* gone once the sheet has covered it; back at once on a fold (the rule above has no delay) */
|
|
334
|
+
}
|
|
335
|
+
/* at the screen's top the open sheet's top corners are SQUARE (Lh 2026-10-08): round, they left two small pockets of the
|
|
336
|
+
bar's ground at the glass. They square as the sheet rises and round again as it folds, in the slide's own time. Paint
|
|
337
|
+
only, the sheet's size is untouched. A pull on the head keeps them square until the finger lets go. */
|
|
338
|
+
.sheet-stack--flood > .card { transition: translate var(--motion-sheet) ease, border-radius var(--motion-sheet) ease; }
|
|
339
|
+
.sheet-stack--flood > .card.is-open { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
340
|
+
|
|
297
341
|
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
298
342
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
299
343
|
slide read as a second thing moving while the sheet itself moved; Lh,
|
package/css/stamp.css
CHANGED
|
@@ -85,6 +85,9 @@
|
|
|
85
85
|
/* a stamp handed over in a Centre: sized by the SCREEN (a little over half its width inside the inset), its greeting centred under it */
|
|
86
86
|
.screen__centre:has(> .stamp) { justify-items: center; text-align: center; }
|
|
87
87
|
.screen__centre > .stamp { max-width: var(--stamp-centre-max-w, 56cqw); }
|
|
88
|
+
/* in the room above a closed stack's heads the stamp is held by that room's HEIGHT too: its height at most 45% of it, so two lines of words under it
|
|
89
|
+
still fit on a short phone (320×568 left 253px for a group 314 tall — reported by timebank, 2026-10-07). cqh reads the base, a size container. */
|
|
90
|
+
.sheet-stack__base > .screen__centre > .stamp { max-width: min(var(--stamp-centre-max-w, 56cqw), 36cqh); }
|
|
88
91
|
|
|
89
92
|
/* ---- stamp place: an empty place for a stamp — an album's gap, "make yours" ----
|
|
90
93
|
<span class="stamp-place">สร้าง</span> · <button class="stamp-place">…</button> · <span class="stamp-place stamp-place--faint"></span> */
|
package/dist/cardds.css
CHANGED
|
@@ -1054,6 +1054,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1054
1054
|
</div>
|
|
1055
1055
|
--closed (with --tap): nothing open on arrival — every sheet peeks at the
|
|
1056
1056
|
bottom, the headroom above is the base's (block below).
|
|
1057
|
+
--flood (with --closed): an open sheet fills the whole screen, over the top bar.
|
|
1057
1058
|
Direct children only, five at most (slots are :nth-child below):
|
|
1058
1059
|
first = the back of the stack, last = the front. README "Sheet stack
|
|
1059
1060
|
markup". The SheetStack component opens and closes a --tap stack; without it the
|
|
@@ -1309,6 +1310,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1309
1310
|
overflow: hidden;
|
|
1310
1311
|
overscroll-behavior: none;
|
|
1311
1312
|
touch-action: none;
|
|
1313
|
+
container-type: size; /* what stands in it may be sized by this room (cqh) — its width is the fill screen's own content width, so cqw reads the same as before */
|
|
1312
1314
|
}
|
|
1313
1315
|
/* a base that is ONE Page is a page's words (a title, a welcome, the news): it reads from the TOP, like any page —
|
|
1314
1316
|
loose things (a picture, a line) still sit against the peeks (Lh 2026-09-23, the home) */
|
|
@@ -1318,6 +1320,48 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1318
1320
|
.sheet-stack__base:has(> .screen__centre:only-child) { align-content: center; }
|
|
1319
1321
|
.sheet-stack__base > .screen__centre { padding-inline: 0; }
|
|
1320
1322
|
|
|
1323
|
+
/* ============================================================
|
|
1324
|
+
.sheet-stack--flood — a closed stack whose open sheet FLOODS the screen: it rises over the top bar and fills the whole screen
|
|
1325
|
+
(`rise="flood"`), instead of stopping under it. Only for a --closed --tap stack in a .screen--fill.
|
|
1326
|
+
|
|
1327
|
+
A sheet is rigid: it cannot grow to cover the bar, it only moves by translate. So the stack is made the
|
|
1328
|
+
whole screen instead, and every sheet is screen-height always. The bar leaves the column and becomes a
|
|
1329
|
+
LAYER at the screen's top, under the sheets (they paint at --level 1-5, the bar at 0): closed, the peeks
|
|
1330
|
+
still end at the stack's bottom, --_headroom unchanged; open, the same translate lands the sheet flush
|
|
1331
|
+
at the stack's top, which is now the screen's top, and the bar is what it covers. Nothing resizes, no
|
|
1332
|
+
new state, no script — Screen only measures the bar (--top-h; a bar is a floor, it grows with the text).
|
|
1333
|
+
The screen's safe-top padding stays: a sheet stops at that line, never under the phone's status bar.
|
|
1334
|
+
|
|
1335
|
+
The base must not sit under the bar or take its taps: it starts below the bar (margin, not padding —
|
|
1336
|
+
its box does not overlap the bar) and is shorter by the same amount, so its room is exactly what it was
|
|
1337
|
+
under a bar in the column. That amount is the bar plus the breathing under it today (--sp-2 under an
|
|
1338
|
+
appbar, the screen's gap under a topbar). Covered, the bar is also hidden (visibility), after the sheet
|
|
1339
|
+
has covered it and at once when the sheet folds, so a keyboard and a screen reader do not reach it.
|
|
1340
|
+
============================================================ */
|
|
1341
|
+
.screen--fill:has(> .sheet-stack--flood) { --_bar-gap: var(--sp-2); }
|
|
1342
|
+
.screen--fill:has(> .sheet-stack--flood):has(> .topbar:first-child) { --_bar-gap: var(--screen-gap); }
|
|
1343
|
+
.screen--fill:has(> .sheet-stack--flood) > :is(.appbar, .topbar):first-child {
|
|
1344
|
+
position: absolute;
|
|
1345
|
+
inset: var(--safe-top) 0 auto;
|
|
1346
|
+
max-width: none;
|
|
1347
|
+
margin: 0;
|
|
1348
|
+
z-index: 0; /* under every sheet (--level 1-5); the base clears it by margin */
|
|
1349
|
+
}
|
|
1350
|
+
.sheet-stack--flood { --_bar: calc(var(--top-h, var(--header-h)) + var(--_bar-gap)); }
|
|
1351
|
+
.sheet-stack--flood > .sheet-stack__base {
|
|
1352
|
+
margin-top: var(--_bar);
|
|
1353
|
+
height: calc(var(--_headroom, 0px) - var(--_bar));
|
|
1354
|
+
}
|
|
1355
|
+
.screen--fill:has(> .sheet-stack--flood > .stack-card.is-open, > .sheet-stack--flood > :not(.stack-card) > .stack-card.is-open) > :is(.appbar, .topbar):first-child {
|
|
1356
|
+
visibility: hidden;
|
|
1357
|
+
transition: visibility 0s linear var(--motion-sheet); /* gone once the sheet has covered it; back at once on a fold (the rule above has no delay) */
|
|
1358
|
+
}
|
|
1359
|
+
/* at the screen's top the open sheet's top corners are SQUARE (Lh 2026-10-08): round, they left two small pockets of the
|
|
1360
|
+
bar's ground at the glass. They square as the sheet rises and round again as it folds, in the slide's own time. Paint
|
|
1361
|
+
only, the sheet's size is untouched. A pull on the head keeps them square until the finger lets go. */
|
|
1362
|
+
.sheet-stack--flood > .card { transition: translate var(--motion-sheet) ease, border-radius var(--motion-sheet) ease; }
|
|
1363
|
+
.sheet-stack--flood > .card.is-open { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
1364
|
+
|
|
1321
1365
|
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
1322
1366
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
1323
1367
|
slide read as a second thing moving while the sheet itself moved; Lh,
|
|
@@ -3939,6 +3983,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3939
3983
|
/* a stamp handed over in a Centre: sized by the SCREEN (a little over half its width inside the inset), its greeting centred under it */
|
|
3940
3984
|
.screen__centre:has(> .stamp) { justify-items: center; text-align: center; }
|
|
3941
3985
|
.screen__centre > .stamp { max-width: var(--stamp-centre-max-w, 56cqw); }
|
|
3986
|
+
/* in the room above a closed stack's heads the stamp is held by that room's HEIGHT too: its height at most 45% of it, so two lines of words under it
|
|
3987
|
+
still fit on a short phone (320×568 left 253px for a group 314 tall — reported by timebank, 2026-10-07). cqh reads the base, a size container. */
|
|
3988
|
+
.sheet-stack__base > .screen__centre > .stamp { max-width: min(var(--stamp-centre-max-w, 56cqw), 36cqh); }
|
|
3942
3989
|
|
|
3943
3990
|
/* ---- stamp place: an empty place for a stamp — an album's gap, "make yours" ----
|
|
3944
3991
|
<span class="stamp-place">สร้าง</span> · <button class="stamp-place">…</button> · <span class="stamp-place stamp-place--faint"></span> */
|
package/dist/scaffold/Screen.js
CHANGED
|
@@ -28,14 +28,16 @@ export function Screen({ fill, palette, as = 'div', top, bottom, ready = true, c
|
|
|
28
28
|
}
|
|
29
29
|
return watchSize([bar], () => el.style.setProperty('--bottom-h', `${bar.offsetHeight}px`));
|
|
30
30
|
}, [bottom, ready]);
|
|
31
|
-
/*
|
|
31
|
+
/* the TOP's height published as --top-h, a TRACK or a bar: a stage keeps that line free under a track (step.css); a
|
|
32
|
+
stack that rises over the bar (stack.css, `rise="flood"`) starts its base below it. A bar's height is a floor, not a size (it grows
|
|
33
|
+
with the text), so it is measured */
|
|
32
34
|
useEffect(() => {
|
|
33
|
-
const el = own.current,
|
|
34
|
-
if (!el || !
|
|
35
|
+
const el = own.current, lead = el?.querySelector(':scope > :is(.track, .appbar, .topbar):first-child');
|
|
36
|
+
if (!el || !lead) {
|
|
35
37
|
el?.style.removeProperty('--top-h');
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
38
|
-
return watchSize([
|
|
40
|
+
return watchSize([lead], () => el.style.setProperty('--top-h', `${lead.offsetHeight}px`));
|
|
39
41
|
}, [top, ready]);
|
|
40
42
|
const setRef = (node) => {
|
|
41
43
|
own.current = node;
|
|
@@ -19,6 +19,8 @@ export interface SheetStackProps extends ComponentProps<'div'> {
|
|
|
19
19
|
fanned?: boolean;
|
|
20
20
|
/** nothing open on arrival (tap mode): every sheet, the front one too, peeks at the stack's bottom; the room above is the base's (`base`). A tap opens any sheet over it */
|
|
21
21
|
closed?: boolean;
|
|
22
|
+
/** how an OPEN sheet meets the top bar of a closed stack in a `Screen fill`: `under` (default) stops under the bar · `flood` rises over it and fills the WHOLE screen (from the safe-top line down), the bar covered and hidden while a sheet is open. Only for a `closed` tap stack (warns once otherwise). Nothing resizes: the bar is a layer under the sheets */
|
|
23
|
+
rise?: 'under' | 'flood';
|
|
22
24
|
/** what the base shows in a closed stack's headroom, behind the sheets, laid out against the peeks (a picture, a greeting) */
|
|
23
25
|
base?: ReactNode;
|
|
24
26
|
}
|
|
@@ -29,11 +31,12 @@ export interface SheetStackProps extends ComponentProps<'div'> {
|
|
|
29
31
|
* with CHILDREN, in this order: `<StackBack />` (tap mode), `<Text level="h2" caps>Shopping</Text>`, an `<IconRow>` of Icons —
|
|
30
32
|
* never the `heading` prop (a stack head is back · h2 · tabs on ONE line — StackSheet builds it). Put it last in a `Screen fill`, it bleeds
|
|
31
33
|
* off the bottom. `closed`: nothing open on arrival — every head peeks at the bottom and the base above shows `base` (a home screen
|
|
32
|
-
* with a picture over the menu); the `base` slot is rendered after the sheets, so it never takes a sheet's slot.
|
|
34
|
+
* with a picture over the menu); the `base` slot is rendered after the sheets, so it never takes a sheet's slot. An open sheet fills everything under the top bar,
|
|
35
|
+
* or with `rise="flood"` the whole screen, covering the bar. This is the sheet STACK; a single bottom sheet is `Sheet`.
|
|
33
36
|
*
|
|
34
37
|
* A COMPONENT, not a wiring: WHICH sheet is open is the app's (`open` + `onOpenChange`, or `defaultOpen`) — so a button anywhere, a route or
|
|
35
38
|
* the phone's back button can open and close a section. WHAT the sheet does with that, and with `refreshing`, lives on the card (`stack`):
|
|
36
39
|
* the place classes, the grip, the pull. The stack only says which index. Sheets are rigid — the open one slides up, the ones after it leave
|
|
37
40
|
* below, the ones before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); never a size.
|
|
38
41
|
*/
|
|
39
|
-
export declare function SheetStack({ mode, open, defaultOpen, onOpenChange, onRefresh, refreshing, mark, fanned, closed, base, className, children, onClickCapture, onPointerDownCapture, ref: passed, ...rest }: SheetStackProps): import("react").JSX.Element;
|
|
42
|
+
export declare function SheetStack({ mode, open, defaultOpen, onOpenChange, onRefresh, refreshing, mark, fanned, closed, rise, base, className, children, onClickCapture, onPointerDownCapture, ref: passed, ...rest }: SheetStackProps): import("react").JSX.Element;
|
|
@@ -48,14 +48,15 @@ function stamp(child, place) {
|
|
|
48
48
|
* with CHILDREN, in this order: `<StackBack />` (tap mode), `<Text level="h2" caps>Shopping</Text>`, an `<IconRow>` of Icons —
|
|
49
49
|
* never the `heading` prop (a stack head is back · h2 · tabs on ONE line — StackSheet builds it). Put it last in a `Screen fill`, it bleeds
|
|
50
50
|
* off the bottom. `closed`: nothing open on arrival — every head peeks at the bottom and the base above shows `base` (a home screen
|
|
51
|
-
* with a picture over the menu); the `base` slot is rendered after the sheets, so it never takes a sheet's slot.
|
|
51
|
+
* with a picture over the menu); the `base` slot is rendered after the sheets, so it never takes a sheet's slot. An open sheet fills everything under the top bar,
|
|
52
|
+
* or with `rise="flood"` the whole screen, covering the bar. This is the sheet STACK; a single bottom sheet is `Sheet`.
|
|
52
53
|
*
|
|
53
54
|
* A COMPONENT, not a wiring: WHICH sheet is open is the app's (`open` + `onOpenChange`, or `defaultOpen`) — so a button anywhere, a route or
|
|
54
55
|
* the phone's back button can open and close a section. WHAT the sheet does with that, and with `refreshing`, lives on the card (`stack`):
|
|
55
56
|
* the place classes, the grip, the pull. The stack only says which index. Sheets are rigid — the open one slides up, the ones after it leave
|
|
56
57
|
* below, the ones before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); never a size.
|
|
57
58
|
*/
|
|
58
|
-
export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChange, onRefresh, refreshing = null, mark = 'dots', fanned, closed, base, className, children, onClickCapture, onPointerDownCapture, ref: passed, ...rest }) {
|
|
59
|
+
export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChange, onRefresh, refreshing = null, mark = 'dots', fanned, closed, rise = 'under', base, className, children, onClickCapture, onPointerDownCapture, ref: passed, ...rest }) {
|
|
59
60
|
const [current, request] = useControlled(open, defaultOpen, onOpenChange);
|
|
60
61
|
const prev = useRef(current);
|
|
61
62
|
const [closingFrom, setClosingFrom] = useState(null);
|
|
@@ -136,6 +137,9 @@ export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChang
|
|
|
136
137
|
e.stopPropagation();
|
|
137
138
|
request(i);
|
|
138
139
|
};
|
|
140
|
+
const flood = rise === 'flood' && !!closed && mode === 'tap';
|
|
141
|
+
if (rise === 'flood' && !flood)
|
|
142
|
+
warnOnce('stack-rise', 'cardds SheetStack: rise="flood" needs a closed tap stack (`closed`, mode "tap") — the bar a sheet rises over is the screen\'s, and only a closed stack leaves its base under it. Ignored.');
|
|
139
143
|
const closing = current == null && closingFrom != null;
|
|
140
144
|
const anchor = current ?? closingFrom;
|
|
141
145
|
const items = Children.toArray(children);
|
|
@@ -158,5 +162,5 @@ export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChang
|
|
|
158
162
|
};
|
|
159
163
|
return stamp(child, place);
|
|
160
164
|
}) : items;
|
|
161
|
-
return (_jsxs("div", { className: cx('sheet-stack', mode !== 'static' && `sheet-stack--${mode}`, fanned && 'sheet-stack--fanned', closed && 'sheet-stack--closed', className), ...rest, ref: setRef, onClickCapture: clickCapture, onPointerDownCapture: pointerDownCapture, children: [stamped, base != null && _jsx("div", { className: "sheet-stack__base", children: base })] }));
|
|
165
|
+
return (_jsxs("div", { className: cx('sheet-stack', mode !== 'static' && `sheet-stack--${mode}`, fanned && 'sheet-stack--fanned', closed && 'sheet-stack--closed', flood && 'sheet-stack--flood', className), ...rest, ref: setRef, onClickCapture: clickCapture, onPointerDownCapture: pointerDownCapture, children: [stamped, base != null && _jsx("div", { className: "sheet-stack__base", children: base })] }));
|
|
162
166
|
}
|
package/dist/templates/Home.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface HomeProps extends Omit<ComponentProps<typeof Screen>, 'children
|
|
|
37
37
|
refreshing?: number | null;
|
|
38
38
|
/** the grip on every sheet: `dots` (default) or `line` — as `SheetStack mark` */
|
|
39
39
|
mark?: 'dots' | 'line';
|
|
40
|
+
/** how an open sheet meets the top: `under` (default) stops under the AppBar · `flood` rises over it and fills the WHOLE screen, the bar covered while a sheet is open — as `SheetStack rise` */
|
|
41
|
+
rise?: 'under' | 'flood';
|
|
40
42
|
/** CONTENT above the sheet heads — one `Page`: a welcome, the news, a picture. It reads
|
|
41
43
|
* from the top; a sheet coming up covers it */
|
|
42
44
|
base?: ReactNode;
|
|
@@ -51,9 +53,9 @@ export interface HomeProps extends Omit<ComponentProps<typeof Screen>, 'children
|
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* Home — TEMPLATE, a `Screen` with its parts filled — top: the AppBar · content: the `base` (title, welcome…) over a closed `SheetStack` · no bottom
|
|
54
|
-
* (the stack is content, never the bottom: open, a sheet fills everything under the top).
|
|
56
|
+
* (the stack is content, never the bottom: open, a sheet fills everything under the top, or with `rise="flood"` the whole screen).
|
|
55
57
|
* Every section is a `StackSheet` of three parts: SHEET HEAD (title + icon tabs, a count over a tab; the back button once open) · SHEET BODY (scrolls) · SHEET FOOT (the CTA,
|
|
56
|
-
* at the bottom). A tap on a head brings that sheet up to fill everything under the top. With a LayerHistory above and no `open` prop, that open sheet is one history entry named `home`. Only the open sheet's body is rendered — three
|
|
58
|
+
* at the bottom). A tap on a head brings that sheet up to fill everything under the top (`rise="flood"`: the whole screen, over the bar). With a LayerHistory above and no `open` prop, that open sheet is one history entry named `home`. Only the open sheet's body is rendered — three
|
|
57
59
|
* sections on one home never cost three pages (`keep` holds a body once opened).
|
|
58
60
|
*/
|
|
59
|
-
export declare function Home({ top, title, sheets, open: openProp, defaultOpen, onOpenChange, onRefresh, refreshing, mark, base, keep, backLabel, backAt, children, ...rest }: HomeProps): import("react").JSX.Element;
|
|
61
|
+
export declare function Home({ top, title, sheets, open: openProp, defaultOpen, onOpenChange, onRefresh, refreshing, mark, rise, base, keep, backLabel, backAt, children, ...rest }: HomeProps): import("react").JSX.Element;
|
package/dist/templates/Home.js
CHANGED
|
@@ -11,12 +11,12 @@ const TONES = [2, 3, 1, 2, 3];
|
|
|
11
11
|
const LEAVE = 800; // ms: a closing sheet keeps its body until it has slid home
|
|
12
12
|
/**
|
|
13
13
|
* Home — TEMPLATE, a `Screen` with its parts filled — top: the AppBar · content: the `base` (title, welcome…) over a closed `SheetStack` · no bottom
|
|
14
|
-
* (the stack is content, never the bottom: open, a sheet fills everything under the top).
|
|
14
|
+
* (the stack is content, never the bottom: open, a sheet fills everything under the top, or with `rise="flood"` the whole screen).
|
|
15
15
|
* Every section is a `StackSheet` of three parts: SHEET HEAD (title + icon tabs, a count over a tab; the back button once open) · SHEET BODY (scrolls) · SHEET FOOT (the CTA,
|
|
16
|
-
* at the bottom). A tap on a head brings that sheet up to fill everything under the top. With a LayerHistory above and no `open` prop, that open sheet is one history entry named `home`. Only the open sheet's body is rendered — three
|
|
16
|
+
* at the bottom). A tap on a head brings that sheet up to fill everything under the top (`rise="flood"`: the whole screen, over the bar). With a LayerHistory above and no `open` prop, that open sheet is one history entry named `home`. Only the open sheet's body is rendered — three
|
|
17
17
|
* sections on one home never cost three pages (`keep` holds a body once opened).
|
|
18
18
|
*/
|
|
19
|
-
export function Home({ top, title, sheets, open: openProp, defaultOpen = null, onOpenChange, onRefresh, refreshing, mark, base, keep, backLabel = word('back'), backAt, children, ...rest }) {
|
|
19
|
+
export function Home({ top, title, sheets, open: openProp, defaultOpen = null, onOpenChange, onRefresh, refreshing, mark, rise, base, keep, backLabel = word('back'), backAt, children, ...rest }) {
|
|
20
20
|
const layer = useOptionalLayer('home');
|
|
21
21
|
const [local, change] = useControlled(openProp, defaultOpen, onOpenChange);
|
|
22
22
|
// A passed `open` is the parent's. With no LayerHistory the index stays local, so a story without a router still opens sheets.
|
|
@@ -50,5 +50,5 @@ export function Home({ top, title, sheets, open: openProp, defaultOpen = null, o
|
|
|
50
50
|
}, [leaving]);
|
|
51
51
|
const [tabs, setTabs] = useState(() => Object.fromEntries(sheets.map((x, i) => [i, x.defaultTab ?? x.tabs?.[0]?.id])));
|
|
52
52
|
const shows = (i) => i === shown || i === leaving || (keep && seen.has(i));
|
|
53
|
-
return (_jsxs(Screen, { fill: true, top: top, ...rest, children: [_jsx(Text, { level: "h1", as: "h1", sr: true, children: title }), _jsx(SheetStack, { closed: true, open: shown, onOpenChange: request, onRefresh: onRefresh, refreshing: refreshing, mark: mark, base: base, children: sheets.map((s, i) => (_jsx(StackSheet, { tone: s.tone ?? TONES[i], paper: s.paper, title: s.title, icon: s.icon, tabs: s.tabs, tab: tabs[i], onTabChange: (t) => setTabs((v) => ({ ...v, [i]: t })), backLabel: backLabel, backAt: backAt, foot: typeof s.foot === 'function' ? s.foot(tabs[i]) : s.foot, children: shows(i) ? (typeof s.body === 'function' ? s.body(tabs[i]) : s.body) : null }, i))) }), children] }));
|
|
53
|
+
return (_jsxs(Screen, { fill: true, top: top, ...rest, children: [_jsx(Text, { level: "h1", as: "h1", sr: true, children: title }), _jsx(SheetStack, { closed: true, open: shown, onOpenChange: request, onRefresh: onRefresh, refreshing: refreshing, mark: mark, rise: rise, base: base, children: sheets.map((s, i) => (_jsx(StackSheet, { tone: s.tone ?? TONES[i], paper: s.paper, title: s.title, icon: s.icon, tabs: s.tabs, tab: tabs[i], onTabChange: (t) => setTabs((v) => ({ ...v, [i]: t })), backLabel: backLabel, backAt: backAt, foot: typeof s.foot === 'function' ? s.foot(tabs[i]) : s.foot, children: shows(i) ? (typeof s.body === 'function' ? s.body(tabs[i]) : s.body) : null }, i))) }), children] }));
|
|
54
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.70",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",
|