@usableapp/cardds 0.7.52 → 0.7.53
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 +4 -2
- package/css/stack.css +23 -0
- package/dist/cardds.css +23 -0
- package/dist/sheets/SheetStack.d.ts +4 -2
- package/dist/sheets/SheetStack.js +35 -3
- package/dist/templates/Home.d.ts +3 -1
- package/dist/templates/Home.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.53 — 2026-10-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **`SheetStack` / `Home`: the pull down that asks `onRefresh` works with a finger** (Lh 2026-10-05). In 0.7.51 it only worked with a mouse: on a phone the browser took the touch for its own scroll and the content only jerked. A pull down from the top of the open content now follows the finger and asks; any other move still scrolls. Nothing to change in a screen.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`SheetStack` and `Home` take `refreshing`** — the index of the sheet you are reloading, or `null`. Set it in `onRefresh`, clear it when your data is in: `onRefresh={(i) => { setRefreshing(i); load(i).finally(() => setRefreshing(null)); }}` with `refreshing={refreshing}`. While it is the open sheet, its grip dots (below) fade up and down, the card is `aria-busy`, and a pull there does not ask again. Without `refreshing` nothing shows that a reload is running. Reload the pulled sheet alone or everything — that is yours; the stack does not fetch.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- **Every sheet of a tap stack wears a grip: four small grey dots at the middle of its top edge** (Lh 2026-10-05) — a sign that the card slides up and down. A mark, not a control; nothing is added to the markup. Every `SheetStack` (tap mode) and `Home` shows it after the update — nothing to change in a screen, and there is no knob for it yet: if your screen needs it tuned or off, report it.
|
|
15
|
+
|
|
5
16
|
## 0.7.52 — 2026-10-05
|
|
6
17
|
|
|
7
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -181,12 +181,14 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
181
181
|
A tap, or a pull up, opens any sheet over it. The open title or its back
|
|
182
182
|
button, tapped or pulled down, drops it home. A pull down in the open
|
|
183
183
|
content, already at the top, asks `onRefresh` — omit it and that pull only
|
|
184
|
-
scrolls. A tab in the head stays a click.
|
|
184
|
+
scrolls. A tab in the head stays a click. Every sheet wears a grip — four
|
|
185
|
+
small dots at the middle of its top edge, a mark that it slides, not a
|
|
186
|
+
control.
|
|
185
187
|
- **Five sheets max.** Slots are declared per `:nth-child` in `stack.css`; a
|
|
186
188
|
sixth would render at slot 0, on top of the first.
|
|
187
189
|
- **Every head needs its own `.sheet-stack__back`** if the stack is `--tap`. It stays
|
|
188
190
|
hidden until that sheet is open, then slides into a reserved header slot.
|
|
189
|
-
- **No ids, no wiring.** `SheetStack` holds which sheet is open (`open` + `onOpenChange`, or `defaultOpen`). A pull down in the open content asks `onRefresh(index)`. The stack does not fetch.
|
|
191
|
+
- **No ids, no wiring.** `SheetStack` holds which sheet is open (`open` + `onOpenChange`, or `defaultOpen`). A pull down in the open content asks `onRefresh(index)`. The stack does not fetch: the app reloads (that sheet alone, or more — its call) and says so with `refreshing` (that index, then `null` when the data is in) — the sheet's grip dots fade up and down meanwhile, and a pull there does not ask again.
|
|
190
192
|
- **`.card-stack` is gone.** That was this component's first name and the wrong
|
|
191
193
|
one — the members are sheets and the stack is the menu, not a list of cards.
|
|
192
194
|
Never bring it back, not even as an alias.
|
package/css/stack.css
CHANGED
|
@@ -183,6 +183,29 @@
|
|
|
183
183
|
}
|
|
184
184
|
.sheet-stack > .card > .sheet__body.is-refreshing,
|
|
185
185
|
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
186
|
+
/* the GRIP (Lh 2026-10-05): four small grey dots at the middle of every sheet's top edge — just enough of a sign that this card
|
|
187
|
+
slides up and down. A mark, not a control: nothing to press, nothing added to the sheet (the card's own background, in the
|
|
188
|
+
room above its head). While the app reloads the open sheet (.is-loading, from `refreshing`) the same four dots fade up and
|
|
189
|
+
down until it clears — nothing travels, nobody knows how long a reload takes. --_grip is their strength, private. */
|
|
190
|
+
@property --_grip { syntax: "<number>"; inherits: false; initial-value: 0.35; }
|
|
191
|
+
.sheet-stack--tap > .card {
|
|
192
|
+
--_grip-dot: var(--sp-1);
|
|
193
|
+
--_grip-step: calc(var(--sp-1) * 2.5);
|
|
194
|
+
--_grip-y: calc((var(--card-pad) - var(--_grip-dot)) / 2);
|
|
195
|
+
--_grip-ink: color-mix(in srgb, currentColor calc(var(--_grip) * 100%), transparent);
|
|
196
|
+
--_grip-img: radial-gradient(circle closest-side, var(--_grip-ink) 85%, transparent);
|
|
197
|
+
background-image: var(--_grip-img), var(--_grip-img), var(--_grip-img), var(--_grip-img);
|
|
198
|
+
background-repeat: no-repeat;
|
|
199
|
+
background-size: var(--_grip-dot) var(--_grip-dot);
|
|
200
|
+
background-position:
|
|
201
|
+
calc(50% - 1.5 * var(--_grip-step)) var(--_grip-y), calc(50% - 0.5 * var(--_grip-step)) var(--_grip-y),
|
|
202
|
+
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
203
|
+
}
|
|
204
|
+
.sheet-stack--tap > .card.is-open.is-loading { animation: stack-grip-breath 0.8s ease-in-out infinite alternate; }
|
|
205
|
+
@keyframes stack-grip-breath { from { --_grip: 0.2; } to { --_grip: 1; } }
|
|
206
|
+
@media (prefers-reduced-motion: reduce) {
|
|
207
|
+
.sheet-stack--tap > .card.is-open.is-loading { animation: none; --_grip: 1; } /* no fade: the dots are simply full while it reloads */
|
|
208
|
+
}
|
|
186
209
|
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
187
210
|
tappable), so its resting offset would leave its last n steps below the
|
|
188
211
|
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
package/dist/cardds.css
CHANGED
|
@@ -1184,6 +1184,29 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1184
1184
|
}
|
|
1185
1185
|
.sheet-stack > .card > .sheet__body.is-refreshing,
|
|
1186
1186
|
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
1187
|
+
/* the GRIP (Lh 2026-10-05): four small grey dots at the middle of every sheet's top edge — just enough of a sign that this card
|
|
1188
|
+
slides up and down. A mark, not a control: nothing to press, nothing added to the sheet (the card's own background, in the
|
|
1189
|
+
room above its head). While the app reloads the open sheet (.is-loading, from `refreshing`) the same four dots fade up and
|
|
1190
|
+
down until it clears — nothing travels, nobody knows how long a reload takes. --_grip is their strength, private. */
|
|
1191
|
+
@property --_grip { syntax: "<number>"; inherits: false; initial-value: 0.35; }
|
|
1192
|
+
.sheet-stack--tap > .card {
|
|
1193
|
+
--_grip-dot: var(--sp-1);
|
|
1194
|
+
--_grip-step: calc(var(--sp-1) * 2.5);
|
|
1195
|
+
--_grip-y: calc((var(--card-pad) - var(--_grip-dot)) / 2);
|
|
1196
|
+
--_grip-ink: color-mix(in srgb, currentColor calc(var(--_grip) * 100%), transparent);
|
|
1197
|
+
--_grip-img: radial-gradient(circle closest-side, var(--_grip-ink) 85%, transparent);
|
|
1198
|
+
background-image: var(--_grip-img), var(--_grip-img), var(--_grip-img), var(--_grip-img);
|
|
1199
|
+
background-repeat: no-repeat;
|
|
1200
|
+
background-size: var(--_grip-dot) var(--_grip-dot);
|
|
1201
|
+
background-position:
|
|
1202
|
+
calc(50% - 1.5 * var(--_grip-step)) var(--_grip-y), calc(50% - 0.5 * var(--_grip-step)) var(--_grip-y),
|
|
1203
|
+
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
1204
|
+
}
|
|
1205
|
+
.sheet-stack--tap > .card.is-open.is-loading { animation: stack-grip-breath 0.8s ease-in-out infinite alternate; }
|
|
1206
|
+
@keyframes stack-grip-breath { from { --_grip: 0.2; } to { --_grip: 1; } }
|
|
1207
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1208
|
+
.sheet-stack--tap > .card.is-open.is-loading { animation: none; --_grip: 1; } /* no fade: the dots are simply full while it reloads */
|
|
1209
|
+
}
|
|
1187
1210
|
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
1188
1211
|
tappable), so its resting offset would leave its last n steps below the
|
|
1189
1212
|
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
|
@@ -11,6 +11,8 @@ export interface SheetStackProps extends ComponentProps<'div'> {
|
|
|
11
11
|
onOpenChange?: (open: number | null) => void;
|
|
12
12
|
/** the open sheet's scroller was already at the top and the finger pulled down past the commit. The stack asks with that index. It does not fetch. Omit it and a pull in the body only scrolls */
|
|
13
13
|
onRefresh?: (index: number) => void;
|
|
14
|
+
/** the sheet the app is reloading (its index), or `null`: while it is the open one its four grip dots fade up and down, at rest again when the app clears it, and a pull there does not ask again. The app sets it in `onRefresh` and clears it when its data is in */
|
|
15
|
+
refreshing?: number | null;
|
|
14
16
|
/** wider peeks so more of each title shows (display stacks) */
|
|
15
17
|
fanned?: boolean;
|
|
16
18
|
/** nothing open on arrival (tap mode): every sheet, the front one too, peeks at the stack's bottom; the room above is the base's (`base`). A tap opens any sheet over it */
|
|
@@ -30,7 +32,7 @@ export interface SheetStackProps extends ComponentProps<'div'> {
|
|
|
30
32
|
* A COMPONENT, not a wiring: WHICH sheet is open is the app's (`open` + `onOpenChange`, or `defaultOpen`) — so a button anywhere, a route or
|
|
31
33
|
* the phone's back button can open and close a section. Its own behaviour stays inside and only asks: a tap on a peeked head, or a pull up on
|
|
32
34
|
* that head (the title, the back button, or the head itself — not a tab), opens it. The open sheet's StackBack or its title, tapped or pulled
|
|
33
|
-
* down, folds it. A pull down in the content, already at the top, asks `onRefresh` and does not fold. How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
|
|
35
|
+
* down, folds it. A pull down in the content, already at the top, asks `onRefresh` and does not fold; `refreshing` fades that sheet's four grip dots up and down while the app reloads. How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
|
|
34
36
|
* before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); it sets classes, never a size.
|
|
35
37
|
*/
|
|
36
|
-
export declare function SheetStack({ mode, open, defaultOpen, onOpenChange, onRefresh, fanned, closed, base, className, children, onClick, onKeyDown, ...rest }: SheetStackProps): import("react").JSX.Element;
|
|
38
|
+
export declare function SheetStack({ mode, open, defaultOpen, onOpenChange, onRefresh, refreshing, fanned, closed, base, className, children, onClick, onKeyDown, ...rest }: SheetStackProps): import("react").JSX.Element;
|
|
@@ -44,10 +44,10 @@ const pullsHead = (target, head) => {
|
|
|
44
44
|
* A COMPONENT, not a wiring: WHICH sheet is open is the app's (`open` + `onOpenChange`, or `defaultOpen`) — so a button anywhere, a route or
|
|
45
45
|
* the phone's back button can open and close a section. Its own behaviour stays inside and only asks: a tap on a peeked head, or a pull up on
|
|
46
46
|
* that head (the title, the back button, or the head itself — not a tab), opens it. The open sheet's StackBack or its title, tapped or pulled
|
|
47
|
-
* down, folds it. A pull down in the content, already at the top, asks `onRefresh` and does not fold. How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
|
|
47
|
+
* down, folds it. A pull down in the content, already at the top, asks `onRefresh` and does not fold; `refreshing` fades that sheet's four grip dots up and down while the app reloads. How the sheets MOVE is its own too: sheets are rigid — the tapped one slides up, the ones after it leave below, the ones
|
|
48
48
|
* before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); it sets classes, never a size.
|
|
49
49
|
*/
|
|
50
|
-
export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChange, onRefresh, fanned, closed, base, className, children, onClick, onKeyDown, ...rest }) {
|
|
50
|
+
export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChange, onRefresh, refreshing = null, fanned, closed, base, className, children, onClick, onKeyDown, ...rest }) {
|
|
51
51
|
const [current, request] = useControlled(open, defaultOpen, onOpenChange);
|
|
52
52
|
const ref = useRef(null);
|
|
53
53
|
const was = useRef(null);
|
|
@@ -56,6 +56,7 @@ export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChang
|
|
|
56
56
|
const bodyPull = useRef(null);
|
|
57
57
|
const swallow = useRef(false);
|
|
58
58
|
const refreshRef = useLatest(onRefresh);
|
|
59
|
+
const loadingRef = useLatest(refreshing);
|
|
59
60
|
/* paint the state onto the sheets — after every render, so a re-rendered Card never loses its place */
|
|
60
61
|
useLayoutEffect(() => {
|
|
61
62
|
const stack = ref.current;
|
|
@@ -74,6 +75,14 @@ export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChang
|
|
|
74
75
|
if (title)
|
|
75
76
|
title.tabIndex = current == null || i === current ? 0 : -1;
|
|
76
77
|
});
|
|
78
|
+
sheets.forEach((c, i) => {
|
|
79
|
+
const loading = refreshing === i && current === i;
|
|
80
|
+
c.classList.toggle('is-loading', loading);
|
|
81
|
+
if (loading)
|
|
82
|
+
c.setAttribute('aria-busy', 'true');
|
|
83
|
+
else
|
|
84
|
+
c.removeAttribute('aria-busy');
|
|
85
|
+
});
|
|
77
86
|
const prev = was.current;
|
|
78
87
|
was.current = current;
|
|
79
88
|
if (current != null) {
|
|
@@ -194,7 +203,7 @@ export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChang
|
|
|
194
203
|
listen(onMove, onUp, onCancel);
|
|
195
204
|
};
|
|
196
205
|
const onBodyDown = (e) => {
|
|
197
|
-
if (!primary(e) || headPull.current || bodyPull.current || !refreshRef.current)
|
|
206
|
+
if (!primary(e) || headPull.current || bodyPull.current || !refreshRef.current || loadingRef.current === index)
|
|
198
207
|
return;
|
|
199
208
|
if (scroller.scrollTop > 0)
|
|
200
209
|
return;
|
|
@@ -262,9 +271,32 @@ export function SheetStack({ mode = 'tap', open, defaultOpen = null, onOpenChang
|
|
|
262
271
|
};
|
|
263
272
|
listen(onMove, onUp, onCancel);
|
|
264
273
|
};
|
|
274
|
+
/* a FINGER: the browser takes a touch for its own scroll at the first move and cancels the pointer — unless that first move is
|
|
275
|
+
refused. So the first move decides, once: down and at the top = the pull's (refused, no scroll); anything else = the scroll's */
|
|
276
|
+
let touch = null;
|
|
277
|
+
const onTouchStart = (e) => {
|
|
278
|
+
const t = e.touches[0];
|
|
279
|
+
const inHead = e.target instanceof Element && !!e.target.closest('.card__head');
|
|
280
|
+
touch = e.touches.length === 1 && t && refreshRef.current && loadingRef.current !== index && scroller.scrollTop <= 0 && !inHead ? { x: t.clientX, y: t.clientY, ours: null } : null;
|
|
281
|
+
};
|
|
282
|
+
const onTouchMove = (e) => {
|
|
283
|
+
const t = e.touches[0];
|
|
284
|
+
if (!touch || !t)
|
|
285
|
+
return;
|
|
286
|
+
const dx = t.clientX - touch.x;
|
|
287
|
+
const dy = t.clientY - touch.y;
|
|
288
|
+
if (touch.ours == null)
|
|
289
|
+
touch.ours = dy > 0 && dy >= Math.abs(dx) && scroller.scrollTop <= 0;
|
|
290
|
+
if (touch.ours && e.cancelable)
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
};
|
|
265
293
|
head?.addEventListener('pointerdown', onHeadDown);
|
|
266
294
|
scroller.addEventListener('pointerdown', onBodyDown);
|
|
295
|
+
scroller.addEventListener('touchstart', onTouchStart, { passive: true });
|
|
296
|
+
scroller.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
267
297
|
return () => {
|
|
298
|
+
scroller.removeEventListener('touchstart', onTouchStart);
|
|
299
|
+
scroller.removeEventListener('touchmove', onTouchMove);
|
|
268
300
|
stopDoc();
|
|
269
301
|
const held = headPull.current;
|
|
270
302
|
headPull.current = null;
|
package/dist/templates/Home.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface HomeProps extends Omit<ComponentProps<typeof Screen>, 'children
|
|
|
31
31
|
onOpenChange?: (open: number | null) => void;
|
|
32
32
|
/** the open sheet's scroller was already at the top and the finger pulled down past the commit. The stack asks with that index. It does not fetch. Omit it and a pull in the body only scrolls */
|
|
33
33
|
onRefresh?: (index: number) => void;
|
|
34
|
+
/** the sheet the app is reloading (its index), or `null` — as `SheetStack refreshing` */
|
|
35
|
+
refreshing?: number | null;
|
|
34
36
|
/** CONTENT above the sheet heads — one `Page`: a welcome, the news, a picture. It reads
|
|
35
37
|
* from the top; a sheet coming up covers it */
|
|
36
38
|
base?: ReactNode;
|
|
@@ -50,4 +52,4 @@ export interface HomeProps extends Omit<ComponentProps<typeof Screen>, 'children
|
|
|
50
52
|
* at the bottom). A tap on a head brings that sheet up to fill everything under the top. Only the open sheet's body is rendered — three
|
|
51
53
|
* sections on one home never cost three pages (`keep` holds a body once opened).
|
|
52
54
|
*/
|
|
53
|
-
export declare function Home({ top, title, sheets, open: openProp, defaultOpen, onOpenChange, onRefresh, base, keep, backLabel, backAt, children, ...rest }: HomeProps): import("react").JSX.Element;
|
|
55
|
+
export declare function Home({ top, title, sheets, open: openProp, defaultOpen, onOpenChange, onRefresh, refreshing, base, keep, backLabel, backAt, children, ...rest }: HomeProps): import("react").JSX.Element;
|
package/dist/templates/Home.js
CHANGED
|
@@ -15,7 +15,7 @@ const LEAVE = 800; // ms: a closing sheet keeps its body until it has slid home
|
|
|
15
15
|
* at the bottom). A tap on a head brings that sheet up to fill everything under the top. Only the open sheet's body is rendered — three
|
|
16
16
|
* sections on one home never cost three pages (`keep` holds a body once opened).
|
|
17
17
|
*/
|
|
18
|
-
export function Home({ top, title, sheets, open: openProp, defaultOpen = null, onOpenChange, onRefresh, base, keep, backLabel = word('back'), backAt, children, ...rest }) {
|
|
18
|
+
export function Home({ top, title, sheets, open: openProp, defaultOpen = null, onOpenChange, onRefresh, refreshing, base, keep, backLabel = word('back'), backAt, children, ...rest }) {
|
|
19
19
|
const [open, change] = useControlled(openProp, defaultOpen, onOpenChange);
|
|
20
20
|
const [leaving, setLeaving] = useState(null);
|
|
21
21
|
const [seen, setSeen] = useState(() => new Set(open != null ? [open] : []));
|
|
@@ -35,5 +35,5 @@ export function Home({ top, title, sheets, open: openProp, defaultOpen = null, o
|
|
|
35
35
|
}, [leaving]);
|
|
36
36
|
const [tabs, setTabs] = useState(() => Object.fromEntries(sheets.map((x, i) => [i, x.defaultTab ?? x.tabs?.[0]?.id])));
|
|
37
37
|
const shows = (i) => i === open || i === leaving || (keep && seen.has(i));
|
|
38
|
-
return (_jsxs(Screen, { fill: true, top: top, ...rest, children: [_jsx(Text, { level: "h1", as: "h1", sr: true, children: title }), _jsx(SheetStack, { closed: true, open: open, onOpenChange: change, onRefresh: onRefresh, base: base, children: sheets.map((s, i) => (_jsx(StackSheet, { tone: s.tone ?? TONES[i], title: s.title, icon: s.icon, tabs: s.tabs, tab: tabs[i], onTabChange: (t) => setTabs((v) => ({ ...v, [i]: t })), backLabel: backLabel, backAt: backAt, foot: typeof s.foot === 'function' ? s.foot(tabs[i]) : s.foot, children: shows(i) ? (typeof s.body === 'function' ? s.body(tabs[i]) : s.body) : null }, i))) }), children] }));
|
|
38
|
+
return (_jsxs(Screen, { fill: true, top: top, ...rest, children: [_jsx(Text, { level: "h1", as: "h1", sr: true, children: title }), _jsx(SheetStack, { closed: true, open: open, onOpenChange: change, onRefresh: onRefresh, refreshing: refreshing, base: base, children: sheets.map((s, i) => (_jsx(StackSheet, { tone: s.tone ?? TONES[i], title: s.title, icon: s.icon, tabs: s.tabs, tab: tabs[i], onTabChange: (t) => setTabs((v) => ({ ...v, [i]: t })), backLabel: backLabel, backAt: backAt, foot: typeof s.foot === 'function' ? s.foot(tabs[i]) : s.foot, children: shows(i) ? (typeof s.body === 'function' ? s.body(tabs[i]) : s.body) : null }, i))) }), children] }));
|
|
39
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.53",
|
|
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.",
|