@usableapp/cardds 0.7.60 → 0.7.62
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 +11 -0
- package/README.md +3 -1
- package/css/sheet.css +17 -1
- package/css/stack.css +7 -3
- package/css/tokens.css +1 -0
- package/dist/cardds.css +25 -4
- package/dist/sheets/Sheet.d.ts +3 -1
- package/dist/sheets/Sheet.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
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.62 — 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`Sheet` takes `refreshing`** (Lh 2026-10-06) — a boolean, off unless you set it. While `true`, the sheet's grab mark (the line, or the dots with `mark="dots"`) changes colour the way a stack sheet's does, and the sheet is `aria-busy`. Set it when you start reloading what the sheet shows, clear it when the data is in: `<Sheet refreshing={loading} …>`. The single sheet has no pull to refresh of its own and does not fetch — when to reload is yours. Omit it and nothing changes.
|
|
9
|
+
|
|
10
|
+
## 0.7.61 — 2026-10-06
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **A sheet's grab line is 1px** (Lh 2026-10-06) — it was 2px. On a single `Sheet` (the default mark) and on a stack sheet with `mark="line"`. Same length, same place. The thickness is one root token, `--hairline-w` (1px): set it on `:root` in your `theme.css` to tune it (`2px` gives the old line back). Nothing to change in a screen; a test that measured the handle's height reads 1.
|
|
14
|
+
- **A `paper` stack sheet's head band is slimmer** (Lh 2026-10-06) — the white starts 8px (`--sp-2`) under the head, not a whole `--card-gap` (20px): the coloured band of an open sheet is 77.5px tall at the defaults, it was 89.5. `SheetStack` / `Home` sheets with `paper` only; a single paper `Sheet`, a stack sheet without `paper`, and the peeked heads are unchanged. Nothing to change in a screen; a test that measured where the white body starts under the head must expect 8px.
|
|
15
|
+
|
|
5
16
|
## 0.7.60 — 2026-10-06
|
|
6
17
|
|
|
7
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -278,7 +278,9 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
278
278
|
`.sheet__body` with what the member acts on. The handle and the head are
|
|
279
279
|
pinned; the body is the one thing that scrolls, to the card's own edges. The
|
|
280
280
|
handle is a line, or four dots (`mark="dots"`): both sit in the card's top
|
|
281
|
-
padding, the same distance from the edge, so the title stays put. `
|
|
281
|
+
padding, the same distance from the edge, so the title stays put. `refreshing`
|
|
282
|
+
(off unless the app sets it) changes the mark's colour while the app reloads
|
|
283
|
+
what the sheet shows, as a stack sheet's does; the sheet does not fetch. `paper` makes
|
|
282
284
|
the content white while the head keeps the sheet's colour (`tone`, as a card).
|
|
283
285
|
The same prop on a `StackSheet`, or on a `Card` in a stack: the head keeps the
|
|
284
286
|
tone, the body and the foot are white, and the gap under the head stays the tone.
|
package/css/sheet.css
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
91
91
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
92
92
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
93
|
-
:is(.step, .sheet-stage) { --_handle: var(--
|
|
93
|
+
:is(.step, .sheet-stage) { --_handle: var(--hairline-w); --sheet-peek: calc(var(--card-pad-rest) + var(--sheet-head-h) + var(--card-gap-rest) + var(--safe-bottom)); } /* + the gesture bar: the peeked head stands above it. The handle sits IN the card's top padding, so the strip is that padding, the title row and the gap under it */
|
|
94
94
|
/* the head's floor: a tap, or an h2 line with its Thai-mark padding when the type is bigger than that (a large text-size
|
|
95
95
|
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
96
96
|
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
@@ -231,6 +231,22 @@
|
|
|
231
231
|
background-size: var(--_dot) var(--_dot);
|
|
232
232
|
background-position: 0 0, var(--_step) 0, calc(2 * var(--_step)) 0, calc(3 * var(--_step)) 0;
|
|
233
233
|
}
|
|
234
|
+
/* the app reloads what the sheet shows (`refreshing`, off unless set): the mark changes colour exactly as a stack sheet's grip
|
|
235
|
+
does — white → black and back, so a light sheet and a dark one each get a moment it stands out. --_grip and the keyframes are
|
|
236
|
+
the stack's (stack.css): one sign for both. Nothing travels. */
|
|
237
|
+
.sheet.is-loading > .sheet__handle {
|
|
238
|
+
opacity: 1;
|
|
239
|
+
background-color: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
|
|
240
|
+
animation: stack-grip-dissolve 0.4s ease-in-out infinite alternate;
|
|
241
|
+
}
|
|
242
|
+
.sheet.is-loading > .sheet__handle--dots {
|
|
243
|
+
--_ink: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
|
|
244
|
+
background-color: transparent;
|
|
245
|
+
}
|
|
246
|
+
@media (prefers-reduced-motion: reduce) {
|
|
247
|
+
.sheet.is-loading > .sheet__handle { animation: none; background-color: currentColor; }
|
|
248
|
+
.sheet.is-loading > .sheet__handle--dots { --_ink: currentColor; background-color: transparent; }
|
|
249
|
+
}
|
|
234
250
|
.sheet__handle::before {
|
|
235
251
|
content: "";
|
|
236
252
|
position: absolute;
|
package/css/stack.css
CHANGED
|
@@ -211,14 +211,14 @@
|
|
|
211
211
|
}
|
|
212
212
|
@keyframes stack-grip-dissolve { from { --_grip: 0; } to { --_grip: 1; } }
|
|
213
213
|
/* the grip as ONE LINE (`mark="line"`, Lh 2026-10-06): the single Sheet's line, at the same place and the same length. It reads the
|
|
214
|
-
same --_grip-ink, so while its sheet reloads it changes colour exactly as the dots do. As thin as the Sheet's (--
|
|
215
|
-
|
|
214
|
+
same --_grip-ink, so while its sheet reloads it changes colour exactly as the dots do. As thin as the Sheet's (--hairline-w),
|
|
215
|
+
from the dots' top, as the Sheet's line starts there too. A bar and two round ends; the resting
|
|
216
216
|
ink is mixed into the card's own colour (opaque), so the ends do not show darker where they lie on the bar. */
|
|
217
217
|
.stack-card--line {
|
|
218
218
|
--_grip-w: calc(var(--sp-base) * 18);
|
|
219
219
|
--_grip-ink: color-mix(in srgb, currentColor 30%, var(--card-bg));
|
|
220
220
|
--_grip-bar: linear-gradient(var(--_grip-ink), var(--_grip-ink));
|
|
221
|
-
--_grip-h: var(--
|
|
221
|
+
--_grip-h: var(--hairline-w);
|
|
222
222
|
--_grip-end: calc((var(--_grip-w) - var(--_grip-h)) / 2);
|
|
223
223
|
background-image: var(--_grip-img), var(--_grip-bar), var(--_grip-img);
|
|
224
224
|
background-size: var(--_grip-h) var(--_grip-h), calc(var(--_grip-w) - var(--_grip-h)) var(--_grip-h), var(--_grip-h) var(--_grip-h);
|
|
@@ -335,7 +335,11 @@
|
|
|
335
335
|
into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
|
|
336
336
|
sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
|
|
337
337
|
at the moment the sheet stops. Rising, the body is white at once. */
|
|
338
|
+
/* the head's band is as slim as it can be (Lh 2026-10-06: 20 over · 48 head · 20 under read as a heavy bar): the white starts
|
|
339
|
+
--sp-2 under the head, not a whole --card-gap. The body is pulled up by the difference — the card's gap is left alone, the
|
|
340
|
+
stack's step and the body's own rows read it. */
|
|
338
341
|
.sheet-stack > .card.sheet--paper > .sheet__body {
|
|
342
|
+
margin-top: calc(var(--sp-2) - var(--card-gap));
|
|
339
343
|
padding-top: calc(var(--sp-6) + var(--_refresh, 0px));
|
|
340
344
|
transition: padding-top var(--motion-sheet) ease, background-color 0s linear;
|
|
341
345
|
}
|
package/css/tokens.css
CHANGED
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
|
|
87
87
|
/* ---- borders / rules / elevation (px on purpose: crisp at any scale) ---- */
|
|
88
88
|
--border-w: 1.5px;
|
|
89
|
+
--hairline-w: 1px; /* the thinnest mark: a sheet's grab line (Lh 2026-10-06) */
|
|
89
90
|
--rule-w: 2px; /* dotted connectors, picker rules */
|
|
90
91
|
--scroll-w: 1px; /* the scrollbar of a .bleed track: a hairline of ink */
|
|
91
92
|
--focus: 2px solid currentColor; /* :focus-visible ring */
|
package/dist/cardds.css
CHANGED
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
|
|
128
128
|
/* ---- borders / rules / elevation (px on purpose: crisp at any scale) ---- */
|
|
129
129
|
--border-w: 1.5px;
|
|
130
|
+
--hairline-w: 1px; /* the thinnest mark: a sheet's grab line (Lh 2026-10-06) */
|
|
130
131
|
--rule-w: 2px; /* dotted connectors, picker rules */
|
|
131
132
|
--scroll-w: 1px; /* the scrollbar of a .bleed track: a hairline of ink */
|
|
132
133
|
--focus: 2px solid currentColor; /* :focus-visible ring */
|
|
@@ -1212,14 +1213,14 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1212
1213
|
}
|
|
1213
1214
|
@keyframes stack-grip-dissolve { from { --_grip: 0; } to { --_grip: 1; } }
|
|
1214
1215
|
/* the grip as ONE LINE (`mark="line"`, Lh 2026-10-06): the single Sheet's line, at the same place and the same length. It reads the
|
|
1215
|
-
same --_grip-ink, so while its sheet reloads it changes colour exactly as the dots do. As thin as the Sheet's (--
|
|
1216
|
-
|
|
1216
|
+
same --_grip-ink, so while its sheet reloads it changes colour exactly as the dots do. As thin as the Sheet's (--hairline-w),
|
|
1217
|
+
from the dots' top, as the Sheet's line starts there too. A bar and two round ends; the resting
|
|
1217
1218
|
ink is mixed into the card's own colour (opaque), so the ends do not show darker where they lie on the bar. */
|
|
1218
1219
|
.stack-card--line {
|
|
1219
1220
|
--_grip-w: calc(var(--sp-base) * 18);
|
|
1220
1221
|
--_grip-ink: color-mix(in srgb, currentColor 30%, var(--card-bg));
|
|
1221
1222
|
--_grip-bar: linear-gradient(var(--_grip-ink), var(--_grip-ink));
|
|
1222
|
-
--_grip-h: var(--
|
|
1223
|
+
--_grip-h: var(--hairline-w);
|
|
1223
1224
|
--_grip-end: calc((var(--_grip-w) - var(--_grip-h)) / 2);
|
|
1224
1225
|
background-image: var(--_grip-img), var(--_grip-bar), var(--_grip-img);
|
|
1225
1226
|
background-size: var(--_grip-h) var(--_grip-h), calc(var(--_grip-w) - var(--_grip-h)) var(--_grip-h), var(--_grip-h) var(--_grip-h);
|
|
@@ -1336,7 +1337,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1336
1337
|
into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
|
|
1337
1338
|
sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
|
|
1338
1339
|
at the moment the sheet stops. Rising, the body is white at once. */
|
|
1340
|
+
/* the head's band is as slim as it can be (Lh 2026-10-06: 20 over · 48 head · 20 under read as a heavy bar): the white starts
|
|
1341
|
+
--sp-2 under the head, not a whole --card-gap. The body is pulled up by the difference — the card's gap is left alone, the
|
|
1342
|
+
stack's step and the body's own rows read it. */
|
|
1339
1343
|
.sheet-stack > .card.sheet--paper > .sheet__body {
|
|
1344
|
+
margin-top: calc(var(--sp-2) - var(--card-gap));
|
|
1340
1345
|
padding-top: calc(var(--sp-6) + var(--_refresh, 0px));
|
|
1341
1346
|
transition: padding-top var(--motion-sheet) ease, background-color 0s linear;
|
|
1342
1347
|
}
|
|
@@ -1540,7 +1545,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1540
1545
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
1541
1546
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
1542
1547
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
1543
|
-
:is(.step, .sheet-stage) { --_handle: var(--
|
|
1548
|
+
:is(.step, .sheet-stage) { --_handle: var(--hairline-w); --sheet-peek: calc(var(--card-pad-rest) + var(--sheet-head-h) + var(--card-gap-rest) + var(--safe-bottom)); } /* + the gesture bar: the peeked head stands above it. The handle sits IN the card's top padding, so the strip is that padding, the title row and the gap under it */
|
|
1544
1549
|
/* the head's floor: a tap, or an h2 line with its Thai-mark padding when the type is bigger than that (a large text-size
|
|
1545
1550
|
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
1546
1551
|
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
@@ -1681,6 +1686,22 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1681
1686
|
background-size: var(--_dot) var(--_dot);
|
|
1682
1687
|
background-position: 0 0, var(--_step) 0, calc(2 * var(--_step)) 0, calc(3 * var(--_step)) 0;
|
|
1683
1688
|
}
|
|
1689
|
+
/* the app reloads what the sheet shows (`refreshing`, off unless set): the mark changes colour exactly as a stack sheet's grip
|
|
1690
|
+
does — white → black and back, so a light sheet and a dark one each get a moment it stands out. --_grip and the keyframes are
|
|
1691
|
+
the stack's (stack.css): one sign for both. Nothing travels. */
|
|
1692
|
+
.sheet.is-loading > .sheet__handle {
|
|
1693
|
+
opacity: 1;
|
|
1694
|
+
background-color: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
|
|
1695
|
+
animation: stack-grip-dissolve 0.4s ease-in-out infinite alternate;
|
|
1696
|
+
}
|
|
1697
|
+
.sheet.is-loading > .sheet__handle--dots {
|
|
1698
|
+
--_ink: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
|
|
1699
|
+
background-color: transparent;
|
|
1700
|
+
}
|
|
1701
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1702
|
+
.sheet.is-loading > .sheet__handle { animation: none; background-color: currentColor; }
|
|
1703
|
+
.sheet.is-loading > .sheet__handle--dots { --_ink: currentColor; background-color: transparent; }
|
|
1704
|
+
}
|
|
1684
1705
|
.sheet__handle::before {
|
|
1685
1706
|
content: "";
|
|
1686
1707
|
position: absolute;
|
package/dist/sheets/Sheet.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface SheetProps extends Omit<ComponentProps<'article'>, 'onChange'>
|
|
|
23
23
|
handle?: boolean | string;
|
|
24
24
|
/** the grab mark. `line` (default) or `dots`: both sit in the card's top padding, the same distance from the edge, so the title does not move */
|
|
25
25
|
mark?: 'line' | 'dots';
|
|
26
|
+
/** the app is reloading what this sheet shows: while `true` the grab mark (line or dots) changes colour, as a stack sheet's does, and the sheet is `aria-busy`. Off unless the app sets it — the sheet does not fetch and has no pull to refresh of its own */
|
|
27
|
+
refreshing?: boolean;
|
|
26
28
|
/** the card tone slot, as `Card tone` — 2 and up add `card--N`. The head shows it; with `paper` the content does not */
|
|
27
29
|
tone?: 1 | 2 | 3 | 4 | 5;
|
|
28
30
|
/** the content is white paper and the head keeps the sheet's colour. The same prop on a `StackSheet` or a `Card` in a stack. Omit it and the content is the same colour as the head */
|
|
@@ -49,4 +51,4 @@ export interface SheetProps extends Omit<ComponentProps<'article'>, 'onChange'>
|
|
|
49
51
|
* crossing and never one continuous move, whatever the app's wiring (both states set in one render, or the next raised from the
|
|
50
52
|
* first's `onSettled`) — the app adds no timer.
|
|
51
53
|
*/
|
|
52
|
-
export declare function Sheet({ state, defaultState, onStateChange, onSettled, states, handle, mark, tone, paper, className, children, onFocus, ...rest }: SheetProps): import("react").JSX.Element;
|
|
54
|
+
export declare function Sheet({ state, defaultState, onStateChange, onSettled, states, handle, mark, refreshing, tone, paper, className, children, onFocus, ...rest }: SheetProps): import("react").JSX.Element;
|
package/dist/sheets/Sheet.js
CHANGED
|
@@ -38,7 +38,7 @@ function markLong(node, long) {
|
|
|
38
38
|
* crossing and never one continuous move, whatever the app's wiring (both states set in one render, or the next raised from the
|
|
39
39
|
* first's `onSettled`) — the app adds no timer.
|
|
40
40
|
*/
|
|
41
|
-
export function Sheet({ state, defaultState = 'half', onStateChange, onSettled, states, handle = true, mark = 'line', tone, paper, className, children, onFocus, ...rest }) {
|
|
41
|
+
export function Sheet({ state, defaultState = 'half', onStateChange, onSettled, states, handle = true, mark = 'line', refreshing, tone, paper, className, children, onFocus, ...rest }) {
|
|
42
42
|
const [current, request] = useControlled(state, defaultState, onStateChange);
|
|
43
43
|
const ref = useRef(null);
|
|
44
44
|
const handleRef = useRef(null);
|
|
@@ -131,5 +131,5 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
131
131
|
return; // the click that follows a drag is not a tap
|
|
132
132
|
request(current === 'peek' ? (allowed().includes(home.current) ? home.current : allowed().find((s) => s !== 'peek') ?? 'half') : 'peek', 'tap');
|
|
133
133
|
};
|
|
134
|
-
return (_jsxs("article", { ref: ref, className: cx('card sheet', tone && tone !== 1 && `card--${tone}`, paper && 'sheet--paper', `sheet--${shown}`, className), inert: current === 'away' || held || undefined, onFocus: focus, ...rest, children: [handle !== false && (_jsx("button", { ref: handleRef, className: cx('sheet__handle', mark === 'dots' && 'sheet__handle--dots'), type: "button", "aria-label": typeof handle === 'string' ? handle : word('resize'), onClick: tap, onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up })), Children.map(children, (node) => markLong(node, titleLong))] }));
|
|
134
|
+
return (_jsxs("article", { ref: ref, className: cx('card sheet', tone && tone !== 1 && `card--${tone}`, paper && 'sheet--paper', `sheet--${shown}`, refreshing && 'is-loading', className), "aria-busy": refreshing || undefined, inert: current === 'away' || held || undefined, onFocus: focus, ...rest, children: [handle !== false && (_jsx("button", { ref: handleRef, className: cx('sheet__handle', mark === 'dots' && 'sheet__handle--dots'), type: "button", "aria-label": typeof handle === 'string' ? handle : word('resize'), onClick: tap, onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up })), Children.map(children, (node) => markLong(node, titleLong))] }));
|
|
135
135
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.62",
|
|
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.",
|