@usableapp/cardds 0.6.4 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +65 -18
- 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/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 +138 -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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.5 — 2026-09-23
|
|
6
|
+
|
|
7
|
+
### A screen is top · content · bottom, one flex column (Lh 2026-09-23)
|
|
8
|
+
- **`Screen top` / `bottom`** (new): `top` = `TopBar` / `AppBar` / `"lead"` (no bar, the words 15% down) · children = content · `bottom` = `ActionBar` / `FloatBar`. The browser measures the bar; the content takes the rest.
|
|
9
|
+
- **Before → after:** in a `Screen fill` the `ActionBar` and the `AppBar` are **rows of the screen's column**, not `position: fixed` over it. Put the bar INSIDE the Screen (as `bottom` or its last child). A bar after `</Screen>`, or one given `position` by hand, is now wrong (`validateScreen` in scripts/validate.mjs fails it). A bar outside a fill screen (an app shell) still works the old fixed way.
|
|
10
|
+
- The walk's dots (`PagerAt`) float over the foot of the pages; the pages run down to the bar.
|
|
11
|
+
- `Screen` warns once when a screen has both a `Sheet` and a bar.
|
|
12
|
+
|
|
13
|
+
### Templates (new)
|
|
14
|
+
- **`Walkthrough`** — top `lead` · content `Pages` (media · heading · body) · bottom `ActionBar` (`controls` rows · the `cta` row — the pager by default, or a function of `{ at, count, go }` · `ctaLink`); children = layers over it.
|
|
15
|
+
- **`Home`** — top `AppBar` · content: `base` over a closed `SheetStack` · no bottom. `title` = the page's h1, read aloud, not shown. Only the open sheet's body is rendered.
|
|
16
|
+
|
|
17
|
+
### New and changed
|
|
18
|
+
- **`StackSheet`** (new): one sheet of a SheetStack — head (back · title · icon tabs with a count) · body · foot. A tap on a tab of a closed sheet opens it on that tab; a tap on the open sheet's title folds it.
|
|
19
|
+
- **`StackBack`**: a small 32px disc with a chevron (was a 44px disc with an arrow); the tap target is still 44px.
|
|
20
|
+
- A stacked sheet with a `SheetBody` has the one sheet shape (head pinned, body scrolls, foot at the bottom). A closed stack sheet can't be text-selected.
|
|
21
|
+
- **`AppBar brandButton`**: the mark + the name become one button (`popoverTarget` opens a `Menu`).
|
|
22
|
+
- **`Toggle text`**: words shown before the switch, one tap target.
|
|
23
|
+
- **`Text sr`**: read by a screen reader, not shown.
|
|
24
|
+
- **`SheetStage over`**: a stage laid over its screen (a Modal a link opened).
|
|
25
|
+
- Icons `megaphone`, `log-out`.
|
|
26
|
+
|
|
5
27
|
## 0.6.4 — 2026-09-23
|
|
6
28
|
|
|
7
29
|
- **Fold card: the corner is cut** (Lh 2026-09-23): the triangle under the flap shows what is behind the card, instead of the card's own colour, which read as a second card underneath. Nothing to change in an app.
|
package/README.md
CHANGED
|
@@ -74,16 +74,30 @@ import '@usableapp/cardds/dist/cardds.css'; // the CSS, fonts resolve from the
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
**เบราว์เซอร์** (เช็ก 2026-09-23): ใช้ได้กับเบราว์เซอร์ตั้งแต่**ต้นปี 2025** ขึ้นไป
|
|
78
78
|
|
|
79
|
-
|
|
|
79
|
+
| เบราว์เซอร์ | ขั้นต่ำ | ออกเมื่อ | เครื่องที่ใช้ได้ |
|
|
80
80
|
|---|---|---|---|
|
|
81
|
-
| Safari / iOS | 18.3 |
|
|
82
|
-
| Chrome | 114 |
|
|
83
|
-
| Firefox | 128 |
|
|
81
|
+
| Safari / iOS | 18.3 | ม.ค. 2025 | iPhone XS / XR (2018) ขึ้นไป ที่อัปเดตแล้ว |
|
|
82
|
+
| Chrome | 114 | พ.ค. 2023 | Android ส่วนใหญ่ที่ Chrome อัปเดตอยู่ |
|
|
83
|
+
| Firefox | 128 | ก.ค. 2024 | — |
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
เก่ากว่านี้: เมนูและ dropdown เปิดไม่ได้ ส่วนเบราว์เซอร์ที่ผ่านขั้นต่ำแต่ยังไม่ใช่รุ่นล่าสุด
|
|
86
|
+
เมนูจะขึ้นกลางจอแทนที่จะอยู่ใต้ปุ่ม
|
|
87
|
+
|
|
88
|
+
สำหรับโปรเจกต์ที่อยากเช็กเบราว์เซอร์ก่อนเริ่ม (เช่นหน้าติดตั้ง): เช็กเฉพาะฟีเจอร์ที่**จำเป็น**
|
|
89
|
+
ตัวอื่น cardds มีตัวสำรองไว้แล้ว ไม่ต้องเช็ก
|
|
90
|
+
|
|
91
|
+
| จำเป็น: ไม่มีแล้วหน้าจอพัง | วิธีเช็ก |
|
|
92
|
+
|---|---|
|
|
93
|
+
| popover | `'popover' in HTMLElement.prototype` |
|
|
94
|
+
| `@property` | `'registerProperty' in CSS` |
|
|
95
|
+
| `color-mix()` | `CSS.supports('color', 'color-mix(in srgb, red, blue)')` |
|
|
96
|
+
| container queries | `CSS.supports('container-type: size') && CSS.supports('height: 1cqh')` |
|
|
97
|
+
| `:has()` | `CSS.supports('selector(:has(a))')` |
|
|
98
|
+
|
|
99
|
+
มีตัวสำรองแล้ว ไม่ต้องเช็ก: anchor positioning, view transitions, `@starting-style`, `text-wrap`
|
|
100
|
+
iOS 17–18.2 เช็กผ่าน แต่ยังรองรับไม่เต็ม เมนูอาจแสดงผิดเล็กน้อย
|
|
87
101
|
|
|
88
102
|
Working with Claude in another project? The `/cardds` skill
|
|
89
103
|
(`~/.claude/skills/cardds/`) carries the install and the contract, and points
|
|
@@ -143,6 +157,37 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
143
157
|
one — the members are sheets and the stack is the menu, not a list of cards.
|
|
144
158
|
Never bring it back, not even as an alias.
|
|
145
159
|
|
|
160
|
+
## Screen: top · content · bottom
|
|
161
|
+
|
|
162
|
+
**Every screen is three parts in ONE flex column** (Lh 2026-09-23) — the words used to ask for a screen, and the props
|
|
163
|
+
of `Screen`:
|
|
164
|
+
|
|
165
|
+
| Part | Prop | What fills it |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| **top** | `top` | `TopBar` · `AppBar` · `"lead"` (no bar: the content starts `--screen-lead`, 15% of the screen, down) |
|
|
168
|
+
| **content** | children | a `Step` (`Pages` · `BaseContent` · `BaseContent` + `Sheet`) · `Centre` · `SheetStack` … |
|
|
169
|
+
| **bottom** | `bottom` | `ActionBar` (rows: `controls` · the one `cta` row — the pager ← → or one wide button · a `ctaLink`) · `FloatBar` · nothing |
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<Screen fill top="lead" bottom={<ActionBar variant="pager">…</ActionBar>}>
|
|
173
|
+
<Step><Pages>…</Pages><PagerAt at={1} of={3} /></Step>
|
|
174
|
+
</Screen>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- **The browser measures, nobody computes.** In `Screen fill` the content takes all the height the top and the bottom
|
|
178
|
+
leave (`flex: 1`); the bottom is as tall as its rows. A one-row bar gives its room straight back to the content.
|
|
179
|
+
No `--bar-reserve` arithmetic for a bar in the screen.
|
|
180
|
+
- **The bar is INSIDE the screen, its last part** — `bottom`, never a sibling after `</Screen>`, never pinned with a
|
|
181
|
+
hand-set `position`. The keyboard lifts the column's end (`--kb`). A `FloatBar` in `bottom` floats over the
|
|
182
|
+
content at the screen's foot (it lies over a map or a stack — not a row).
|
|
183
|
+
- **A screen with a `Sheet` has no bottom.** The sheet finishes on its own `CardFoot`; `Screen` warns once when both
|
|
184
|
+
are there.
|
|
185
|
+
- **Checked:** `validateScreen` (`scripts/validate.mjs`, run on every story and template by
|
|
186
|
+
`tests/composition.spec.js`) fails a bar after `</Screen>` or a pinned bar, warns on a Sheet + ActionBar.
|
|
187
|
+
- **Templates** are `Screen`s with the three parts filled (`Walkthrough` = top `lead` · content `Pages` · bottom
|
|
188
|
+
`ActionBar`); the gallery draws those parts on a template's page. The gallery's nav groups the components by
|
|
189
|
+
the part they fill: *top* · *content* · *bottom*.
|
|
190
|
+
|
|
146
191
|
## Step markup
|
|
147
192
|
|
|
148
193
|
A step is one screen, one task (see *Anatomy*). It is the other component
|
|
@@ -156,7 +201,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
156
201
|
<p class="t-body">…</p>
|
|
157
202
|
</section>
|
|
158
203
|
</section>
|
|
159
|
-
<div class="action-bar action-bar--pager">…</div>
|
|
204
|
+
<div class="action-bar action-bar--pager">…</div> <!-- the screen's bottom: inside .screen, after the step -->
|
|
160
205
|
|
|
161
206
|
<!-- acts: the words + a sheet -->
|
|
162
207
|
<section class="step">
|
|
@@ -201,9 +246,10 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
201
246
|
breathing (`--header-gap`), and the card takes that room as its lead. A
|
|
202
247
|
step that follows anything else keeps the screen's gap, and the sheet
|
|
203
248
|
stops under that thing.
|
|
204
|
-
- **The bar is
|
|
205
|
-
|
|
206
|
-
|
|
249
|
+
- **The bar is after the step, inside the screen** (the screen's bottom — *Screen: top · content · bottom*). In a
|
|
250
|
+
`.screen--fill` the step and the bar share one flex column and touch; the step takes the rest. (A bar outside a
|
|
251
|
+
fill screen — an app shell — is still fixed to the viewport and the step reads `--bar-reserve`: the old way,
|
|
252
|
+
kept until 0.7.0.)
|
|
207
253
|
- **Every sheet has one shape** (Lh 2026-09-21): the handle and the head stay at the top and never scroll · the
|
|
208
254
|
foot's button stands at the bottom — above the keyboard when there is one (`--kb`: a step gives the keyboard
|
|
209
255
|
its room, a stage sheet gives it up itself as `--_lift`) · everything between them scrolls, as far as it
|
|
@@ -259,10 +305,9 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
259
305
|
the inset as padding, so a deck's fanned cards may overhang and clip at the
|
|
260
306
|
screen's edges (the fold visual, not a bug), and stacks its children at the
|
|
261
307
|
screen's gap. Never hand-lay `top: 40%` on a screen.
|
|
262
|
-
- **The bar is
|
|
308
|
+
- **The bar is the screen's bottom.** Plain `.action-bar` for one move; with a
|
|
263
309
|
control that belongs to the screen (a slider), `.action-bar--tiers` stacks a
|
|
264
|
-
control tier above the buttons
|
|
265
|
-
(`--bar-reserve`) from whatever parent hosts the bar.
|
|
310
|
+
control tier above the buttons (it publishes the taller `--bar-reserve` the centred card still reads for its cap).
|
|
266
311
|
- **The round pager** is `.action-bar--pager` (or an `.action-bar__tier--pager`)
|
|
267
312
|
holding two `.icon-btn`s and nothing else: they stand at the two edges. The
|
|
268
313
|
step that commits still goes back to one wide button (rule 3).
|
|
@@ -282,8 +327,8 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
282
327
|
- **A pager turns pages of words, never a screen with a sheet** (Lh 2026-09-20). A
|
|
283
328
|
sheet means this screen has work that cannot be walked past: no ← →, no bar at all — the sheet's
|
|
284
329
|
own foot finishes it. (Guidance — not enforced.)
|
|
285
|
-
- A screen is **
|
|
286
|
-
surface;
|
|
330
|
+
- A screen is **top · content · bottom** on the base (*Screen: top · content · bottom*). The base is the
|
|
331
|
+
surface; top and bottom are the chrome above and below (`.topbar` / `.appbar`,
|
|
287
332
|
`.action-bar` / `.float-bar`); content is everything between.
|
|
288
333
|
- Content has three homes: **on the base**, **in a card**, **in a bottom
|
|
289
334
|
sheet**. Content normally comes in cards; static data (a blurb, a legal
|
|
@@ -323,8 +368,8 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
323
368
|
retired.) Demos: `sheet.html`, `stack.html`.
|
|
324
369
|
- **A step is one screen, one task: `.step`.** What the member reads is
|
|
325
370
|
words on the base (`.base-content` — 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
|
|
326
|
-
`.sheet` in front of it; the screen's one move is `.action-bar`,
|
|
327
|
-
the step
|
|
371
|
+
`.sheet` in front of it; the screen's one move is `.action-bar`, the screen's
|
|
372
|
+
bottom, after the step. A step where the member only READS is the card alone.
|
|
328
373
|
A step where the member ACTS (types, picks, shoots a photo, records) adds
|
|
329
374
|
the sheet: peek shows its handle and title row, half shows half of it,
|
|
330
375
|
full slides up until its top edge touches the top bar — and at every one
|
|
@@ -374,6 +419,8 @@ Demo: the *skeleton* block at the top of `elements.html`.
|
|
|
374
419
|
room itself, as a bottom margin, reading the same `--bar-reserve`. The
|
|
375
420
|
reserves use the nominal control height, so a bar whose button wraps to two
|
|
376
421
|
lines can still overlap the last line of content.
|
|
422
|
+
**In a `.screen--fill` none of this applies to a bar in the screen** (2026-09-23): it is the column's last
|
|
423
|
+
part and the browser measures it — the reserves are the old way for a bar outside a fill screen.
|
|
377
424
|
**Padding is published, not copied.** `.screen` sets `--screen-pad` and
|
|
378
425
|
`--screen-gap`, its appbar reserve sets `--header-gap`, and `.card` sets
|
|
379
426
|
`--card-pad` and `--card-gap`; anything that has to cancel a container's
|
package/css/actions.css
CHANGED
|
@@ -64,6 +64,15 @@
|
|
|
64
64
|
.screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
|
|
65
65
|
.appbar__brand { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
|
|
66
66
|
.appbar__text { display: grid; min-width: 0; flex: 1; } /* pushes trailing button to the right edge */
|
|
67
|
+
/* 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 */
|
|
68
|
+
.appbar__who {
|
|
69
|
+
flex: 1; min-width: 0; min-height: var(--tap);
|
|
70
|
+
display: flex; align-items: center; gap: var(--sp-2);
|
|
71
|
+
padding: 0; margin: 0; border: 0; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
|
|
72
|
+
border-radius: var(--appbar-who-radius, var(--r-chip));
|
|
73
|
+
-webkit-tap-highlight-color: transparent;
|
|
74
|
+
}
|
|
75
|
+
.appbar__who:focus-visible { outline: var(--focus); outline-offset: 2px; }
|
|
67
76
|
.appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.35; } /* clipped lines: room for Thai marks */
|
|
68
77
|
.appbar__title { font: var(--type-label); }
|
|
69
78
|
.appbar__sub { font: var(--type-caption); color: var(--on-surface-muted); }
|
|
@@ -280,7 +289,13 @@
|
|
|
280
289
|
shadow and read on their own (Lh, 2026-09-12; replaces the 09-11 fade) */
|
|
281
290
|
.action-bar__tier { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); }
|
|
282
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 */
|
|
283
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
|
+
/* 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
|
+
adds a tap-tall row and its gap to the reserve, so the words above never run under the bar */
|
|
296
|
+
: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)); }
|
|
297
|
+
: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)); }
|
|
298
|
+
: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)); }
|
|
284
299
|
|
|
285
300
|
/* the first round button sits at the left edge, the last at the right; a skip link between them stays centred */
|
|
286
301
|
:is(.action-bar--pager, .action-bar__tier--pager) > .icon-btn:first-child { margin-right: auto; }
|
package/css/base.css
CHANGED
|
@@ -105,6 +105,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
105
105
|
.card--3 .t-muted { color: inherit; opacity: 0.7; } /* card-muted is tuned for light cards; dim instead on the dark slot */
|
|
106
106
|
.t-caps { text-transform: uppercase; }
|
|
107
107
|
.grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
|
|
108
|
+
/* 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.
|
|
109
|
+
The 1px is the one the platform needs to keep it in the accessibility tree, not a length of the design. */
|
|
110
|
+
.t-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
108
111
|
|
|
109
112
|
/* ---- base content: words straight on the base, no card around them — an
|
|
110
113
|
a heading, a paragraph, maybe a .cta-pack. Was .section until
|
package/css/choice.css
CHANGED
|
@@ -303,3 +303,14 @@
|
|
|
303
303
|
box-shadow: var(--shadow-float);
|
|
304
304
|
}
|
|
305
305
|
.slider__range:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
306
|
+
|
|
307
|
+
/* ---- a switch that names itself (Toggle text, Lh 2026-09-23 — "อยู่บ้าน" on the home's top): the words, then the switch,
|
|
308
|
+
one label — the whole of it is the tap target ---- */
|
|
309
|
+
.toggle-field {
|
|
310
|
+
display: inline-flex; align-items: center; gap: var(--toggle-field-gap, var(--sp-2));
|
|
311
|
+
min-height: var(--tap);
|
|
312
|
+
font: var(--toggle-field-font, var(--type-label));
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
-webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
|
|
315
|
+
flex: none;
|
|
316
|
+
}
|
package/css/layover.css
CHANGED
|
@@ -190,6 +190,9 @@
|
|
|
190
190
|
align-items: center;
|
|
191
191
|
z-index: 30;
|
|
192
192
|
}
|
|
193
|
+
/* in a fill screen (its `bottom`) the float bar floats over the SCREEN's foot, not the window's — it lies over the content
|
|
194
|
+
(a map, a stack), so it is not a row of the column; the screen is its box (Lh 2026-09-23) */
|
|
195
|
+
.screen--fill > .float-bar { position: absolute; }
|
|
193
196
|
.sheet-stage > .float-bar { position: absolute; padding-inline: var(--sp-4); }
|
|
194
197
|
/* .screen's default bottom padding clears an .action-bar; a .float-bar is
|
|
195
198
|
taller (--bar-h + its own bottom offset), so reserve the difference. Any
|
package/css/sheet.css
CHANGED
|
@@ -243,6 +243,13 @@
|
|
|
243
243
|
that bar — the stage keeps the bar's line free itself (--_top), so the reserve
|
|
244
244
|
would be paid twice and the page would scroll by it */
|
|
245
245
|
.screen > .sheet-stage { margin-inline: calc(-1 * var(--screen-pad)); width: auto; } /* auto: 100% would keep the inset width */
|
|
246
|
+
/* 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 */
|
|
247
|
+
.sheet-stage--over, .screen > .sheet-stage--over {
|
|
248
|
+
position: absolute; inset: 0; z-index: 40;
|
|
249
|
+
width: auto; height: auto; max-width: none; margin: 0;
|
|
250
|
+
background: transparent;
|
|
251
|
+
--_top: 0rem;
|
|
252
|
+
}
|
|
246
253
|
.appbar + .sheet-stage { margin-top: calc(-1 * (var(--header-h) + var(--header-gap, 0rem))); }
|
|
247
254
|
.sheet-stage__bg { position: absolute; inset: 0; } /* map / previous cards behind */
|
|
248
255
|
/* the hero: ONE thing shown in the band a half sheet leaves open (a postcard being made,
|
package/css/stack.css
CHANGED
|
@@ -230,6 +230,9 @@
|
|
|
230
230
|
gap: var(--card-gap, var(--sp-5));
|
|
231
231
|
overflow: clip;
|
|
232
232
|
}
|
|
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
|
+
loose things (a picture, a line) still sit against the peeks (Lh 2026-09-23, the home) */
|
|
235
|
+
.sheet-stack__base:has(> .base-content:only-child) { align-content: start; }
|
|
233
236
|
|
|
234
237
|
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
235
238
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
@@ -256,3 +259,55 @@
|
|
|
256
259
|
opacity: 1;
|
|
257
260
|
visibility: visible;
|
|
258
261
|
}
|
|
262
|
+
|
|
263
|
+
/* ---- a stacked sheet with a SheetBody has the ONE sheet shape (Lh 2026-09-23, the home layout): sheet head
|
|
264
|
+
pinned at the top · sheet body scrolls, as long as it needs · sheet foot (a CardFoot, the CTA) at the bottom.
|
|
265
|
+
The card becomes a flex column and stops scrolling itself — only the body does. A sheet with no SheetBody
|
|
266
|
+
keeps the old way: the whole open card scrolls. ---- */
|
|
267
|
+
.sheet-stack--tap > .card:has(> .sheet__body) { display: flex; flex-direction: column; gap: var(--card-gap); }
|
|
268
|
+
.sheet-stack--tap > .card:has(> .sheet__body).is-open { overflow: clip; }
|
|
269
|
+
.sheet-stack > .card:has(> .sheet__body) > :not(.sheet__body) { flex-shrink: 0; }
|
|
270
|
+
.sheet-stack > .card > .sheet__body { --_cover: 0rem; } /* nothing slid under here: the stack's sheet is always whole */
|
|
271
|
+
|
|
272
|
+
/* ---- 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
|
|
273
|
+
filled (.icon-btn--invert); a count is a small circle over the icon's top right corner ---- */
|
|
274
|
+
.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 */
|
|
275
|
+
.stack-sheet__tab { position: relative; }
|
|
276
|
+
.stack-sheet__tab > .stack-sheet__count { /* outranks .badge (numbers.css loads after this file) */
|
|
277
|
+
position: absolute;
|
|
278
|
+
top: calc(-1 * var(--sp-1));
|
|
279
|
+
right: calc(-1 * var(--sp-1));
|
|
280
|
+
min-width: var(--stack-sheet-count-h, 1.25rem);
|
|
281
|
+
height: var(--stack-sheet-count-h, 1.25rem);
|
|
282
|
+
padding: 0 var(--sp-1);
|
|
283
|
+
font: var(--type-caption);
|
|
284
|
+
line-height: 1;
|
|
285
|
+
pointer-events: none;
|
|
286
|
+
}
|
|
287
|
+
/* 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
|
|
288
|
+
to the opposite colour (.icon-btn--invert). */
|
|
289
|
+
.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 */
|
|
290
|
+
/* 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
|
|
291
|
+
margin around it reaches into the card's padding — and an open head (back · title · three tabs) keeps one line (Lh 2026-09-23) */
|
|
292
|
+
.card__head > .stack-sheet__tabs { margin-right: calc(-1 * var(--sp-2)); }
|
|
293
|
+
|
|
294
|
+
/* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
|
|
295
|
+
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
|
|
296
|
+
(was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
|
|
297
|
+
.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))); }
|
|
298
|
+
.sheet-stack--tap .sheet-stack__back {
|
|
299
|
+
width: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
|
|
300
|
+
height: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
|
|
301
|
+
min-width: 0;
|
|
302
|
+
min-height: 0;
|
|
303
|
+
padding: 0;
|
|
304
|
+
}
|
|
305
|
+
.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) */
|
|
306
|
+
.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); }
|
|
307
|
+
|
|
308
|
+
/* the open sheet's title folds it (SheetStack.tsx): it says so to a pointer */
|
|
309
|
+
.sheet-stack--tap > .card.is-open > .card__head > :is(h1, h2, h3) { cursor: pointer; }
|
|
310
|
+
/* 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),
|
|
311
|
+
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) */
|
|
312
|
+
.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; }
|
|
313
|
+
.sheet-stack--tap > .card.is-open > :not(.card__head) { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
|
package/css/step.css
CHANGED
|
@@ -57,13 +57,16 @@
|
|
|
57
57
|
/* 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):
|
|
58
58
|
the words start a share of the screen down — --screen-lead, 15% of its height (Lh 2026-09-22), so a tall phone gives
|
|
59
59
|
more air and a short one less; a screen with a bar keeps the bar's own room */
|
|
60
|
-
.screen > .step:first-child { --_lead: var(--screen-lead, 15cqh); }
|
|
60
|
+
.screen > .step:first-child, .screen--lead > .step { --_lead: var(--screen-lead, 15cqh); } /* top="lead" says it; a Step first says it too */
|
|
61
61
|
|
|
62
62
|
/* the bar's room — read from actions.css, never recomputed. Margin, not
|
|
63
63
|
padding, so the clip cuts at the box edge and nothing shows behind the
|
|
64
64
|
bar. Any parent that hosts the bar counts — <body>, an app shell, a demo
|
|
65
65
|
frame — the bar is fixed to the viewport wherever it sits. */
|
|
66
66
|
:has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
|
|
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
|
+
is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
|
|
69
|
+
:has(> .action-bar) .step:has(> .pager__at) { margin-bottom: calc(var(--bar-reserve) - var(--sp-6) + var(--kb, 0rem)); }
|
|
67
70
|
/* LAYERS (Lh, 2026-09-20): the base, then the sheet over it, then the top bar. A bar is the BASE's — the
|
|
68
71
|
move of a page of words — so a step with a sheet has NO bar: the sheet finishes on its own .card__foot
|
|
69
72
|
(sheet.css), and a form lives whole in ONE sheet, never item by item on the base. That is how cardds's
|
|
@@ -95,7 +98,16 @@
|
|
|
95
98
|
}
|
|
96
99
|
/* 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
|
|
97
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) */
|
|
98
|
-
.step > .pager__at {
|
|
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)); }
|
|
99
111
|
.step > :is(.base-content, .pages) {
|
|
100
112
|
min-height: 0;
|
|
101
113
|
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 */
|
|
@@ -113,3 +125,33 @@
|
|
|
113
125
|
z-index: 1;
|
|
114
126
|
background-color: var(--surface); /* the words pass UNDER it, never through a transparent picture */
|
|
115
127
|
}
|
|
128
|
+
|
|
129
|
+
/* ============================================================
|
|
130
|
+
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
|
|
131
|
+
column, after the step: the step takes what is left (flex: 1), the bar takes what its rows need — measured by the
|
|
132
|
+
browser, no --bar-reserve arithmetic, so a one-row bar gives its room straight back to the pages. The keyboard lifts
|
|
133
|
+
the column's end: the screen pads its bottom by --kb, the bar rides on it.
|
|
134
|
+
(A bar OUTSIDE a fill screen — an app shell, a scrolling screen — is still fixed to the viewport, actions.css.)
|
|
135
|
+
============================================================ */
|
|
136
|
+
.screen--fill > .action-bar {
|
|
137
|
+
position: relative; inset: auto; bottom: auto;
|
|
138
|
+
flex: none;
|
|
139
|
+
margin-top: auto; /* the column's end, even on a screen with nothing that grows (a centred card is absolute) */
|
|
140
|
+
max-width: none;
|
|
141
|
+
margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge, as the step; the bar's own padding keeps the inset */
|
|
142
|
+
}
|
|
143
|
+
.screen--fill:has(> .action-bar) { padding-bottom: var(--kb, 0rem); }
|
|
144
|
+
.screen--fill:has(> .action-bar) > .step { margin-bottom: 0; }
|
|
145
|
+
/* 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) */
|
|
146
|
+
.screen--fill:has(> .step) > .action-bar { margin-top: calc(-1 * var(--screen-gap)); }
|
|
147
|
+
/* 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
|
+
reserves nothing for it; the breathing under it stays the one it had (--sp-2), not the screen's gap */
|
|
149
|
+
.screen--fill > .appbar {
|
|
150
|
+
position: relative; inset: auto; top: auto;
|
|
151
|
+
flex: none;
|
|
152
|
+
max-width: none;
|
|
153
|
+
margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge; its own padding is the inset */
|
|
154
|
+
margin-bottom: calc(var(--sp-2) - var(--screen-gap));
|
|
155
|
+
}
|
|
156
|
+
.screen.screen--fill:has(> .appbar) { padding-top: 0; --header-gap: 0rem; }
|
|
157
|
+
.screen--fill > .appbar + .sheet-stage { margin-top: 0; }
|
package/dist/actions/Btn.d.ts
CHANGED
|
@@ -307,6 +307,6 @@ export declare function Btn({ primary, quiet, block, xl, href, className, type,
|
|
|
307
307
|
onTransitionStart?: import("react").TransitionEventHandler<HTMLButtonElement> | undefined;
|
|
308
308
|
onTransitionStartCapture?: import("react").TransitionEventHandler<HTMLButtonElement> | undefined;
|
|
309
309
|
href: string | undefined;
|
|
310
|
-
type: "button" | "
|
|
310
|
+
type: "button" | "submit" | "reset" | undefined;
|
|
311
311
|
className: string;
|
|
312
312
|
}, HTMLButtonElement>;
|
|
@@ -306,6 +306,6 @@ export declare function IconBtn({ label, icon, sm, invert, href, className, type
|
|
|
306
306
|
onTransitionStart?: import("react").TransitionEventHandler<HTMLButtonElement> | undefined;
|
|
307
307
|
onTransitionStartCapture?: import("react").TransitionEventHandler<HTMLButtonElement> | undefined;
|
|
308
308
|
href: string | undefined;
|
|
309
|
-
type: "button" | "
|
|
309
|
+
type: "button" | "submit" | "reset" | undefined;
|
|
310
310
|
className: string;
|
|
311
311
|
}, HTMLButtonElement>;
|