@usableapp/cardds 0.7.0 → 0.7.1

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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  > For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
4
4
 
5
+ ## 0.7.1 — 2026-09-24
6
+
7
+ ### The bar fixed from outside is gone
8
+ - Before: an `ActionBar` written after `</Screen>` (in `<body>` or an app shell) was fixed to the viewport, and the screen
9
+ / step kept `--bar-reserve` of room for it. After: `.action-bar` is in flow; a bar is always the Screen's `bottom` (its
10
+ last child) — a layer over the content's foot in a fill screen, a sticky last row on a scrolling one (0.7.0). A bar left
11
+ outside a Screen now sits wherever it was written. `--bar-reserve` is still published (the centred card's cap reads it);
12
+ nothing reserves room with it any more. `ActionBar inline` stays, for a bare bar in a preview.
13
+
5
14
  ## 0.7.0 — 2026-09-24
6
15
 
7
16
  Breaking — the clean-up promised since 0.6.5. Four renames/removals, one new way.
package/README.md CHANGED
@@ -410,17 +410,9 @@ Demo: the *skeleton* block at the top of `elements.html`.
410
410
  5. **`.screen` carries the horizontal inset and nothing vertical.** The inset
411
411
  is `--screen-pad`; cards and on-surface UI sit flush inside it, with no
412
412
  per-component side padding. Top and bottom are zero: vertical room only
413
- exists as a reserve for something fixed parked over the screen, so each bar
414
- adds its own — `.appbar` the top (`.screen:has(> .appbar)`), `.action-bar`
415
- and `.float-bar` the bottom, each from its own height token. Whatever
416
- parent hosts the bar counts (`:has(> .action-bar) .screen`): `<body>` or an
417
- app shell alike. A page with no bars needs no override, and `.screen--fill`
418
- is excluded from the bottom reserves — there a `.step` carries the bar's
419
- room itself, as a bottom margin, reading the same `--bar-reserve`. The
420
- reserves use the nominal control height, so a bar whose button wraps to two
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.
413
+ is taken by what is in the column. The bottom bar is always the screen's last child (0.7.1 — the bar fixed to
414
+ the viewport from outside and its reserves are gone): in a `.screen--fill` a layer over the content's foot, on a
415
+ scrolling screen a `sticky` last row. `--bar-reserve` stays published only for a `.card--centre`'s cap.
424
416
  **Padding is published, not copied.** `.screen` sets `--screen-pad` and
425
417
  `--screen-gap`, its appbar reserve sets `--header-gap`, and `.card` sets
426
418
  `--card-pad` and `--card-gap`; anything that has to cancel a container's
package/css/actions.css CHANGED
@@ -216,21 +216,15 @@
216
216
  opacity: 1;
217
217
  }
218
218
 
219
- /* ---- bottom CTA pack: fixed action zone under the content ----
220
- --bar-reserve is the room the bar needs — its own height plus a breathing
221
- gap above it — published here so anything else that must stop clear of it
222
- reads this instead of recomputing it. .screen's own reserve below reads
223
- it; so does .step (step.css), the box a sheet slides in — one number, so
224
- the two can't drift apart.
225
- --kb is the on-screen keyboard's height, published on :root by cardds.js
226
- from the visual viewport (0 without one, and on a platform that shrinks
227
- the layout viewport itself): the bar rides above the keyboard on iOS the
228
- way it already does on Android, and .step grows its room by the same. */
219
+ /* ---- bottom CTA pack: the screen's bottom (Screen `bottom`) ----
220
+ A bar is always INSIDE its .screen, its last child (0.7.1: the bar fixed to the viewport from outside, and the
221
+ reserves the screen and the step kept for it, are gone). In a .screen--fill it is a layer over the content's foot
222
+ (step.css); on a scrolling screen a sticky last row (below). --bar-reserve stays published — the bar's nominal
223
+ height — for the one rule that still needs it without measuring: a .card--centre's cap (card.css).
224
+ --kb is the on-screen keyboard's height (cardds.js): the bar rides above it. */
229
225
  :root { --bar-reserve: calc(var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
230
226
  .action-bar {
231
- position: fixed;
232
- inset-inline: 0;
233
- bottom: var(--kb, 0);
227
+ position: relative;
234
228
  z-index: 30; /* above .sheet-stack sheets, which paint at --level 1-5 */
235
229
  max-width: var(--screen-w);
236
230
  margin-inline: auto;
@@ -241,9 +235,9 @@
241
235
  /* a bar INSIDE a scrolling (not fill) screen (Lh 2026-09-24, 0.7.0): the screen is a column at least one phone tall, the bar its
242
236
  last row — at the foot when the words are short, and sticky to the viewport's foot (above the keyboard) while they scroll;
243
237
  it takes the room it needs in the column, so nothing is added to the content for it */
244
- .screen:not(.screen--fill):has(> .action-bar) { display: flex; flex-direction: column; min-height: 100dvh; }
245
- .screen:not(.screen--fill) > .action-bar {
246
- position: sticky; inset-inline: auto; bottom: var(--kb, 0rem);
238
+ .screen:not(.screen--fill):has(> .action-bar:last-child) { display: flex; flex-direction: column; min-height: 100dvh; }
239
+ .screen:not(.screen--fill) > .action-bar:last-child {
240
+ position: sticky; bottom: var(--kb, 0rem);
247
241
  max-width: none;
248
242
  margin: auto calc(-1 * var(--screen-pad)) 0;
249
243
  }
@@ -313,13 +307,6 @@
313
307
  :is(.action-bar--pager, .action-bar__tier--pager) > :not(.icon-btn):not(:first-child):not(:last-child) { margin-inline: auto; }
314
308
  :is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > .icon-btn:first-child { margin-right: 0; }
315
309
 
316
- /* .screen's default bottom padding is a constant; derive the reserve from the
317
- bar instead, so a taller button (a longer label, a script with more leading)
318
- can never end up underneath it. Any parent that hosts the bar counts —
319
- <body>, or an app shell (#app > .screen + .action-bar) — since the bar is
320
- fixed to the viewport wherever it sits. (.screen--fill reserves nothing:
321
- its .step carries the bar's room itself, step.css.) */
322
- :has(> .action-bar) .screen:not(.screen--fill) { padding-bottom: var(--bar-reserve); }
323
310
 
324
311
  /* ---- action note (Lh 2026-09-24): the main button's place, the main button's size, but ON the ground — no fill, no border, no
325
312
  rounding — one line of muted words, an inline icon allowed; nothing to press. In a CardFoot or an ActionBar row it takes the
package/css/step.css CHANGED
@@ -10,7 +10,7 @@
10
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
- <div class="action-bar">…</div> ← only when there is no sheet
13
+ <div class="action-bar">…</div> ← the screen's last child, only when there is no sheet
14
14
 
15
15
  The step is the box the sheet slides in. It fills the screen
16
16
  column between the top bar's reserve (.screen already pads for
@@ -59,11 +59,6 @@
59
59
  more air and a short one less; a screen with a bar keeps the bar's own room */
60
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
- /* the bar's room — read from actions.css, never recomputed. Margin, not
63
- padding, so the clip cuts at the box edge and nothing shows behind the
64
- bar. Any parent that hosts the bar counts — <body>, an app shell, a demo
65
- frame — the bar is fixed to the viewport wherever it sits. */
66
- :has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
67
62
  /* 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
63
  is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
69
64
 
@@ -119,7 +114,7 @@
119
114
  TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
120
115
  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
121
116
  under it, on purpose; nothing is added to the content to make room. The keyboard lifts the layer (--kb).
122
- (A bar OUTSIDE a fill screen — an app shell, a scrolling screen — is still fixed to the viewport, actions.css.)
117
+ (On a scrolling screen the bar is a sticky last row instead, actions.css.)
123
118
  ============================================================ */
124
119
  .screen--fill > .action-bar {
125
120
  position: absolute; inset-inline: 0; top: auto; bottom: var(--kb, 0rem);
@@ -127,7 +122,6 @@
127
122
  margin: 0;
128
123
  padding-top: 0; /* no ground of its own, never a solid strip */
129
124
  }
130
- .screen--fill:has(> .action-bar) > .step { margin-bottom: 0; }
131
125
  /* the walk's dots are the BOTTOM layer's too (Lh 2026-09-23): a row of the bar, over its buttons, centred */
132
126
  .action-bar > .pager__at, .action-bar__tier > .pager__at { align-self: center; }
133
127
  /* 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
package/dist/cardds.css CHANGED
@@ -1543,7 +1543,7 @@ button { font: inherit; cursor: pointer; }
1543
1543
  <section class="page">…the words…</section> ← first
1544
1544
  <article class="card sheet sheet--half">…the controls… <div class="card__foot">…</div></article> ← last, optional
1545
1545
  </section>
1546
- <div class="action-bar">…</div> ← only when there is no sheet
1546
+ <div class="action-bar">…</div> ← the screen's last child, only when there is no sheet
1547
1547
 
1548
1548
  The step is the box the sheet slides in. It fills the screen
1549
1549
  column between the top bar's reserve (.screen already pads for
@@ -1592,11 +1592,6 @@ button { font: inherit; cursor: pointer; }
1592
1592
  more air and a short one less; a screen with a bar keeps the bar's own room */
1593
1593
  .screen > .step:first-child, .screen--lead > .step { --_lead: var(--screen-lead, 15cqh); } /* top="lead" says it; a Step first says it too */
1594
1594
 
1595
- /* the bar's room — read from actions.css, never recomputed. Margin, not
1596
- padding, so the clip cuts at the box edge and nothing shows behind the
1597
- bar. Any parent that hosts the bar counts — <body>, an app shell, a demo
1598
- frame — the bar is fixed to the viewport wherever it sits. */
1599
- :has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
1600
1595
  /* 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)
1601
1596
  is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
1602
1597
 
@@ -1652,7 +1647,7 @@ button { font: inherit; cursor: pointer; }
1652
1647
  TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
1653
1648
  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
1654
1649
  under it, on purpose; nothing is added to the content to make room. The keyboard lifts the layer (--kb).
1655
- (A bar OUTSIDE a fill screen — an app shell, a scrolling screen — is still fixed to the viewport, actions.css.)
1650
+ (On a scrolling screen the bar is a sticky last row instead, actions.css.)
1656
1651
  ============================================================ */
1657
1652
  .screen--fill > .action-bar {
1658
1653
  position: absolute; inset-inline: 0; top: auto; bottom: var(--kb, 0rem);
@@ -1660,7 +1655,6 @@ button { font: inherit; cursor: pointer; }
1660
1655
  margin: 0;
1661
1656
  padding-top: 0; /* no ground of its own, never a solid strip */
1662
1657
  }
1663
- .screen--fill:has(> .action-bar) > .step { margin-bottom: 0; }
1664
1658
  /* the walk's dots are the BOTTOM layer's too (Lh 2026-09-23): a row of the bar, over its buttons, centred */
1665
1659
  .action-bar > .pager__at, .action-bar__tier > .pager__at { align-self: center; }
1666
1660
  /* 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
@@ -2048,21 +2042,15 @@ button { font: inherit; cursor: pointer; }
2048
2042
  opacity: 1;
2049
2043
  }
2050
2044
 
2051
- /* ---- bottom CTA pack: fixed action zone under the content ----
2052
- --bar-reserve is the room the bar needs — its own height plus a breathing
2053
- gap above it — published here so anything else that must stop clear of it
2054
- reads this instead of recomputing it. .screen's own reserve below reads
2055
- it; so does .step (step.css), the box a sheet slides in — one number, so
2056
- the two can't drift apart.
2057
- --kb is the on-screen keyboard's height, published on :root by cardds.js
2058
- from the visual viewport (0 without one, and on a platform that shrinks
2059
- the layout viewport itself): the bar rides above the keyboard on iOS the
2060
- way it already does on Android, and .step grows its room by the same. */
2045
+ /* ---- bottom CTA pack: the screen's bottom (Screen `bottom`) ----
2046
+ A bar is always INSIDE its .screen, its last child (0.7.1: the bar fixed to the viewport from outside, and the
2047
+ reserves the screen and the step kept for it, are gone). In a .screen--fill it is a layer over the content's foot
2048
+ (step.css); on a scrolling screen a sticky last row (below). --bar-reserve stays published — the bar's nominal
2049
+ height — for the one rule that still needs it without measuring: a .card--centre's cap (card.css).
2050
+ --kb is the on-screen keyboard's height (cardds.js): the bar rides above it. */
2061
2051
  :root { --bar-reserve: calc(var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2062
2052
  .action-bar {
2063
- position: fixed;
2064
- inset-inline: 0;
2065
- bottom: var(--kb, 0);
2053
+ position: relative;
2066
2054
  z-index: 30; /* above .sheet-stack sheets, which paint at --level 1-5 */
2067
2055
  max-width: var(--screen-w);
2068
2056
  margin-inline: auto;
@@ -2073,9 +2061,9 @@ button { font: inherit; cursor: pointer; }
2073
2061
  /* a bar INSIDE a scrolling (not fill) screen (Lh 2026-09-24, 0.7.0): the screen is a column at least one phone tall, the bar its
2074
2062
  last row — at the foot when the words are short, and sticky to the viewport's foot (above the keyboard) while they scroll;
2075
2063
  it takes the room it needs in the column, so nothing is added to the content for it */
2076
- .screen:not(.screen--fill):has(> .action-bar) { display: flex; flex-direction: column; min-height: 100dvh; }
2077
- .screen:not(.screen--fill) > .action-bar {
2078
- position: sticky; inset-inline: auto; bottom: var(--kb, 0rem);
2064
+ .screen:not(.screen--fill):has(> .action-bar:last-child) { display: flex; flex-direction: column; min-height: 100dvh; }
2065
+ .screen:not(.screen--fill) > .action-bar:last-child {
2066
+ position: sticky; bottom: var(--kb, 0rem);
2079
2067
  max-width: none;
2080
2068
  margin: auto calc(-1 * var(--screen-pad)) 0;
2081
2069
  }
@@ -2145,13 +2133,6 @@ button { font: inherit; cursor: pointer; }
2145
2133
  :is(.action-bar--pager, .action-bar__tier--pager) > :not(.icon-btn):not(:first-child):not(:last-child) { margin-inline: auto; }
2146
2134
  :is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > .icon-btn:first-child { margin-right: 0; }
2147
2135
 
2148
- /* .screen's default bottom padding is a constant; derive the reserve from the
2149
- bar instead, so a taller button (a longer label, a script with more leading)
2150
- can never end up underneath it. Any parent that hosts the bar counts —
2151
- <body>, or an app shell (#app > .screen + .action-bar) — since the bar is
2152
- fixed to the viewport wherever it sits. (.screen--fill reserves nothing:
2153
- its .step carries the bar's room itself, step.css.) */
2154
- :has(> .action-bar) .screen:not(.screen--fill) { padding-bottom: var(--bar-reserve); }
2155
2136
 
2156
2137
  /* ---- action note (Lh 2026-09-24): the main button's place, the main button's size, but ON the ground — no fill, no border, no
2157
2138
  rounding — one line of muted words, an inline icon allowed; nothing to press. In a CardFoot or an ActionBar row it takes the
@@ -2,7 +2,7 @@ import type { ComponentProps } from 'react';
2
2
  export interface ActionBarProps extends ComponentProps<'div'> {
3
3
  /** `pager`: previous / next as two equal buttons at the edges (a Link may sit between them; the PagerAt dots never do — they go under the card) · `tiers`: rows stacked — a control tier above the buttons */
4
4
  variant?: 'pager' | 'tiers';
5
- /** render in flow instead of fixed to the viewport bottom (for a preview or a mock frame); the real bar is fixed */
5
+ /** a bare bar for a preview: no position, full width, no side padding (a real bar is a Screen's `bottom`) */
6
6
  inline?: boolean;
7
7
  }
8
8
  /**
@@ -11,7 +11,7 @@ export interface ActionBarProps extends ComponentProps<'div'> {
11
11
  * Plain: one wide primary Btn (the step that commits). `pager`: Back + Next as peers — buttons only; where you are is the
12
12
  * PagerAt under the card in a Centre group, never in the bar (Lh, 2026-09-12).
13
13
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
14
- * In a `Screen fill` the content takes the rest; outside one (an app shell) the bar is still fixed and `--bar-reserve` keeps its room.
14
+ * In a `Screen fill` it is a layer over the content's foot; on a scrolling Screen a sticky last row.
15
15
  */
16
16
  export declare function ActionBar({ variant, inline, className, style, ...rest }: ActionBarProps): import("react").JSX.Element;
17
17
  export interface ActionBarTierProps extends ComponentProps<'div'> {
@@ -6,7 +6,7 @@ import { cx } from '../cx.js';
6
6
  * Plain: one wide primary Btn (the step that commits). `pager`: Back + Next as peers — buttons only; where you are is the
7
7
  * PagerAt under the card in a Centre group, never in the bar (Lh, 2026-09-12).
8
8
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
9
- * In a `Screen fill` the content takes the rest; outside one (an app shell) the bar is still fixed and `--bar-reserve` keeps its room.
9
+ * In a `Screen fill` it is a layer over the content's foot; on a scrolling Screen a sticky last row.
10
10
  */
11
11
  export function ActionBar({ variant, inline, className, style, ...rest }) {
12
12
  return (_jsx("div", { className: cx('action-bar', variant && `action-bar--${variant}`, className), style: inline ? { position: 'static', width: '100%', maxWidth: 'none', paddingInline: 0, ...style } : style, ...rest }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usableapp/cardds",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
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.",