@usableapp/cardds 0.6.5 → 0.6.6
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 +20 -0
- package/README.md +14 -15
- package/css/actions.css +1 -1
- package/css/base.css +6 -6
- package/css/card.css +4 -4
- package/css/lists.css +4 -0
- package/css/sheet.css +1 -1
- package/css/stack.css +1 -1
- package/css/step.css +21 -33
- package/css/tokens.css +2 -1
- package/dist/actions/Link.d.ts +3 -1
- package/dist/actions/Link.js +2 -2
- package/dist/cardds.css +40 -47
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/scaffold/Centre.d.ts +1 -1
- package/dist/scaffold/Centre.js +1 -1
- package/dist/scaffold/Page.d.ts +17 -0
- package/dist/scaffold/Page.js +24 -0
- package/dist/scaffold/Screen.d.ts +5 -5
- package/dist/scaffold/Screen.js +4 -4
- package/dist/sheets/Step.d.ts +2 -2
- package/dist/sheets/Step.js +2 -2
- package/dist/templates/Home.d.ts +1 -1
- package/dist/templates/Walkthrough.d.ts +2 -2
- package/dist/templates/Walkthrough.js +5 -4
- package/package.json +1 -1
- package/dist/scaffold/BaseContent.d.ts +0 -17
- package/dist/scaffold/BaseContent.js +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
> For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
|
|
4
4
|
|
|
5
|
+
## 0.6.6 — 2026-09-23
|
|
6
|
+
|
|
7
|
+
### BOTTOM is a layer (Lh 2026-09-23, replaces 0.6.5's "the bar is a row")
|
|
8
|
+
- In a `Screen fill`, top + content are one flex column and **the content runs to the screen's bottom edge**. The bottom
|
|
9
|
+
(`ActionBar`) is a **layer over the content's foot** — it covers what is under it, on purpose. Nothing is added to the
|
|
10
|
+
content to make room.
|
|
11
|
+
- **The walk's `PagerAt` is the bottom layer's first row**: a direct child of the `ActionBar`, before its tiers, centred.
|
|
12
|
+
It is no longer a child of the `Step`. (The 2026-09-16 rule "the dots never in the bar" is retired.)
|
|
13
|
+
- `ActionBar` in a fill screen has no top padding.
|
|
14
|
+
|
|
15
|
+
### `BaseContent` → `Page`
|
|
16
|
+
- The content is a box. What is in it is always components one after another. A page of words (picture · heading · words ·
|
|
17
|
+
a CtaPack) is **`Page`** (class `.page`, was `.base-content`), and a walk is `Pages` of `Page`s.
|
|
18
|
+
- `BaseContent` still works for this version as an alias of `Page`; it warns once in the console. Rename it.
|
|
19
|
+
- The knobs keep their names for now (`--base-content-media-h`, `--base-content-gap`, …).
|
|
20
|
+
|
|
21
|
+
### Fixes and small additions
|
|
22
|
+
- A `Step` under an `AppBar` touches it again, so a full sheet stops on the bar's bottom edge (0.6.5 left an 8px strip — timebank #137).
|
|
23
|
+
- `Link sm`: a minor link at a new size `--fs-2xs` (15px) on a 28px line; its tap target stays 44px. A `ctaLink` row is as tall as that line.
|
|
24
|
+
|
|
5
25
|
## 0.6.5 — 2026-09-23
|
|
6
26
|
|
|
7
27
|
### A screen is top · content · bottom, one flex column (Lh 2026-09-23)
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ npm test # Playwright: the tokens and the step/sheet/centre geometry
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
```tsx
|
|
40
|
-
import { Screen, TopBar, Step,
|
|
40
|
+
import { Screen, TopBar, Step, Page, Sheet, SheetBody, CardHead, Field, Pin, ActionBar, Btn } from '@usableapp/cardds';
|
|
41
41
|
import '@usableapp/cardds/dist/cardds.css';
|
|
42
42
|
import './theme.css'; // the project's own look, after the system's — see "A project's theme"
|
|
43
43
|
```
|
|
@@ -165,7 +165,7 @@ of `Screen`:
|
|
|
165
165
|
| Part | Prop | What fills it |
|
|
166
166
|
|---|---|---|
|
|
167
167
|
| **top** | `top` | `TopBar` · `AppBar` · `"lead"` (no bar: the content starts `--screen-lead`, 15% of the screen, down) |
|
|
168
|
-
| **content** | children | a `Step` (`Pages` · `
|
|
168
|
+
| **content** | children | a `Step` (`Pages` · `Page` · `Page` + `Sheet`) · `Centre` · `SheetStack` … |
|
|
169
169
|
| **bottom** | `bottom` | `ActionBar` (rows: `controls` · the one `cta` row — the pager ← → or one wide button · a `ctaLink`) · `FloatBar` · nothing |
|
|
170
170
|
|
|
171
171
|
```tsx
|
|
@@ -174,12 +174,11 @@ of `Screen`:
|
|
|
174
174
|
</Screen>
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
- **
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- **The bar is INSIDE the screen
|
|
181
|
-
|
|
182
|
-
content at the screen's foot (it lies over a map or a stack — not a row).
|
|
177
|
+
- **Top and content are one flex column; the bottom is a LAYER** (Lh 2026-09-23). In `Screen fill` the content runs to the
|
|
178
|
+
screen's bottom edge; the bottom (`ActionBar`, with the walk's `PagerAt` as its first row) lies over it at the foot and
|
|
179
|
+
covers what is under it — on purpose, nothing is added to the content to make room. The keyboard lifts the layer (`--kb`).
|
|
180
|
+
- **The bar is INSIDE the screen** — `bottom`, never a sibling after `</Screen>`, never pinned with a hand-set `position`.
|
|
181
|
+
A `FloatBar` in `bottom` floats the same way.
|
|
183
182
|
- **A screen with a `Sheet` has no bottom.** The sheet finishes on its own `CardFoot`; `Screen` warns once when both
|
|
184
183
|
are there.
|
|
185
184
|
- **Checked:** `validateScreen` (`scripts/validate.mjs`, run on every story and template by
|
|
@@ -196,7 +195,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
196
195
|
```html
|
|
197
196
|
<!-- reads: the words only -->
|
|
198
197
|
<section class="step">
|
|
199
|
-
<section class="
|
|
198
|
+
<section class="page">
|
|
200
199
|
<div class="card__head"><h2 class="t-h2">…</h2></div>
|
|
201
200
|
<p class="t-body">…</p>
|
|
202
201
|
</section>
|
|
@@ -205,7 +204,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
205
204
|
|
|
206
205
|
<!-- acts: the words + a sheet -->
|
|
207
206
|
<section class="step">
|
|
208
|
-
<section class="
|
|
207
|
+
<section class="page">…the words…</section>
|
|
209
208
|
<article class="card sheet sheet--half">
|
|
210
209
|
<button class="sheet__handle" type="button" aria-label="ปรับความสูง"></button>
|
|
211
210
|
<div class="card__head"><h2 class="t-h2">…</h2><span class="chip">…</span></div>
|
|
@@ -229,7 +228,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
229
228
|
card takes the inset back as its side margins — so the card and the bar's
|
|
230
229
|
buttons stand on the inset line, and the sheet runs edge to edge like the
|
|
231
230
|
bar and like a sheet in a `.sheet-stage`.
|
|
232
|
-
- **`.
|
|
231
|
+
- **`.page` first, `.sheet` last.** The block carries the step's words
|
|
233
232
|
ON THE BASE — no frame, no area colour (it was a `.card` until 2026-09-16;
|
|
234
233
|
Lh: a step reads like a page, not a card in a page). It keeps a card's
|
|
235
234
|
rhythm: a `.card__head`, `--card-gap` between things. The sheet is
|
|
@@ -311,8 +310,8 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
311
310
|
- **The round pager** is `.action-bar--pager` (or an `.action-bar__tier--pager`)
|
|
312
311
|
holding two `.icon-btn`s and nothing else: they stand at the two edges. The
|
|
313
312
|
step that commits still goes back to one wide button (rule 3).
|
|
314
|
-
- **
|
|
315
|
-
- *Pages of words:* `.step > .pages > .
|
|
313
|
+
- **Page that moves** (Lh 2026-09-20) — two ways, never both on one screen:
|
|
314
|
+
- *Pages of words:* `.step > .pages > .page` (one `aria-current="step"`, the rest `inert`) — every page
|
|
316
315
|
one height, the round pager turns them (`slideTo()`), the words slide like a page. No sheet there.
|
|
317
316
|
- *Sheets the app calls:* the base offers WAYS IN — sign in · new phone · join — as buttons (`.cta-pack`); each
|
|
318
317
|
way has a sheet that holds its WHOLE form and its own submit on its foot, starting `.sheet--away` (not on the
|
|
@@ -367,7 +366,7 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
367
366
|
by `translate` only. (The stack was first named "card stack": wrong, and
|
|
368
367
|
retired.) Demos: `sheet.html`, `stack.html`.
|
|
369
368
|
- **A step is one screen, one task: `.step`.** What the member reads is
|
|
370
|
-
words on the base (`.
|
|
369
|
+
words on the base (`.page` — the same block as anywhere on the base; it was `.step__card` until 2026-09-16); what the member does, if the step asks for it, is a
|
|
371
370
|
`.sheet` in front of it; the screen's one move is `.action-bar`, the screen's
|
|
372
371
|
bottom, after the step. A step where the member only READS is the card alone.
|
|
373
372
|
A step where the member ACTS (types, picks, shoots a photo, records) adds
|
|
@@ -394,7 +393,7 @@ Demo: the *skeleton* block at the top of `elements.html`.
|
|
|
394
393
|
1. **One card = one boundary.** A card frames one thing: a category, a stat
|
|
395
394
|
group, a list row. Tone slots: `.card` (card-1), `.card--2`, `.card--3`.
|
|
396
395
|
2. **Sections are not cards.** Page sections = frameless on-surface content
|
|
397
|
-
(`.
|
|
396
|
+
(`.page`) + a lone/packed CTA (`.cta-pack`).
|
|
398
397
|
3. **Surrounding UI is separate.** `.topbar`, `.filter-row`, `.chip-row`,
|
|
399
398
|
`.action-bar` live outside cards, above/below. The bottom bar has two
|
|
400
399
|
arrangements and they mean different things: the default pack (one wide
|
package/css/actions.css
CHANGED
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
shadow and read on their own (Lh, 2026-09-12; replaces the 09-11 fade) */
|
|
290
290
|
.action-bar__tier { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); }
|
|
291
291
|
.action-bar__tier > .btn { flex: 1; }
|
|
292
|
-
.action-bar__tier--link { justify-content: center; } /* a Link under the buttons, e.g. "details" that opens a Modal */
|
|
292
|
+
.action-bar__tier--link { justify-content: center; min-height: 0; } /* a Link under the buttons, e.g. "details" that opens a Modal — its row as tall as a small link, not a tap (Lh 2026-09-23) */
|
|
293
293
|
:has(> .action-bar--tiers) { --bar-reserve: calc(var(--tap) + var(--sp-3) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
|
|
294
294
|
/* 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
|
|
295
295
|
adds a tap-tall row and its gap to the reserve, so the words above never run under the bar */
|
package/css/base.css
CHANGED
|
@@ -113,21 +113,21 @@ button { font: inherit; cursor: pointer; }
|
|
|
113
113
|
a heading, a paragraph, maybe a .cta-pack. Was .section until
|
|
114
114
|
2026-09-16 (Lh: the name said nothing about WHERE it lives; this one does).
|
|
115
115
|
In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
|
|
116
|
-
.
|
|
116
|
+
.page {
|
|
117
117
|
display: grid;
|
|
118
118
|
gap: var(--base-content-gap, var(--sp-3));
|
|
119
119
|
align-content: start; /* a tall column never stretches the rows — the words hug the top */
|
|
120
120
|
padding-block: var(--base-content-pad-y, var(--sp-4));
|
|
121
121
|
}
|
|
122
|
-
.
|
|
122
|
+
.page > .t-body { color: var(--base-content-body-ink, var(--on-surface)); max-width: 34ch; } /* full ink: a sentence is read, muted is for meta (Lh 2026-09-22 — the words on the base were grey and hard to read) */
|
|
123
123
|
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
124
124
|
(the walk's dots are NOT here — they are the Step's, after its Pages: step.css, Lh 2026-09-22) */
|
|
125
125
|
/* the page's ONE heading on the base (a screen with no TopBar: CardHead level="h1") reads at the card heading's size —
|
|
126
126
|
h1 is the semantic level, not a bigger face; --fs-h1 is the bar title's size (Lh 2026-09-22) */
|
|
127
|
-
.
|
|
128
|
-
.
|
|
129
|
-
.
|
|
130
|
-
.
|
|
127
|
+
.page > .card__head > .t-h1 { font-size: var(--fs-h2); }
|
|
128
|
+
.page > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; align-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
|
|
129
|
+
.page > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
130
|
+
.page .cta-pack {
|
|
131
131
|
display: flex;
|
|
132
132
|
align-items: start; /* buttons keep their own height, never the row's */
|
|
133
133
|
gap: var(--sp-2);
|
package/css/card.css
CHANGED
|
@@ -227,13 +227,13 @@
|
|
|
227
227
|
user-select: none; /* a walk is dragged, not read-and-selected — without this, a finger crossing the words highlights them instead of turning the page (timebank #59) */
|
|
228
228
|
touch-action: pan-y; /* leaves vertical scroll to the browser; horizontal is the app's own drag, never a native pan */
|
|
229
229
|
}
|
|
230
|
-
.pages > :is(.card, .
|
|
230
|
+
.pages > :is(.card, .page) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
|
|
231
231
|
.pages > [inert] { visibility: hidden; }
|
|
232
|
-
/* pages of WORDS (Lh, 2026-09-20): the same walk with .
|
|
232
|
+
/* pages of WORDS (Lh, 2026-09-20): the same walk with .page pages, on the base — in a .step the
|
|
233
233
|
.pages is the reading block (step.css), the round pager in the bar turns it, the same slideTo().
|
|
234
234
|
<section class="step"><div class="pages">
|
|
235
|
-
<section class="
|
|
236
|
-
<section class="
|
|
235
|
+
<section class="page" aria-current="step">…</section>
|
|
236
|
+
<section class="page" inert>…</section>
|
|
237
237
|
</div></section> <div class="action-bar action-bar--pager">← →</div> */
|
|
238
238
|
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
|
|
239
239
|
::view-transition-old(centre-card), ::view-transition-new(centre-card) {
|
package/css/lists.css
CHANGED
|
@@ -205,5 +205,9 @@
|
|
|
205
205
|
padding: 0;
|
|
206
206
|
cursor: pointer;
|
|
207
207
|
}
|
|
208
|
+
/* a SMALL link (Lh 2026-09-23): a thing tapped rarely — "details", a ctaLink — at the minor-link size (15) and 28px of height;
|
|
209
|
+
its 44px tap target stays, an invisible ::after around it */
|
|
210
|
+
.link--sm { position: relative; font-size: var(--link-sm-font-size, var(--fs-2xs)); min-height: var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)); }
|
|
211
|
+
.link--sm::after { content: ""; position: absolute; inset: calc((var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)) - var(--tap-sm)) / 2) 0; }
|
|
208
212
|
.link--muted { color: var(--link-muted-ink, var(--card-muted)); }
|
|
209
213
|
.card--3 .link--muted { color: var(--card-3-muted); }
|
package/css/sheet.css
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
95
95
|
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
96
96
|
|
|
97
|
-
/* ---- called sheets (Lh, 2026-09-20): a .step whose .
|
|
97
|
+
/* ---- called sheets (Lh, 2026-09-20): a .step whose .page has buttons that each CALL a sheet ----
|
|
98
98
|
.sheet--away = not on the screen: slid all the way out (translate, like every state — the material never
|
|
99
99
|
changes), and hidden once it has left so its shadow does not lie on the step's bottom edge; write it
|
|
100
100
|
with `inert` (the React Sheet does). WHO brings it onto the screen is the app's business — a button, a route:
|
package/css/stack.css
CHANGED
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
}
|
|
233
233
|
/* 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 —
|
|
234
234
|
loose things (a picture, a line) still sit against the peeks (Lh 2026-09-23, the home) */
|
|
235
|
-
.sheet-stack__base:has(> .
|
|
235
|
+
.sheet-stack__base:has(> .page:only-child) { align-content: start; }
|
|
236
236
|
|
|
237
237
|
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
238
238
|
into it — an opacity fade in place, never a slide in from the left (the
|
package/css/step.css
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
(see "LAYERS" below).
|
|
8
8
|
|
|
9
9
|
<section class="step">
|
|
10
|
-
<section class="
|
|
10
|
+
<section class="page">…the words…</section> ← first
|
|
11
11
|
<article class="card sheet sheet--half">…the controls… <div class="card__foot">…</div></article> ← last, optional
|
|
12
12
|
</section>
|
|
13
13
|
<div class="action-bar">…</div> ← only when there is no sheet
|
|
@@ -66,14 +66,14 @@
|
|
|
66
66
|
:has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
|
|
67
67
|
/* 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)
|
|
68
68
|
is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
/* LAYERS (Lh, 2026-09-20): the base, then the sheet over it, then the top bar. A bar is the BASE's — the
|
|
71
71
|
move of a page of words — so a step with a sheet has NO bar: the sheet finishes on its own .card__foot
|
|
72
72
|
(sheet.css), and a form lives whole in ONE sheet, never item by item on the base. That is how cardds's
|
|
73
73
|
own screens are composed (the guide); the framework does not police it — a component never changes
|
|
74
74
|
because another one is there. */
|
|
75
75
|
|
|
76
|
-
/* the reading block: a .
|
|
76
|
+
/* the reading block: a .page (base.css) — the same words-on-the-base
|
|
77
77
|
block as anywhere else (it was .step__card, and a .card before 2026-09-16;
|
|
78
78
|
Lh: a step reads like a page, not a card in a page — so it IS the page's
|
|
79
79
|
block, one name). Here it keeps a card's inner rhythm (a head, --card-gap
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
gets the flex rule: it hugs its content, and when the column runs out (a
|
|
82
82
|
short phone, large text) it shrinks and scrolls inside itself instead of
|
|
83
83
|
pushing the bar or the sheet */
|
|
84
|
-
.step > .
|
|
84
|
+
.step > .page, .step > .pages > .page {
|
|
85
85
|
--card-pad: 0rem; /* published for .bleed: nothing to cancel */
|
|
86
86
|
--card-gap: var(--sp-4);
|
|
87
87
|
gap: var(--card-gap);
|
|
@@ -89,26 +89,14 @@
|
|
|
89
89
|
align-content: start;
|
|
90
90
|
color: var(--on-surface);
|
|
91
91
|
}
|
|
92
|
-
/* the reading block is the .
|
|
92
|
+
/* the reading block is the .page — or the .pages that holds several of them, a walk of words
|
|
93
93
|
(card.css). Behind a PEEKED sheet the words keep the strip's room under them, so whatever is down there
|
|
94
94
|
(the buttons that call the other sheets) scrolls clear of it — CSS alone: the box declares --sheet-peek. */
|
|
95
|
-
.step:has(> .sheet--peek) > :is(.
|
|
96
|
-
.step > :is(.
|
|
95
|
+
.step:has(> .sheet--peek) > :is(.page, .pages) { padding-bottom: var(--sheet-peek); }
|
|
96
|
+
.step > :is(.page, .pages) {
|
|
97
97
|
flex: 0 1 auto;
|
|
98
98
|
}
|
|
99
|
-
|
|
100
|
-
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) */
|
|
101
|
-
.step > .pager__at {
|
|
102
|
-
/* it FLOATS over the reading block's foot (Lh 2026-09-23: the strip it took under the words was wasted room) — the pages
|
|
103
|
-
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 */
|
|
104
|
-
position: absolute; bottom: 0; left: 50%; translate: -50% 0; z-index: 2;
|
|
105
|
-
padding: var(--sp-3) var(--sp-2);
|
|
106
|
-
border-radius: var(--r-chip);
|
|
107
|
-
background: var(--surface);
|
|
108
|
-
}
|
|
109
|
-
/* the reading block fills the step, and keeps the dots' height free at the end of its scroll so the last line clears them */
|
|
110
|
-
.step:has(> .pager__at) > :is(.base-content, .pages) { flex: 1 1 auto; padding-bottom: calc(var(--dot) + 2 * var(--sp-3)); }
|
|
111
|
-
.step > :is(.base-content, .pages) {
|
|
99
|
+
.step > :is(.page, .pages) {
|
|
112
100
|
min-height: 0;
|
|
113
101
|
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 */
|
|
114
102
|
overflow-y: auto;
|
|
@@ -118,8 +106,8 @@
|
|
|
118
106
|
/* a picture that opens the words stays put, only the words scroll under it (Lh 2026-09-23, timebank's walk at large text:
|
|
119
107
|
the picture scrolled away with the words). Sticky at the top of the reading block's scroller; its height is
|
|
120
108
|
--base-content-media-h as everywhere, never shrunk (Lh 2026-09-23: a fixed height, no scaling). */
|
|
121
|
-
.step > .
|
|
122
|
-
.step > .pages > .
|
|
109
|
+
.step > .page > :is(img, .ph):first-child,
|
|
110
|
+
.step > .pages > .page > :is(img, .ph):first-child {
|
|
123
111
|
position: sticky;
|
|
124
112
|
top: 0;
|
|
125
113
|
z-index: 1;
|
|
@@ -127,23 +115,20 @@
|
|
|
127
115
|
}
|
|
128
116
|
|
|
129
117
|
/* ============================================================
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
the column's end: the screen pads its bottom by --kb, the bar rides on it.
|
|
118
|
+
TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
|
|
119
|
+
in a .screen--fill the content runs to the screen's bottom edge, and the bar lies over it at the foot — it covers what is
|
|
120
|
+
under it, on purpose; nothing is added to the content to make room. The keyboard lifts the layer (--kb).
|
|
134
121
|
(A bar OUTSIDE a fill screen — an app shell, a scrolling screen — is still fixed to the viewport, actions.css.)
|
|
135
122
|
============================================================ */
|
|
136
123
|
.screen--fill > .action-bar {
|
|
137
|
-
position:
|
|
138
|
-
flex: none;
|
|
139
|
-
margin-top: auto; /* the column's end, even on a screen with nothing that grows (a centred card is absolute) */
|
|
124
|
+
position: absolute; inset-inline: 0; top: auto; bottom: var(--kb, 0rem);
|
|
140
125
|
max-width: none;
|
|
141
|
-
margin
|
|
126
|
+
margin: 0;
|
|
127
|
+
padding-top: 0; /* no ground of its own, never a solid strip */
|
|
142
128
|
}
|
|
143
|
-
.screen--fill:has(> .action-bar) { padding-bottom: var(--kb, 0rem); }
|
|
144
129
|
.screen--fill:has(> .action-bar) > .step { margin-bottom: 0; }
|
|
145
|
-
/* the
|
|
146
|
-
.
|
|
130
|
+
/* the walk's dots are the BOTTOM layer's too (Lh 2026-09-23): a row of the bar, over its buttons, centred */
|
|
131
|
+
.action-bar > .pager__at, .action-bar__tier > .pager__at { align-self: center; }
|
|
147
132
|
/* 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
|
|
148
133
|
reserves nothing for it; the breathing under it stays the one it had (--sp-2), not the screen's gap */
|
|
149
134
|
.screen--fill > .appbar {
|
|
@@ -154,4 +139,7 @@
|
|
|
154
139
|
margin-bottom: calc(var(--sp-2) - var(--screen-gap));
|
|
155
140
|
}
|
|
156
141
|
.screen.screen--fill:has(> .appbar) { padding-top: 0; --header-gap: 0rem; }
|
|
142
|
+
/* …and a STEP touches it, as it touches a TopBar: a full sheet's top edge on the bar's bottom edge (0.6.5 left 8px of base between
|
|
143
|
+
them — timebank #137); the words keep the screen's gap as their lead (--_lead, .appbar + .step above) */
|
|
144
|
+
.screen--fill > .appbar:has(+ .step) { margin-bottom: calc(-1 * var(--screen-gap)); }
|
|
157
145
|
.screen--fill > .appbar + .sheet-stage { margin-top: 0; }
|
package/css/tokens.css
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
and never a multiplier, so the proportions of the system hold at any size.
|
|
21
21
|
The px in the comments are at these defaults (1rem = 16px). */
|
|
22
22
|
--sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
|
|
23
|
-
--fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.
|
|
23
|
+
--fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.68 (15, minor links — was 0.775/17 until 2026-09-23). A new size is Lh's call only.
|
|
24
24
|
Also the member's text-size setting: the app writes it on <html> at run time (2.2rem = 160%) — words grow, things don't */
|
|
25
25
|
--r-base: 1rem; /* 16 radius: --r-* (--r-chip stays fully round) */
|
|
26
26
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
|
|
104
104
|
/* THE SCALE (Lh 2026-09-22): 7 sizes for 16 jobs — a size is a job's fit, not a rung; jobs share sizes.
|
|
105
105
|
Nothing that reads is under 17 (the smallest ui: chips). No clamp/vw: a size is one number. */
|
|
106
|
+
--fs-2xs: calc(var(--fs-base) * 0.68); /* 15 minor links — a ctaLink, a "details" (Lh 2026-09-23: added by Lh; ONLY Lh adds a size) */
|
|
106
107
|
--fs-xs: calc(var(--fs-base) * 0.775); /* 17 the smallest ui words: ring xs, a tile-badge label */
|
|
107
108
|
--fs-caption: calc(var(--fs-base) * 0.775); /* 17 */
|
|
108
109
|
--fs-label: calc(var(--fs-base) * 0.775); /* 17 chips, buttons' small words */
|
package/dist/actions/Link.d.ts
CHANGED
|
@@ -5,11 +5,13 @@ export interface LinkProps extends ComponentProps<'a'> {
|
|
|
5
5
|
muted?: boolean;
|
|
6
6
|
/** a trailing xs icon (arrow-up-right for "See all") */
|
|
7
7
|
icon?: IconName;
|
|
8
|
+
/** SMALL: the minor-link size (15) and a 28px line, for what is tapped rarely (details, a ctaLink); the tap target stays 44px */
|
|
9
|
+
sm?: boolean;
|
|
8
10
|
/** without an href it renders as a button */
|
|
9
11
|
href?: string;
|
|
10
12
|
}
|
|
11
13
|
/** Link — an inline text action big enough to tap (40px): "See all ↗" in a card head, "Skip" between pager buttons. */
|
|
12
|
-
export declare function Link({ muted, icon, href, className, children, ...rest }: LinkProps): import("react").DetailedReactHTMLElement<{
|
|
14
|
+
export declare function Link({ muted, sm, icon, href, className, children, ...rest }: LinkProps): import("react").DetailedReactHTMLElement<{
|
|
13
15
|
ref?: import("react").Ref<HTMLAnchorElement> | undefined;
|
|
14
16
|
key?: import("react").Key | null | undefined;
|
|
15
17
|
download?: any;
|
package/dist/actions/Link.js
CHANGED
|
@@ -3,6 +3,6 @@ import { createElement } from 'react';
|
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
4
|
import { Icon } from '../type/Icon.js';
|
|
5
5
|
/** Link — an inline text action big enough to tap (40px): "See all ↗" in a card head, "Skip" between pager buttons. */
|
|
6
|
-
export function Link({ muted, icon, href, className, children, ...rest }) {
|
|
7
|
-
return createElement(href ? 'a' : 'button', { href, type: href ? undefined : 'button', className: cx('link', muted && 'link--muted', className), ...rest }, children, icon && _jsx(Icon, { name: icon, size: "xs" }));
|
|
6
|
+
export function Link({ muted, sm, icon, href, className, children, ...rest }) {
|
|
7
|
+
return createElement(href ? 'a' : 'button', { href, type: href ? undefined : 'button', className: cx('link', muted && 'link--muted', sm && 'link--sm', className), ...rest }, children, icon && _jsx(Icon, { name: icon, size: "xs" }));
|
|
8
8
|
}
|
package/dist/cardds.css
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
and never a multiplier, so the proportions of the system hold at any size.
|
|
62
62
|
The px in the comments are at these defaults (1rem = 16px). */
|
|
63
63
|
--sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
|
|
64
|
-
--fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.
|
|
64
|
+
--fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.68 (15, minor links — was 0.775/17 until 2026-09-23). A new size is Lh's call only.
|
|
65
65
|
Also the member's text-size setting: the app writes it on <html> at run time (2.2rem = 160%) — words grow, things don't */
|
|
66
66
|
--r-base: 1rem; /* 16 radius: --r-* (--r-chip stays fully round) */
|
|
67
67
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
|
|
@@ -144,6 +144,7 @@
|
|
|
144
144
|
|
|
145
145
|
/* THE SCALE (Lh 2026-09-22): 7 sizes for 16 jobs — a size is a job's fit, not a rung; jobs share sizes.
|
|
146
146
|
Nothing that reads is under 17 (the smallest ui: chips). No clamp/vw: a size is one number. */
|
|
147
|
+
--fs-2xs: calc(var(--fs-base) * 0.68); /* 15 minor links — a ctaLink, a "details" (Lh 2026-09-23: added by Lh; ONLY Lh adds a size) */
|
|
147
148
|
--fs-xs: calc(var(--fs-base) * 0.775); /* 17 the smallest ui words: ring xs, a tile-badge label */
|
|
148
149
|
--fs-caption: calc(var(--fs-base) * 0.775); /* 17 */
|
|
149
150
|
--fs-label: calc(var(--fs-base) * 0.775); /* 17 chips, buttons' small words */
|
|
@@ -492,21 +493,21 @@ button { font: inherit; cursor: pointer; }
|
|
|
492
493
|
a heading, a paragraph, maybe a .cta-pack. Was .section until
|
|
493
494
|
2026-09-16 (Lh: the name said nothing about WHERE it lives; this one does).
|
|
494
495
|
In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
|
|
495
|
-
.
|
|
496
|
+
.page {
|
|
496
497
|
display: grid;
|
|
497
498
|
gap: var(--base-content-gap, var(--sp-3));
|
|
498
499
|
align-content: start; /* a tall column never stretches the rows — the words hug the top */
|
|
499
500
|
padding-block: var(--base-content-pad-y, var(--sp-4));
|
|
500
501
|
}
|
|
501
|
-
.
|
|
502
|
+
.page > .t-body { color: var(--base-content-body-ink, var(--on-surface)); max-width: 34ch; } /* full ink: a sentence is read, muted is for meta (Lh 2026-09-22 — the words on the base were grey and hard to read) */
|
|
502
503
|
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
503
504
|
(the walk's dots are NOT here — they are the Step's, after its Pages: step.css, Lh 2026-09-22) */
|
|
504
505
|
/* the page's ONE heading on the base (a screen with no TopBar: CardHead level="h1") reads at the card heading's size —
|
|
505
506
|
h1 is the semantic level, not a bigger face; --fs-h1 is the bar title's size (Lh 2026-09-22) */
|
|
506
|
-
.
|
|
507
|
-
.
|
|
508
|
-
.
|
|
509
|
-
.
|
|
507
|
+
.page > .card__head > .t-h1 { font-size: var(--fs-h2); }
|
|
508
|
+
.page > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; align-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
|
|
509
|
+
.page > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
510
|
+
.page .cta-pack {
|
|
510
511
|
display: flex;
|
|
511
512
|
align-items: start; /* buttons keep their own height, never the row's */
|
|
512
513
|
gap: var(--sp-2);
|
|
@@ -758,13 +759,13 @@ button { font: inherit; cursor: pointer; }
|
|
|
758
759
|
user-select: none; /* a walk is dragged, not read-and-selected — without this, a finger crossing the words highlights them instead of turning the page (timebank #59) */
|
|
759
760
|
touch-action: pan-y; /* leaves vertical scroll to the browser; horizontal is the app's own drag, never a native pan */
|
|
760
761
|
}
|
|
761
|
-
.pages > :is(.card, .
|
|
762
|
+
.pages > :is(.card, .page) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
|
|
762
763
|
.pages > [inert] { visibility: hidden; }
|
|
763
|
-
/* pages of WORDS (Lh, 2026-09-20): the same walk with .
|
|
764
|
+
/* pages of WORDS (Lh, 2026-09-20): the same walk with .page pages, on the base — in a .step the
|
|
764
765
|
.pages is the reading block (step.css), the round pager in the bar turns it, the same slideTo().
|
|
765
766
|
<section class="step"><div class="pages">
|
|
766
|
-
<section class="
|
|
767
|
-
<section class="
|
|
767
|
+
<section class="page" aria-current="step">…</section>
|
|
768
|
+
<section class="page" inert>…</section>
|
|
768
769
|
</div></section> <div class="action-bar action-bar--pager">← →</div> */
|
|
769
770
|
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
|
|
770
771
|
::view-transition-old(centre-card), ::view-transition-new(centre-card) {
|
|
@@ -1070,7 +1071,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1070
1071
|
}
|
|
1071
1072
|
/* 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
1073
|
loose things (a picture, a line) still sit against the peeks (Lh 2026-09-23, the home) */
|
|
1073
|
-
.sheet-stack__base:has(> .
|
|
1074
|
+
.sheet-stack__base:has(> .page:only-child) { align-content: start; }
|
|
1074
1075
|
|
|
1075
1076
|
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
1076
1077
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
@@ -1247,7 +1248,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1247
1248
|
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
1248
1249
|
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
1249
1250
|
|
|
1250
|
-
/* ---- called sheets (Lh, 2026-09-20): a .step whose .
|
|
1251
|
+
/* ---- called sheets (Lh, 2026-09-20): a .step whose .page has buttons that each CALL a sheet ----
|
|
1251
1252
|
.sheet--away = not on the screen: slid all the way out (translate, like every state — the material never
|
|
1252
1253
|
changes), and hidden once it has left so its shadow does not lie on the step's bottom edge; write it
|
|
1253
1254
|
with `inert` (the React Sheet does). WHO brings it onto the screen is the app's business — a button, a route:
|
|
@@ -1532,7 +1533,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1532
1533
|
(see "LAYERS" below).
|
|
1533
1534
|
|
|
1534
1535
|
<section class="step">
|
|
1535
|
-
<section class="
|
|
1536
|
+
<section class="page">…the words…</section> ← first
|
|
1536
1537
|
<article class="card sheet sheet--half">…the controls… <div class="card__foot">…</div></article> ← last, optional
|
|
1537
1538
|
</section>
|
|
1538
1539
|
<div class="action-bar">…</div> ← only when there is no sheet
|
|
@@ -1591,14 +1592,14 @@ button { font: inherit; cursor: pointer; }
|
|
|
1591
1592
|
:has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
|
|
1592
1593
|
/* 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
1594
|
is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
|
|
1594
|
-
|
|
1595
|
+
|
|
1595
1596
|
/* LAYERS (Lh, 2026-09-20): the base, then the sheet over it, then the top bar. A bar is the BASE's — the
|
|
1596
1597
|
move of a page of words — so a step with a sheet has NO bar: the sheet finishes on its own .card__foot
|
|
1597
1598
|
(sheet.css), and a form lives whole in ONE sheet, never item by item on the base. That is how cardds's
|
|
1598
1599
|
own screens are composed (the guide); the framework does not police it — a component never changes
|
|
1599
1600
|
because another one is there. */
|
|
1600
1601
|
|
|
1601
|
-
/* the reading block: a .
|
|
1602
|
+
/* the reading block: a .page (base.css) — the same words-on-the-base
|
|
1602
1603
|
block as anywhere else (it was .step__card, and a .card before 2026-09-16;
|
|
1603
1604
|
Lh: a step reads like a page, not a card in a page — so it IS the page's
|
|
1604
1605
|
block, one name). Here it keeps a card's inner rhythm (a head, --card-gap
|
|
@@ -1606,7 +1607,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1606
1607
|
gets the flex rule: it hugs its content, and when the column runs out (a
|
|
1607
1608
|
short phone, large text) it shrinks and scrolls inside itself instead of
|
|
1608
1609
|
pushing the bar or the sheet */
|
|
1609
|
-
.step > .
|
|
1610
|
+
.step > .page, .step > .pages > .page {
|
|
1610
1611
|
--card-pad: 0rem; /* published for .bleed: nothing to cancel */
|
|
1611
1612
|
--card-gap: var(--sp-4);
|
|
1612
1613
|
gap: var(--card-gap);
|
|
@@ -1614,26 +1615,14 @@ button { font: inherit; cursor: pointer; }
|
|
|
1614
1615
|
align-content: start;
|
|
1615
1616
|
color: var(--on-surface);
|
|
1616
1617
|
}
|
|
1617
|
-
/* the reading block is the .
|
|
1618
|
+
/* the reading block is the .page — or the .pages that holds several of them, a walk of words
|
|
1618
1619
|
(card.css). Behind a PEEKED sheet the words keep the strip's room under them, so whatever is down there
|
|
1619
1620
|
(the buttons that call the other sheets) scrolls clear of it — CSS alone: the box declares --sheet-peek. */
|
|
1620
|
-
.step:has(> .sheet--peek) > :is(.
|
|
1621
|
-
.step > :is(.
|
|
1621
|
+
.step:has(> .sheet--peek) > :is(.page, .pages) { padding-bottom: var(--sheet-peek); }
|
|
1622
|
+
.step > :is(.page, .pages) {
|
|
1622
1623
|
flex: 0 1 auto;
|
|
1623
1624
|
}
|
|
1624
|
-
|
|
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) */
|
|
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)); }
|
|
1636
|
-
.step > :is(.base-content, .pages) {
|
|
1625
|
+
.step > :is(.page, .pages) {
|
|
1637
1626
|
min-height: 0;
|
|
1638
1627
|
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 */
|
|
1639
1628
|
overflow-y: auto;
|
|
@@ -1643,8 +1632,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
1643
1632
|
/* a picture that opens the words stays put, only the words scroll under it (Lh 2026-09-23, timebank's walk at large text:
|
|
1644
1633
|
the picture scrolled away with the words). Sticky at the top of the reading block's scroller; its height is
|
|
1645
1634
|
--base-content-media-h as everywhere, never shrunk (Lh 2026-09-23: a fixed height, no scaling). */
|
|
1646
|
-
.step > .
|
|
1647
|
-
.step > .pages > .
|
|
1635
|
+
.step > .page > :is(img, .ph):first-child,
|
|
1636
|
+
.step > .pages > .page > :is(img, .ph):first-child {
|
|
1648
1637
|
position: sticky;
|
|
1649
1638
|
top: 0;
|
|
1650
1639
|
z-index: 1;
|
|
@@ -1652,23 +1641,20 @@ button { font: inherit; cursor: pointer; }
|
|
|
1652
1641
|
}
|
|
1653
1642
|
|
|
1654
1643
|
/* ============================================================
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
the column's end: the screen pads its bottom by --kb, the bar rides on it.
|
|
1644
|
+
TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
|
|
1645
|
+
in a .screen--fill the content runs to the screen's bottom edge, and the bar lies over it at the foot — it covers what is
|
|
1646
|
+
under it, on purpose; nothing is added to the content to make room. The keyboard lifts the layer (--kb).
|
|
1659
1647
|
(A bar OUTSIDE a fill screen — an app shell, a scrolling screen — is still fixed to the viewport, actions.css.)
|
|
1660
1648
|
============================================================ */
|
|
1661
1649
|
.screen--fill > .action-bar {
|
|
1662
|
-
position:
|
|
1663
|
-
flex: none;
|
|
1664
|
-
margin-top: auto; /* the column's end, even on a screen with nothing that grows (a centred card is absolute) */
|
|
1650
|
+
position: absolute; inset-inline: 0; top: auto; bottom: var(--kb, 0rem);
|
|
1665
1651
|
max-width: none;
|
|
1666
|
-
margin
|
|
1652
|
+
margin: 0;
|
|
1653
|
+
padding-top: 0; /* no ground of its own, never a solid strip */
|
|
1667
1654
|
}
|
|
1668
|
-
.screen--fill:has(> .action-bar) { padding-bottom: var(--kb, 0rem); }
|
|
1669
1655
|
.screen--fill:has(> .action-bar) > .step { margin-bottom: 0; }
|
|
1670
|
-
/* the
|
|
1671
|
-
.
|
|
1656
|
+
/* the walk's dots are the BOTTOM layer's too (Lh 2026-09-23): a row of the bar, over its buttons, centred */
|
|
1657
|
+
.action-bar > .pager__at, .action-bar__tier > .pager__at { align-self: center; }
|
|
1672
1658
|
/* 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
1659
|
reserves nothing for it; the breathing under it stays the one it had (--sp-2), not the screen's gap */
|
|
1674
1660
|
.screen--fill > .appbar {
|
|
@@ -1679,6 +1665,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
1679
1665
|
margin-bottom: calc(var(--sp-2) - var(--screen-gap));
|
|
1680
1666
|
}
|
|
1681
1667
|
.screen.screen--fill:has(> .appbar) { padding-top: 0; --header-gap: 0rem; }
|
|
1668
|
+
/* …and a STEP touches it, as it touches a TopBar: a full sheet's top edge on the bar's bottom edge (0.6.5 left 8px of base between
|
|
1669
|
+
them — timebank #137); the words keep the screen's gap as their lead (--_lead, .appbar + .step above) */
|
|
1670
|
+
.screen--fill > .appbar:has(+ .step) { margin-bottom: calc(-1 * var(--screen-gap)); }
|
|
1682
1671
|
.screen--fill > .appbar + .sheet-stage { margin-top: 0; }
|
|
1683
1672
|
|
|
1684
1673
|
/* ---- css/forms.css — outlined fields, add-row ---- */
|
|
@@ -2099,7 +2088,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2099
2088
|
shadow and read on their own (Lh, 2026-09-12; replaces the 09-11 fade) */
|
|
2100
2089
|
.action-bar__tier { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); }
|
|
2101
2090
|
.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 */
|
|
2091
|
+
.action-bar__tier--link { justify-content: center; min-height: 0; } /* a Link under the buttons, e.g. "details" that opens a Modal — its row as tall as a small link, not a tap (Lh 2026-09-23) */
|
|
2103
2092
|
: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
2093
|
/* 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
2094
|
adds a tap-tall row and its gap to the reserve, so the words above never run under the bar */
|
|
@@ -3004,6 +2993,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
3004
2993
|
padding: 0;
|
|
3005
2994
|
cursor: pointer;
|
|
3006
2995
|
}
|
|
2996
|
+
/* a SMALL link (Lh 2026-09-23): a thing tapped rarely — "details", a ctaLink — at the minor-link size (15) and 28px of height;
|
|
2997
|
+
its 44px tap target stays, an invisible ::after around it */
|
|
2998
|
+
.link--sm { position: relative; font-size: var(--link-sm-font-size, var(--fs-2xs)); min-height: var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)); }
|
|
2999
|
+
.link--sm::after { content: ""; position: absolute; inset: calc((var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)) - var(--tap-sm)) / 2) 0; }
|
|
3007
3000
|
.link--muted { color: var(--link-muted-ink, var(--card-muted)); }
|
|
3008
3001
|
.card--3 .link--muted { color: var(--card-3-muted); }
|
|
3009
3002
|
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './scaffold/ActionBar.js';
|
|
|
10
10
|
export * from './scaffold/PagerAt.js';
|
|
11
11
|
export * from './scaffold/FloatBar.js';
|
|
12
12
|
export * from './scaffold/FilterRow.js';
|
|
13
|
-
export * from './scaffold/
|
|
13
|
+
export * from './scaffold/Page.js';
|
|
14
14
|
export * from './scaffold/EmptyState.js';
|
|
15
15
|
export * from './scaffold/Centre.js';
|
|
16
16
|
export * from './cards/Card.js';
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export * from './scaffold/ActionBar.js';
|
|
|
13
13
|
export * from './scaffold/PagerAt.js';
|
|
14
14
|
export * from './scaffold/FloatBar.js';
|
|
15
15
|
export * from './scaffold/FilterRow.js';
|
|
16
|
-
export * from './scaffold/
|
|
16
|
+
export * from './scaffold/Page.js';
|
|
17
17
|
export * from './scaffold/EmptyState.js';
|
|
18
18
|
export * from './scaffold/Centre.js';
|
|
19
19
|
export * from './cards/Card.js';
|
|
@@ -17,7 +17,7 @@ export interface PagesProps extends ComponentProps<'div'> {
|
|
|
17
17
|
* Pages — the pages of a walk, all one height: every page is rendered, stacked in one cell as tall as the
|
|
18
18
|
* tallest at any text size. Give the current one `aria-current="step"` and every other one `inert`
|
|
19
19
|
* (hidden in place, out of the tab order). Turn with slideTo().
|
|
20
|
-
* Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `
|
|
20
|
+
* Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `Page`s, as a Step's
|
|
21
21
|
* reading block (no Sheet on that screen — a pager never works beside one), the round pager in the ActionBar.
|
|
22
22
|
*/
|
|
23
23
|
export declare function Pages({ className, ...rest }: PagesProps): import("react").JSX.Element;
|
package/dist/scaffold/Centre.js
CHANGED
|
@@ -17,7 +17,7 @@ export function Centre({ className, ...rest }) {
|
|
|
17
17
|
* Pages — the pages of a walk, all one height: every page is rendered, stacked in one cell as tall as the
|
|
18
18
|
* tallest at any text size. Give the current one `aria-current="step"` and every other one `inert`
|
|
19
19
|
* (hidden in place, out of the tab order). Turn with slideTo().
|
|
20
|
-
* Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `
|
|
20
|
+
* Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `Page`s, as a Step's
|
|
21
21
|
* reading block (no Sheet on that screen — a pager never works beside one), the round pager in the ActionBar.
|
|
22
22
|
*/
|
|
23
23
|
export function Pages({ className, ...rest }) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export interface PageProps extends ComponentProps<'section'> {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Page — ONE page of words in the content: a picture (optional, first — it stays put while the words scroll), a heading, the words,
|
|
6
|
+
* maybe a CtaPack. A component INSIDE the content box, never the box itself (Lh 2026-09-23): several Pages in a `Pages` = a walk the
|
|
7
|
+
* pager turns (slideTo); a single one sits in the content (a Step) directly. Was BaseContent until 2026-09-23 — "words on the base" meant
|
|
8
|
+
* a still, one-scaffold screen, which is gone.
|
|
9
|
+
*/
|
|
10
|
+
export declare function Page({ className, ...rest }: PageProps): import("react").JSX.Element;
|
|
11
|
+
/** @deprecated renamed `Page` (2026-09-23) — same props, same markup; kept one version for the move */
|
|
12
|
+
export declare function BaseContent(props: PageProps): import("react").JSX.Element;
|
|
13
|
+
export type BaseContentProps = PageProps;
|
|
14
|
+
export interface CtaPackProps extends ComponentProps<'div'> {
|
|
15
|
+
}
|
|
16
|
+
/** CtaPack — the buttons under a Page's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
|
|
17
|
+
export declare function CtaPack({ className, ...rest }: CtaPackProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
/**
|
|
4
|
+
* Page — ONE page of words in the content: a picture (optional, first — it stays put while the words scroll), a heading, the words,
|
|
5
|
+
* maybe a CtaPack. A component INSIDE the content box, never the box itself (Lh 2026-09-23): several Pages in a `Pages` = a walk the
|
|
6
|
+
* pager turns (slideTo); a single one sits in the content (a Step) directly. Was BaseContent until 2026-09-23 — "words on the base" meant
|
|
7
|
+
* a still, one-scaffold screen, which is gone.
|
|
8
|
+
*/
|
|
9
|
+
export function Page({ className, ...rest }) {
|
|
10
|
+
return _jsx("section", { className: cx('page', className), ...rest });
|
|
11
|
+
}
|
|
12
|
+
let warned = false;
|
|
13
|
+
/** @deprecated renamed `Page` (2026-09-23) — same props, same markup; kept one version for the move */
|
|
14
|
+
export function BaseContent(props) {
|
|
15
|
+
if (!warned) {
|
|
16
|
+
warned = true;
|
|
17
|
+
console.warn('cardds: BaseContent is now Page — rename it (same props).');
|
|
18
|
+
}
|
|
19
|
+
return _jsx(Page, { ...props });
|
|
20
|
+
}
|
|
21
|
+
/** CtaPack — the buttons under a Page's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
|
|
22
|
+
export function CtaPack({ className, ...rest }) {
|
|
23
|
+
return _jsx("div", { className: cx('cta-pack', className), ...rest });
|
|
24
|
+
}
|
|
@@ -8,15 +8,15 @@ export interface ScreenProps extends ComponentProps<'div'> {
|
|
|
8
8
|
as?: 'div' | 'main' | 'section';
|
|
9
9
|
/** TOP: a `TopBar` / `AppBar`, or `"lead"` — no bar, the content starts `--screen-lead` (15% of the screen) down */
|
|
10
10
|
top?: ReactNode | 'lead';
|
|
11
|
-
/** BOTTOM: an `ActionBar` (or a `FloatBar`) —
|
|
11
|
+
/** BOTTOM: an `ActionBar` (or a `FloatBar`) — a LAYER over the content's foot, as tall as its rows need.
|
|
12
12
|
* Never with a `Sheet` in the content: a sheet finishes on its own foot (warns once). */
|
|
13
13
|
bottom?: ReactNode;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Screen — the mobile screen
|
|
17
|
-
* Top = `TopBar` / `AppBar`, or `top="lead"` (no bar, the words start
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Screen — the mobile screen in THREE parts: **top** and **content** (the children) in one flex column, the content down to the
|
|
17
|
+
* screen's foot; **bottom** a LAYER over it there (Lh 2026-09-23). Top = `TopBar` / `AppBar`, or `top="lead"` (no bar, the words start
|
|
18
|
+
* 15% down). Content = a box of components one after another — a `Step` (`Pages` of `Page`s / a `Page` [+ `Sheet`]), a `Centre`, a
|
|
19
|
+
* `SheetStack`… Bottom = an `ActionBar` (the walk's `PagerAt` its first row) or a `FloatBar`, covering the content's foot on purpose. Max 25rem wide, the standard inset, no vertical padding. A plain Screen (no `fill`) scrolls.
|
|
20
20
|
* The slots set the order; children laid by hand in the same order still work.
|
|
21
21
|
*/
|
|
22
22
|
export declare function Screen({ fill, palette, as, top, bottom, className, children, ref, ...rest }: ScreenProps): import("react").DetailedReactHTMLElement<{
|
package/dist/scaffold/Screen.js
CHANGED
|
@@ -2,10 +2,10 @@ import { createElement, useEffect, useRef } from 'react';
|
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
3
|
let warnedSheetBar = false;
|
|
4
4
|
/**
|
|
5
|
-
* Screen — the mobile screen
|
|
6
|
-
* Top = `TopBar` / `AppBar`, or `top="lead"` (no bar, the words start
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Screen — the mobile screen in THREE parts: **top** and **content** (the children) in one flex column, the content down to the
|
|
6
|
+
* screen's foot; **bottom** a LAYER over it there (Lh 2026-09-23). Top = `TopBar` / `AppBar`, or `top="lead"` (no bar, the words start
|
|
7
|
+
* 15% down). Content = a box of components one after another — a `Step` (`Pages` of `Page`s / a `Page` [+ `Sheet`]), a `Centre`, a
|
|
8
|
+
* `SheetStack`… Bottom = an `ActionBar` (the walk's `PagerAt` its first row) or a `FloatBar`, covering the content's foot on purpose. Max 25rem wide, the standard inset, no vertical padding. A plain Screen (no `fill`) scrolls.
|
|
9
9
|
* The slots set the order; children laid by hand in the same order still work.
|
|
10
10
|
*/
|
|
11
11
|
export function Screen({ fill, palette, as = 'div', top, bottom, className, children, ref, ...rest }) {
|
package/dist/sheets/Step.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import type { ComponentProps } from 'react';
|
|
|
2
2
|
export interface StepProps extends ComponentProps<'section'> {
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
|
-
* Step — one screen, one task. Children: a
|
|
5
|
+
* Step — one screen, one task. Children: a Page first (what the member READS — words on the base, not a card), an optional Sheet last
|
|
6
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
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
|
-
* Reads →
|
|
8
|
+
* Reads → Page only. Acts → Page + 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;
|
package/dist/sheets/Step.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
3
|
/**
|
|
4
|
-
* Step — one screen, one task. Children: a
|
|
4
|
+
* Step — one screen, one task. Children: a Page first (what the member READS — words on the base, not a card), an optional Sheet last
|
|
5
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
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
|
-
* Reads →
|
|
7
|
+
* Reads → Page only. Acts → Page + Sheet. Never hand-lay a step, never write a sheet height.
|
|
8
8
|
*/
|
|
9
9
|
export function Step({ className, ...rest }) {
|
|
10
10
|
return _jsx("section", { className: cx('step', className), ...rest });
|
package/dist/templates/Home.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface HomeProps extends Omit<ComponentProps<typeof Screen>, 'children
|
|
|
28
28
|
open?: number | null;
|
|
29
29
|
defaultOpen?: number | null;
|
|
30
30
|
onOpenChange?: (open: number | null) => void;
|
|
31
|
-
/** CONTENT above the sheet heads — one `
|
|
31
|
+
/** CONTENT above the sheet heads — one `Page`: a welcome, the news, a picture. It reads
|
|
32
32
|
* from the top; a sheet coming up covers it */
|
|
33
33
|
base?: ReactNode;
|
|
34
34
|
/** keep a sheet's body mounted after it closes (it opens again at once, scrolled where it was); default: dropped */
|
|
@@ -25,7 +25,7 @@ export interface WalkthroughProps extends Omit<ComponentProps<typeof Screen>, 'c
|
|
|
25
25
|
cta?: ReactNode | ((c: WalkthroughCta) => ReactNode);
|
|
26
26
|
/** rows of controls ABOVE the CTA row (a text-size Slider…), one row each, top to bottom — keep them few */
|
|
27
27
|
controls?: ReactNode[];
|
|
28
|
-
/** a line UNDER the row: a `Link` (e.g. "developer details" that opens a Modal) — centred, one line */
|
|
28
|
+
/** a line UNDER the row: a `Link sm` (e.g. "developer details" that opens a Modal) — centred, one short line */
|
|
29
29
|
ctaLink?: ReactNode;
|
|
30
30
|
/** layers over the screen: the app's `SheetStage dim` + `Modal` that a link or button opens (the app wires it) */
|
|
31
31
|
children?: ReactNode;
|
|
@@ -34,7 +34,7 @@ export interface WalkthroughProps extends Omit<ComponentProps<typeof Screen>, 'c
|
|
|
34
34
|
nextLabel?: string;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Walkthrough — TEMPLATE, a `Screen` with its three parts filled — top: `lead` · content: `Pages` · bottom: `ActionBar
|
|
37
|
+
* Walkthrough — TEMPLATE, a `Screen` with its three parts filled — top: `lead` · content: `Pages` (down to the screen's foot) · bottom: a LAYER over it — the dots, then the `ActionBar` rows.
|
|
38
38
|
* A walk of pages that tell (onboarding, an introduction). Each page = a picture, a heading,
|
|
39
39
|
* the words; the dots at the foot; one row of calls to action at the bottom. No top bar. Fixed: the order, the picture
|
|
40
40
|
* first and fixed while the words scroll, one CTA row. Yours: the pages, the row, the state.
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useRef, useState } from 'react';
|
|
3
3
|
import { Screen } from '../scaffold/Screen.js';
|
|
4
4
|
import { Step } from '../sheets/Step.js';
|
|
5
|
-
import {
|
|
5
|
+
import { Page } from '../scaffold/Page.js';
|
|
6
6
|
import { Pages, slideTo } from '../scaffold/Centre.js';
|
|
7
7
|
import { PagerAt } from '../scaffold/PagerAt.js';
|
|
8
8
|
import { ActionBar, ActionBarTier } from '../scaffold/ActionBar.js';
|
|
@@ -10,7 +10,7 @@ import { CardHead } from '../cards/CardHead.js';
|
|
|
10
10
|
import { Text } from '../type/Text.js';
|
|
11
11
|
import { IconBtn } from '../actions/IconBtn.js';
|
|
12
12
|
/**
|
|
13
|
-
* Walkthrough — TEMPLATE, a `Screen` with its three parts filled — top: `lead` · content: `Pages` · bottom: `ActionBar
|
|
13
|
+
* Walkthrough — TEMPLATE, a `Screen` with its three parts filled — top: `lead` · content: `Pages` (down to the screen's foot) · bottom: a LAYER over it — the dots, then the `ActionBar` rows.
|
|
14
14
|
* A walk of pages that tell (onboarding, an introduction). Each page = a picture, a heading,
|
|
15
15
|
* the words; the dots at the foot; one row of calls to action at the bottom. No top bar. Fixed: the order, the picture
|
|
16
16
|
* first and fixed while the words scroll, one CTA row. Yours: the pages, the row, the state.
|
|
@@ -29,6 +29,7 @@ export function Walkthrough({ pages, at: atProp, defaultAt = 0, onAtChange, cta,
|
|
|
29
29
|
const row = typeof cta === 'function' ? cta({ at, count, go }) : cta;
|
|
30
30
|
const custom = row !== undefined && row !== null;
|
|
31
31
|
const pager = (_jsxs(_Fragment, { children: [_jsx(IconBtn, { icon: "arrow-left", label: backLabel, disabled: at === 0, onClick: () => go(at - 1) }), _jsx(IconBtn, { invert: true, icon: "arrow-right", label: nextLabel, disabled: at === count - 1, onClick: () => go(at + 1) })] }));
|
|
32
|
-
const
|
|
33
|
-
|
|
32
|
+
const dots = count > 1 ? _jsx(PagerAt, { at: at + 1, of: count }) : null;
|
|
33
|
+
const bottom = dots || controls.length || ctaLink != null ? (_jsxs(ActionBar, { variant: "tiers", children: [dots, controls.map((c, i) => _jsx(ActionBarTier, { children: c }, i)), _jsx(ActionBarTier, { pager: true, children: custom ? row : pager }), ctaLink != null && _jsx(ActionBarTier, { className: "action-bar__tier--link", children: ctaLink })] })) : (_jsx(ActionBar, { variant: "pager", children: custom ? row : pager }));
|
|
34
|
+
return (_jsxs(Screen, { fill: true, top: "lead", bottom: bottom, ...rest, children: [_jsx(Step, { ref: step, children: _jsx(Pages, { children: pages.map((p, i) => (_jsxs(Page, { "aria-current": i === at ? 'step' : undefined, inert: i !== at || undefined, children: [p.media, _jsx(CardHead, { level: "h1", heading: p.heading }), typeof p.body === 'string' ? _jsx(Text, { level: "body", children: p.body }) : p.body] }, i))) }) }), children] }));
|
|
34
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ComponentProps } from 'react';
|
|
2
|
-
export interface BaseContentProps extends ComponentProps<'section'> {
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* BaseContent — words straight on the base, no card around them: a heading, a paragraph,
|
|
6
|
-
* maybe a CtaPack. Content has three homes (Card · Sheet · the base); this is the third one, named.
|
|
7
|
-
* Was Section (and, inside a Step, StepCard) until 2026-09-16 — one block, one name.
|
|
8
|
-
* In a Step it is the reading block (first child; the step's MOVE goes to the ActionBar, never here). It can move two ways
|
|
9
|
-
* (Lh 2026-09-20): several of them in a `Pages` = a walk of words the round pager turns (slideTo); or a CtaPack of WAYS IN
|
|
10
|
-
* (sign in · new phone · join) — plain `Btn`s; what each one opens (a `Sheet` holding that way's whole form) is the APP's wiring, not
|
|
11
|
-
* cardds's. The base itself carries no control; a form is never taken apart across it. Never both: a pager never works beside a sheet.
|
|
12
|
-
*/
|
|
13
|
-
export declare function BaseContent({ className, ...rest }: BaseContentProps): import("react").JSX.Element;
|
|
14
|
-
export interface CtaPackProps extends ComponentProps<'div'> {
|
|
15
|
-
}
|
|
16
|
-
/** CtaPack — the buttons under a BaseContent's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
|
|
17
|
-
export declare function CtaPack({ className, ...rest }: CtaPackProps): import("react").JSX.Element;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cx } from '../cx.js';
|
|
3
|
-
/**
|
|
4
|
-
* BaseContent — words straight on the base, no card around them: a heading, a paragraph,
|
|
5
|
-
* maybe a CtaPack. Content has three homes (Card · Sheet · the base); this is the third one, named.
|
|
6
|
-
* Was Section (and, inside a Step, StepCard) until 2026-09-16 — one block, one name.
|
|
7
|
-
* In a Step it is the reading block (first child; the step's MOVE goes to the ActionBar, never here). It can move two ways
|
|
8
|
-
* (Lh 2026-09-20): several of them in a `Pages` = a walk of words the round pager turns (slideTo); or a CtaPack of WAYS IN
|
|
9
|
-
* (sign in · new phone · join) — plain `Btn`s; what each one opens (a `Sheet` holding that way's whole form) is the APP's wiring, not
|
|
10
|
-
* cardds's. The base itself carries no control; a form is never taken apart across it. Never both: a pager never works beside a sheet.
|
|
11
|
-
*/
|
|
12
|
-
export function BaseContent({ className, ...rest }) {
|
|
13
|
-
return _jsx("section", { className: cx('base-content', className), ...rest });
|
|
14
|
-
}
|
|
15
|
-
/** CtaPack — the buttons under a BaseContent's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
|
|
16
|
-
export function CtaPack({ className, ...rest }) {
|
|
17
|
-
return _jsx("div", { className: cx('cta-pack', className), ...rest });
|
|
18
|
-
}
|