@usableapp/cardds 0.6.3 → 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.
- package/CHANGELOG.md +28 -0
- package/README.md +69 -11
- package/css/actions.css +15 -0
- package/css/base.css +3 -0
- package/css/choice.css +11 -0
- package/css/layover.css +3 -0
- package/css/media.css +6 -0
- package/css/sheet.css +7 -0
- package/css/stack.css +55 -0
- package/css/step.css +44 -2
- package/dist/actions/Btn.d.ts +1 -1
- package/dist/actions/IconBtn.d.ts +1 -1
- package/dist/cardds.css +144 -2
- package/dist/choice/Check.d.ts +6 -3
- package/dist/choice/Check.js +7 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/scaffold/ActionBar.d.ts +3 -2
- package/dist/scaffold/ActionBar.js +3 -2
- package/dist/scaffold/AppBar.d.ts +4 -1
- package/dist/scaffold/AppBar.js +4 -3
- package/dist/scaffold/Screen.d.ts +13 -8
- package/dist/scaffold/Screen.js +24 -7
- package/dist/sheets/SheetStack.d.ts +1 -1
- package/dist/sheets/SheetStack.js +6 -1
- package/dist/sheets/SheetStage.d.ts +4 -1
- package/dist/sheets/SheetStage.js +2 -2
- package/dist/sheets/StackBack.d.ts +2 -1
- package/dist/sheets/StackBack.js +3 -2
- package/dist/sheets/StackSheet.d.ts +35 -0
- package/dist/sheets/StackSheet.js +24 -0
- package/dist/sheets/Step.d.ts +2 -2
- package/dist/sheets/Step.js +2 -2
- package/dist/templates/Home.d.ts +48 -0
- package/dist/templates/Home.js +40 -0
- package/dist/templates/Walkthrough.d.ts +42 -0
- package/dist/templates/Walkthrough.js +34 -0
- package/dist/type/Text.d.ts +3 -1
- package/dist/type/Text.js +2 -2
- package/dist/type/icons.d.ts +1 -1
- package/dist/type/icons.js +2 -2
- package/icons.svg +2 -0
- 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 {
|
|
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:
|
|
@@ -2998,6 +3131,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
2998
3131
|
background-repeat: no-repeat;
|
|
2999
3132
|
background-position: bottom right;
|
|
3000
3133
|
background-size: var(--sp-8) var(--sp-8);
|
|
3134
|
+
background-origin: border-box; /* the flap sits on the true corner, not inside the (transparent) border */
|
|
3135
|
+
/* the corner is CUT: the triangle under the flap shows what is behind the card. Painted in the card's own colour it
|
|
3136
|
+
read as the square corner of a second card underneath (Lh 2026-09-23). The polygon reaches 3rem past the other
|
|
3137
|
+
edges so a coloured palette's shadow is not clipped there. */
|
|
3138
|
+
clip-path: polygon(-3rem -3rem, calc(100% + 3rem) -3rem, calc(100% + 3rem) calc(100% - var(--sp-8) - 3rem),
|
|
3139
|
+
calc(100% - var(--sp-8) - 3rem) calc(100% + 3rem), -3rem calc(100% + 3rem));
|
|
3001
3140
|
}
|
|
3002
3141
|
|
|
3003
3142
|
/* ---- wave: audio waveform stand-in for voice rows ----
|
|
@@ -3337,6 +3476,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
3337
3476
|
align-items: center;
|
|
3338
3477
|
z-index: 30;
|
|
3339
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; }
|
|
3340
3482
|
.sheet-stage > .float-bar { position: absolute; padding-inline: var(--sp-4); }
|
|
3341
3483
|
/* .screen's default bottom padding clears an .action-bar; a .float-bar is
|
|
3342
3484
|
taller (--bar-h + its own bottom offset), so reserve the difference. Any
|
package/dist/choice/Check.d.ts
CHANGED
|
@@ -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;
|
package/dist/choice/Check.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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;
|
package/dist/scaffold/AppBar.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
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>;
|
package/dist/scaffold/Screen.js
CHANGED
|
@@ -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:
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
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
|
-
|
|
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;
|
package/dist/sheets/StackBack.js
CHANGED
|
@@ -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: "
|
|
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
|
+
}
|
package/dist/sheets/Step.d.ts
CHANGED
|
@@ -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
|
|
7
|
-
* and the
|
|
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;
|