@usableapp/cardds 0.6.4 → 0.6.5

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +65 -18
  3. package/css/actions.css +15 -0
  4. package/css/base.css +3 -0
  5. package/css/choice.css +11 -0
  6. package/css/layover.css +3 -0
  7. package/css/sheet.css +7 -0
  8. package/css/stack.css +55 -0
  9. package/css/step.css +44 -2
  10. package/dist/actions/Btn.d.ts +1 -1
  11. package/dist/actions/IconBtn.d.ts +1 -1
  12. package/dist/cardds.css +138 -2
  13. package/dist/choice/Check.d.ts +6 -3
  14. package/dist/choice/Check.js +7 -4
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.js +3 -0
  17. package/dist/scaffold/ActionBar.d.ts +3 -2
  18. package/dist/scaffold/ActionBar.js +3 -2
  19. package/dist/scaffold/AppBar.d.ts +4 -1
  20. package/dist/scaffold/AppBar.js +4 -3
  21. package/dist/scaffold/Screen.d.ts +13 -8
  22. package/dist/scaffold/Screen.js +24 -7
  23. package/dist/sheets/SheetStack.d.ts +1 -1
  24. package/dist/sheets/SheetStack.js +6 -1
  25. package/dist/sheets/SheetStage.d.ts +4 -1
  26. package/dist/sheets/SheetStage.js +2 -2
  27. package/dist/sheets/StackBack.d.ts +2 -1
  28. package/dist/sheets/StackBack.js +3 -2
  29. package/dist/sheets/StackSheet.d.ts +35 -0
  30. package/dist/sheets/StackSheet.js +24 -0
  31. package/dist/sheets/Step.d.ts +2 -2
  32. package/dist/sheets/Step.js +2 -2
  33. package/dist/templates/Home.d.ts +48 -0
  34. package/dist/templates/Home.js +40 -0
  35. package/dist/templates/Walkthrough.d.ts +42 -0
  36. package/dist/templates/Walkthrough.js +34 -0
  37. package/dist/type/Text.d.ts +3 -1
  38. package/dist/type/Text.js +2 -2
  39. package/dist/type/icons.d.ts +1 -1
  40. package/dist/type/icons.js +2 -2
  41. package/icons.svg +2 -0
  42. package/package.json +1 -1
package/dist/cardds.css CHANGED
@@ -484,6 +484,9 @@ button { font: inherit; cursor: pointer; }
484
484
  .card--3 .t-muted { color: inherit; opacity: 0.7; } /* card-muted is tuned for light cards; dim instead on the dark slot */
485
485
  .t-caps { text-transform: uppercase; }
486
486
  .grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
487
+ /* read, not shown (Lh 2026-09-23): words a screen reader needs — a page's h1 the design leaves out — kept in the page, taking no room.
488
+ The 1px is the one the platform needs to keep it in the accessibility tree, not a length of the design. */
489
+ .t-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
487
490
 
488
491
  /* ---- base content: words straight on the base, no card around them — an
489
492
  a heading, a paragraph, maybe a .cta-pack. Was .section until
@@ -1065,6 +1068,9 @@ button { font: inherit; cursor: pointer; }
1065
1068
  gap: var(--card-gap, var(--sp-5));
1066
1069
  overflow: clip;
1067
1070
  }
1071
+ /* a base that is ONE BaseContent is a page's words (a title, a welcome, the news): it reads from the TOP, like any page —
1072
+ loose things (a picture, a line) still sit against the peeks (Lh 2026-09-23, the home) */
1073
+ .sheet-stack__base:has(> .base-content:only-child) { align-content: start; }
1068
1074
 
1069
1075
  /* back button: lives in a slot the header reserves once open, and DISSOLVES
1070
1076
  into it — an opacity fade in place, never a slide in from the left (the
@@ -1092,6 +1098,58 @@ button { font: inherit; cursor: pointer; }
1092
1098
  visibility: visible;
1093
1099
  }
1094
1100
 
1101
+ /* ---- a stacked sheet with a SheetBody has the ONE sheet shape (Lh 2026-09-23, the home layout): sheet head
1102
+ pinned at the top · sheet body scrolls, as long as it needs · sheet foot (a CardFoot, the CTA) at the bottom.
1103
+ The card becomes a flex column and stops scrolling itself — only the body does. A sheet with no SheetBody
1104
+ keeps the old way: the whole open card scrolls. ---- */
1105
+ .sheet-stack--tap > .card:has(> .sheet__body) { display: flex; flex-direction: column; gap: var(--card-gap); }
1106
+ .sheet-stack--tap > .card:has(> .sheet__body).is-open { overflow: clip; }
1107
+ .sheet-stack > .card:has(> .sheet__body) > :not(.sheet__body) { flex-shrink: 0; }
1108
+ .sheet-stack > .card > .sheet__body { --_cover: 0rem; } /* nothing slid under here: the stack's sheet is always whole */
1109
+
1110
+ /* ---- StackSheet's tabs (Lh 2026-09-23): the head's end is a row of small icon buttons, the sheet's pages — the chosen one
1111
+ filled (.icon-btn--invert); a count is a small circle over the icon's top right corner ---- */
1112
+ .stack-sheet__tabs { display: flex; gap: var(--stack-sheet-tabs-gap, 0rem); flex: none; } /* no gap: each tab is a 40px target already, and an open head (back · title · tabs) must fit one line — Lh 2026-09-23 */
1113
+ .stack-sheet__tab { position: relative; }
1114
+ .stack-sheet__tab > .stack-sheet__count { /* outranks .badge (numbers.css loads after this file) */
1115
+ position: absolute;
1116
+ top: calc(-1 * var(--sp-1));
1117
+ right: calc(-1 * var(--sp-1));
1118
+ min-width: var(--stack-sheet-count-h, 1.25rem);
1119
+ height: var(--stack-sheet-count-h, 1.25rem);
1120
+ padding: 0 var(--sp-1);
1121
+ font: var(--type-caption);
1122
+ line-height: 1;
1123
+ pointer-events: none;
1124
+ }
1125
+ /* a tab not chosen is the icon alone — no ring, no ground (Lh 2026-09-23: "the circle around is not pretty"); the chosen one turns
1126
+ to the opposite colour (.icon-btn--invert). */
1127
+ .stack-sheet__tab:not(.icon-btn--invert) { border-color: transparent; background: transparent; box-shadow: none; color: inherit; } /* the sheet's own ink, on any paper */
1128
+ /* the last tab's ICON lines up with the inset, not its button's edge: an unchosen tab shows the glyph only, so the button's own
1129
+ margin around it reaches into the card's padding — and an open head (back · title · three tabs) keeps one line (Lh 2026-09-23) */
1130
+ .card__head > .stack-sheet__tabs { margin-right: calc(-1 * var(--sp-2)); }
1131
+
1132
+ /* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
1133
+ tappable"): a SMALL DISC — its ring kept, 32px instead of 44, the chevron in it at 20 — so the head gives it 32px + a gap
1134
+ (was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
1135
+ .sheet-stack--tap > .card.is-open > .card__head { padding-left: var(--stack-back-room, calc(var(--stack-back-size, calc(var(--tap-base) * 2 / 3)) + var(--sp-2))); }
1136
+ .sheet-stack--tap .sheet-stack__back {
1137
+ width: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
1138
+ height: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
1139
+ min-width: 0;
1140
+ min-height: 0;
1141
+ padding: 0;
1142
+ }
1143
+ .sheet-stack--tap .sheet-stack__back > .icon { width: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); height: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); flex: none; } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
1144
+ .sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--stack-back-size, calc(var(--tap-base) * 2 / 3)) - var(--tap-sm)) / 2); }
1145
+
1146
+ /* the open sheet's title folds it (SheetStack.tsx): it says so to a pointer */
1147
+ .sheet-stack--tap > .card.is-open > .card__head > :is(h1, h2, h3) { cursor: pointer; }
1148
+ /* a stacked sheet is a control until it is open — tapped, never read into: no text selection (a drag, a double tap, a long press),
1149
+ no grey flash, no iOS callout; its head stays one when open. Only the OPEN sheet's body is text to select (Lh 2026-09-23) */
1150
+ .sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; }
1151
+ .sheet-stack--tap > .card.is-open > :not(.card__head) { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
1152
+
1095
1153
  /* ---- css/sheet.css — sheet single: .sheet — one card as a bottom sheet, peek / half / full ---- */
1096
1154
  /* ============================================================
1097
1155
  cardds/sheet.css — sheet single: one card as a bottom sheet.
@@ -1338,6 +1396,13 @@ button { font: inherit; cursor: pointer; }
1338
1396
  that bar — the stage keeps the bar's line free itself (--_top), so the reserve
1339
1397
  would be paid twice and the page would scroll by it */
1340
1398
  .screen > .sheet-stage { margin-inline: calc(-1 * var(--screen-pad)); width: auto; } /* auto: 100% would keep the inset width */
1399
+ /* a stage laid OVER its screen (a Modal a link opened, Lh 2026-09-23): the page shows through, the stage covers it and its bar */
1400
+ .sheet-stage--over, .screen > .sheet-stage--over {
1401
+ position: absolute; inset: 0; z-index: 40;
1402
+ width: auto; height: auto; max-width: none; margin: 0;
1403
+ background: transparent;
1404
+ --_top: 0rem;
1405
+ }
1341
1406
  .appbar + .sheet-stage { margin-top: calc(-1 * (var(--header-h) + var(--header-gap, 0rem))); }
1342
1407
  .sheet-stage__bg { position: absolute; inset: 0; } /* map / previous cards behind */
1343
1408
  /* the hero: ONE thing shown in the band a half sheet leaves open (a postcard being made,
@@ -1517,13 +1582,16 @@ button { font: inherit; cursor: pointer; }
1517
1582
  /* no bar at all — the step is the screen's first child (a page whose one heading is the base's own, Lh 2026-09-22):
1518
1583
  the words start a share of the screen down — --screen-lead, 15% of its height (Lh 2026-09-22), so a tall phone gives
1519
1584
  more air and a short one less; a screen with a bar keeps the bar's own room */
1520
- .screen > .step:first-child { --_lead: var(--screen-lead, 15cqh); }
1585
+ .screen > .step:first-child, .screen--lead > .step { --_lead: var(--screen-lead, 15cqh); } /* top="lead" says it; a Step first says it too */
1521
1586
 
1522
1587
  /* the bar's room — read from actions.css, never recomputed. Margin, not
1523
1588
  padding, so the clip cuts at the box edge and nothing shows behind the
1524
1589
  bar. Any parent that hosts the bar counts — <body>, an app shell, a demo
1525
1590
  frame — the bar is fixed to the viewport wherever it sits. */
1526
1591
  :has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
1592
+ /* a walk (the dots float on its foot): the reading block runs right to the bar's top edge — the reserve's breathing gap (--sp-6)
1593
+ is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
1594
+ :has(> .action-bar) .step:has(> .pager__at) { margin-bottom: calc(var(--bar-reserve) - var(--sp-6) + var(--kb, 0rem)); }
1527
1595
  /* LAYERS (Lh, 2026-09-20): the base, then the sheet over it, then the top bar. A bar is the BASE's — the
1528
1596
  move of a page of words — so a step with a sheet has NO bar: the sheet finishes on its own .card__foot
1529
1597
  (sheet.css), and a form lives whole in ONE sheet, never item by item on the base. That is how cardds's
@@ -1555,7 +1623,16 @@ button { font: inherit; cursor: pointer; }
1555
1623
  }
1556
1624
  /* the "where am I" dots of a walk are the STEP's, one for all its pages — written after .pages, pinned to the base's foot above the bar's
1557
1625
  room, so a short page never leaves them stranded under its last line (Lh 2026-09-22: the dots belong to the page, not a card, not a paragraph) */
1558
- .step > .pager__at { margin-top: auto; align-self: center; padding-block: var(--sp-3); flex: none; }
1626
+ .step > .pager__at {
1627
+ /* it FLOATS over the reading block's foot (Lh 2026-09-23: the strip it took under the words was wasted room) — the pages
1628
+ run down to the bar, the dots sit on their bottom edge, centred, on the base's ground so a line passing under stays clear of them */
1629
+ position: absolute; bottom: 0; left: 50%; translate: -50% 0; z-index: 2;
1630
+ padding: var(--sp-3) var(--sp-2);
1631
+ border-radius: var(--r-chip);
1632
+ background: var(--surface);
1633
+ }
1634
+ /* the reading block fills the step, and keeps the dots' height free at the end of its scroll so the last line clears them */
1635
+ .step:has(> .pager__at) > :is(.base-content, .pages) { flex: 1 1 auto; padding-bottom: calc(var(--dot) + 2 * var(--sp-3)); }
1559
1636
  .step > :is(.base-content, .pages) {
1560
1637
  min-height: 0;
1561
1638
  margin: var(--_lead, 0) var(--screen-pad) 0; /* the screen's gap, moved in here from above the step; the inset, taken back from the bleed */
@@ -1574,6 +1651,36 @@ button { font: inherit; cursor: pointer; }
1574
1651
  background-color: var(--surface); /* the words pass UNDER it, never through a transparent picture */
1575
1652
  }
1576
1653
 
1654
+ /* ============================================================
1655
+ ONE FLEX COLUMN (Lh 2026-09-23: every part of a screen is laid by flex). In a .screen--fill the bar is IN the
1656
+ column, after the step: the step takes what is left (flex: 1), the bar takes what its rows need — measured by the
1657
+ browser, no --bar-reserve arithmetic, so a one-row bar gives its room straight back to the pages. The keyboard lifts
1658
+ the column's end: the screen pads its bottom by --kb, the bar rides on it.
1659
+ (A bar OUTSIDE a fill screen — an app shell, a scrolling screen — is still fixed to the viewport, actions.css.)
1660
+ ============================================================ */
1661
+ .screen--fill > .action-bar {
1662
+ position: relative; inset: auto; bottom: auto;
1663
+ flex: none;
1664
+ margin-top: auto; /* the column's end, even on a screen with nothing that grows (a centred card is absolute) */
1665
+ max-width: none;
1666
+ margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge, as the step; the bar's own padding keeps the inset */
1667
+ }
1668
+ .screen--fill:has(> .action-bar) { padding-bottom: var(--kb, 0rem); }
1669
+ .screen--fill:has(> .action-bar) > .step { margin-bottom: 0; }
1670
+ /* the step and its bar touch: the screen's gap between two stacked things is not for these two (the bar's own padding is its room) */
1671
+ .screen--fill:has(> .step) > .action-bar { margin-top: calc(-1 * var(--screen-gap)); }
1672
+ /* the TOP is in the column too (Lh 2026-09-23): an .appbar in a fill screen is its first row, not fixed over it — the screen
1673
+ reserves nothing for it; the breathing under it stays the one it had (--sp-2), not the screen's gap */
1674
+ .screen--fill > .appbar {
1675
+ position: relative; inset: auto; top: auto;
1676
+ flex: none;
1677
+ max-width: none;
1678
+ margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge; its own padding is the inset */
1679
+ margin-bottom: calc(var(--sp-2) - var(--screen-gap));
1680
+ }
1681
+ .screen.screen--fill:has(> .appbar) { padding-top: 0; --header-gap: 0rem; }
1682
+ .screen--fill > .appbar + .sheet-stage { margin-top: 0; }
1683
+
1577
1684
  /* ---- css/forms.css — outlined fields, add-row ---- */
1578
1685
  /* ============================================================
1579
1686
  cardds/forms.css — inputs + inline form actions
@@ -1767,6 +1874,15 @@ button { font: inherit; cursor: pointer; }
1767
1874
  .screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
1768
1875
  .appbar__brand { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
1769
1876
  .appbar__text { display: grid; min-width: 0; flex: 1; } /* pushes trailing button to the right edge */
1877
+ /* the mark + the words as ONE button (AppBar brandButton — the member's name opens their menu, Lh 2026-09-23): the same row, a tap target */
1878
+ .appbar__who {
1879
+ flex: 1; min-width: 0; min-height: var(--tap);
1880
+ display: flex; align-items: center; gap: var(--sp-2);
1881
+ padding: 0; margin: 0; border: 0; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
1882
+ border-radius: var(--appbar-who-radius, var(--r-chip));
1883
+ -webkit-tap-highlight-color: transparent;
1884
+ }
1885
+ .appbar__who:focus-visible { outline: var(--focus); outline-offset: 2px; }
1770
1886
  .appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.35; } /* clipped lines: room for Thai marks */
1771
1887
  .appbar__title { font: var(--type-label); }
1772
1888
  .appbar__sub { font: var(--type-caption); color: var(--on-surface-muted); }
@@ -1983,7 +2099,13 @@ button { font: inherit; cursor: pointer; }
1983
2099
  shadow and read on their own (Lh, 2026-09-12; replaces the 09-11 fade) */
1984
2100
  .action-bar__tier { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); }
1985
2101
  .action-bar__tier > .btn { flex: 1; }
2102
+ .action-bar__tier--link { justify-content: center; } /* a Link under the buttons, e.g. "details" that opens a Modal */
1986
2103
  :has(> .action-bar--tiers) { --bar-reserve: calc(var(--tap) + var(--sp-3) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2104
+ /* more rows (Lh 2026-09-23: a bar may hold several rows of controls — keeping it short is the designer's job): each row past two
2105
+ adds a tap-tall row and its gap to the reserve, so the words above never run under the bar */
2106
+ :has(> .action-bar--tiers > .action-bar__tier:nth-child(3)) { --bar-reserve: calc(2 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2107
+ :has(> .action-bar--tiers > .action-bar__tier:nth-child(4)) { --bar-reserve: calc(3 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2108
+ :has(> .action-bar--tiers > .action-bar__tier:nth-child(5)) { --bar-reserve: calc(4 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
1987
2109
 
1988
2110
  /* the first round button sits at the left edge, the last at the right; a skip link between them stays centred */
1989
2111
  :is(.action-bar--pager, .action-bar__tier--pager) > .icon-btn:first-child { margin-right: auto; }
@@ -2663,6 +2785,17 @@ button { font: inherit; cursor: pointer; }
2663
2785
  }
2664
2786
  .slider__range:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2665
2787
 
2788
+ /* ---- a switch that names itself (Toggle text, Lh 2026-09-23 — "อยู่บ้าน" on the home's top): the words, then the switch,
2789
+ one label — the whole of it is the tap target ---- */
2790
+ .toggle-field {
2791
+ display: inline-flex; align-items: center; gap: var(--toggle-field-gap, var(--sp-2));
2792
+ min-height: var(--tap);
2793
+ font: var(--toggle-field-font, var(--type-label));
2794
+ cursor: pointer;
2795
+ -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
2796
+ flex: none;
2797
+ }
2798
+
2666
2799
  /* ---- css/lists.css — row, rows, key-value, kv grid, timeline, legend, link ---- */
2667
2800
  /* ============================================================
2668
2801
  cardds/lists.css — frameless rows and pairs INSIDE a card:
@@ -3343,6 +3476,9 @@ button { font: inherit; cursor: pointer; }
3343
3476
  align-items: center;
3344
3477
  z-index: 30;
3345
3478
  }
3479
+ /* in a fill screen (its `bottom`) the float bar floats over the SCREEN's foot, not the window's — it lies over the content
3480
+ (a map, a stack), so it is not a row of the column; the screen is its box (Lh 2026-09-23) */
3481
+ .screen--fill > .float-bar { position: absolute; }
3346
3482
  .sheet-stage > .float-bar { position: absolute; padding-inline: var(--sp-4); }
3347
3483
  /* .screen's default bottom padding clears an .action-bar; a .float-bar is
3348
3484
  taller (--bar-h + its own bottom offset), so reserve the difference. Any
@@ -1,4 +1,4 @@
1
- import type { ComponentProps } from 'react';
1
+ import type { ComponentProps, ReactNode } from 'react';
2
2
  export interface CheckProps extends ComponentProps<'input'> {
3
3
  /** 40px instead of 48px */
4
4
  sm?: boolean;
@@ -8,7 +8,10 @@ export interface CheckProps extends ComponentProps<'input'> {
8
8
  /** Check — the round check circle at a Row's end (48px); the row's label wraps it. */
9
9
  export declare function Check({ sm, label, className, ...rest }: CheckProps): import("react").JSX.Element;
10
10
  export interface ToggleProps extends ComponentProps<'input'> {
11
+ /** accessible name, when no words show */
11
12
  label?: string;
13
+ /** WORDS shown before the switch, naming it ("อยู่บ้าน") — the switch and its words are one label, one tap target */
14
+ text?: ReactNode;
12
15
  }
13
- /** Toggle — the big switch (64×36) at a Row's end. */
14
- export declare function Toggle({ label, className, ...rest }: ToggleProps): import("react").JSX.Element;
16
+ /** Toggle — the big switch (64×36) at a Row's end; with `text`, a switch that names itself (a bar's own switch — "at home"). */
17
+ export declare function Toggle({ label, text, className, ...rest }: ToggleProps): import("react").JSX.Element;
@@ -1,10 +1,13 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /** Check — the round check circle at a Row's end (48px); the row's label wraps it. */
4
4
  export function Check({ sm, label, className, ...rest }) {
5
5
  return _jsx("input", { type: "checkbox", className: cx('check', sm && 'check--sm', className), "aria-label": label, ...rest });
6
6
  }
7
- /** Toggle — the big switch (64×36) at a Row's end. */
8
- export function Toggle({ label, className, ...rest }) {
9
- return _jsx("input", { type: "checkbox", role: "switch", className: cx('toggle', className), "aria-label": label, ...rest });
7
+ /** Toggle — the big switch (64×36) at a Row's end; with `text`, a switch that names itself (a bar's own switch — "at home"). */
8
+ export function Toggle({ label, text, className, ...rest }) {
9
+ const input = _jsx("input", { type: "checkbox", role: "switch", className: cx('toggle', className), "aria-label": text == null ? label : undefined, ...rest });
10
+ if (text == null)
11
+ return input;
12
+ return _jsxs("label", { className: "toggle-field", children: [_jsx("span", { className: "toggle-field__text", children: text }), input] });
10
13
  }
package/dist/index.d.ts CHANGED
@@ -72,3 +72,6 @@ export * from './layover/Deck.js';
72
72
  export * from './journey/Route.js';
73
73
  export * from './journey/TileBadge.js';
74
74
  export * from './journey/NoteRow.js';
75
+ export * from './templates/Walkthrough.js';
76
+ export * from './templates/Home.js';
77
+ export * from './sheets/StackSheet.js';
package/dist/index.js CHANGED
@@ -75,3 +75,6 @@ export * from './layover/Deck.js';
75
75
  export * from './journey/Route.js';
76
76
  export * from './journey/TileBadge.js';
77
77
  export * from './journey/NoteRow.js';
78
+ export * from './templates/Walkthrough.js';
79
+ export * from './templates/Home.js';
80
+ export * from './sheets/StackSheet.js';
@@ -6,11 +6,12 @@ export interface ActionBarProps extends ComponentProps<'div'> {
6
6
  inline?: boolean;
7
7
  }
8
8
  /**
9
- * ActionBar — the screen's ONE move, fixed at the bottom, OUTSIDE the Screen/Step (a sibling after it).
9
+ * ActionBar — the screen's ONE move: the Screen's `bottom` (`<Screen fill bottom={<ActionBar…>}>`), the last part of its flex
10
+ * column, as tall as its rows. Never after `</Screen>`, never positioned by hand.
10
11
  * Plain: one wide primary Btn (the step that commits). `pager`: Back + Next as peers — buttons only; where you are is the
11
12
  * PagerAt under the card in a Centre group, never in the bar (Lh, 2026-09-12).
12
13
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
13
- * The parent hosting the bar reserves its room (`--bar-reserve`); never pad a screen for it by hand.
14
+ * In a `Screen fill` the content takes the rest; outside one (an app shell) the bar is still fixed and `--bar-reserve` keeps its room.
14
15
  */
15
16
  export declare function ActionBar({ variant, inline, className, style, ...rest }: ActionBarProps): import("react").JSX.Element;
16
17
  export interface ActionBarTierProps extends ComponentProps<'div'> {
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /**
4
- * ActionBar — the screen's ONE move, fixed at the bottom, OUTSIDE the Screen/Step (a sibling after it).
4
+ * ActionBar — the screen's ONE move: the Screen's `bottom` (`<Screen fill bottom={<ActionBar…>}>`), the last part of its flex
5
+ * column, as tall as its rows. Never after `</Screen>`, never positioned by hand.
5
6
  * Plain: one wide primary Btn (the step that commits). `pager`: Back + Next as peers — buttons only; where you are is the
6
7
  * PagerAt under the card in a Centre group, never in the bar (Lh, 2026-09-12).
7
8
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
8
- * The parent hosting the bar reserves its room (`--bar-reserve`); never pad a screen for it by hand.
9
+ * In a `Screen fill` the content takes the rest; outside one (an app shell) the bar is still fixed and `--bar-reserve` keeps its room.
9
10
  */
10
11
  export function ActionBar({ variant, inline, className, style, ...rest }) {
11
12
  return (_jsx("div", { className: cx('action-bar', variant && `action-bar--${variant}`, className), style: inline ? { position: 'static', width: '100%', maxWidth: 'none', paddingInline: 0, ...style } : style, ...rest }));
@@ -9,10 +9,13 @@ export interface AppBarProps extends Omit<ComponentProps<'header'>, 'title'> {
9
9
  /** the trailing control(s) — an IconBtn with a chevron-down to switch context; on the home bar a labelled
10
10
  * switch chip (ChipPick sign, --tap tall here) and the wallet Btn, side by side (Lh 2026-09-13) */
11
11
  action?: ReactNode;
12
+ /** makes the mark + the words ONE button (the member's own name → their menu): its props — `popoverTarget` to open a `Menu`
13
+ * (the app places the Menu), or an `onClick`. Needs an accessible name when the words don't say what it opens: `aria-label`. */
14
+ brandButton?: ComponentProps<'button'>;
12
15
  }
13
16
  /**
14
17
  * AppBar — the app-context bar fixed at the very top: logo · two-line label · switcher.
15
18
  * It is fixed, so the Screen it sits in reserves its height (`--header-h`, 60px) automatically.
16
19
  * Use a TopBar for a screen title; use both when the app context matters (home).
17
20
  */
18
- export declare function AppBar({ icon, title, sub, action, className, children, ...rest }: AppBarProps): import("react").JSX.Element;
21
+ export declare function AppBar({ icon, title, sub, action, brandButton, className, children, ...rest }: AppBarProps): import("react").JSX.Element;
@@ -1,10 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /**
4
4
  * AppBar — the app-context bar fixed at the very top: logo · two-line label · switcher.
5
5
  * It is fixed, so the Screen it sits in reserves its height (`--header-h`, 60px) automatically.
6
6
  * Use a TopBar for a screen title; use both when the app context matters (home).
7
7
  */
8
- export function AppBar({ icon, title, sub, action, className, children, ...rest }) {
9
- return (_jsxs("header", { className: cx('appbar', className), ...rest, children: [_jsxs("div", { className: "appbar__brand", children: [icon, _jsxs("span", { className: "appbar__text", children: [_jsx("span", { className: "appbar__title", children: title }), sub != null && _jsx("span", { className: "appbar__sub", children: sub })] }), action] }), children] }));
8
+ export function AppBar({ icon, title, sub, action, brandButton, className, children, ...rest }) {
9
+ const who = (_jsxs(_Fragment, { children: [icon, _jsxs("span", { className: "appbar__text", children: [_jsx("span", { className: "appbar__title", children: title }), sub != null && _jsx("span", { className: "appbar__sub", children: sub })] })] }));
10
+ return (_jsxs("header", { className: cx('appbar', className), ...rest, children: [_jsxs("div", { className: "appbar__brand", children: [brandButton ? (_jsx("button", { type: "button", ...brandButton, className: cx('appbar__who', brandButton.className), children: who })) : who, action] }), children] }));
10
11
  }
@@ -1,4 +1,4 @@
1
- import { type ComponentProps } from 'react';
1
+ import { type ComponentProps, type ReactNode, type Ref } from 'react';
2
2
  export type Palette = 'clay' | 'shine' | 'field' | 'roast' | 'hydro';
3
3
  export interface ScreenProps extends ComponentProps<'div'> {
4
4
  /** exactly one phone height (100dvh), clipped: the box a Step or a SheetStack fills — an app screen, not a scrolling page */
@@ -6,15 +6,20 @@ export interface ScreenProps extends ComponentProps<'div'> {
6
6
  /** a colour preset from palette.css; omit for the black-and-white wireframe */
7
7
  palette?: Palette;
8
8
  as?: 'div' | 'main' | 'section';
9
+ /** TOP: a `TopBar` / `AppBar`, or `"lead"` — no bar, the content starts `--screen-lead` (15% of the screen) down */
10
+ top?: ReactNode | 'lead';
11
+ /** BOTTOM: an `ActionBar` (or a `FloatBar`) — laid at the end of the screen's flex column, as tall as its rows need.
12
+ * Never with a `Sheet` in the content: a sheet finishes on its own foot (warns once). */
13
+ bottom?: ReactNode;
9
14
  }
10
15
  /**
11
- * Screen — the mobile screen column: max 25rem wide, the standard horizontal inset, NO vertical padding
12
- * (bars reserve their own room). Every screen is base · top menu · bottom menu · content.
13
- * `fill` makes it one phone height for a Step or a SheetStack; a plain Screen scrolls.
14
- * Put an AppBar/TopBar first, the content next, and an ActionBar AFTER the screen (outside it).
16
+ * Screen — the mobile screen, THREE parts in one flex column: **top** · **content** (the children) · **bottom**.
17
+ * Top = `TopBar` / `AppBar`, or `top="lead"` (no bar, the words start 15% down). Content = a `Step` (`Pages` / `BaseContent`
18
+ * [+ `Sheet`]), a `Centre`, a `SheetStack`… Bottom = an `ActionBar` (`FloatBar`), measured by the browser: the content takes
19
+ * all the rest (`fill`). Max 25rem wide, the standard inset, no vertical padding. A plain Screen (no `fill`) scrolls.
20
+ * The slots set the order; children laid by hand in the same order still work.
15
21
  */
16
- export declare function Screen({ fill, palette, as, className, ...rest }: ScreenProps): import("react").DetailedReactHTMLElement<{
17
- ref?: import("react").Ref<HTMLDivElement> | undefined;
22
+ export declare function Screen({ fill, palette, as, top, bottom, className, children, ref, ...rest }: ScreenProps): import("react").DetailedReactHTMLElement<{
18
23
  key?: import("react").Key | null | undefined;
19
24
  defaultChecked?: boolean | undefined;
20
25
  defaultValue?: string | number | readonly string[] | undefined;
@@ -123,7 +128,6 @@ export declare function Screen({ fill, palette, as, className, ...rest }: Screen
123
128
  "aria-valuemin"?: number | undefined;
124
129
  "aria-valuenow"?: number | undefined;
125
130
  "aria-valuetext"?: string | undefined;
126
- children?: import("react").ReactNode | undefined;
127
131
  dangerouslySetInnerHTML?: {
128
132
  __html: string | TrustedHTML;
129
133
  } | undefined;
@@ -297,4 +301,5 @@ export declare function Screen({ fill, palette, as, className, ...rest }: Screen
297
301
  onTransitionStartCapture?: import("react").TransitionEventHandler<HTMLDivElement> | undefined;
298
302
  className: string;
299
303
  'data-palette': Palette | undefined;
304
+ ref: Ref<HTMLDivElement>;
300
305
  }, HTMLDivElement>;
@@ -1,11 +1,28 @@
1
- import { createElement } from 'react';
1
+ import { createElement, useEffect, useRef } from 'react';
2
2
  import { cx } from '../cx.js';
3
+ let warnedSheetBar = false;
3
4
  /**
4
- * Screen — the mobile screen column: max 25rem wide, the standard horizontal inset, NO vertical padding
5
- * (bars reserve their own room). Every screen is base · top menu · bottom menu · content.
6
- * `fill` makes it one phone height for a Step or a SheetStack; a plain Screen scrolls.
7
- * Put an AppBar/TopBar first, the content next, and an ActionBar AFTER the screen (outside it).
5
+ * Screen — the mobile screen, THREE parts in one flex column: **top** · **content** (the children) · **bottom**.
6
+ * Top = `TopBar` / `AppBar`, or `top="lead"` (no bar, the words start 15% down). Content = a `Step` (`Pages` / `BaseContent`
7
+ * [+ `Sheet`]), a `Centre`, a `SheetStack`… Bottom = an `ActionBar` (`FloatBar`), measured by the browser: the content takes
8
+ * all the rest (`fill`). Max 25rem wide, the standard inset, no vertical padding. A plain Screen (no `fill`) scrolls.
9
+ * The slots set the order; children laid by hand in the same order still work.
8
10
  */
9
- export function Screen({ fill, palette, as = 'div', className, ...rest }) {
10
- return createElement(as, { className: cx('screen', fill && 'screen--fill', className), 'data-palette': palette, ...rest });
11
+ export function Screen({ fill, palette, as = 'div', top, bottom, className, children, ref, ...rest }) {
12
+ const own = useRef(null);
13
+ useEffect(() => {
14
+ const el = own.current;
15
+ if (warnedSheetBar || !el || !el.querySelector(':scope > .action-bar') || !el.querySelector(':scope > .step > .sheet'))
16
+ return;
17
+ warnedSheetBar = true;
18
+ console.warn('cardds Screen: a bottom bar on a screen with a Sheet — the sheet finishes on its own foot (CardFoot); drop the bar.');
19
+ });
20
+ const setRef = (node) => {
21
+ own.current = node;
22
+ if (typeof ref === 'function')
23
+ ref(node);
24
+ else if (ref)
25
+ ref.current = node;
26
+ };
27
+ return createElement(as, { className: cx('screen', fill && 'screen--fill', top === 'lead' && 'screen--lead', className), 'data-palette': palette, ref: setRef, ...rest }, top !== 'lead' ? top : null, children, bottom);
11
28
  }
@@ -27,7 +27,7 @@ export interface SheetStackProps extends ComponentProps<'div'> {
27
27
  *
28
28
  * A COMPONENT, not a wiring: WHICH sheet is open is the app's (`open` + `onOpenChange`, or `defaultOpen`) — so a button anywhere, a route or
29
29
  * the phone's back button can open and close a section. Its own behaviour stays inside and only asks: a tap on a peeked head, the open
30
- * sheet's StackBack. How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
30
+ * sheet's StackBack or its title (folds it). How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
31
31
  * before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); it sets classes, never a size.
32
32
  */
33
33
  export declare function SheetStack({ mode, open, defaultOpen, onOpenChange, fanned, closed, base, className, children, onClick, ...rest }: SheetStackProps): import("react").JSX.Element;
@@ -19,7 +19,7 @@ const sheetsOf = (stack) => [...stack.children]
19
19
  *
20
20
  * A COMPONENT, not a wiring: WHICH sheet is open is the app's (`open` + `onOpenChange`, or `defaultOpen`) — so a button anywhere, a route or
21
21
  * the phone's back button can open and close a section. Its own behaviour stays inside and only asks: a tap on a peeked head, the open
22
- * sheet's StackBack. How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
22
+ * sheet's StackBack or its title (folds it). How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
23
23
  * before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); it sets classes, never a size.
24
24
  */
25
25
  export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChange, fanned, closed, base, className, children, onClick, ...rest }) {
@@ -79,6 +79,11 @@ export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChang
79
79
  request(null);
80
80
  return;
81
81
  }
82
+ // the open sheet's TITLE folds it too (Lh 2026-09-23) — a bigger target than the back button; the back stays the keyboard's way
83
+ if (current != null && at === current && target.closest('.card__head > :is(h1, h2, h3)')) {
84
+ request(null);
85
+ return;
86
+ }
82
87
  if (at >= 0 && current == null)
83
88
  request(at);
84
89
  };
@@ -6,6 +6,9 @@ export interface SheetStageProps extends ComponentProps<'div'> {
6
6
  hero?: ReactNode;
7
7
  /** blur and darken the background (the dim page sheet) */
8
8
  dim?: boolean;
9
+ /** laid OVER the screen it sits in (a Modal a link or button opened): no ground of its own, the page shows through
10
+ * (dimmed with `dim`), above the screen's bar */
11
+ over?: boolean;
9
12
  /** a footnote strip at the stage's bottom, on the scrim */
10
13
  foot?: ReactNode;
11
14
  }
@@ -15,4 +18,4 @@ export interface SheetStageProps extends ComponentProps<'div'> {
15
18
  * (`dim` + a `Modal`), the ask (`dim` + a `Modal`, which centres itself — or a Modal on a clean ground, no `dim`). Full stops under the top bar's line.
16
19
  * NEVER inside a Step — an app screen where the member acts is a Step.
17
20
  */
18
- export declare function SheetStage({ bg, hero, dim, foot, className, children, ...rest }: SheetStageProps): import("react").JSX.Element;
21
+ export declare function SheetStage({ bg, hero, dim, over, foot, className, children, ...rest }: SheetStageProps): import("react").JSX.Element;
@@ -6,6 +6,6 @@ import { cx } from '../cx.js';
6
6
  * (`dim` + a `Modal`), the ask (`dim` + a `Modal`, which centres itself — or a Modal on a clean ground, no `dim`). Full stops under the top bar's line.
7
7
  * NEVER inside a Step — an app screen where the member acts is a Step.
8
8
  */
9
- export function SheetStage({ bg, hero, dim, foot, className, children, ...rest }) {
10
- return (_jsxs("div", { className: cx('sheet-stage', dim && 'sheet-stage--dim', className), ...rest, children: [bg != null && _jsx("div", { className: "sheet-stage__bg", children: bg }), hero != null && _jsx("div", { className: "sheet-stage__hero", children: hero }), children, foot != null && _jsx("p", { className: "sheet-stage__foot", children: foot })] }));
9
+ export function SheetStage({ bg, hero, dim, over, foot, className, children, ...rest }) {
10
+ return (_jsxs("div", { className: cx('sheet-stage', dim && 'sheet-stage--dim', over && 'sheet-stage--over', className), ...rest, children: [bg != null && _jsx("div", { className: "sheet-stage__bg", children: bg }), hero != null && _jsx("div", { className: "sheet-stage__hero", children: hero }), children, foot != null && _jsx("p", { className: "sheet-stage__foot", children: foot })] }));
11
11
  }
@@ -3,5 +3,6 @@ export interface StackBackProps extends ComponentProps<'button'> {
3
3
  /** accessible name */
4
4
  label?: string;
5
5
  }
6
- /** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's CardHead. Hidden until the sheet is open. */
6
+ /** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's CardHead. Hidden until the sheet is open.
7
+ * A SMALL disc (Lh 2026-09-23): 32px with a chevron — the head keeps its room for the title and the tabs; the 44px tap target stays. */
7
8
  export declare function StackBack({ label, className, ...rest }: StackBackProps): import("react").JSX.Element;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  import { Icon } from '../type/Icon.js';
4
- /** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's CardHead. Hidden until the sheet is open. */
4
+ /** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's CardHead. Hidden until the sheet is open.
5
+ * A SMALL disc (Lh 2026-09-23): 32px with a chevron — the head keeps its room for the title and the tabs; the 44px tap target stays. */
5
6
  export function StackBack({ label = 'Back', className, ...rest }) {
6
- return (_jsx("button", { type: "button", className: cx('icon-btn icon-btn--sm sheet-stack__back', className), "aria-label": label, ...rest, children: _jsx(Icon, { name: "arrow-left" }) }));
7
+ return (_jsx("button", { type: "button", className: cx('icon-btn icon-btn--sm sheet-stack__back', className), "aria-label": label, ...rest, children: _jsx(Icon, { name: "chevron-left" }) }));
7
8
  }
@@ -0,0 +1,35 @@
1
+ import { type ComponentProps, type ReactNode } from 'react';
2
+ import { type IconName } from '../type/Icon.js';
3
+ export interface StackSheetTab {
4
+ id: string;
5
+ icon: IconName;
6
+ /** the tab's name, read aloud (the head shows the icon only) */
7
+ label: string;
8
+ /** a small count over the icon: what waits there (alerts, new asks); 0 or none = no circle */
9
+ count?: number;
10
+ }
11
+ export interface StackSheetProps extends Omit<ComponentProps<'article'>, 'title'> {
12
+ /** the SHEET HEAD's title — an h2 (the page's h1 is its own) */
13
+ title: ReactNode;
14
+ /** the paper: 1 light · 2 tinted · 3 dark */
15
+ tone?: 1 | 2 | 3;
16
+ /** the head's end when the sheet has no tabs: an Icon, an IconRow */
17
+ icon?: ReactNode;
18
+ /** TABS in the head: icon buttons at the end — the sheet's pages. A tap on one in a peeked head opens the sheet on it */
19
+ tabs?: StackSheetTab[];
20
+ /** CONTROLLED: the tab shown (its id) — with `onTabChange`; or `defaultTab` (default: the first) */
21
+ tab?: string;
22
+ defaultTab?: string;
23
+ onTabChange?: (tab: string) => void;
24
+ /** the SHEET FOOT: the CTA at the sheet's bottom (one row) */
25
+ foot?: ReactNode;
26
+ /** the back button's label (shown once the sheet is open) */
27
+ backLabel?: string;
28
+ }
29
+ /**
30
+ * StackSheet — ONE sheet of a `SheetStack`, in the one sheet shape: SHEET HEAD (back · title · tabs or an icon) pinned at the top ·
31
+ * SHEET BODY (the children; scrolls) · SHEET FOOT (`foot`, the CTA) at the bottom. Its tabs are icon buttons, the chosen one filled;
32
+ * a count shows as a small circle over its icon. The body is the app's: render the page of the chosen tab (and nothing while the sheet
33
+ * is closed — see `Home`). A direct child of `SheetStack`.
34
+ */
35
+ export declare function StackSheet({ title, tone, icon, tabs, tab: tabProp, defaultTab, onTabChange, foot, backLabel, className, children, ...rest }: StackSheetProps): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { cx } from '../cx.js';
4
+ import { Card } from '../cards/Card.js';
5
+ import { CardHead } from '../cards/CardHead.js';
6
+ import { CardFoot } from '../cards/CardFoot.js';
7
+ import { SheetBody } from './SheetBody.js';
8
+ import { StackBack } from './StackBack.js';
9
+ import { Text } from '../type/Text.js';
10
+ import { Icon } from '../type/Icon.js';
11
+ import { Badge } from '../numbers/Badge.js';
12
+ /**
13
+ * StackSheet — ONE sheet of a `SheetStack`, in the one sheet shape: SHEET HEAD (back · title · tabs or an icon) pinned at the top ·
14
+ * SHEET BODY (the children; scrolls) · SHEET FOOT (`foot`, the CTA) at the bottom. Its tabs are icon buttons, the chosen one filled;
15
+ * a count shows as a small circle over its icon. The body is the app's: render the page of the chosen tab (and nothing while the sheet
16
+ * is closed — see `Home`). A direct child of `SheetStack`.
17
+ */
18
+ export function StackSheet({ title, tone, icon, tabs, tab: tabProp, defaultTab, onTabChange, foot, backLabel = 'Back', className, children, ...rest }) {
19
+ const [own, setOwn] = useState(defaultTab ?? tabs?.[0]?.id);
20
+ const tab = tabProp ?? own;
21
+ const pick = (id) => { if (tabProp === undefined)
22
+ setOwn(id); onTabChange?.(id); };
23
+ return (_jsxs(Card, { tone: tone, className: cx('stack-sheet', className), ...rest, children: [_jsxs(CardHead, { children: [_jsx(StackBack, { label: backLabel }), _jsx(Text, { level: "h2", children: title }), tabs?.length ? (_jsx("span", { className: "stack-sheet__tabs", role: "tablist", children: tabs.map((t) => (_jsxs("button", { type: "button", role: "tab", "aria-selected": t.id === tab, "aria-label": t.label, className: cx('icon-btn icon-btn--sm stack-sheet__tab', t.id === tab && 'icon-btn--invert'), onClick: () => pick(t.id), children: [_jsx(Icon, { name: t.icon }), t.count ? _jsx(Badge, { className: "stack-sheet__count", children: t.count > 99 ? '99+' : t.count }) : null] }, t.id))) })) : icon] }), _jsx(SheetBody, { role: tabs?.length ? 'tabpanel' : undefined, children: children }), foot != null && _jsx(CardFoot, { children: foot })] }));
24
+ }
@@ -3,8 +3,8 @@ export interface StepProps extends ComponentProps<'section'> {
3
3
  }
4
4
  /**
5
5
  * Step — one screen, one task. Children: a BaseContent first (what the member READS — words on the base, not a card), an optional Sheet last
6
- * (what the member DOES); the ActionBar goes OUTSIDE, after the Step. It fills a `Screen fill` between the top bar
7
- * and the bar's room, bleeds to the screen's edges (the sheet is full-width), and follows its TopBar/AppBar directly.
6
+ * (what the member DOES); the ActionBar is the Screen's `bottom`, after the Step. It is a `Screen fill`'s content: it takes
7
+ * all the height the top and the bottom leave (one flex column), bleeds to the screen's edges (the sheet is full-width), and follows its TopBar/AppBar directly.
8
8
  * Reads → BaseContent only. Acts → BaseContent + Sheet. Never hand-lay a step, never write a sheet height.
9
9
  */
10
10
  export declare function Step({ className, ...rest }: StepProps): import("react").JSX.Element;
@@ -2,8 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /**
4
4
  * Step — one screen, one task. Children: a BaseContent first (what the member READS — words on the base, not a card), an optional Sheet last
5
- * (what the member DOES); the ActionBar goes OUTSIDE, after the Step. It fills a `Screen fill` between the top bar
6
- * and the bar's room, bleeds to the screen's edges (the sheet is full-width), and follows its TopBar/AppBar directly.
5
+ * (what the member DOES); the ActionBar is the Screen's `bottom`, after the Step. It is a `Screen fill`'s content: it takes
6
+ * all the height the top and the bottom leave (one flex column), bleeds to the screen's edges (the sheet is full-width), and follows its TopBar/AppBar directly.
7
7
  * Reads → BaseContent only. Acts → BaseContent + Sheet. Never hand-lay a step, never write a sheet height.
8
8
  */
9
9
  export function Step({ className, ...rest }) {