@usableapp/cardds 0.7.52 → 0.7.54
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 +18 -0
- package/README.md +19 -9
- package/css/sheet.css +44 -14
- package/css/stack.css +29 -1
- package/css/theme.template.css +1 -1
- package/css/tokens.css +5 -5
- package/dist/cardds.css +78 -20
- package/dist/cards/Card.d.ts +868 -6
- package/dist/cards/Card.js +43 -7
- package/dist/cards/CardHead.d.ts +1 -6
- package/dist/cards/CardHead.js +26 -1
- package/dist/choice/Slider.js +9 -13
- package/dist/forms/Composer.js +3 -1
- package/dist/forms/Field.js +3 -1
- package/dist/forms/FileBtn.js +3 -1
- package/dist/forms/Pin.js +3 -1
- package/dist/sheets/Sheet.d.ts +7 -1
- package/dist/sheets/Sheet.js +2 -2
- package/dist/sheets/SheetStack.d.ts +6 -5
- package/dist/sheets/SheetStack.js +60 -410
- package/dist/sheets/SheetSteps.js +9 -13
- package/dist/sheets/StackSheet.d.ts +4 -1
- package/dist/sheets/StackSheet.js +2 -2
- package/dist/sheets/stackCard.d.ts +31 -0
- package/dist/sheets/stackCard.js +389 -0
- package/dist/sheets/stepCursor.d.ts +12 -0
- package/dist/sheets/stepCursor.js +15 -0
- package/dist/templates/Home.d.ts +3 -1
- package/dist/templates/Home.js +2 -2
- package/package.json +1 -1
package/dist/cards/Card.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Children, createElement, isValidElement } from 'react';
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, createElement, isValidElement, useRef } from 'react';
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
4
|
import { warnOnce } from '../state.js';
|
|
5
5
|
import { CardHead } from './CardHead.js';
|
|
6
6
|
import { CardFoot } from './CardFoot.js';
|
|
7
|
+
import { StackHeadCtx, stackPaint, useStackCard } from '../sheets/stackCard.js';
|
|
7
8
|
/**
|
|
8
9
|
* Card — ONE boundary: head · content · foot. Cards are content the system offers (the words, the thing to read).
|
|
9
10
|
* The head and the foot are SLOTS: `heading` · `caption` · `end` (the card writes its CardHead first) and `foot` · `footLink`
|
|
@@ -11,7 +12,7 @@ import { CardFoot } from './CardFoot.js';
|
|
|
11
12
|
* boundary). The order is the card's, never the writer's. A head of its own shape: write `<CardHead>` as the first child instead.
|
|
12
13
|
* A page with one feature and one CTA may be a single card. Keep everything big.
|
|
13
14
|
*/
|
|
14
|
-
export function Card({ tone, sm, row, cover, fold, centre, band, as = 'article', heading, caption, end, level, foot, footLink, className, children, ...rest }) {
|
|
15
|
+
export function Card({ tone, sm, row, cover, fold, centre, band, as = 'article', heading, caption, end, level, foot, footLink, stack, className, style, children, onClick, ref, ...rest }) {
|
|
15
16
|
const head = heading != null || caption != null || end != null;
|
|
16
17
|
const tail = foot != null || footLink != null;
|
|
17
18
|
if (head || tail) {
|
|
@@ -21,9 +22,44 @@ export function Card({ tone, sm, row, cover, fold, centre, band, as = 'article',
|
|
|
21
22
|
if (tail && parts.some((c) => c.type === CardFoot))
|
|
22
23
|
warnOnce('card-foot-twice', 'cardds Card: `foot`/`footLink` AND a <CardFoot> child — one foot per card; use one way');
|
|
23
24
|
}
|
|
24
|
-
const
|
|
25
|
+
const own = useRef(null);
|
|
26
|
+
const swallow = useRef(false);
|
|
27
|
+
useStackCard(own, stack, swallow);
|
|
28
|
+
const painted = stackPaint(stack);
|
|
29
|
+
const setRef = (node) => {
|
|
30
|
+
own.current = node;
|
|
31
|
+
const passed = ref;
|
|
32
|
+
if (typeof passed === 'function')
|
|
33
|
+
passed(node);
|
|
34
|
+
else if (passed)
|
|
35
|
+
passed.current = node;
|
|
36
|
+
};
|
|
37
|
+
const click = (e) => {
|
|
38
|
+
onClick?.(e);
|
|
39
|
+
if (!stack || e.defaultPrevented || swallow.current)
|
|
40
|
+
return;
|
|
41
|
+
const target = e.target;
|
|
42
|
+
if (target.closest('.sheet-stack__back')) {
|
|
43
|
+
stack.onClose();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (stack.open && target.closest('.card__head > :is(h1, h2, h3)')) {
|
|
47
|
+
stack.onClose();
|
|
48
|
+
return;
|
|
49
|
+
} // the title folds it; the back stays the keyboard's way
|
|
50
|
+
if (!stack.open && !stack.before && !stack.after)
|
|
51
|
+
stack.onOpen();
|
|
52
|
+
};
|
|
53
|
+
const inner = (body) => (_jsx(StackHeadCtx.Provider, { value: stack ? (stack.before || stack.after ? -1 : 0) : null, children: body }));
|
|
54
|
+
const props = {
|
|
55
|
+
...rest,
|
|
56
|
+
ref: setRef,
|
|
57
|
+
className: cx('card', tone && tone !== 1 && `card--${tone}`, sm && 'card--sm', row && 'card--row', cover && 'card--cover', fold && 'card--fold', centre && 'card--centre', band && 'card--band', painted.className, className),
|
|
58
|
+
style: painted.style || style ? { ...painted.style, ...style } : undefined,
|
|
59
|
+
...(stack ? { 'aria-busy': painted.busy } : {}),
|
|
60
|
+
onClick: stack ? click : onClick,
|
|
61
|
+
};
|
|
25
62
|
if (!head && !tail)
|
|
26
|
-
return createElement(as, props, children);
|
|
27
|
-
return createElement(as, props, head && _jsx(CardHead, { heading: heading, caption: caption, end: end, level: level }, "head"),
|
|
28
|
-
tail && _jsx(CardFoot, { link: footLink, children: foot }, "foot"));
|
|
63
|
+
return createElement(as, props, inner(children));
|
|
64
|
+
return createElement(as, props, inner(_jsxs(_Fragment, { children: [head && _jsx(CardHead, { heading: heading, caption: caption, end: end, level: level }, "head"), Children.toArray(children), tail && _jsx(CardFoot, { link: footLink, children: foot }, "foot")] })));
|
|
29
65
|
}
|
package/dist/cards/CardHead.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
2
|
export interface CardHeadProps extends ComponentProps<'div'> {
|
|
3
3
|
/** the heading (a string becomes an h2 at level `t-h2`) — wraps freely, as many lines as the words need */
|
|
4
4
|
heading?: ReactNode;
|
|
@@ -11,9 +11,4 @@ export interface CardHeadProps extends ComponentProps<'div'> {
|
|
|
11
11
|
/** the heading level to render the string heading with (h2 default; h1 for the one-feature page) */
|
|
12
12
|
level?: 'h1' | 'h2' | 'display';
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* CardHead — the card's head: the heading on the left takes the room, the contextual info hugs the far right.
|
|
16
|
-
* Use `heading` (+ `caption`) and `end` for the standard head, or pass children for a custom one
|
|
17
|
-
* (the FIRST child takes the room). Flat markup: no wrapper element, no <p> — the caption is a span on a row of its own (card.css). Never put buttons here — they go in the CardFoot.
|
|
18
|
-
*/
|
|
19
14
|
export declare function CardHead({ heading, caption, end, top, level, className, children, ...rest }: CardHeadProps): import("react").JSX.Element;
|
package/dist/cards/CardHead.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, cloneElement, isValidElement, useContext } from 'react';
|
|
2
3
|
import { cx } from '../cx.js';
|
|
4
|
+
import { Text } from '../type/Text.js';
|
|
5
|
+
import { StackHeadCtx } from '../sheets/stackCard.js';
|
|
3
6
|
/**
|
|
4
7
|
* CardHead — the card's head: the heading on the left takes the room, the contextual info hugs the far right.
|
|
5
8
|
* Use `heading` (+ `caption`) and `end` for the standard head, or pass children for a custom one
|
|
6
9
|
* (the FIRST child takes the room). Flat markup: no wrapper element, no <p> — the caption is a span on a row of its own (card.css). Never put buttons here — they go in the CardFoot.
|
|
7
10
|
*/
|
|
11
|
+
const onTitleKey = (e) => {
|
|
12
|
+
if (e.key !== 'Enter' && e.key !== ' ')
|
|
13
|
+
return;
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
e.currentTarget.click();
|
|
16
|
+
};
|
|
17
|
+
/** the stack's title is the keyboard's way in. The heading renders the tab stop; nothing sets it on the node afterwards */
|
|
18
|
+
function markTitle(node, tab) {
|
|
19
|
+
if (!isValidElement(node))
|
|
20
|
+
return node;
|
|
21
|
+
const el = node;
|
|
22
|
+
const level = el.type === Text ? el.props.level : null;
|
|
23
|
+
const intrinsic = typeof el.type === 'string' && /^h[1-3]$/.test(el.type);
|
|
24
|
+
if (level !== 'h1' && level !== 'h2' && level !== 'display' && !intrinsic)
|
|
25
|
+
return node;
|
|
26
|
+
return cloneElement(el, {
|
|
27
|
+
tabIndex: tab,
|
|
28
|
+
onKeyDown: (e) => { el.props.onKeyDown?.(e); if (!e.defaultPrevented)
|
|
29
|
+
onTitleKey(e); },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
8
32
|
export function CardHead({ heading, caption, end, top, level = 'h2', className, children, ...rest }) {
|
|
33
|
+
const tab = useContext(StackHeadCtx);
|
|
9
34
|
const H = level === 'h1' ? 'h1' : 'h2';
|
|
10
|
-
return (_jsxs("div", { className: cx('card__head', top && 'card__head--top', className), ...rest, children: [heading != null && _jsx(H, { className: `t-${level}`, children: heading }), caption != null && _jsx("span", { className: "t-caption t-muted", children: caption }), children, end] }));
|
|
35
|
+
return (_jsxs("div", { className: cx('card__head', top && 'card__head--top', className), ...rest, children: [heading != null && _jsx(H, { className: `t-${level}`, tabIndex: tab ?? undefined, onKeyDown: tab != null ? onTitleKey : undefined, children: heading }), caption != null && _jsx("span", { className: "t-caption t-muted", children: caption }), tab == null ? children : Children.map(children, (c) => markTitle(c, tab)), end] }));
|
|
11
36
|
}
|
package/dist/choice/Slider.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useState } from 'react';
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
|
+
import { useStepCursor } from '../sheets/stepCursor.js';
|
|
5
|
+
const share = (value, min, max) => (max > min ? Math.min(1, Math.max(0, (value - min) / (max - min))) : 0);
|
|
4
6
|
/** Slider — one value along a hairline rail with a 48px knob; the whole range is thumb-tall. */
|
|
5
7
|
export function Slider({ label, glyph, className, style, onInput, ...input }) {
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return;
|
|
13
|
-
const min = Number(r.min) || 0, max = Number(r.max) || 100;
|
|
14
|
-
box.current.style.setProperty('--slider-at', String(max > min ? Math.min(1, Math.max(0, (Number(r.value) - min) / (max - min))) : 0));
|
|
15
|
-
};
|
|
16
|
-
useEffect(place); // after every render: a controlled value, a new min / max
|
|
17
|
-
return (_jsxs("label", { ref: box, className: cx('slider', className), style: style, children: [label != null && _jsx("span", { className: "slider__label", children: label }), glyph != null && _jsx("span", { className: "slider__glyph", "aria-hidden": "true", children: glyph }), _jsx("input", { ref: range, className: "slider__range", type: "range", onInput: (e) => { place(); onInput?.(e); }, ...input })] }));
|
|
8
|
+
const cursor = useStepCursor();
|
|
9
|
+
const min = Number(input.min) || 0, max = Number(input.max) || 100;
|
|
10
|
+
const [live, setLive] = useState(Number(input.defaultValue ?? input.value ?? min));
|
|
11
|
+
const value = input.value != null && input.value !== '' ? Number(input.value) : live;
|
|
12
|
+
return (_jsxs("label", { className: cx('slider', className), style: { ...style, '--slider-at': String(share(value, min, max)) }, children: [label != null && _jsx("span", { className: "slider__label", children: label }), glyph != null && _jsx("span", { className: "slider__glyph", "aria-hidden": "true", children: glyph }), _jsx("input", { className: "slider__range", type: "range", onInput: (e) => { if (input.value == null)
|
|
13
|
+
setLive(Number(e.currentTarget.value)); onInput?.(e); }, ...input, ...cursor })] }));
|
|
18
14
|
}
|
package/dist/forms/Composer.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
|
+
import { useStepCursor } from '../sheets/stepCursor.js';
|
|
3
4
|
/** Composer — a big write-in area (display type) with a bottom bar: icon · hint · send. The write-in step's sheet content. */
|
|
4
5
|
export function Composer({ hint, lead, send, className, style, ...textarea }) {
|
|
5
|
-
|
|
6
|
+
const cursor = useStepCursor();
|
|
7
|
+
return (_jsxs("div", { className: cx('composer', className), style: style, children: [_jsx("textarea", { className: "composer__input", ...textarea, ...cursor }), _jsxs("div", { className: "composer__bar", children: [lead, hint != null && _jsx("span", { className: "composer__hint", children: hint }), send] })] }));
|
|
6
8
|
}
|
package/dist/forms/Field.js
CHANGED
|
@@ -4,6 +4,7 @@ import { watchSize } from '../state.js';
|
|
|
4
4
|
import { formatOk, useCheck } from './check.js';
|
|
5
5
|
import { cx } from '../cx.js';
|
|
6
6
|
import { Icon } from '../type/Icon.js';
|
|
7
|
+
import { useStepCursor } from '../sheets/stepCursor.js';
|
|
7
8
|
/** the digits of `v`, written into `mask`; a mark is written only when a digit comes after it */
|
|
8
9
|
export function applyMask(v, mask) {
|
|
9
10
|
const d = v.replace(/\D/g, '');
|
|
@@ -36,6 +37,7 @@ export function Field({ icon, action, label, error, format, digits, formatError,
|
|
|
36
37
|
};
|
|
37
38
|
const { say } = useCheck((strict) => { const w = verdict(strict); if (w != null)
|
|
38
39
|
setBad(true); return w; }, () => { ref.current?.focus(); ref.current?.select(); });
|
|
40
|
+
const cursor = useStepCursor();
|
|
39
41
|
/* a placeholder longer than the box SHRINKS to show whole (Lh 2026-09-25): its width is measured in the input's own font,
|
|
40
42
|
and `--_ph-fit` (the share that fits, ≤ 1) scales the placeholder's size — again whenever the box or the font changes */
|
|
41
43
|
useLayoutEffect(() => {
|
|
@@ -70,7 +72,7 @@ export function Field({ icon, action, label, error, format, digits, formatError,
|
|
|
70
72
|
}
|
|
71
73
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- when the app's error comes or changes
|
|
72
74
|
}, [error]);
|
|
73
|
-
return (_jsxs("label", { className: cx('field', action != null && 'field--action', wrong && 'field--error', className), style: style, children: [icon && _jsx(Icon, { name: icon, size: "sm" }), _jsx("input", { ref: ref, "aria-label": label, "aria-invalid": wrong || undefined, ...input, onBlur: (e) => { onBlur?.(e); if (!checked)
|
|
75
|
+
return (_jsxs("label", { className: cx('field', action != null && 'field--action', wrong && 'field--error', className), style: style, children: [icon && _jsx(Icon, { name: icon, size: "sm" }), _jsx("input", { ref: ref, "aria-label": label, "aria-invalid": wrong || undefined, ...input, ...cursor, onBlur: (e) => { onBlur?.(e); if (!checked)
|
|
74
76
|
return; const w = verdict(false); setBad(w != null); say(w); }, onChange: (e) => {
|
|
75
77
|
if (mask) {
|
|
76
78
|
const el = e.currentTarget, before = el.value.slice(0, el.selectionStart ?? el.value.length).replace(/\D/g, '').length;
|
package/dist/forms/FileBtn.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
|
+
import { useStepCursor } from '../sheets/stepCursor.js';
|
|
3
4
|
/**
|
|
4
5
|
* FileBtn — a Btn that opens the device's file picker: the button IS the label of a hidden file input, so the
|
|
5
6
|
* whole button is the tap target and nothing is forwarded by script. `accept="image/*" capture="environment"`
|
|
6
7
|
* opens the camera; without `capture` the gallery. The picked file arrives in `onChange` like any input.
|
|
7
8
|
*/
|
|
8
9
|
export function FileBtn({ primary, quiet, block, className, children, ...input }) {
|
|
9
|
-
|
|
10
|
+
const cursor = useStepCursor();
|
|
11
|
+
return (_jsxs("label", { className: cx('btn', primary && 'btn--primary', quiet && 'btn--quiet', block && 'btn--block', className), children: [_jsx("input", { type: "file", className: "t-sr", ...input, ...cursor }), children] }));
|
|
10
12
|
}
|
package/dist/forms/Pin.js
CHANGED
|
@@ -4,6 +4,7 @@ import { useControlled, useLatest } from '../state.js';
|
|
|
4
4
|
import { cx } from '../cx.js';
|
|
5
5
|
import { useCheck } from './check.js';
|
|
6
6
|
import { word } from '../words.js';
|
|
7
|
+
import { useStepCursor } from '../sheets/stepCursor.js';
|
|
7
8
|
const RESET_MS = 900; // a code that does not match: red + shake this long, then the cells empty for a new try
|
|
8
9
|
/**
|
|
9
10
|
* Pin — big code entry, one digit per pill; a filled cell turns accent. Lives in a Sheet.
|
|
@@ -13,6 +14,7 @@ const RESET_MS = 900; // a code that does not match: red + shake this long, then
|
|
|
13
14
|
*/
|
|
14
15
|
export function Pin({ length = 6, value, defaultValue = '', label = word('digit'), onChange, autoFocus, error, match, formatError, secret, className, onAnimationEnd, ...rest }) {
|
|
15
16
|
const ref = useRef(null);
|
|
17
|
+
const cursor = useStepCursor();
|
|
16
18
|
const [typed, request] = useControlled(value, defaultValue, onChange);
|
|
17
19
|
const code = typed.replace(/\D/g, '').slice(0, length);
|
|
18
20
|
const cell = (i) => ref.current?.querySelectorAll('.pin__cell')[i];
|
|
@@ -171,7 +173,7 @@ export function Pin({ length = 6, value, defaultValue = '', label = word('digit'
|
|
|
171
173
|
setFresh(null); onAnimationEnd?.(e); }, children: Array.from({ length }, (_, i) => (
|
|
172
174
|
// no maxLength: the browser would cut a pasted code before this ever saw it. A cell holds one digit — typing into a full one
|
|
173
175
|
// replaces it, so its digit is selected whenever the cell takes the focus
|
|
174
|
-
_jsx("input", { className: cx('pin__cell', secret && fresh === i && 'is-fresh'), inputMode: "numeric", pattern: "[0-9]*", placeholder: " ", value: code[i] ?? '', "aria-label": `${label} ${i + 1}`, "aria-invalid": wrong || undefined, autoComplete: i === 0 && !secret ? 'one-time-code' : 'off', onChange: input(i), onPaste: paste, onKeyDown: key(i), onFocus: (e) => {
|
|
176
|
+
_jsx("input", { className: cx('pin__cell', secret && fresh === i && 'is-fresh'), inputMode: "numeric", pattern: "[0-9]*", placeholder: " ", value: code[i] ?? '', "aria-label": `${label} ${i + 1}`, "aria-invalid": wrong || undefined, ...(i === 0 ? cursor : undefined), autoComplete: i === 0 && !secret ? 'one-time-code' : 'off', onChange: input(i), onPaste: paste, onKeyDown: key(i), onFocus: (e) => {
|
|
175
177
|
if (!restart.current)
|
|
176
178
|
e.currentTarget.select();
|
|
177
179
|
if (performance.now() - tapped.current > 700 && window.matchMedia('(pointer: coarse)').matches && !keyboardUp())
|
package/dist/sheets/Sheet.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface SheetProps extends Omit<ComponentProps<'article'>, 'onChange'>
|
|
|
21
21
|
states?: string | readonly Resting[];
|
|
22
22
|
/** the grab handle; `false` for a sheet with no handle, a string sets its accessible name */
|
|
23
23
|
handle?: boolean | string;
|
|
24
|
+
/** the grab mark. `line` (default) or `dots`: both sit in the card's top padding, the same distance from the edge, so the title does not move */
|
|
25
|
+
mark?: 'line' | 'dots';
|
|
26
|
+
/** the card tone slot, as `Card tone` — 2 and up add `card--N`. The head shows it; with `paper` the content does not */
|
|
27
|
+
tone?: 1 | 2 | 3 | 4 | 5;
|
|
28
|
+
/** the content is white paper and the head keeps the sheet's colour. Omit it and the content is the same colour as the head */
|
|
29
|
+
paper?: boolean;
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* Sheet — the SINGLE sheet: one card as a bottom sheet holding what the member ACTS on. Always the full height of its
|
|
@@ -43,4 +49,4 @@ export interface SheetProps extends Omit<ComponentProps<'article'>, 'onChange'>
|
|
|
43
49
|
* crossing and never one continuous move, whatever the app's wiring (both states set in one render, or the next raised from the
|
|
44
50
|
* first's `onSettled`) — the app adds no timer.
|
|
45
51
|
*/
|
|
46
|
-
export declare function Sheet({ state, defaultState, onStateChange, onSettled, states, handle, className, children, onFocus, ...rest }: SheetProps): import("react").JSX.Element;
|
|
52
|
+
export declare function Sheet({ state, defaultState, onStateChange, onSettled, states, handle, mark, tone, paper, className, children, onFocus, ...rest }: SheetProps): import("react").JSX.Element;
|
package/dist/sheets/Sheet.js
CHANGED
|
@@ -28,7 +28,7 @@ const RANK = { away: 0, peek: 1, half: 2, '3q': 3, full: 4 };
|
|
|
28
28
|
* crossing and never one continuous move, whatever the app's wiring (both states set in one render, or the next raised from the
|
|
29
29
|
* first's `onSettled`) — the app adds no timer.
|
|
30
30
|
*/
|
|
31
|
-
export function Sheet({ state, defaultState = 'half', onStateChange, onSettled, states, handle = true, className, children, onFocus, ...rest }) {
|
|
31
|
+
export function Sheet({ state, defaultState = 'half', onStateChange, onSettled, states, handle = true, mark = 'line', tone, paper, className, children, onFocus, ...rest }) {
|
|
32
32
|
const [current, request] = useControlled(state, defaultState, onStateChange);
|
|
33
33
|
const ref = useRef(null);
|
|
34
34
|
const handleRef = useRef(null);
|
|
@@ -120,5 +120,5 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
120
120
|
return; // the click that follows a drag is not a tap
|
|
121
121
|
request(current === 'peek' ? (allowed().includes(home.current) ? home.current : allowed().find((s) => s !== 'peek') ?? 'half') : 'peek', 'tap');
|
|
122
122
|
};
|
|
123
|
-
return (_jsxs("article", { ref: ref, className: cx('card sheet', `sheet--${shown}`, className), inert: current === 'away' || held || undefined, onFocus: focus, ...rest, children: [handle !== false && (_jsx("button", { ref: handleRef, className:
|
|
123
|
+
return (_jsxs("article", { ref: ref, className: cx('card sheet', tone && tone !== 1 && `card--${tone}`, paper && 'sheet--paper', `sheet--${shown}`, className), inert: current === 'away' || held || undefined, onFocus: focus, ...rest, children: [handle !== false && (_jsx("button", { ref: handleRef, className: cx('sheet__handle', mark === 'dots' && 'sheet__handle--dots'), type: "button", "aria-label": typeof handle === 'string' ? handle : word('resize'), onClick: tap, onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up })), children] }));
|
|
124
124
|
}
|
|
@@ -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 */
|
|
@@ -28,9 +30,8 @@ export interface SheetStackProps extends ComponentProps<'div'> {
|
|
|
28
30
|
* with a picture over the menu); the `base` slot is rendered after the sheets, so it never takes a sheet's slot. This is the sheet STACK; a single bottom sheet is `Sheet`.
|
|
29
31
|
*
|
|
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
|
-
* the phone's back button can open and close a section.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); it sets classes, never a size.
|
|
33
|
+
* the phone's back button can open and close a section. WHAT the sheet does with that, and with `refreshing`, lives on the card (`stack`):
|
|
34
|
+
* the place classes, the grip, the pull. The stack only says which index. Sheets are rigid — the open one slides up, the ones after it leave
|
|
35
|
+
* below, the ones before tuck under nearest-first; closing reverses in two beats. Only `translate` (stack.css); never a size.
|
|
35
36
|
*/
|
|
36
|
-
export declare function SheetStack({ mode, open, defaultOpen, onOpenChange, onRefresh, fanned, closed, base, className, children,
|
|
37
|
+
export declare function SheetStack({ mode, open, defaultOpen, onOpenChange, onRefresh, refreshing, fanned, closed, base, className, children, ...rest }: SheetStackProps): import("react").JSX.Element;
|