@usableapp/cardds 0.7.105 → 0.7.107
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 +134 -0
- package/bin/cardds.mjs +6 -2
- package/cardds.css +1 -0
- package/css/actions.css +16 -9
- package/css/base.css +6 -9
- package/css/card.css +21 -173
- package/css/finish.css +4 -4
- package/css/journey.css +4 -1
- package/css/layover.css +4 -14
- package/css/lists.css +3 -18
- package/css/media.css +1 -7
- package/css/parts.css +380 -0
- package/css/sheet.css +21 -140
- package/css/stack.css +15 -26
- package/css/stamp.css +1 -1
- package/css/step.css +3 -3
- package/css/theme.template.css +1 -1
- package/css/tokens.css +5 -4
- package/dist/actions/BarNote.d.ts +2 -2
- package/dist/actions/BarNote.js +2 -2
- package/dist/actions/Btn.d.ts +2 -2
- package/dist/actions/Btn.js +1 -1
- package/dist/actions/BtnRow.d.ts +1 -1
- package/dist/actions/BtnRow.js +1 -1
- package/dist/actions/Chip.d.ts +7 -3
- package/dist/actions/Chip.js +5 -5
- package/dist/actions/IconBtn.d.ts +1 -1
- package/dist/actions/IconBtn.js +1 -1
- package/dist/actions/IconBtnGroup.d.ts +1 -1
- package/dist/cardds.css +486 -410
- package/dist/cards/Card.d.ts +6 -22
- package/dist/cards/Card.js +11 -22
- package/dist/cards/CardFeed.d.ts +1 -1
- package/dist/cards/CardFeed.js +5 -5
- package/dist/cards/CardParts.d.ts +0 -12
- package/dist/cards/CardParts.js +0 -12
- package/dist/cards/cardGo.js +1 -1
- package/dist/cards/cardGrounds.js +2 -2
- package/dist/catalog.json +1 -1
- package/dist/forms/Field.d.ts +1 -1
- package/dist/forms/Field.js +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -5
- package/dist/lists/Row.d.ts +7 -22
- package/dist/lists/Row.js +6 -4
- package/dist/media/MapArea.d.ts +7 -3
- package/dist/media/MapArea.js +4 -3
- package/dist/media/Tile.d.ts +3 -7
- package/dist/media/Tile.js +5 -5
- package/dist/numbers/Spinner.d.ts +1 -1
- package/dist/numbers/Spinner.js +1 -1
- package/dist/parts/Body.d.ts +7 -0
- package/dist/parts/Body.js +11 -0
- package/dist/parts/End.d.ts +10 -0
- package/dist/parts/End.js +7 -0
- package/dist/parts/Foot.d.ts +17 -0
- package/dist/parts/Foot.js +11 -0
- package/dist/parts/Head.d.ts +15 -0
- package/dist/parts/Head.js +34 -0
- package/dist/parts/Lead.d.ts +10 -0
- package/dist/parts/Lead.js +9 -0
- package/dist/parts/Words.d.ts +13 -0
- package/dist/parts/Words.js +20 -0
- package/dist/parts/headCtx.d.ts +7 -0
- package/dist/parts/headCtx.js +9 -0
- package/dist/scaffold/EmptyState.d.ts +1 -1
- package/dist/scaffold/EmptyState.js +1 -1
- package/dist/scaffold/Screen.js +1 -1
- package/dist/sheets/Modal.d.ts +3 -7
- package/dist/sheets/Modal.js +3 -7
- package/dist/sheets/Sheet.d.ts +2 -2
- package/dist/sheets/Sheet.js +8 -9
- package/dist/sheets/SheetStack.d.ts +3 -3
- package/dist/sheets/SheetStack.js +4 -4
- package/dist/sheets/SheetSteps.d.ts +1 -1
- package/dist/sheets/SheetSteps.js +2 -2
- package/dist/sheets/StackBack.d.ts +1 -1
- package/dist/sheets/StackBack.js +1 -1
- package/dist/sheets/StackSheet.js +7 -7
- package/dist/sheets/sheetMeasure.js +3 -3
- package/dist/sheets/sheetStepsPile.js +2 -2
- package/dist/sheets/stackCard.js +6 -6
- package/dist/templates/CallSheet.js +6 -5
- package/dist/templates/Walkthrough.js +3 -2
- package/docs/reference.md +75 -36
- package/lib/catalog.mjs +2 -0
- package/lib/mcp.mjs +1 -1
- package/lint/rules.mjs +22 -8
- package/package.json +2 -1
- package/scripts/codemod-parts.mjs +324 -0
- package/dist/cards/CardFoot.d.ts +0 -15
- package/dist/cards/CardFoot.js +0 -9
- package/dist/cards/CardHead.d.ts +0 -21
- package/dist/cards/CardHead.js +0 -39
- package/dist/cards/Stat.d.ts +0 -19
- package/dist/cards/Stat.js +0 -14
- package/dist/sheets/SheetBody.d.ts +0 -5
- package/dist/sheets/SheetBody.js +0 -9
- package/dist/sheets/SheetHead.d.ts +0 -15
- package/dist/sheets/SheetHead.js +0 -6
package/dist/sheets/Sheet.d.ts
CHANGED
|
@@ -33,8 +33,8 @@ export interface SheetProps extends Omit<ComponentProps<'article'>, 'onChange'>
|
|
|
33
33
|
/**
|
|
34
34
|
* Sheet — the SINGLE sheet: one card as a bottom sheet holding what the member ACTS on. Always the full height of its
|
|
35
35
|
* box — a Step or a SheetStage — a state is only how far it slid (translate; the material never changes size).
|
|
36
|
-
* ONE SHAPE, written top-down: handle · head (a
|
|
37
|
-
* it needs) ·
|
|
36
|
+
* ONE SHAPE, written top-down: handle · head (a Head — pinned, never scrolls) · Body (scrolls, as far as
|
|
37
|
+
* it needs) · Foot (the button that finishes the sheet — at the bottom, above the keyboard; gone at peek).
|
|
38
38
|
*
|
|
39
39
|
* It is a COMPONENT, not a wiring: it knows nothing of what opens it. The app owns `state` and sets it from whatever it
|
|
40
40
|
* likes — a button, a route, a timer: `<Btn onClick={() => setSheet('full')}>` … `<Sheet state={sheet} onStateChange={setSheet}>`.
|
package/dist/sheets/Sheet.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Children, cloneElement, isValidElement, useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { focusWhenActive, useControlled, visibleBottom, watchSize } from '../state.js';
|
|
4
|
-
import {
|
|
5
|
-
import { SheetHead } from './SheetHead.js';
|
|
4
|
+
import { Head } from '../parts/Head.js';
|
|
6
5
|
import { cx } from '../cx.js';
|
|
7
6
|
import { word } from '../words.js';
|
|
8
7
|
import { ORDER, slid, stopsOf, useFootRoom, usePeekStrip, useTitleRun } from './sheetMeasure.js';
|
|
@@ -13,15 +12,15 @@ const RANK = { away: 0, peek: 1, half: 2, '3q': 3, full: 4 };
|
|
|
13
12
|
function markLong(node, long) {
|
|
14
13
|
if (!long || !isValidElement(node))
|
|
15
14
|
return node;
|
|
16
|
-
if (node.type !==
|
|
15
|
+
if (node.type !== Head)
|
|
17
16
|
return node;
|
|
18
17
|
return cloneElement(node, { long: true });
|
|
19
18
|
}
|
|
20
19
|
/**
|
|
21
20
|
* Sheet — the SINGLE sheet: one card as a bottom sheet holding what the member ACTS on. Always the full height of its
|
|
22
21
|
* box — a Step or a SheetStage — a state is only how far it slid (translate; the material never changes size).
|
|
23
|
-
* ONE SHAPE, written top-down: handle · head (a
|
|
24
|
-
* it needs) ·
|
|
22
|
+
* ONE SHAPE, written top-down: handle · head (a Head — pinned, never scrolls) · Body (scrolls, as far as
|
|
23
|
+
* it needs) · Foot (the button that finishes the sheet — at the bottom, above the keyboard; gone at peek).
|
|
25
24
|
*
|
|
26
25
|
* It is a COMPONENT, not a wiring: it knows nothing of what opens it. The app owns `state` and sets it from whatever it
|
|
27
26
|
* likes — a button, a route, a timer: `<Btn onClick={() => setSheet('full')}>` … `<Sheet state={sheet} onStateChange={setSheet}>`.
|
|
@@ -64,7 +63,7 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
64
63
|
was.current = live;
|
|
65
64
|
if (!rose)
|
|
66
65
|
return;
|
|
67
|
-
const cancel = focusWhenActive(ref.current, { scope: ':scope > .
|
|
66
|
+
const cancel = focusWhenActive(ref.current, { scope: ':scope > .body', fallback: () => (from === 'away' ? handleRef.current : null) });
|
|
68
67
|
return () => { if (!cancel())
|
|
69
68
|
was.current = from; }; // cut short (StrictMode's trial run, or a new state mid-slide): the rise still counts from where it began
|
|
70
69
|
}, [live]);
|
|
@@ -79,11 +78,11 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
79
78
|
const from = Math.max(list.indexOf(current), atLeast ? list.indexOf(atLeast) : -1, 0);
|
|
80
79
|
const rect = el.getBoundingClientRect();
|
|
81
80
|
const restTop = rect.top - slid(el);
|
|
82
|
-
const bodyTop = (el.querySelector(':scope > .
|
|
81
|
+
const bodyTop = (el.querySelector(':scope > .body') ?? el).getBoundingClientRect().top - rect.top;
|
|
83
82
|
const box = el.parentElement.getBoundingClientRect(); // what clips it: the Step or the SheetStage
|
|
84
83
|
const edge = Math.min(box.bottom, restTop + rect.height); // the sheet's own bottom edge at rest — above the box's on a stage that gave the keyboard its room
|
|
85
84
|
const cut = Math.min(edge, visibleBottom());
|
|
86
|
-
const button = el.querySelector(':scope > .
|
|
85
|
+
const button = el.querySelector(':scope > .foot')?.firstElementChild; // the foot's button stands on that edge at every state: the control has to clear its strip too
|
|
87
86
|
const need = control.getBoundingClientRect().height + (button ? edge - button.getBoundingClientRect().top : 0);
|
|
88
87
|
let to = list[list.length - 1];
|
|
89
88
|
for (let i = from; i < list.length; i++)
|
|
@@ -121,7 +120,7 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
121
120
|
onFocus?.(e);
|
|
122
121
|
if (current !== 'peek' || e.target.closest('.sheet__handle'))
|
|
123
122
|
return;
|
|
124
|
-
const body = ref.current?.querySelector(':scope > .
|
|
123
|
+
const body = ref.current?.querySelector(':scope > .body');
|
|
125
124
|
if (body)
|
|
126
125
|
body.scrollTop = 0; // the browser's own reveal scrolled it against the peek strip: start over
|
|
127
126
|
reveal(e.target, 'focus', home.current);
|
|
@@ -27,9 +27,9 @@ export interface SheetStackProps extends ComponentProps<'div'> {
|
|
|
27
27
|
/**
|
|
28
28
|
* SheetStack — the MENU: sheets stacked like paper at the middle of the home screen, every one a main section.
|
|
29
29
|
* Children are Cards, direct children only, at most five: first = the back of the stack, last = the front (open) sheet.
|
|
30
|
-
* Every sheet is the stack's full height; a peek is the strip the next sheet doesn't cover. A stack head is a
|
|
31
|
-
* with CHILDREN, in this order: `<StackBack />` (tap mode), `<Text level="h2" caps
|
|
32
|
-
*
|
|
30
|
+
* Every sheet is the stack's full height; a peek is the strip the next sheet doesn't cover. A stack head is a Head
|
|
31
|
+
* with CHILDREN, in this order: `<StackBack />` (tap mode), the title (`<Words title="Shopping" />`, or a bare `<Text level="h2" caps>`), an `<IconRow>` of Icons —
|
|
32
|
+
* a stack head is back · title · tabs on ONE line (StackSheet builds it). Put it last in a `Screen fill`, it bleeds
|
|
33
33
|
* off the bottom. `closed`: nothing open on arrival — every head peeks at the bottom and the base above shows `base` (a home screen
|
|
34
34
|
* with a picture over the menu); the `base` slot is rendered after the sheets, so it never takes a sheet's slot. An open sheet fills everything under the top bar,
|
|
35
35
|
* or with `rise="flood"` the whole screen, covering the bar. This is the sheet STACK; a single bottom sheet is `Sheet`.
|
|
@@ -18,7 +18,7 @@ function measureRests(stack) {
|
|
|
18
18
|
return {
|
|
19
19
|
height: box.height,
|
|
20
20
|
strips: cards.map((c, i) => {
|
|
21
|
-
const head = c.querySelector(':scope > .
|
|
21
|
+
const head = c.querySelector(':scope > .head')?.getBoundingClientRect();
|
|
22
22
|
const own = head ? head.bottom - box.top + (head.top - box.top - tops[i]) : tops[i];
|
|
23
23
|
return { top: tops[i], end: i < cards.length - 1 ? tops[i + 1] : own };
|
|
24
24
|
}),
|
|
@@ -57,9 +57,9 @@ function stamp(child, place) {
|
|
|
57
57
|
/**
|
|
58
58
|
* SheetStack — the MENU: sheets stacked like paper at the middle of the home screen, every one a main section.
|
|
59
59
|
* Children are Cards, direct children only, at most five: first = the back of the stack, last = the front (open) sheet.
|
|
60
|
-
* Every sheet is the stack's full height; a peek is the strip the next sheet doesn't cover. A stack head is a
|
|
61
|
-
* with CHILDREN, in this order: `<StackBack />` (tap mode), `<Text level="h2" caps
|
|
62
|
-
*
|
|
60
|
+
* Every sheet is the stack's full height; a peek is the strip the next sheet doesn't cover. A stack head is a Head
|
|
61
|
+
* with CHILDREN, in this order: `<StackBack />` (tap mode), the title (`<Words title="Shopping" />`, or a bare `<Text level="h2" caps>`), an `<IconRow>` of Icons —
|
|
62
|
+
* a stack head is back · title · tabs on ONE line (StackSheet builds it). Put it last in a `Screen fill`, it bleeds
|
|
63
63
|
* off the bottom. `closed`: nothing open on arrival — every head peeks at the bottom and the base above shows `base` (a home screen
|
|
64
64
|
* with a picture over the menu); the `base` slot is rendered after the sheets, so it never takes a sheet's slot. An open sheet fills everything under the top bar,
|
|
65
65
|
* or with `rise="flood"` the whole screen, covering the bar. This is the sheet STACK; a single bottom sheet is `Sheet`.
|
|
@@ -29,7 +29,7 @@ export interface SheetStepsProps extends ComponentProps<'div'> {
|
|
|
29
29
|
* SheetSteps — one task taken in steps INSIDE one sheet (phone · code · PIN · PIN again · face · notices). The done steps pile up
|
|
30
30
|
* on top, each one line (title · summary · ✓); the current step is open under them, its form at the form's own spacing; the next
|
|
31
31
|
* steps wait below as their titles. A thin rule over every step. The last step's own action sends, or a `finish` stands under the
|
|
32
|
-
* steps on the last one. Goes in a `
|
|
32
|
+
* steps on the last one. Goes in a Sheet's `Body`.
|
|
33
33
|
* The sheet may be dragged down to peek and back — the body keeps where it was scrolled.
|
|
34
34
|
*/
|
|
35
35
|
export declare function SheetSteps({ at, reached, defaultAt, onAtChange, reversible, numbered, finish, className, style, children, onFocus, onBlur, onPointerDown, onPointerUp, onPointerCancel, ...rest }: SheetStepsProps): import("react").JSX.Element;
|
|
@@ -19,7 +19,7 @@ const StepCtx = createContext(null);
|
|
|
19
19
|
* SheetSteps — one task taken in steps INSIDE one sheet (phone · code · PIN · PIN again · face · notices). The done steps pile up
|
|
20
20
|
* on top, each one line (title · summary · ✓); the current step is open under them, its form at the form's own spacing; the next
|
|
21
21
|
* steps wait below as their titles. A thin rule over every step. The last step's own action sends, or a `finish` stands under the
|
|
22
|
-
* steps on the last one. Goes in a `
|
|
22
|
+
* steps on the last one. Goes in a Sheet's `Body`.
|
|
23
23
|
* The sheet may be dragged down to peek and back — the body keeps where it was scrolled.
|
|
24
24
|
*/
|
|
25
25
|
export function SheetSteps({ at, reached, defaultAt = 0, onAtChange, reversible = false, numbered = false, finish, className, style, children, onFocus, onBlur, onPointerDown, onPointerUp, onPointerCancel, ...rest }) {
|
|
@@ -115,7 +115,7 @@ export function SheetSteps({ at, reached, defaultAt = 0, onAtChange, reversible
|
|
|
115
115
|
if (!typing(e.target) || performance.now() < moving.current)
|
|
116
116
|
return; // the old step's field losing the cursor to the new one
|
|
117
117
|
glideNow();
|
|
118
|
-
const body = ref.current?.closest('.
|
|
118
|
+
const body = ref.current?.closest('.body');
|
|
119
119
|
setTimeout(() => { if (!typing(document.activeElement) && body)
|
|
120
120
|
body.scrollTo({ top: 0 }); }, 0); // Tab to the next field keeps it up
|
|
121
121
|
};
|
|
@@ -6,6 +6,6 @@ export interface StackBackProps extends ComponentProps<'button'> {
|
|
|
6
6
|
* default: at its start, chevron left */
|
|
7
7
|
end?: boolean;
|
|
8
8
|
}
|
|
9
|
-
/** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's
|
|
9
|
+
/** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's Head. Hidden until the sheet is open.
|
|
10
10
|
* A SMALL disc (Lh 2026-09-23): 32px with a chevron — the head keeps its room for the title and the tabs; the 44px tap target stays. */
|
|
11
11
|
export declare function StackBack({ label, end, className, ...rest }: StackBackProps): import("react").JSX.Element;
|
package/dist/sheets/StackBack.js
CHANGED
|
@@ -3,7 +3,7 @@ import { cx } from '../cx.js';
|
|
|
3
3
|
import { Icon } from '../type/Icon.js';
|
|
4
4
|
import { IconBtn } from '../actions/IconBtn.js';
|
|
5
5
|
import { word } from '../words.js';
|
|
6
|
-
/** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's
|
|
6
|
+
/** StackBack — the back button a tapped stack sheet reveals in its head; first child of that sheet's Head. Hidden until the sheet is open.
|
|
7
7
|
* A SMALL disc (Lh 2026-09-23): 32px with a chevron — the head keeps its room for the title and the tabs; the 44px tap target stays. */
|
|
8
8
|
export function StackBack({ label = word('back'), end, className, ...rest }) {
|
|
9
9
|
return _jsx(IconBtn, { sm: true, label: label, className: cx('sheet-stack__back', end && 'sheet-stack__back--end', className), ...rest, children: _jsx(Icon, { name: end ? 'chevron-down' : 'chevron-left' }) });
|
|
@@ -4,11 +4,11 @@ import { useControlled, watchSize } from '../state.js';
|
|
|
4
4
|
import { cx } from '../cx.js';
|
|
5
5
|
import { Card } from '../cards/Card.js';
|
|
6
6
|
import { IconBtn } from '../actions/IconBtn.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { Head } from '../parts/Head.js';
|
|
8
|
+
import { Words } from '../parts/Words.js';
|
|
9
|
+
import { Foot } from '../parts/Foot.js';
|
|
10
|
+
import { Body } from '../parts/Body.js';
|
|
10
11
|
import { StackBack } from './StackBack.js';
|
|
11
|
-
import { Text } from '../type/Text.js';
|
|
12
12
|
import { Icon } from '../type/Icon.js';
|
|
13
13
|
import { Badge } from '../numbers/Badge.js';
|
|
14
14
|
import { word } from '../words.js';
|
|
@@ -24,15 +24,15 @@ export function StackSheet({ title, tone, paper, icon, tabs, tab: tabProp, defau
|
|
|
24
24
|
const [tab, pick] = useControlled(tabProp, defaultTab ?? tabs?.[0]?.id, onTabChange);
|
|
25
25
|
// a title longer than its room does not wrap: it runs sideways, like a song title in a short slot — the room it overflows by
|
|
26
26
|
// is measured here (the tabs may grow with the text size) and handed to the CSS as --_over / --_run, the Sheet's measurer
|
|
27
|
-
const
|
|
27
|
+
const headRef = useRef(null);
|
|
28
28
|
const [long, setLong] = useState(false);
|
|
29
29
|
useEffect(() => {
|
|
30
|
-
const h =
|
|
30
|
+
const h = headRef.current?.querySelector(':scope > .words__title'), run = h?.firstElementChild;
|
|
31
31
|
if (!h || !run)
|
|
32
32
|
return;
|
|
33
33
|
const measure = () => setLong(runOver(h, run));
|
|
34
34
|
measure();
|
|
35
35
|
return watchSize([h, run], measure);
|
|
36
36
|
}, [title]);
|
|
37
|
-
return (_jsxs(Card, { stack: stack, tone: tone, paper: paper, className: cx('stack-sheet', className), ...rest, children: [_jsxs(
|
|
37
|
+
return (_jsxs(Card, { stack: stack, tone: tone, paper: paper, className: cx('stack-sheet', className), ...rest, children: [_jsxs(Head, { ref: headRef, long: long, children: [backAt === 'start' && _jsx(StackBack, { label: backLabel }), _jsx(Words, { title: _jsx("span", { className: "stack-sheet__run", children: title }) }), tabs?.length ? (_jsx("span", { className: "stack-sheet__tabs", role: "tablist", children: tabs.map((t) => (_jsxs(IconBtn, { sm: true, invert: t.id === tab, role: "tab", "aria-selected": t.id === tab, label: t.label, className: "stack-sheet__tab", onClick: () => pick(t.id), children: [_jsx(Icon, { name: t.icon }), t.count ? _jsx(Badge, { className: "stack-sheet__count", children: t.count > 99 ? '99+' : t.count }) : null] }, t.id))) })) : icon, backAt === 'end' && _jsx(StackBack, { end: true, label: backLabel })] }), _jsx(Body, { role: tabs?.length ? 'tabpanel' : undefined, children: children }), foot != null && _jsx(Foot, { children: foot })] }));
|
|
38
38
|
}
|
|
@@ -23,7 +23,7 @@ export const stopsOf = (el) => {
|
|
|
23
23
|
22-base type scale). Removed when the head fits the floor again, so the CSS number is the one in force. */
|
|
24
24
|
export function usePeekStrip(ref) {
|
|
25
25
|
useEffect(() => {
|
|
26
|
-
const el = ref.current, box = el?.parentElement, head = el?.querySelector(':scope > .
|
|
26
|
+
const el = ref.current, box = el?.parentElement, head = el?.querySelector(':scope > .head');
|
|
27
27
|
if (!el || !box || !head)
|
|
28
28
|
return;
|
|
29
29
|
const publish = () => {
|
|
@@ -53,7 +53,7 @@ export function runOver(line, words = line) {
|
|
|
53
53
|
/* the Sheet's title: the app's own heading, measured whole (sheet.css runs it with text-indent) */
|
|
54
54
|
export function useTitleRun(ref, onLong) {
|
|
55
55
|
useEffect(() => {
|
|
56
|
-
const el = ref.current, title = el?.querySelector(':scope > .
|
|
56
|
+
const el = ref.current, title = el?.querySelector(':scope > .head > :is(.words__title, :is(h1, h2, h3):first-child)');
|
|
57
57
|
if (!el || !title)
|
|
58
58
|
return;
|
|
59
59
|
let was = false;
|
|
@@ -75,7 +75,7 @@ export function useTitleRun(ref, onLong) {
|
|
|
75
75
|
sheet measures its foot's rows and publishes --sheet-foot-h; the strip, its ground and the body's stop all read it. */
|
|
76
76
|
export function useFootRoom(ref) {
|
|
77
77
|
useEffect(() => {
|
|
78
|
-
const el = ref.current, foot = el?.querySelector(':scope > .
|
|
78
|
+
const el = ref.current, foot = el?.querySelector(':scope > .foot');
|
|
79
79
|
if (!el || !foot) {
|
|
80
80
|
el?.style.removeProperty('--sheet-foot-h');
|
|
81
81
|
return;
|
|
@@ -13,7 +13,7 @@ export function usePile(ref, moving) {
|
|
|
13
13
|
above the open step, and half of the one before — older ones go up out of sight, one row at a time, so what was done never seems to vanish (Lh
|
|
14
14
|
2026-09-24: moving the pile further startled; with two rows or fewer nothing moves) */
|
|
15
15
|
const settle = () => {
|
|
16
|
-
const list = ref.current, body = list?.closest('.
|
|
16
|
+
const list = ref.current, body = list?.closest('.body'), now = list?.querySelector('.sheet-step--now');
|
|
17
17
|
if (!list || !body || !now)
|
|
18
18
|
return;
|
|
19
19
|
const above = [];
|
|
@@ -43,7 +43,7 @@ export function usePile(ref, moving) {
|
|
|
43
43
|
For a short while after a field in the steps gains or loses the focus, every such jump is played as a quick slide (FLIP: the steps
|
|
44
44
|
are set back by the jump, then eased to rest) so the eye can follow the pile moving out of the way and back. */
|
|
45
45
|
useEffect(() => {
|
|
46
|
-
const el = ref.current, body = el?.closest('.
|
|
46
|
+
const el = ref.current, body = el?.closest('.body');
|
|
47
47
|
if (!el || !body)
|
|
48
48
|
return;
|
|
49
49
|
last.current = body.scrollTop;
|
package/dist/sheets/stackCard.js
CHANGED
|
@@ -38,13 +38,13 @@ const readFrame = (el) => { el.getBoundingClientRect(); };
|
|
|
38
38
|
const along = (dir, startY, y) => (dir === 'down' ? Math.max(0, y - startY) : Math.max(0, startY - y));
|
|
39
39
|
/** the head the sheet owns: the card's own child. A card standing in the sheet's body has a head too, and it is that card's */
|
|
40
40
|
export const ownHead = (card, target) => {
|
|
41
|
-
const head = target.closest('.
|
|
41
|
+
const head = target.closest('.head');
|
|
42
42
|
return head?.parentElement === card ? head : null;
|
|
43
43
|
};
|
|
44
44
|
const pullsHead = (target, head) => {
|
|
45
45
|
if (!(target instanceof Element))
|
|
46
46
|
return false;
|
|
47
|
-
return target === head || !!target.closest('.sheet-stack__back') || !!target.closest('.
|
|
47
|
+
return target === head || !!target.closest('.sheet-stack__back') || !!target.closest('.head > :is(h1, h2, h3)');
|
|
48
48
|
};
|
|
49
49
|
/* once a finger is down the DOCUMENT hears it, not the head or the body it started on: the finger leaves them while it drags.
|
|
50
50
|
One set of move / up / cancel at a time; `stop` takes the set off, and is safe to call with none on */
|
|
@@ -160,7 +160,7 @@ export function useStackCard(ref, place, swallow) {
|
|
|
160
160
|
useEffect(() => {
|
|
161
161
|
if (!place?.open || !ref.current)
|
|
162
162
|
return;
|
|
163
|
-
const cancel = focusWhenActive(ref.current, { scope: ':scope > .
|
|
163
|
+
const cancel = focusWhenActive(ref.current, { scope: ':scope > .body' });
|
|
164
164
|
return () => { cancel(); };
|
|
165
165
|
}, [place?.open, ref]);
|
|
166
166
|
/* open: a pull down on the head folds it; a pull down in the content, already at the top, asks for a reload */
|
|
@@ -170,8 +170,8 @@ export function useStackCard(ref, place, swallow) {
|
|
|
170
170
|
if (!card || !here?.open)
|
|
171
171
|
return;
|
|
172
172
|
const stack = card.closest('.sheet-stack');
|
|
173
|
-
const head = card.querySelector(':scope > .
|
|
174
|
-
const scroller = card.querySelector(':scope > .
|
|
173
|
+
const head = card.querySelector(':scope > .head');
|
|
174
|
+
const scroller = card.querySelector(':scope > .body') ?? card;
|
|
175
175
|
const doc = docPointer();
|
|
176
176
|
const primary = (e) => e.isPrimary && e.button === 0;
|
|
177
177
|
const onHeadDown = (e) => {
|
|
@@ -298,7 +298,7 @@ export function useStackCard(ref, place, swallow) {
|
|
|
298
298
|
if (!card || !here || here.open || here.before || here.after)
|
|
299
299
|
return;
|
|
300
300
|
const stack = card.closest('.sheet-stack');
|
|
301
|
-
const head = card.querySelector(':scope > .
|
|
301
|
+
const head = card.querySelector(':scope > .head');
|
|
302
302
|
if (!head || !stack)
|
|
303
303
|
return;
|
|
304
304
|
const doc = docPointer();
|
|
@@ -5,9 +5,10 @@ import { Screen } from '../scaffold/Screen.js';
|
|
|
5
5
|
import { Step } from '../sheets/Step.js';
|
|
6
6
|
import { Page, CtaPack } from '../scaffold/Page.js';
|
|
7
7
|
import { Sheet } from '../sheets/Sheet.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { Body } from '../parts/Body.js';
|
|
9
|
+
import { Head } from '../parts/Head.js';
|
|
10
|
+
import { Words } from '../parts/Words.js';
|
|
11
|
+
import { Foot } from '../parts/Foot.js';
|
|
11
12
|
import { Text } from '../type/Text.js';
|
|
12
13
|
import { Btn } from '../actions/Btn.js';
|
|
13
14
|
const index = (o) => (o === true ? 0 : typeof o === 'number' ? o : null);
|
|
@@ -29,9 +30,9 @@ export function CallSheet({ media, heading, body, action, sheet, ways, open: ope
|
|
|
29
30
|
if (was !== null)
|
|
30
31
|
setLeaving(was);
|
|
31
32
|
}
|
|
32
|
-
return (_jsxs(Screen, { fill: true, ...rest, children: [_jsxs(Step, { children: [_jsxs(Page, { children: [media, _jsx(
|
|
33
|
+
return (_jsxs(Screen, { fill: true, ...rest, children: [_jsxs(Step, { children: [_jsxs(Page, { children: [media, _jsx(Head, { children: _jsx(Words, { title: heading, level: "h1" }) }), typeof body === 'string' ? _jsx(Text, { level: "body", children: body }) : body, _jsx(CtaPack, { children: list.map((w, i) => w.action == null ? null : (_jsx(Btn, { primary: i === first, quiet: i !== first, "aria-expanded": on === i, onClick: () => { setAt('full'); set(i); }, children: w.action }, i))) })] }), list.map(({ sheet: sh }, i) => (_jsxs(Sheet, { state: on === i ? at : 'away', onStateChange: (s) => { if (s === 'away')
|
|
33
34
|
set(null);
|
|
34
35
|
else
|
|
35
36
|
setAt(s); }, onSettled: (s) => { if (s === 'away' && leaving === i)
|
|
36
|
-
setLeaving(null); }, states: states, handle: sh.handle ?? handle, children: [_jsx(
|
|
37
|
+
setLeaving(null); }, states: states, handle: sh.handle ?? handle, children: [_jsx(Head, { children: _jsx(Words, { title: sh.title }) }), _jsx(Body, { children: on === i || leaving === i ? (typeof sh.body === 'function' ? sh.body() : sh.body) : null }), (sh.foot != null || sh.footLink != null) && _jsx(Foot, { link: sh.footLink, children: sh.foot })] }, i)))] }), children] }));
|
|
37
38
|
}
|
|
@@ -7,7 +7,8 @@ import { Page } from '../scaffold/Page.js';
|
|
|
7
7
|
import { Pages, slideTo } from '../scaffold/Centre.js';
|
|
8
8
|
import { Pager } from '../scaffold/Pager.js';
|
|
9
9
|
import { ActionBar, ActionBarTier } from '../scaffold/ActionBar.js';
|
|
10
|
-
import {
|
|
10
|
+
import { Head } from '../parts/Head.js';
|
|
11
|
+
import { Words } from '../parts/Words.js';
|
|
11
12
|
import { Text } from '../type/Text.js';
|
|
12
13
|
import { IconBtn } from '../actions/IconBtn.js';
|
|
13
14
|
import { word } from '../words.js';
|
|
@@ -51,5 +52,5 @@ export function Walkthrough({ pages, at: atProp, defaultAt = 0, onAtChange, cta,
|
|
|
51
52
|
const pager = (_jsxs(_Fragment, { children: [_jsx(IconBtn, { icon: "arrow-left", label: backLabel, disabled: at === 0, onClick: () => go(at - 1) }), _jsx(IconBtn, { invert: true, icon: "arrow-right", label: nextLabel, disabled: at === count - 1, onClick: () => go(at + 1) })] }));
|
|
52
53
|
const dots = count > 1 ? _jsx(Pager, { at: at + 1, of: count }) : null;
|
|
53
54
|
const bottom = dots || controls.length || ctaLink != null ? (_jsxs(ActionBar, { variant: "tiers", children: [dots, controls.map((c, i) => _jsx(ActionBarTier, { children: c }, i)), _jsx(ActionBarTier, { pager: true, children: custom ? row : pager }), ctaLink != null && _jsx(ActionBarTier, { className: "action-bar__tier--link", children: ctaLink })] })) : (_jsx(ActionBar, { variant: "pager", children: custom ? row : pager }));
|
|
54
|
-
return (_jsxs(Screen, { fill: true, bottom: bottom, ...rest, children: [_jsx(Step, { ref: step, children: _jsx(Pages, { ...swipe, children: pages.map((p, i) => (_jsxs(Page, { "aria-current": i === at ? 'step' : undefined, inert: i !== at || undefined, children: [p.media, _jsx(
|
|
55
|
+
return (_jsxs(Screen, { fill: true, bottom: bottom, ...rest, children: [_jsx(Step, { ref: step, children: _jsx(Pages, { ...swipe, children: pages.map((p, i) => (_jsxs(Page, { "aria-current": i === at ? 'step' : undefined, inert: i !== at || undefined, children: [p.media, _jsx(Head, { children: _jsx(Words, { title: p.heading, level: "h1" }) }), typeof p.body === 'string' ? _jsx(Text, { level: "body", children: p.body }) : p.body] }, i))) }) }), children] }));
|
|
55
56
|
}
|
package/docs/reference.md
CHANGED
|
@@ -42,7 +42,7 @@ npm test # Playwright: the tokens and the step/sheet/centre geometry
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
```tsx
|
|
45
|
-
import { Screen, TopBar, Step, Page, Sheet,
|
|
45
|
+
import { Screen, TopBar, Step, Page, Sheet, Head, Words, Body, Field, Pin, ActionBar, Btn } from '@usableapp/cardds';
|
|
46
46
|
import '@usableapp/cardds/dist/cardds.css';
|
|
47
47
|
import './theme.css'; // the project's own look, after the system's — see "A project's theme"
|
|
48
48
|
```
|
|
@@ -95,7 +95,7 @@ npm i @usableapp/cardds
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
```tsx
|
|
98
|
-
import { Card,
|
|
98
|
+
import { Card, Head, Words, Btn } from '@usableapp/cardds';
|
|
99
99
|
import '@usableapp/cardds/dist/cardds.css'; // the CSS, fonts resolve from the package
|
|
100
100
|
import './theme.css'; // the project's look, after cardds.css
|
|
101
101
|
```
|
|
@@ -177,7 +177,7 @@ A scaffolded tsx may import `./_story` or `./_*Art`: story glue, not in the pack
|
|
|
177
177
|
### React adapter — how a component is written
|
|
178
178
|
|
|
179
179
|
`src/<group>/<Name>.tsx`, one export per class family; sub-parts are their own
|
|
180
|
-
components (`
|
|
180
|
+
components (`Head`, `KvRow`, `TrackStep`, `Body`). A component
|
|
181
181
|
chooses classes from props (`tone`, `state`, `primary`, `sm`…) and lays the
|
|
182
182
|
contract's children in order. A component that has STATE is controlled like an input
|
|
183
183
|
(`state` / `open` / `value` / `flipped` / `selected` + its `on…`, or a `default…` twin), keeps its own behaviour
|
|
@@ -188,6 +188,37 @@ wrapper follows. **Claude Design** builds with these components through
|
|
|
188
188
|
stories, the conventions the design agent reads) — a new component needs a
|
|
189
189
|
preview story there.
|
|
190
190
|
|
|
191
|
+
### Outer and inner — a paper holds rows
|
|
192
|
+
|
|
193
|
+
An outer is the paper and how it behaves: a `Card`, a `Sheet`, a stack sheet (a `Card` in a `SheetStack`), a `Modal`.
|
|
194
|
+
Inside every outer stand the same rows, top to bottom: a `Head` (what this is, and the one thing beside it), a `Body`
|
|
195
|
+
(what the member reads or fills in, the part of a sheet that scrolls) and a `Foot` (what the member can do next).
|
|
196
|
+
A line inside a head or a row is made of `Lead` · `Words` · `End`, the same parts a `Row` and a `Card row` are made of.
|
|
197
|
+
To change the behaviour, change the outer and keep what is inside: the same children read the same in a card and a sheet.
|
|
198
|
+
An outer governs an inner only through the inner's knobs (`--head-*`, `--body-*`, `--foot-*`, `--words-*`) or a rule in
|
|
199
|
+
`css/parts.css` that names both (`.sheet > .foot`), never through a second component (Lh 2026-10-21).
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<Card>
|
|
203
|
+
<Head><Words title="Sunday market" meta="Saturday · 07:00" /><Chip>3 new</Chip></Head>
|
|
204
|
+
<Text level="body">Neighbours trade what they grew.</Text>
|
|
205
|
+
<Foot><Btn primary>Join</Btn></Foot>
|
|
206
|
+
</Card>
|
|
207
|
+
|
|
208
|
+
<Sheet defaultState="half">
|
|
209
|
+
<Head><Words title="Sunday market" meta="Saturday · 07:00" /><Chip>3 new</Chip></Head>
|
|
210
|
+
<Body><Text level="body">Neighbours trade what they grew.</Text></Body>
|
|
211
|
+
<Foot><Btn primary block>Join</Btn></Foot>
|
|
212
|
+
</Sheet>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A card's content stands straight in it, between the head and the foot; a sheet's and a modal's content stands in its
|
|
216
|
+
`Body`, the one part that scrolls (`cardds check` names a control written straight in a `Sheet` or a `Modal`). `ModalAsk`
|
|
217
|
+
is one piece, the question and its answers. The templates take data and draw the parts themselves (`StackSheet title tabs foot`,
|
|
218
|
+
`Home sheets`, `CallSheet`, `Walkthrough pages`). `cardds get Card` lists the parts a card holds; `cardds get Head` the outers it fits.
|
|
219
|
+
A screen written on the retired names (`CardHead`, `SheetHead`, `SheetBody`, `ModalBody`, `CardFoot`, `RowLead`, `RowBody`, `RowEnd`,
|
|
220
|
+
`CardThumb`, `CardBody`, `CardEnd`, `Card heading caption end level foot footLink footRound`) moves with `npx cardds codemod-parts <files>`.
|
|
221
|
+
|
|
191
222
|
## Sheet stack markup
|
|
192
223
|
|
|
193
224
|
The sheet stack (`.sheet-stack`, `stack.css` — the menu, see *Anatomy*) is the
|
|
@@ -198,9 +229,9 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
198
229
|
```html
|
|
199
230
|
<div class="sheet-stack sheet-stack--tap"> <!-- drop --tap for a static stack -->
|
|
200
231
|
<article class="card card--2">
|
|
201
|
-
<div class="
|
|
232
|
+
<div class="head">
|
|
202
233
|
<button class="icon-btn icon-btn--sm sheet-stack__back" aria-label="Back">…</button>
|
|
203
|
-
<h2 class="t-h2 t-caps">Shopping</h2>
|
|
234
|
+
<h2 class="words__title t-h2 t-caps">Shopping</h2>
|
|
204
235
|
<span class="icon-row">…</span> <!-- optional trailing icons -->
|
|
205
236
|
</div>
|
|
206
237
|
…the sheet's own content…
|
|
@@ -279,7 +310,7 @@ of `Screen`:
|
|
|
279
310
|
covers what is under it — on purpose, nothing is added to the content to make room. The keyboard lifts the layer (`--kb`).
|
|
280
311
|
- **The bar is INSIDE the screen** — `bottom`, never a sibling after `</Screen>`, never pinned with a hand-set `position`.
|
|
281
312
|
A `FloatBar` in `bottom` floats the same way.
|
|
282
|
-
- **A screen with a `Sheet` has no bottom.** The sheet finishes on its own `
|
|
313
|
+
- **A screen with a `Sheet` has no bottom.** The sheet finishes on its own `Foot`; `Screen` warns once when both
|
|
283
314
|
are there.
|
|
284
315
|
- **Checked:** `validateScreen` (`scripts/validate.mjs`, run on every story and template by
|
|
285
316
|
`tests/composition.spec.js`) fails a bar after `</Screen>` or a pinned bar, warns on a Sheet + ActionBar.
|
|
@@ -297,7 +328,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
297
328
|
<!-- reads: the words only -->
|
|
298
329
|
<section class="step">
|
|
299
330
|
<section class="page">
|
|
300
|
-
<div class="
|
|
331
|
+
<div class="head"><h2 class="words__title t-h2">…</h2></div>
|
|
301
332
|
<p class="t-body">…</p>
|
|
302
333
|
</section>
|
|
303
334
|
</section>
|
|
@@ -308,12 +339,12 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
308
339
|
<section class="page">…the words…</section>
|
|
309
340
|
<article class="card sheet sheet--half">
|
|
310
341
|
<button class="sheet__handle" type="button" aria-label="ปรับความสูง"></button>
|
|
311
|
-
<div class="
|
|
312
|
-
<div class="
|
|
342
|
+
<div class="head"><h2 class="words__title t-h2">…</h2><span class="chip">…</span></div>
|
|
343
|
+
<div class="body">
|
|
313
344
|
…cardds controls (.field / .pin / .composer / .rows + .check / .segment /
|
|
314
345
|
.chip-grid), or small content cards (.card--sm)…
|
|
315
346
|
</div>
|
|
316
|
-
<div class="
|
|
347
|
+
<div class="foot"><button class="btn btn--primary btn--block">…the one button that finishes the sheet…</button></div>
|
|
317
348
|
</article>
|
|
318
349
|
</section>
|
|
319
350
|
<!-- no .action-bar: a bar is the base's, never in front of a sheet -->
|
|
@@ -332,10 +363,10 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
332
363
|
- **`.page` first, `.sheet` last.** The block carries the step's words
|
|
333
364
|
ON THE BASE — no frame, no area colour (it was a `.card` until 2026-09-16;
|
|
334
365
|
Lh: a step reads like a page, not a card in a page). It keeps a card's
|
|
335
|
-
rhythm: a `.
|
|
336
|
-
**handle · head · body**: the handle, then a `.
|
|
366
|
+
rhythm: a `.head`, `--card-gap` between things. The sheet is
|
|
367
|
+
**handle · head · body**: the handle, then a `.head` title row (that
|
|
337
368
|
row is what `peek` shows — heading left, the contextual thing right), then
|
|
338
|
-
`.
|
|
369
|
+
a `.body` with what the member acts on. The handle and the head are
|
|
339
370
|
pinned; the body is the one thing that scrolls, to the card's own edges. The
|
|
340
371
|
handle is a line, or four dots (`mark="dots"`): both sit in the card's top
|
|
341
372
|
padding, the same distance from the edge, so the title stays put. `refreshing`
|
|
@@ -367,7 +398,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
367
398
|
the button at half and 3q as it does at full.
|
|
368
399
|
- **Layers: the base, the sheet over it, the top bar** (Lh 2026-09-20). The `.action-bar` belongs to the
|
|
369
400
|
BASE — the move of a page of words (a pager bar, or one wide button). It is never in front of a sheet. A
|
|
370
|
-
screen with a sheet finishes ON the sheet: a `.
|
|
401
|
+
screen with a sheet finishes ON the sheet: a `.foot`, the sheet's last child — ONE row, the page's
|
|
371
402
|
conclusion. The foot stands on the screen's bottom edge at half, 3q and full alike (a layer as tall as
|
|
372
403
|
the sheet, slid up by the share the sheet slid down — `sheet.css`), and is gone at peek. Nothing inside
|
|
373
404
|
the sheet's body commits on its own (a `Composer` there takes no `send`). This is how cardds's own screens are
|
|
@@ -415,7 +446,7 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
415
446
|
<div class="screen screen--fill">
|
|
416
447
|
<header class="topbar"><h1 class="t-h1">ธนาคารเวลายินดีต้อนรับ</h1></header>
|
|
417
448
|
<article class="card card--centre">
|
|
418
|
-
<div class="
|
|
449
|
+
<div class="head"><h2 class="words__title t-h2">ปรับขนาดตัวหนังสือ</h2></div>
|
|
419
450
|
<p class="t-body">…</p>
|
|
420
451
|
</article>
|
|
421
452
|
</div>
|
|
@@ -495,19 +526,28 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
495
526
|
sheet**. Content normally comes in cards; static data (a blurb, a legal
|
|
496
527
|
note, help steps) may sit on the base directly.
|
|
497
528
|
- **A card is head · content · foot.**
|
|
498
|
-
- **Head** (`.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
529
|
+
- **Head** (`.head`, the `Head` part): the first row, what this is and
|
|
530
|
+
the one thing beside it. Its children are, in order, an optional lead
|
|
531
|
+
(a `Lead`, a `StackBack`, a `JourneyBadge`), the `Words` (the title takes
|
|
532
|
+
the room), chips, and an end: hints, actions or a more-menu — the
|
|
533
|
+
contextual info — at the far right (`IconBtn sm`, `Chip`, `Link`, `End`).
|
|
534
|
+
With nothing at the far right the title runs all the way across. The title
|
|
535
|
+
wraps, no squeeze, up to three lines and is cut there with an ellipsis (Lh
|
|
536
|
+
2026-10-21; `--head-title-lines`, default 3). A date
|
|
503
537
|
(`JourneyBadge stretch`) beside it is as tall as the title block up to
|
|
504
|
-
its own three lines, at the head's top. The head is FLAT markup —
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
538
|
+
its own three lines, at the head's top. The head is FLAT markup — the
|
|
539
|
+
title (`h2.words__title`, `h1` with `level="h1"`), then an optional meta
|
|
540
|
+
as a span (`.words__meta`), then the far-right thing; no
|
|
541
|
+
wrapper element, no `<p>`: `Words` in a `Head` writes its title and its
|
|
542
|
+
meta as the head's own children. The head wraps, the meta takes the row
|
|
543
|
+
below (`order`), the gap tokens do the rest (Lh 2026-09-16). The
|
|
544
|
+
title's type is the knob `--words-title-font` (default the level's own
|
|
545
|
+
face), which a stack sheet, a sheet and a page set for their heads. `top`
|
|
546
|
+
keeps a tall title's control at the top; `centre` is the stage sheet's
|
|
547
|
+
head, a round button · the title over its meta · a round button.
|
|
508
548
|
- **Content**: whatever the card frames — rows, key-values, a gauge, a
|
|
509
549
|
paragraph. Frameless, per rule 9.
|
|
510
|
-
- **Foot** (`.
|
|
550
|
+
- **Foot** (`.foot`): the card's action buttons on one line. One
|
|
511
551
|
button takes the width; up to four share it in even parts; from three
|
|
512
552
|
on they drop their side padding so the labels keep the room. A panel of
|
|
513
553
|
up to four icon buttons (`IconBtnGroup`) takes the foot the same way; with
|
|
@@ -515,10 +555,10 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
515
555
|
as `Btn form` does — in a foot, a `Page`, a `Centre` or a `CtaPack` — and
|
|
516
556
|
with `form labelled` its `label` is SHOWN as words at its left, the two
|
|
517
557
|
centred as one line (Lh 2026-10-10).
|
|
518
|
-
- In React the head
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
558
|
+
- In React the head is the card's first child, a `<Head>` (`<Head><Words title meta /><Chip>…</Chip></Head>`),
|
|
559
|
+
and the foot its last, a `<Foot>` (`<Foot link={<Link sm>…</Link>}><Btn primary>…</Btn></Foot>`, `round` for a few
|
|
560
|
+
big round buttons), the content between them. The card finds its `Head` and its `Foot` by their type, so a
|
|
561
|
+
component of your own that wraps one is not found (Lh 2026-10-21: one head, body and foot for a card, a sheet and a stack sheet).
|
|
522
562
|
- **A card that goes on is pressed whole** (Lh 2026-10-09). A card whose ONLY
|
|
523
563
|
control is its foot's button, or the end button of a row card, is pressed
|
|
524
564
|
anywhere: a touch off the button runs that button's own action once, and the
|
|
@@ -566,7 +606,7 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
566
606
|
sheet.
|
|
567
607
|
- **The sheet is the paper the member fills in** (Lh 2026-10-18). Every
|
|
568
608
|
control the member fills in or picks stands on a sheet: a `Sheet`'s
|
|
569
|
-
`
|
|
609
|
+
`Body`, a `SheetStep`, or a stack sheet. That is `Field`, `Composer`
|
|
570
610
|
and `ComposerArea`, `Pin`, `Dropdown`, `Slider`, `Toggle`, `Check`,
|
|
571
611
|
`ChipPick`, `Calendar`, `DayStrip` and `Day`, `Picker`, `Mood`,
|
|
572
612
|
`AvatarPick`, `FileBtn`, `AddRow`, `MapPick`, a `Tile pick` and a
|
|
@@ -705,8 +745,7 @@ it up. cardds draws the paper; the pictures, their words and which stamp a membe
|
|
|
705
745
|
its third, 4:5, the gaps one `--screen-pad` both ways (`--stamp-book-fill-gap`), nothing at the row's sides; with `bare`
|
|
706
746
|
at 375 a stamp is 103.67 × 129.58. A `false` or `null` child holds no place.
|
|
707
747
|
- A stamp stands for a person in three more places. In a `Centre` — a `Stamp` or a `StampPlace` — it stands in the
|
|
708
|
-
middle and the words and link under it centre. In a card's head — `<Card
|
|
709
|
-
end={<Stamp bare …/>} foot={<Btn primary block>…</Btn>} />` — the words stand at the left and the stamp at the right,
|
|
748
|
+
middle and the words and link under it centre. In a card's head — `<Card foot={<Btn primary block>…</Btn>}><Head><Words title={name} meta={greeting} /><Stamp bare …/></Head></Card>` — the words stand at the left and the stamp at the right,
|
|
710
749
|
beside both lines; the card's one button presses it whole. In an `AvatarStack` it is a head, the small size
|
|
711
750
|
(`more="+9"` for the count).
|
|
712
751
|
- **`StampOn`** — a stamp stuck on a surface. `at` is the stamp's middle as a share of the surface (`{ x, y }`, 0 to 1),
|
|
@@ -860,7 +899,7 @@ The gallery's `PostScreens` page has the three finished screens (read, write, ke
|
|
|
860
899
|
state — the stack's rule, inherited. The box clips whatever slid below its
|
|
861
900
|
bottom edge, so a sheet at any height is still a card: rounded top, a
|
|
862
901
|
straight cut at the box's edge — and its shape is handle · head · body:
|
|
863
|
-
the handle and the title row are pinned, `.
|
|
902
|
+
the handle and the title row are pinned, its `.body` scrolls beneath
|
|
864
903
|
them, and the body's `scroll-padding-bottom` is the slid-under part (in
|
|
865
904
|
the sheet's own container units, `--_cover`), so a focused control is
|
|
866
905
|
revealed above the cut, not under it. `--sheet-half` is a share of the
|
|
@@ -889,7 +928,7 @@ question and hunts for its answer. Rules 11–13 serve that:
|
|
|
889
928
|
11. **Everything holds to edges.** Every element touches at least two edges —
|
|
890
929
|
the card's, the column's, or a line made by the thing above it (a row, the
|
|
891
930
|
avatar + name line). Words to READ are aligned start, values end (the
|
|
892
|
-
receipt: items left, prices right; `.
|
|
931
|
+
receipt: items left, prices right; `.head`, `.kv`, `.row`); centred
|
|
893
932
|
only what is not read — dots, a lone number, an icon, a mark. Empty room is
|
|
894
933
|
not filled with more content; a new edge is made instead (line the buttons
|
|
895
934
|
up with the avatar). One exception: a small control with its own words
|
|
@@ -908,14 +947,14 @@ question and hunts for its answer. Rules 11–13 serve that:
|
|
|
908
947
|
bolder, never a second colour.
|
|
909
948
|
14. **A frame says "tap me".** Inside a card, only a control draws its own
|
|
910
949
|
edge (a chip, a field, a pick tile, a small `Card sm` to pick). What is
|
|
911
|
-
only READ never gets a frame — no card in a card: a
|
|
950
|
+
only READ never gets a frame — no card in a card: a KvCell stands on
|
|
912
951
|
the card's ground, a note is a `Banner` (a light band, a rule above and
|
|
913
952
|
below, edge to edge), a count is words (`Text caption muted`), never a
|
|
914
953
|
chip. A chip or tile that looks pressable and is not lies.
|
|
915
954
|
15. **A component adjusts another only through its knobs.** A component that
|
|
916
955
|
holds another (a sheet holding a card, a stack holding a sheet) sets the
|
|
917
956
|
child's knobs (`.sheet { --card-pad: … }`), never the child's own
|
|
918
|
-
property (`.sheet .
|
|
957
|
+
property (`.sheet .head { color: … }`). The child's file is the one
|
|
919
958
|
place its look is written, so what a story shows is what a project gets.
|
|
920
959
|
Missing the knob? Add it in the child's file. No `!important` either, but
|
|
921
960
|
`[hidden]`'s. A component belongs to the file that writes its bare class
|
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
|
+
fits: c.fits ?? [],
|
|
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) }),
|