@usableapp/cardds 0.7.105 → 0.7.106
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 +37 -0
- package/bin/cardds.mjs +2 -0
- package/css/actions.css +10 -3
- package/css/card.css +22 -4
- package/css/journey.css +4 -1
- package/css/sheet.css +10 -2
- package/css/tokens.css +1 -0
- package/dist/actions/Chip.d.ts +7 -3
- package/dist/actions/Chip.js +5 -5
- package/dist/actions/IconBtnGroup.d.ts +1 -1
- package/dist/cardds.css +47 -10
- package/dist/catalog.json +1 -1
- package/dist/lists/Row.d.ts +32 -21
- package/dist/lists/Row.js +21 -4
- package/dist/media/MapArea.d.ts +5 -1
- package/dist/media/MapArea.js +3 -2
- package/dist/numbers/Spinner.d.ts +1 -1
- package/dist/numbers/Spinner.js +1 -1
- package/lib/catalog.mjs +2 -0
- package/lib/mcp.mjs +1 -1
- package/lint/rules.mjs +8 -1
- package/package.json +1 -1
package/dist/lists/Row.d.ts
CHANGED
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
2
|
import { type IconName } from '../type/Icon.js';
|
|
3
3
|
export interface RowProps extends Omit<ComponentProps<'div'>, 'title'> {
|
|
4
|
-
/** the leading visual: an Avatar, an AvatarStack, an Icon */
|
|
5
|
-
lead?: ReactNode;
|
|
6
|
-
/** an icon in the outlined 48px lead box (menu rows) */
|
|
7
|
-
leadIcon?: IconName;
|
|
8
|
-
/** the title line */
|
|
9
|
-
title?: ReactNode;
|
|
10
|
-
/** the caption under it (may hold a Dot) */
|
|
11
|
-
meta?: ReactNode;
|
|
12
|
-
/** custom body content instead of (or under) title/meta — a Wave for a voice note */
|
|
13
|
-
body?: ReactNode;
|
|
14
|
-
/** the trailing control: an IconBtn, a Chip, a Check, a Toggle, a caption */
|
|
15
|
-
end?: ReactNode;
|
|
16
|
-
/** a trailing value, right-aligned (title type) */
|
|
17
|
-
endValue?: ReactNode;
|
|
18
|
-
/** the muted caption under `endValue` */
|
|
19
|
-
endCaption?: ReactNode;
|
|
20
4
|
/** menu row: more air, a bigger title */
|
|
21
5
|
lg?: boolean;
|
|
22
|
-
/** a finished checklist item: muted words, a filled tick at the end, no longer a tap target (aria-disabled) —
|
|
6
|
+
/** a finished checklist item: muted words, a filled tick at the end, no longer a tap target (aria-disabled) — give it no control at the end */
|
|
23
7
|
done?: boolean;
|
|
24
8
|
/** div (default), a link, a label wrapping a Check/Toggle, li */
|
|
25
9
|
as?: 'div' | 'a' | 'label' | 'li';
|
|
26
|
-
/** with `as="label"`: the `id` of the control the row presses (its `Check`). Needed when
|
|
10
|
+
/** with `as="label"`: the `id` of the control the row presses (its `Check`). Needed when a button stands BEFORE the check (a picked row's own move, "open the map"): a label presses the first control inside it, so without this a touch on the row's words would press that button */
|
|
27
11
|
htmlFor?: string;
|
|
28
12
|
/** makes the row a link (an `a`); not with `more`, whose row is a summary */
|
|
29
13
|
href?: string;
|
|
@@ -37,13 +21,40 @@ export interface RowProps extends Omit<ComponentProps<'div'>, 'title'> {
|
|
|
37
21
|
onOpenChange?: (open: boolean) => void;
|
|
38
22
|
}
|
|
39
23
|
/**
|
|
40
|
-
* Row — a frameless row inside a card: lead ·
|
|
24
|
+
* Row — a frameless row inside a card: lead · body · end, its children in that order. 48px+ tall, a tap target.
|
|
25
|
+
* The lead is a `RowLead` box, or a bare Avatar / AvatarStack; the words are a `RowBody`; the end is a `RowEnd` (a value and its caption)
|
|
26
|
+
* or a bare control (two side by side).
|
|
41
27
|
* Several go in Rows (hairlines between). A row that IS a card is `Card row`.
|
|
42
28
|
* `done` is the checklist's finished line: ticked, muted, not tappable any more.
|
|
43
29
|
* `more` makes it expand: the row is the summary of a `<details class="row-expand">`, `more` comes out under it; `open` + `onOpenChange`, or `defaultOpen`.
|
|
44
|
-
*
|
|
30
|
+
* A chevron at the end turns when it is open — the app fetches whatever `more` lists; the row only shows it.
|
|
45
31
|
*/
|
|
46
|
-
export declare function Row({
|
|
32
|
+
export declare function Row({ lg, done, as, href, more, open, defaultOpen, onOpenChange, className, children, ...rest }: RowProps): import("react").JSX.Element;
|
|
33
|
+
export interface RowLeadProps extends ComponentProps<'span'> {
|
|
34
|
+
/** an icon drawn in the box; without it, the box holds its children */
|
|
35
|
+
icon?: IconName;
|
|
36
|
+
}
|
|
37
|
+
/** RowLead — a Row's lead: the outlined 48px box of a menu row, holding an `icon` or its children.
|
|
38
|
+
* A person's or a group's lead takes no box: an Avatar or an AvatarStack goes straight in the Row as its first child. */
|
|
39
|
+
export declare function RowLead({ icon, className, children, ...rest }: RowLeadProps): import("react").JSX.Element;
|
|
40
|
+
export interface RowBodyProps extends Omit<ComponentProps<'div'>, 'title'> {
|
|
41
|
+
/** the title line */
|
|
42
|
+
title?: ReactNode;
|
|
43
|
+
/** the caption under it (may hold a Dot) */
|
|
44
|
+
meta?: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
/** RowBody — a Row's words, filling the room between lead and end: a `title` over a `meta` caption;
|
|
47
|
+
* its children come under them, or instead of them (a Wave for a voice note). */
|
|
48
|
+
export declare function RowBody({ title, meta, className, children, ...rest }: RowBodyProps): import("react").JSX.Element;
|
|
49
|
+
export interface RowEndProps extends ComponentProps<'span'> {
|
|
50
|
+
/** the trailing value, right-aligned (title type) */
|
|
51
|
+
value?: ReactNode;
|
|
52
|
+
/** the muted caption under `value` */
|
|
53
|
+
caption?: ReactNode;
|
|
54
|
+
}
|
|
55
|
+
/** RowEnd — a Row's trailing value: a `value` over its muted `caption`, right-aligned (+1.0 h over Tue); or its children.
|
|
56
|
+
* A control at the end (an IconBtn, a Chip, a Check) takes no RowEnd: it goes straight in the Row after the RowBody. */
|
|
57
|
+
export declare function RowEnd({ value, caption, className, children, ...rest }: RowEndProps): import("react").JSX.Element;
|
|
47
58
|
export interface RowsProps extends ComponentProps<'div'> {
|
|
48
59
|
/** the group's name, above its rows (Today · Tomorrow · Later): a long list is GROUPED, not spaced out — several Rows one after another.
|
|
49
60
|
* Words = the small label; an ELEMENT (`<Text level="title" as="h3">`) is used as the heading itself — the group is labelled by it. */
|
package/dist/lists/Row.js
CHANGED
|
@@ -4,20 +4,37 @@ import { cx } from '../cx.js';
|
|
|
4
4
|
import { Icon } from '../type/Icon.js';
|
|
5
5
|
import { glide, reducedMotion, setRefs, stopMoves, useDisclosure } from '../state.js';
|
|
6
6
|
/**
|
|
7
|
-
* Row — a frameless row inside a card: lead ·
|
|
7
|
+
* Row — a frameless row inside a card: lead · body · end, its children in that order. 48px+ tall, a tap target.
|
|
8
|
+
* The lead is a `RowLead` box, or a bare Avatar / AvatarStack; the words are a `RowBody`; the end is a `RowEnd` (a value and its caption)
|
|
9
|
+
* or a bare control (two side by side).
|
|
8
10
|
* Several go in Rows (hairlines between). A row that IS a card is `Card row`.
|
|
9
11
|
* `done` is the checklist's finished line: ticked, muted, not tappable any more.
|
|
10
12
|
* `more` makes it expand: the row is the summary of a `<details class="row-expand">`, `more` comes out under it; `open` + `onOpenChange`, or `defaultOpen`.
|
|
11
|
-
*
|
|
13
|
+
* A chevron at the end turns when it is open — the app fetches whatever `more` lists; the row only shows it.
|
|
12
14
|
*/
|
|
13
|
-
export function Row({
|
|
15
|
+
export function Row({ lg, done, as, href, more, open, defaultOpen, onOpenChange, className, children, ...rest }) {
|
|
14
16
|
const disclosure = useDisclosure(open, defaultOpen, onOpenChange);
|
|
15
17
|
const el = more !== undefined ? 'summary' : as ?? (href ? 'a' : 'div');
|
|
16
|
-
const row = createElement(el, { href, className: cx('row', lg && 'row--lg', done && 'row--done', className), 'aria-disabled': done || undefined, ...(more === undefined ? rest : {}) },
|
|
18
|
+
const row = createElement(el, { href, className: cx('row', lg && 'row--lg', done && 'row--done', className), 'aria-disabled': done || undefined, ...(more === undefined ? rest : {}) }, children, done && _jsx("span", { className: "row__done", children: _jsx(Icon, { name: "check", size: "sm" }) }));
|
|
17
19
|
if (more === undefined)
|
|
18
20
|
return row;
|
|
19
21
|
return (_jsxs("details", { className: "row-expand", ...rest, ...disclosure, children: [row, _jsx("div", { className: "row-expand__more", children: more })] }));
|
|
20
22
|
}
|
|
23
|
+
/** RowLead — a Row's lead: the outlined 48px box of a menu row, holding an `icon` or its children.
|
|
24
|
+
* A person's or a group's lead takes no box: an Avatar or an AvatarStack goes straight in the Row as its first child. */
|
|
25
|
+
export function RowLead({ icon, className, children, ...rest }) {
|
|
26
|
+
return _jsx("span", { className: cx('row__lead', className), ...rest, children: icon ? _jsx(Icon, { name: icon }) : children });
|
|
27
|
+
}
|
|
28
|
+
/** RowBody — a Row's words, filling the room between lead and end: a `title` over a `meta` caption;
|
|
29
|
+
* its children come under them, or instead of them (a Wave for a voice note). */
|
|
30
|
+
export function RowBody({ title, meta, className, children, ...rest }) {
|
|
31
|
+
return (_jsxs("div", { className: cx('row__body', className), ...rest, children: [title != null && _jsx("span", { className: "row__title", children: title }), meta != null && _jsx("span", { className: "row__meta", children: meta }), children] }));
|
|
32
|
+
}
|
|
33
|
+
/** RowEnd — a Row's trailing value: a `value` over its muted `caption`, right-aligned (+1.0 h over Tue); or its children.
|
|
34
|
+
* A control at the end (an IconBtn, a Chip, a Check) takes no RowEnd: it goes straight in the Row after the RowBody. */
|
|
35
|
+
export function RowEnd({ value, caption, className, children, ...rest }) {
|
|
36
|
+
return (_jsxs("span", { className: cx('row__end', className), ...rest, children: [value != null && _jsx("span", { className: "t-title", children: value }), caption != null && _jsx("span", { className: "t-caption t-muted", children: caption }), children] }));
|
|
37
|
+
}
|
|
21
38
|
/** Rows — a list of Rows separated by hairlines; `heading` names the group (a `role="group"` labelled by it).
|
|
22
39
|
* REORDERED (the app renders the same keyed rows in another order): each row SLIDES to its new place — the one going up passes
|
|
23
40
|
* over the others — instead of jumping (Lh 2026-10-01). The order is the app's; the move is the list's. */
|
package/dist/media/MapArea.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export interface MapAreaProps extends ComponentProps<'div'> {
|
|
|
3
3
|
/** IN FLOW instead of behind a stage: the map is a box in a card or a sheet's body, the ring and the hint in its own box.
|
|
4
4
|
* The only thing in a SheetBody, it fills the body: the sheet's head stays on top, its foot under it */
|
|
5
5
|
inline?: boolean;
|
|
6
|
+
/** with `inline`: EDGE TO EDGE of what holds it, the sheet's or the card's inset given back, no corner — the map under a step's
|
|
7
|
+
* controls (Lh 2026-10-19, the ask sheet's จุดนัด step). In a `SheetStep` it reaches the sheet's edges; ignored without `inline` */
|
|
8
|
+
bleed?: boolean;
|
|
6
9
|
/** the words over the map, under the bar's line */
|
|
7
10
|
hint?: ReactNode;
|
|
8
11
|
/** the tile provider's credit, bottom right (OSM requires it) — a caption, never the engine's own control */
|
|
@@ -28,5 +31,6 @@ export interface MapAreaProps extends ComponentProps<'div'> {
|
|
|
28
31
|
* `ringRef` is how the engine learns where that is. Places to pick go after the mount, as `MapPick`s.
|
|
29
32
|
* Variation `mark="pin"`: for a meeting place the centre is an exact point, so a clear pin stands there instead of the ring,
|
|
30
33
|
* its tip on the point the ring's centre would be (the engine reads the pin's bottom-centre). The ring stays for a rough area.
|
|
34
|
+
* Variation `bleed` (with `inline`): the map edge to edge of what holds it, no corner, for the map under a step's controls.
|
|
31
35
|
*/
|
|
32
|
-
export declare function MapArea({ inline, hint, credit, mark, ringLabel, ringRef, className, children, ...rest }: MapAreaProps): import("react").JSX.Element;
|
|
36
|
+
export declare function MapArea({ inline, bleed, hint, credit, mark, ringLabel, ringRef, className, children, ...rest }: MapAreaProps): import("react").JSX.Element;
|
package/dist/media/MapArea.js
CHANGED
|
@@ -10,9 +10,10 @@ import { Icon } from '../type/Icon.js';
|
|
|
10
10
|
* `ringRef` is how the engine learns where that is. Places to pick go after the mount, as `MapPick`s.
|
|
11
11
|
* Variation `mark="pin"`: for a meeting place the centre is an exact point, so a clear pin stands there instead of the ring,
|
|
12
12
|
* its tip on the point the ring's centre would be (the engine reads the pin's bottom-centre). The ring stays for a rough area.
|
|
13
|
+
* Variation `bleed` (with `inline`): the map edge to edge of what holds it, no corner, for the map under a step's controls.
|
|
13
14
|
*/
|
|
14
|
-
export function MapArea({ inline, hint, credit, mark = 'ring', ringLabel = mark === 'pin' ? 'จุดนัด' : 'ย่านของคุณโดยประมาณ', ringRef, className, children, ...rest }) {
|
|
15
|
-
return (_jsxs("div", { className: cx('map', inline && 'map--inline', className), ...rest, children: [children, mark === 'pin'
|
|
15
|
+
export function MapArea({ inline, bleed, hint, credit, mark = 'ring', ringLabel = mark === 'pin' ? 'จุดนัด' : 'ย่านของคุณโดยประมาณ', ringRef, className, children, ...rest }) {
|
|
16
|
+
return (_jsxs("div", { className: cx('map', inline && 'map--inline', inline && bleed && 'map--bleed', className), ...rest, children: [children, mark === 'pin'
|
|
16
17
|
? _jsx("span", { ref: ringRef, className: "map__pin", role: "img", "aria-label": ringLabel, children: _jsx(Icon, { name: "map-pin" }) })
|
|
17
18
|
: _jsx("span", { ref: ringRef, className: "map__ring", role: "img", "aria-label": ringLabel }), hint != null && _jsx("span", { className: "map__hint", children: hint }), credit != null && _jsx("span", { className: "map__credit", children: credit })] }));
|
|
18
19
|
}
|
|
@@ -8,6 +8,6 @@ export interface SpinnerProps extends Omit<ComponentProps<'span'>, 'children'> {
|
|
|
8
8
|
children?: ReactNode;
|
|
9
9
|
}
|
|
10
10
|
/** Spinner — "working on it": a ring that turns, alone or with a line of words beside it. For a wait of unknown length — a known
|
|
11
|
-
* share is a `Progress`. `role="status"`: the words (or `label`) are read out when it appears.
|
|
11
|
+
* share is a `Progress`. `role="status"`: the words (or `label`) are read out when it appears. At a row's end, a card, a
|
|
12
12
|
* sheet's body, an `EmptyState`'s `icon`; a whole SCREEN that is not known yet shows nothing instead (`Screen ready`). */
|
|
13
13
|
export declare function Spinner({ size, label, className, children, ...rest }: SpinnerProps): import("react").JSX.Element;
|
package/dist/numbers/Spinner.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
3
|
import { Icon } from '../type/Icon.js';
|
|
4
4
|
/** Spinner — "working on it": a ring that turns, alone or with a line of words beside it. For a wait of unknown length — a known
|
|
5
|
-
* share is a `Progress`. `role="status"`: the words (or `label`) are read out when it appears.
|
|
5
|
+
* share is a `Progress`. `role="status"`: the words (or `label`) are read out when it appears. At a row's end, a card, a
|
|
6
6
|
* sheet's body, an `EmptyState`'s `icon`; a whole SCREEN that is not known yet shows nothing instead (`Screen ready`). */
|
|
7
7
|
export function Spinner({ size = 'md', label, className, children, ...rest }) {
|
|
8
8
|
return (_jsxs("span", { className: cx('spinner', className), role: "status", "aria-label": children == null ? label : undefined, ...rest, children: [_jsx(Icon, { name: "loader-circle", size: size }), children] }));
|
package/lib/catalog.mjs
CHANGED
|
@@ -76,6 +76,8 @@ export function get(cat, name, { html = false, all = false } = {}) {
|
|
|
76
76
|
...(c.params && { params: c.params }),
|
|
77
77
|
composes: c.composes,
|
|
78
78
|
composedBy: c.composedBy,
|
|
79
|
+
parts: c.parts ?? [],
|
|
80
|
+
partOf: c.partOf ?? null,
|
|
79
81
|
knobs: c.knobs,
|
|
80
82
|
variations: [...own, ...elsewhere.filter((s) => shown.includes(s))].map(show),
|
|
81
83
|
...(elsewhere.length > shown.length && { alsoUsedIn: elsewhere.filter((s) => !shown.includes(s)).map((s) => s.id) }),
|
package/lib/mcp.mjs
CHANGED
|
@@ -42,7 +42,7 @@ const TOOLS = [
|
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
name: 'get_component',
|
|
45
|
-
description: 'One component in full: what it is, its props (a prop marked slot takes children: ReactNode), what it composes and what composes it (composes · composedBy: the catalog names its source renders or calls, and the inverse), its knobs (the CSS custom properties a theme sets to retune it, each with its default, what it sets and the selector that reads it — joined by the classes the component emits, so a knob named for an old name is listed under the component that has it now), and its variations, each a working story with its code. Call it before you use a component you have not used yet, and copy a variation rather than guessing a prop. A component used everywhere (Btn, Text) comes with a few of its stories and the ids of the rest (pass all for their code). Names are case-insensitive; a miss returns the closest names.',
|
|
45
|
+
description: 'One component in full: what it is, its props (a prop marked slot takes children: ReactNode), what it composes and what composes it (composes · composedBy: the catalog names its source renders or calls, and the inverse), its parts (parts · partOf: the exports a whole is built of as children, and the whole a part belongs to), its knobs (the CSS custom properties a theme sets to retune it, each with its default, what it sets and the selector that reads it — joined by the classes the component emits, so a knob named for an old name is listed under the component that has it now), and its variations, each a working story with its code. Call it before you use a component you have not used yet, and copy a variation rather than guessing a prop. A component used everywhere (Btn, Text) comes with a few of its stories and the ids of the rest (pass all for their code). Names are case-insensitive; a miss returns the closest names.',
|
|
46
46
|
inputSchema: { type: 'object', properties: { name: str('component name, e.g. Menu'), html: bool('also return each variation\'s rendered HTML — only to see the markup it makes'), all: bool('include the code of every story that uses the component') }, required: ['name'] },
|
|
47
47
|
run: (cat, a) => catalog.get(cat, a.name, { html: a.html, all: a.all }),
|
|
48
48
|
},
|
package/lint/rules.mjs
CHANGED
|
@@ -218,7 +218,7 @@ export function checkSource(source, file, { classes = carddsClasses() } = {}) {
|
|
|
218
218
|
// 8 · a label presses the FIRST control inside it: a button before the row's Check takes the press meant for the row
|
|
219
219
|
for (const m of text.matchAll(/<Row\b/g)) {
|
|
220
220
|
const tag = tagAt(text, m.index);
|
|
221
|
-
if (/\sas\s*=\s*\{?\s*["']label["']/.test(tag) && /<(?:IconBtn|Btn|Link)\b/.test(
|
|
221
|
+
if (/\sas\s*=\s*\{?\s*["']label["']/.test(tag) && /<(?:IconBtn|Btn|Link)\b/.test(elementAt(text, m.index)) && !/\shtmlFor\s*=/.test(tag))
|
|
222
222
|
add('row-label-button', 'warn', m.index, '<Row as="label"> holding a button and no htmlFor: a label presses the first control inside it, so a touch on the row\'s words presses that button, not its Check. Give the Check an id and the row htmlFor={id}.');
|
|
223
223
|
}
|
|
224
224
|
|
|
@@ -294,6 +294,13 @@ export function checkSource(source, file, { classes = carddsClasses() } = {}) {
|
|
|
294
294
|
});
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
// 12 · MapArea bleed is read only with inline (the map in flow): alone it does nothing, and the map stays in its inset
|
|
298
|
+
for (const m of text.matchAll(/<MapArea\b(?![\w.])/g)) {
|
|
299
|
+
const tag = tagAt(text, m.index);
|
|
300
|
+
if (/\sbleed\b(?!\s*=\s*\{\s*false)/.test(tag) && !/\sinline\b(?!\s*=\s*\{\s*false)/.test(tag))
|
|
301
|
+
add('map-bleed-without-inline', 'warn', m.index, '<MapArea bleed> without inline: bleed is read only with inline (the map in flow), so the map stays in its inset. Add inline, or drop bleed.');
|
|
302
|
+
}
|
|
303
|
+
|
|
297
304
|
return findings.sort((a, b) => a.line - b.line);
|
|
298
305
|
}
|
|
299
306
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.106",
|
|
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.",
|