@usableapp/cardds 0.7.66 → 0.7.67
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 +14 -0
- package/README.md +60 -0
- package/cardds.css +2 -0
- package/css/actions.css +11 -11
- package/css/base.css +22 -0
- package/css/post.css +326 -0
- package/css/stamp.css +182 -0
- package/css/step.css +3 -0
- package/css/sticky.css +4 -0
- package/dist/cardds.css +552 -11
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/post/PostBox.d.ts +34 -0
- package/dist/post/PostBox.js +34 -0
- package/dist/post/PostDesk.d.ts +68 -0
- package/dist/post/PostDesk.js +81 -0
- package/dist/post/PostLetter.d.ts +52 -0
- package/dist/post/PostLetter.js +53 -0
- package/dist/post/Postmark.d.ts +12 -0
- package/dist/post/Postmark.js +9 -0
- package/dist/post/postCorner.d.ts +5 -0
- package/dist/post/postCorner.js +8 -0
- package/dist/scaffold/Page.d.ts +3 -1
- package/dist/scaffold/Page.js +2 -2
- package/dist/stamp/Stamp.d.ts +32 -0
- package/dist/stamp/Stamp.js +21 -0
- package/dist/stamp/StampBook.d.ts +19 -0
- package/dist/stamp/StampBook.js +17 -0
- package/dist/stamp/StampOn.d.ts +25 -0
- package/dist/stamp/StampOn.js +69 -0
- package/dist/sticky/Sticky.d.ts +3 -1
- package/dist/sticky/Sticky.js +2 -2
- package/dist/type/Mark.d.ts +11 -0
- package/dist/type/Mark.js +9 -0
- package/dist/type/icons.d.ts +1 -1
- package/dist/type/icons.js +2 -2
- package/dist/words.d.ts +1 -1
- package/dist/words.js +4 -2
- package/icons.svg +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { cx } from './cx.js';
|
|
|
4
4
|
export * from './type/Icon.js';
|
|
5
5
|
export { ICON_NAMES } from './type/icons.js';
|
|
6
6
|
export * from './type/Text.js';
|
|
7
|
+
export * from './type/Mark.js';
|
|
7
8
|
export { setTextScale, controlsFor } from './type/textScale.js';
|
|
8
9
|
export { setWords, word, type WordKey } from './words.js';
|
|
9
10
|
export * from './scaffold/Screen.js';
|
|
@@ -78,6 +79,13 @@ export * from './sticky/StickyDate.js';
|
|
|
78
79
|
export * from './sticky/StickyField.js';
|
|
79
80
|
export * from './sticky/StickyPick.js';
|
|
80
81
|
export * from './sticky/StickyThread.js';
|
|
82
|
+
export * from './stamp/Stamp.js';
|
|
83
|
+
export * from './stamp/StampBook.js';
|
|
84
|
+
export * from './stamp/StampOn.js';
|
|
85
|
+
export * from './post/Postmark.js';
|
|
86
|
+
export * from './post/PostLetter.js';
|
|
87
|
+
export * from './post/PostDesk.js';
|
|
88
|
+
export * from './post/PostBox.js';
|
|
81
89
|
export * from './media/Quote.js';
|
|
82
90
|
export * from './media/Postcard.js';
|
|
83
91
|
export * from './media/MapArea.js';
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { cx } from './cx.js';
|
|
|
8
8
|
export * from './type/Icon.js';
|
|
9
9
|
export { ICON_NAMES } from './type/icons.js';
|
|
10
10
|
export * from './type/Text.js';
|
|
11
|
+
export * from './type/Mark.js';
|
|
11
12
|
export { setTextScale, controlsFor } from './type/textScale.js';
|
|
12
13
|
export { setWords, word } from './words.js';
|
|
13
14
|
export * from './scaffold/Screen.js';
|
|
@@ -82,6 +83,13 @@ export * from './sticky/StickyDate.js';
|
|
|
82
83
|
export * from './sticky/StickyField.js';
|
|
83
84
|
export * from './sticky/StickyPick.js';
|
|
84
85
|
export * from './sticky/StickyThread.js';
|
|
86
|
+
export * from './stamp/Stamp.js';
|
|
87
|
+
export * from './stamp/StampBook.js';
|
|
88
|
+
export * from './stamp/StampOn.js';
|
|
89
|
+
export * from './post/Postmark.js';
|
|
90
|
+
export * from './post/PostLetter.js';
|
|
91
|
+
export * from './post/PostDesk.js';
|
|
92
|
+
export * from './post/PostBox.js';
|
|
85
93
|
export * from './media/Quote.js';
|
|
86
94
|
export * from './media/Postcard.js';
|
|
87
95
|
export * from './media/MapArea.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
export interface PostBoxProps extends ComponentProps<'ul'> {
|
|
3
|
+
/** CONTROLLED: the `id` of the card the box is parted at; `null` = closed, only the front card (the last one) shows whole — with `onOpenChange` */
|
|
4
|
+
open?: string | null;
|
|
5
|
+
/** UNCONTROLLED: the card it starts parted at (default none: the front card) */
|
|
6
|
+
defaultOpen?: string | null;
|
|
7
|
+
/** an edge was pressed: the card it asks to part at — `null` when the open card's own edge was pressed (the box closes back to the front card) */
|
|
8
|
+
onOpenChange?: (open: string | null) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* PostBox — the postcards KEPT: PostBoxCards whole and landscape, standing one behind another, each showing its top edge. The last
|
|
12
|
+
* child is the front card, nearest the thumb — put the newest there. Pressing an edge parts the box: the cards in front slide down
|
|
13
|
+
* and that card is there to read; pressing it again, or another edge, closes it. Which card is open is a state: `open` + `onOpenChange`, or `defaultOpen`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function PostBox({ open, defaultOpen, onOpenChange, className, children, ...rest }: PostBoxProps): import("react").JSX.Element;
|
|
16
|
+
export interface PostBoxCardProps extends Omit<ComponentProps<'li'>, 'id'> {
|
|
17
|
+
/** the card's own name in its box — what `open` / `onOpenChange` speak */
|
|
18
|
+
id: string;
|
|
19
|
+
/** who sent it — the edge's big line */
|
|
20
|
+
from: ReactNode;
|
|
21
|
+
/** where and when — the edge's small line ("เชียงใหม่ · 3 ม.ค. 69") */
|
|
22
|
+
when?: ReactNode;
|
|
23
|
+
/** the corner's stamp — a `Stamp` */
|
|
24
|
+
stamp?: ReactNode;
|
|
25
|
+
/** the mark struck over the stamp — a `Postmark`; behind another card only its place shows */
|
|
26
|
+
postmark?: ReactNode;
|
|
27
|
+
/** the address lines, top down */
|
|
28
|
+
to?: ReactNode[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* PostBoxCard — one kept postcard of a PostBox: its top edge is the button (who · where and when · the stamp and its postmark),
|
|
32
|
+
* the rest is the card whole — the message (`children`) left, the address right. Read only while its box is parted at it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function PostBoxCard({ id, from, when, stamp, postmark, to, className, children, ...rest }: PostBoxCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, createContext, isValidElement, useContext, useEffect, useId, useRef } from 'react';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { reducedMotion, useControlled } from '../state.js';
|
|
5
|
+
import { PostCorner } from './postCorner.js';
|
|
6
|
+
const Box = createContext({ open: null, press: () => { } });
|
|
7
|
+
/**
|
|
8
|
+
* PostBox — the postcards KEPT: PostBoxCards whole and landscape, standing one behind another, each showing its top edge. The last
|
|
9
|
+
* child is the front card, nearest the thumb — put the newest there. Pressing an edge parts the box: the cards in front slide down
|
|
10
|
+
* and that card is there to read; pressing it again, or another edge, closes it. Which card is open is a state: `open` + `onOpenChange`, or `defaultOpen`.
|
|
11
|
+
*/
|
|
12
|
+
export function PostBox({ open, defaultOpen = null, onOpenChange, className, children, ...rest }) {
|
|
13
|
+
const [parted, ask] = useControlled(open, defaultOpen, onOpenChange);
|
|
14
|
+
const front = Children.toArray(children).filter(isValidElement).at(-1)?.props;
|
|
15
|
+
const shown = parted ?? front?.id ?? null;
|
|
16
|
+
return (_jsx("ul", { className: cx('post-box', className), ...rest, children: _jsx(Box.Provider, { value: { open: shown, press: (id) => ask(id === shown ? null : id) }, children: children }) }));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* PostBoxCard — one kept postcard of a PostBox: its top edge is the button (who · where and when · the stamp and its postmark),
|
|
20
|
+
* the rest is the card whole — the message (`children`) left, the address right. Read only while its box is parted at it.
|
|
21
|
+
*/
|
|
22
|
+
export function PostBoxCard({ id, from, when, stamp, postmark, to = [], className, children, ...rest }) {
|
|
23
|
+
const box = useContext(Box);
|
|
24
|
+
const open = box.open === id;
|
|
25
|
+
const body = useId();
|
|
26
|
+
const el = useRef(null);
|
|
27
|
+
const was = useRef(open);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (open && !was.current)
|
|
30
|
+
el.current?.scrollIntoView({ block: 'nearest', behavior: reducedMotion() ? 'auto' : 'smooth' });
|
|
31
|
+
was.current = open;
|
|
32
|
+
}, [open]);
|
|
33
|
+
return (_jsxs("li", { ref: el, className: cx('post-box__card', 'post-paper', open && 'is-open', className), ...rest, children: [_jsxs("button", { className: "post-box__edge", type: "button", "aria-expanded": open, "aria-controls": body, onClick: () => box.press(id), children: [_jsx("span", { className: "post-box__from", children: from }), when != null && _jsx("span", { className: "post-box__when", children: when })] }), _jsx(PostCorner, { stamp: stamp, postmark: postmark, "aria-hidden": "true" }), _jsxs("div", { className: "post-box__body", id: body, inert: !open, children: [_jsx("p", { className: "post-box__message", children: children }), _jsx("div", { className: "post-box__to", children: to.map((line, i) => _jsx("span", { children: line }, i)) })] })] }));
|
|
34
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
/** the half of the paper the phone looks at: the message (left), the address and the stamp (right) */
|
|
3
|
+
export type PostHalf = 'message' | 'address';
|
|
4
|
+
/** where the card is on its way out: being written · the postmark struck on it · gone */
|
|
5
|
+
export type PostState = 'writing' | 'struck' | 'sent';
|
|
6
|
+
/** one of the sender's stamps to choose from: its `id`, the `Stamp` itself, its accessible name */
|
|
7
|
+
export interface PostDeskStamp {
|
|
8
|
+
id: string;
|
|
9
|
+
stamp: ReactNode;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PostDeskProps extends ComponentProps<'div'> {
|
|
13
|
+
/** CONTROLLED: the half in sight — with `onHalfChange` */
|
|
14
|
+
half?: PostHalf;
|
|
15
|
+
/** UNCONTROLLED: the half it starts on (default the message) */
|
|
16
|
+
defaultHalf?: PostHalf;
|
|
17
|
+
/** the other half's showing edge was pressed, or the paper dragged sideways: the half it asks for */
|
|
18
|
+
onHalfChange?: (half: PostHalf) => void;
|
|
19
|
+
/** CONTROLLED: the message — with `onMessageChange` */
|
|
20
|
+
message?: string;
|
|
21
|
+
/** UNCONTROLLED: the words it starts with */
|
|
22
|
+
defaultMessage?: string;
|
|
23
|
+
/** the member typed: the message it asks for. Never more than the half holds */
|
|
24
|
+
onMessageChange?: (message: string) => void;
|
|
25
|
+
/** the paper refused what was typed: the half is full */
|
|
26
|
+
onFull?: () => void;
|
|
27
|
+
/** the room on the paper changed: ruled lines still free, and how many the half holds — the app words it ("7 lines left") */
|
|
28
|
+
onLinesChange?: (left: number, fits: number) => void;
|
|
29
|
+
/** the message's placeholder */
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
/** the address lines — `PostDeskLine`s; their values are the app's, like any input's */
|
|
32
|
+
address?: ReactNode;
|
|
33
|
+
/** the sender's own stamps, shown under the card on the address half */
|
|
34
|
+
stamps?: PostDeskStamp[];
|
|
35
|
+
/** CONTROLLED: the `id` of the stamp in the stamp box, `null` = none — with `onStampChange` */
|
|
36
|
+
stamp?: string | null;
|
|
37
|
+
/** UNCONTROLLED: the stamp it starts with */
|
|
38
|
+
defaultStamp?: string | null;
|
|
39
|
+
/** a stamp was chosen, or the stamp box pressed (empty: the first stamp; filled: `null`, the stamp comes off) */
|
|
40
|
+
onStampChange?: (stamp: string | null) => void;
|
|
41
|
+
/** CONTROLLED: `writing` · `struck` (the postmark lands; the app sets it when the member sends) · `sent` (the card has left) — with `onStateChange` */
|
|
42
|
+
state?: PostState;
|
|
43
|
+
/** UNCONTROLLED: the state it starts in */
|
|
44
|
+
defaultState?: PostState;
|
|
45
|
+
/** struck, the card asks to be `sent` once the mark has landed (at once with reduced motion) */
|
|
46
|
+
onStateChange?: (state: PostState) => void;
|
|
47
|
+
/** the mark struck over the stamp — a `Postmark`; drawn from `struck` on */
|
|
48
|
+
postmark?: ReactNode;
|
|
49
|
+
/** what stands where the card was once it is `sent` */
|
|
50
|
+
sent?: ReactNode;
|
|
51
|
+
/** one small line under the card, read out when it changes — the app's words (lines left, what is missing, ready) */
|
|
52
|
+
note?: ReactNode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* PostDesk — a postcard being WRITTEN, stamped and sent. The paper is landscape and twice the screen; the phone looks at one half:
|
|
56
|
+
* the message on ruled lines (typing stops when the half is full — the paper is the limit), then the address with the stamp box.
|
|
57
|
+
* The other half's showing edge, or a sideways drag, slides the paper. The stamp box and the stamps under the card choose one of
|
|
58
|
+
* the sender's own stamps. Sending is the SCREEN's button, never the desk's: the app sets `state="struck"` — the postmark lands,
|
|
59
|
+
* the desk asks for `sent`, and the card leaves upward; `sent` stands in its place. Every state is the app's (`x` + `onXChange`, or `defaultX`).
|
|
60
|
+
* It sizes itself to the screen it is in; put it in a `Page bleed`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function PostDesk({ half, defaultHalf, onHalfChange, message, defaultMessage, onMessageChange, onFull, onLinesChange, placeholder, address, stamps, stamp, defaultStamp, onStampChange, state, defaultState, onStateChange, postmark, sent, note, className, ...rest }: PostDeskProps): import("react").JSX.Element;
|
|
63
|
+
export interface PostDeskLineProps extends ComponentProps<'input'> {
|
|
64
|
+
/** the small word over the line ("ถึง", "ที่") */
|
|
65
|
+
label: ReactNode;
|
|
66
|
+
}
|
|
67
|
+
/** PostDeskLine — one address line of a PostDesk (`address`): a small word and a bare input on a rule. The value is the app's, like any input's. */
|
|
68
|
+
export declare function PostDeskLine({ label, type, autoComplete, className, ...rest }: PostDeskLineProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { reducedMotion, useControlled, useLatest, watchSize } from '../state.js';
|
|
5
|
+
import { word } from '../words.js';
|
|
6
|
+
import { StampPlace } from '../stamp/Stamp.js';
|
|
7
|
+
const STRIKE = 700; // ms the struck card is seen before it asks to leave
|
|
8
|
+
const SWIPE = 48; // px a sideways drag must travel to move the paper
|
|
9
|
+
/* ruled lines the half holds, and how many the words leave free (the box is collapsed for a moment to read the words' own height) */
|
|
10
|
+
function linesOf(ta) {
|
|
11
|
+
const line = parseFloat(getComputedStyle(ta).lineHeight);
|
|
12
|
+
const fits = Math.floor(ta.clientHeight / line);
|
|
13
|
+
const { height, flex } = ta.style;
|
|
14
|
+
ta.style.flex = 'none';
|
|
15
|
+
ta.style.height = '0px';
|
|
16
|
+
const used = ta.value ? Math.round(ta.scrollHeight / line) : 0;
|
|
17
|
+
ta.style.height = height;
|
|
18
|
+
ta.style.flex = flex;
|
|
19
|
+
return { left: Math.max(0, fits - used), fits };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* PostDesk — a postcard being WRITTEN, stamped and sent. The paper is landscape and twice the screen; the phone looks at one half:
|
|
23
|
+
* the message on ruled lines (typing stops when the half is full — the paper is the limit), then the address with the stamp box.
|
|
24
|
+
* The other half's showing edge, or a sideways drag, slides the paper. The stamp box and the stamps under the card choose one of
|
|
25
|
+
* the sender's own stamps. Sending is the SCREEN's button, never the desk's: the app sets `state="struck"` — the postmark lands,
|
|
26
|
+
* the desk asks for `sent`, and the card leaves upward; `sent` stands in its place. Every state is the app's (`x` + `onXChange`, or `defaultX`).
|
|
27
|
+
* It sizes itself to the screen it is in; put it in a `Page bleed`.
|
|
28
|
+
*/
|
|
29
|
+
export function PostDesk({ half, defaultHalf = 'message', onHalfChange, message, defaultMessage = '', onMessageChange, onFull, onLinesChange, placeholder, address, stamps = [], stamp, defaultStamp = null, onStampChange, state, defaultState = 'writing', onStateChange, postmark, sent, note, className, ...rest }) {
|
|
30
|
+
const [side, askSide] = useControlled(half, defaultHalf, onHalfChange);
|
|
31
|
+
const [text, askText] = useControlled(message, defaultMessage, onMessageChange);
|
|
32
|
+
const [stuck, askStamp] = useControlled(stamp, defaultStamp, onStampChange);
|
|
33
|
+
const [at, askState] = useControlled(state, defaultState, onStateChange);
|
|
34
|
+
const writing = at === 'writing';
|
|
35
|
+
const chosen = stamps.find((s) => s.id === stuck);
|
|
36
|
+
const paper = useRef(null);
|
|
37
|
+
const lines = useLatest(onLinesChange);
|
|
38
|
+
const told = useRef('');
|
|
39
|
+
const measure = () => {
|
|
40
|
+
const ta = paper.current;
|
|
41
|
+
if (!ta)
|
|
42
|
+
return;
|
|
43
|
+
const { left, fits } = linesOf(ta);
|
|
44
|
+
if (told.current === `${left}/${fits}`)
|
|
45
|
+
return;
|
|
46
|
+
told.current = `${left}/${fits}`;
|
|
47
|
+
lines.current?.(left, fits);
|
|
48
|
+
};
|
|
49
|
+
useLayoutEffect(measure, [text]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
document.fonts?.ready.then(measure);
|
|
52
|
+
return watchSize([paper.current], measure);
|
|
53
|
+
}, []);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (at !== 'struck')
|
|
56
|
+
return;
|
|
57
|
+
const t = setTimeout(() => askState('sent'), reducedMotion() ? 0 : STRIKE);
|
|
58
|
+
return () => clearTimeout(t);
|
|
59
|
+
}, [at, askState]);
|
|
60
|
+
const write = (e) => {
|
|
61
|
+
const ta = e.currentTarget;
|
|
62
|
+
if (ta.scrollHeight > ta.clientHeight + 1 && ta.value.length >= text.length) {
|
|
63
|
+
ta.value = text;
|
|
64
|
+
onFull?.();
|
|
65
|
+
return;
|
|
66
|
+
} // taking words away is never refused
|
|
67
|
+
askText(ta.value);
|
|
68
|
+
};
|
|
69
|
+
const from = useRef(null);
|
|
70
|
+
const drop = (e) => {
|
|
71
|
+
const dx = from.current == null ? 0 : e.clientX - from.current;
|
|
72
|
+
from.current = null;
|
|
73
|
+
if (writing && Math.abs(dx) >= SWIPE)
|
|
74
|
+
askSide(dx < 0 ? 'address' : 'message');
|
|
75
|
+
};
|
|
76
|
+
return (_jsxs("div", { className: cx('post-desk', `post-desk--${side}`, `post-desk--${at}`, className), ...rest, children: [_jsxs("div", { className: "post-desk__window", onPointerDown: (e) => { from.current = e.clientX; }, onPointerUp: drop, children: [_jsxs("div", { className: "post-desk__card post-paper", children: [_jsx("section", { className: "post-desk__half post-desk__half--message", "aria-label": word('message'), inert: !writing || side !== 'message', children: _jsx("textarea", { ref: paper, className: "post-desk__write", "aria-label": word('message'), placeholder: placeholder, value: text, onChange: write }) }), _jsxs("section", { className: "post-desk__half post-desk__half--address", "aria-label": word('address'), inert: !writing || side !== 'address', children: [_jsx("div", { className: "post-desk__corner", children: _jsxs("div", { className: "post-corner", children: [!writing && postmark, _jsx("button", { className: "post-corner__stamp post-desk__slot", type: "button", "aria-pressed": chosen != null, "aria-label": word(chosen ? 'stampOff' : 'stampOn'), onClick: () => askStamp(chosen ? null : stamps[0]?.id ?? null), children: chosen ? chosen.stamp : _jsx(StampPlace, { "aria-hidden": "true", children: word('stampOn') }) })] }) }), _jsx("div", { className: "post-desk__lines", children: address })] })] }), writing && _jsx("button", { className: `post-desk__reach post-desk__reach--${side === 'message' ? 'address' : 'message'}`, type: "button", "aria-label": word(side === 'message' ? 'toAddress' : 'toMessage'), onClick: () => askSide(side === 'message' ? 'address' : 'message') }), at === 'sent' && _jsx("div", { className: "post-desk__gone", children: sent })] }), _jsx("p", { className: "post-desk__note", "aria-live": "polite", children: note }), writing && side === 'address' && stamps.length > 0 && (_jsx("div", { className: "post-desk__stamps", role: "group", "aria-label": word('stamps'), children: stamps.map((s) => (_jsx("button", { className: "post-desk__pick", type: "button", "aria-pressed": s.id === stuck, "aria-label": s.label, onClick: () => askStamp(s.id), children: s.stamp }, s.id))) }))] }));
|
|
77
|
+
}
|
|
78
|
+
/** PostDeskLine — one address line of a PostDesk (`address`): a small word and a bare input on a rule. The value is the app's, like any input's. */
|
|
79
|
+
export function PostDeskLine({ label, type = 'text', autoComplete = 'off', className, ...rest }) {
|
|
80
|
+
return (_jsxs("label", { className: cx('post-desk__line', className), children: [_jsx("span", { children: label }), _jsx("input", { type: type, autoComplete: autoComplete, ...rest })] }));
|
|
81
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
/** a postcard's two sides: the bought `picture`, the written `back` */
|
|
3
|
+
export type PostFace = 'picture' | 'back';
|
|
4
|
+
export interface PostLetterProps extends ComponentProps<'div'> {
|
|
5
|
+
/** CONTROLLED: the side in front — with `onFaceChange` */
|
|
6
|
+
face?: PostFace;
|
|
7
|
+
/** UNCONTROLLED: the side it starts on (default the picture) */
|
|
8
|
+
defaultFace?: PostFace;
|
|
9
|
+
/** the tab or a sheet was tapped: the side it asks to bring forward */
|
|
10
|
+
onFaceChange?: (face: PostFace) => void;
|
|
11
|
+
/** the picture side's print — the app's own (an img, an svg); it fills the sheet inside its white margin */
|
|
12
|
+
picture: ReactNode;
|
|
13
|
+
/** the place printed under the picture */
|
|
14
|
+
caption?: ReactNode;
|
|
15
|
+
/** a voice note: its time as the app words it ("0:12"). With it the picture side has the play control — cardds never plays: the control asks, the app plays and passes the time */
|
|
16
|
+
voice?: ReactNode;
|
|
17
|
+
/** CONTROLLED: the voice note is playing — with `onPlayingChange` */
|
|
18
|
+
playing?: boolean;
|
|
19
|
+
/** UNCONTROLLED: it starts playing */
|
|
20
|
+
defaultPlaying?: boolean;
|
|
21
|
+
/** the play control was tapped: the state it asks for */
|
|
22
|
+
onPlayingChange?: (playing: boolean) => void;
|
|
23
|
+
/** the small word printed over the address ("โปสการ์ด") */
|
|
24
|
+
kicker?: ReactNode;
|
|
25
|
+
/** the address lines, top down: the first is the name, the rest where they live */
|
|
26
|
+
to?: ReactNode[];
|
|
27
|
+
/** the corner's stamp — a `Stamp` */
|
|
28
|
+
stamp?: ReactNode;
|
|
29
|
+
/** the mark struck over the stamp — a `Postmark` */
|
|
30
|
+
postmark?: ReactNode;
|
|
31
|
+
/** how the message ends, at the right ("คิดถึง · ต้น") */
|
|
32
|
+
sign?: ReactNode;
|
|
33
|
+
/** one line under the card: the trip it made */
|
|
34
|
+
journey?: ReactNode;
|
|
35
|
+
/** the tab's words while the picture is in front (default: cardds's own, by `<html lang>`) */
|
|
36
|
+
backLabel?: string;
|
|
37
|
+
/** the tab's words while the back is in front */
|
|
38
|
+
pictureLabel?: string;
|
|
39
|
+
/** the play control's names */
|
|
40
|
+
playLabel?: string;
|
|
41
|
+
pauseLabel?: string;
|
|
42
|
+
/** the small word before the name (default "To", by `<html lang>`) */
|
|
43
|
+
toLabel?: ReactNode;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* PostLetter — a postcard that arrived, to READ: two upright 2:3 sheets, one in front. The written side is the address half over the
|
|
47
|
+
* message half (`children`, set large); the picture side is the print in its margin, a voice note's control on its corner.
|
|
48
|
+
* The tab over the pair (or a tap on the sheet) turns it: the sheet in front swings to its edge, the other swings in — both stay
|
|
49
|
+
* whole and readable, nothing turns with reduced motion. Which side shows is a state: `face` + `onFaceChange`, or `defaultFace`.
|
|
50
|
+
* It sizes itself to the screen it is in (the sheet, the tab and the `journey` line stay above the bottom layer); put it in a `Page bleed`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function PostLetter({ face, defaultFace, onFaceChange, picture, caption, voice, playing, defaultPlaying, onPlayingChange, kicker, to, stamp, postmark, sign, journey, backLabel, pictureLabel, playLabel, pauseLabel, toLabel, className, children, ...rest }: PostLetterProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { reducedMotion, useControlled } from '../state.js';
|
|
5
|
+
import { word } from '../words.js';
|
|
6
|
+
import { Icon } from '../type/Icon.js';
|
|
7
|
+
import { IconBtn } from '../actions/IconBtn.js';
|
|
8
|
+
import { PostCorner } from './postCorner.js';
|
|
9
|
+
const OTHER = { picture: 'back', back: 'picture' };
|
|
10
|
+
/* half a turn: the sheet swings on its upright middle between flat (0) and edge-on (±90). `hold` keeps it edge-on until the other sheet is rendered */
|
|
11
|
+
function swing(el, from, to, easing, hold = false) {
|
|
12
|
+
const lens = `perspective(${el.offsetWidth * 3}px)`;
|
|
13
|
+
const flip = parseFloat(getComputedStyle(el).getPropertyValue('--motion-flip')) || 0.45;
|
|
14
|
+
return el.animate([{ transform: `${lens} rotateY(${from}deg)` }, { transform: `${lens} rotateY(${to}deg)` }], { duration: flip * 500, easing, fill: hold ? 'forwards' : 'none' });
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* PostLetter — a postcard that arrived, to READ: two upright 2:3 sheets, one in front. The written side is the address half over the
|
|
18
|
+
* message half (`children`, set large); the picture side is the print in its margin, a voice note's control on its corner.
|
|
19
|
+
* The tab over the pair (or a tap on the sheet) turns it: the sheet in front swings to its edge, the other swings in — both stay
|
|
20
|
+
* whole and readable, nothing turns with reduced motion. Which side shows is a state: `face` + `onFaceChange`, or `defaultFace`.
|
|
21
|
+
* It sizes itself to the screen it is in (the sheet, the tab and the `journey` line stay above the bottom layer); put it in a `Page bleed`.
|
|
22
|
+
*/
|
|
23
|
+
export function PostLetter({ face, defaultFace = 'picture', onFaceChange, picture, caption, voice, playing, defaultPlaying = false, onPlayingChange, kicker, to = [], stamp, postmark, sign, journey, backLabel, pictureLabel, playLabel, pauseLabel, toLabel, className, children, ...rest }) {
|
|
24
|
+
const [asked, ask] = useControlled(face, defaultFace, onFaceChange);
|
|
25
|
+
const [sound, askSound] = useControlled(playing, defaultPlaying, onPlayingChange);
|
|
26
|
+
const [lying, setLying] = useState(asked); // the sheet in front right now: it trails `asked` by half a turn
|
|
27
|
+
const sheets = useRef({ picture: null, back: null });
|
|
28
|
+
const landed = useRef(asked);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (asked === lying)
|
|
31
|
+
return;
|
|
32
|
+
const el = sheets.current[lying];
|
|
33
|
+
if (reducedMotion() || !el?.animate) {
|
|
34
|
+
landed.current = asked;
|
|
35
|
+
setLying(asked);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const out = swing(el, 0, 90, 'ease-in', true);
|
|
39
|
+
out.onfinish = () => setLying(asked);
|
|
40
|
+
return () => out.cancel();
|
|
41
|
+
}, [asked, lying]);
|
|
42
|
+
useLayoutEffect(() => {
|
|
43
|
+
if (landed.current === lying)
|
|
44
|
+
return;
|
|
45
|
+
landed.current = lying;
|
|
46
|
+
const el = sheets.current[lying];
|
|
47
|
+
if (el?.animate)
|
|
48
|
+
swing(el, -90, 0, 'ease-out');
|
|
49
|
+
}, [lying]);
|
|
50
|
+
const turn = () => ask(OTHER[asked]);
|
|
51
|
+
const [name, ...lines] = to;
|
|
52
|
+
return (_jsxs("div", { className: cx('post-letter', `post-letter--${lying}`, className), ...rest, children: [_jsxs("div", { className: "post-letter__sheet post-letter__sheet--picture post-paper", ref: (el) => { sheets.current.picture = el; }, inert: lying !== 'picture', onClick: turn, children: [_jsx("div", { className: "post-letter__pic", children: picture }), _jsx("p", { className: "post-letter__caption", children: caption }), voice != null && (_jsxs("div", { className: "post-letter__voice", onClick: (e) => e.stopPropagation(), children: [_jsx(IconBtn, { invert: true, icon: sound ? 'pause' : 'play', label: sound ? pauseLabel ?? word('pause') : playLabel ?? word('play'), "aria-pressed": sound, onClick: () => askSound(!sound) }), _jsx("span", { className: "post-letter__time", children: voice })] }))] }), _jsxs("article", { className: "post-letter__sheet post-letter__sheet--back post-paper", ref: (el) => { sheets.current.back = el; }, inert: lying !== 'back', onClick: turn, children: [_jsxs("header", { className: "post-letter__head", children: [_jsxs("div", { className: "post-letter__to", children: [_jsx("span", { className: "post-letter__kicker", children: kicker }), _jsxs("span", { className: "post-letter__lines", children: [name != null && _jsxs("span", { className: "post-letter__line post-letter__line--name", children: [_jsx("small", { children: toLabel ?? word('to') }), name] }), lines.map((line, i) => _jsx("span", { className: "post-letter__line", children: line }, i))] })] }), _jsx(PostCorner, { stamp: stamp, postmark: postmark })] }), _jsxs("div", { className: "post-letter__message", children: [children, sign != null && _jsx("p", { className: "post-letter__sign", children: sign })] })] }), _jsxs("button", { className: "post-letter__turn", type: "button", onClick: turn, children: [_jsx(Icon, { name: lying === 'back' ? 'image' : 'file-text', size: "xs" }), _jsx("span", { children: lying === 'back' ? pictureLabel ?? word('turnToPicture') : backLabel ?? word('turnToBack') })] }), journey != null && _jsx("p", { className: "post-letter__journey", children: journey })] }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
export interface PostmarkProps extends ComponentProps<'div'> {
|
|
3
|
+
/** where it was posted — the mark's first line */
|
|
4
|
+
place: ReactNode;
|
|
5
|
+
/** when — the second line, short ("12 เม.ย."); behind another card in a PostBox only the place shows */
|
|
6
|
+
date?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Postmark — the round mark the post strikes: where and when, two rings, turned a little, its cancel lines running on to the side.
|
|
10
|
+
* On a card's corner (`postmark` of PostLetter · PostDesk · PostBoxCard) it lies over the edge of the stamp. Information: nothing to press.
|
|
11
|
+
*/
|
|
12
|
+
export declare function Postmark({ place, date, className, ...rest }: PostmarkProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
/**
|
|
4
|
+
* Postmark — the round mark the post strikes: where and when, two rings, turned a little, its cancel lines running on to the side.
|
|
5
|
+
* On a card's corner (`postmark` of PostLetter · PostDesk · PostBoxCard) it lies over the edge of the stamp. Information: nothing to press.
|
|
6
|
+
*/
|
|
7
|
+
export function Postmark({ place, date, className, ...rest }) {
|
|
8
|
+
return (_jsxs("div", { className: cx('postmark', className), ...rest, children: [_jsx("span", { children: place }), date != null && _jsx("span", { children: date })] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/* the corner of a card that proves the journey: the stamp (a `Stamp`, the app's), the postmark struck over its edge.
|
|
3
|
+
A part of the three post views, never exported: a stamp here shows its pictures only (stamp.css). */
|
|
4
|
+
export function PostCorner({ stamp, postmark, ...rest }) {
|
|
5
|
+
if (stamp == null && postmark == null)
|
|
6
|
+
return null;
|
|
7
|
+
return (_jsxs("div", { className: "post-corner", ...rest, children: [postmark, stamp != null && _jsx("span", { className: "post-corner__stamp", children: stamp })] }));
|
|
8
|
+
}
|
package/dist/scaffold/Page.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
2
|
export interface PageProps extends ComponentProps<'section'> {
|
|
3
|
+
/** in a Step: the page runs to the screen's edges and keeps the inset as its own padding — for a thing that leans, slides or casts a shadow past the inset (a postcard), which is then cut at the glass, never at the inset. Nothing pans sideways */
|
|
4
|
+
bleed?: boolean;
|
|
3
5
|
}
|
|
4
6
|
/**
|
|
5
7
|
* Page — ONE page of words in the content: a picture (optional, first — it stays put while the words scroll), a heading, the words,
|
|
@@ -7,7 +9,7 @@ export interface PageProps extends ComponentProps<'section'> {
|
|
|
7
9
|
* pager turns (slideTo); a single one sits in the content (a Step) directly. Was BaseContent until 2026-09-23 — "words on the base" meant
|
|
8
10
|
* a still, one-scaffold screen, which is gone.
|
|
9
11
|
*/
|
|
10
|
-
export declare function Page({ className, ...rest }: PageProps): import("react").JSX.Element;
|
|
12
|
+
export declare function Page({ bleed, className, ...rest }: PageProps): import("react").JSX.Element;
|
|
11
13
|
export interface CtaPackProps extends ComponentProps<'div'> {
|
|
12
14
|
}
|
|
13
15
|
/** CtaPack — the buttons under a Page's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
|
package/dist/scaffold/Page.js
CHANGED
|
@@ -6,8 +6,8 @@ import { cx } from '../cx.js';
|
|
|
6
6
|
* pager turns (slideTo); a single one sits in the content (a Step) directly. Was BaseContent until 2026-09-23 — "words on the base" meant
|
|
7
7
|
* a still, one-scaffold screen, which is gone.
|
|
8
8
|
*/
|
|
9
|
-
export function Page({ className, ...rest }) {
|
|
10
|
-
return _jsx("section", { className: cx('page', className), ...rest });
|
|
9
|
+
export function Page({ bleed, className, ...rest }) {
|
|
10
|
+
return _jsx("section", { className: cx('page', bleed && 'page--bleed', className), ...rest });
|
|
11
11
|
}
|
|
12
12
|
/** CtaPack — the buttons under a Page's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
|
|
13
13
|
export function CtaPack({ className, ...rest }) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
import type { StickyTone } from '../sticky/tones.js';
|
|
3
|
+
export interface StampProps extends Omit<ComponentProps<'span'>, 'onClick'> {
|
|
4
|
+
/** the pictures, ONE to FOUR, in placement order — the app's own art (an svg, an img). The stamp places, sizes and turns them by how many there are; a fifth is not drawn */
|
|
5
|
+
pictures: ReactNode[];
|
|
6
|
+
/** the ground behind the pictures — one of STICKY_TONES (default custard). Which picture gives it is the app's choice, stored with the stamp */
|
|
7
|
+
tone?: StickyTone;
|
|
8
|
+
/** the big line at the foot, on the picture — whose stamp it is */
|
|
9
|
+
name?: ReactNode;
|
|
10
|
+
/** a small line above the name (the pictures' words); it goes when the stamp is tiny */
|
|
11
|
+
words?: ReactNode;
|
|
12
|
+
/** the pictures alone: `name` and `words` are not drawn. For a stamp stuck on something (a postcard's corner); in a `StampOn` every stamp is bare already. The words are for the album */
|
|
13
|
+
bare?: boolean;
|
|
14
|
+
/** turned a little on its middle — the one stamp a start screen hands over */
|
|
15
|
+
tilt?: 'left' | 'right';
|
|
16
|
+
/** the whole stamp is a button: it renders a `<button>` */
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Stamp — a perforated paper stamp, 4:5: the pictures fill the inner frame on a tone, the words sit on the picture's foot.
|
|
21
|
+
* It is sized by its column (up to `--stamp-max-w`) and every inner length follows its width, so it reads from a tray to a sheet.
|
|
22
|
+
* The outer box holds the size and the shadow; the paper inside is the one whose edge is punched (a mask would cut the shadow too).
|
|
23
|
+
*/
|
|
24
|
+
export declare function Stamp({ pictures, tone, name, words, bare, tilt, onClick, className, ...rest }: StampProps): import("react").JSX.Element;
|
|
25
|
+
export interface StampPlaceProps extends Omit<ComponentProps<'span'>, 'onClick'> {
|
|
26
|
+
/** a gap nobody is asked to fill: a paler edge (an album's free places) */
|
|
27
|
+
faint?: boolean;
|
|
28
|
+
/** the place is a button ("make yours"): it renders a `<button>` */
|
|
29
|
+
onClick?: () => void;
|
|
30
|
+
}
|
|
31
|
+
/** StampPlace — an empty place for a stamp: a dashed 4:5 frame the size a Stamp would be, a word or an Icon inside. */
|
|
32
|
+
export declare function StampPlace({ faint, onClick, className, ...rest }: StampPlaceProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
/**
|
|
4
|
+
* Stamp — a perforated paper stamp, 4:5: the pictures fill the inner frame on a tone, the words sit on the picture's foot.
|
|
5
|
+
* It is sized by its column (up to `--stamp-max-w`) and every inner length follows its width, so it reads from a tray to a sheet.
|
|
6
|
+
* The outer box holds the size and the shadow; the paper inside is the one whose edge is punched (a mask would cut the shadow too).
|
|
7
|
+
*/
|
|
8
|
+
export function Stamp({ pictures, tone = 'custard', name, words, bare, tilt, onClick, className, ...rest }) {
|
|
9
|
+
const shown = pictures.slice(0, 4);
|
|
10
|
+
const tap = onClick != null;
|
|
11
|
+
const Box = (tap ? 'button' : 'span');
|
|
12
|
+
const boxProps = (tap ? { type: 'button', onClick, ...rest } : rest);
|
|
13
|
+
return (_jsx(Box, { className: cx('stamp', `stamp--${shown.length}`, bare && 'stamp--bare', tilt && `stamp--tilt-${tilt}`, tap && 'stamp--tap', className), ...boxProps, children: _jsx("span", { className: "stamp__paper", children: _jsxs("span", { className: cx('stamp__pic', `sticky--${tone}`), children: [shown.map((picture, i) => _jsx("span", { className: "stamp__art", "aria-hidden": "true", children: picture }, i)), (name != null || words != null) && (_jsxs("span", { className: "stamp__cap", children: [words != null && _jsx("span", { className: "stamp__words", children: words }), name != null && _jsx("span", { className: "stamp__name", children: name })] }))] }) }) }));
|
|
14
|
+
}
|
|
15
|
+
/** StampPlace — an empty place for a stamp: a dashed 4:5 frame the size a Stamp would be, a word or an Icon inside. */
|
|
16
|
+
export function StampPlace({ faint, onClick, className, ...rest }) {
|
|
17
|
+
const tap = onClick != null;
|
|
18
|
+
const Box = (tap ? 'button' : 'span');
|
|
19
|
+
const boxProps = (tap ? { type: 'button', onClick, ...rest } : rest);
|
|
20
|
+
return _jsx(Box, { className: cx('stamp-place', faint && 'stamp-place--faint', className), ...boxProps });
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
export interface StampBookProps extends ComponentProps<'div'> {
|
|
3
|
+
/** how many places the page shows: the ones no StampCell fills are drawn empty */
|
|
4
|
+
places?: number;
|
|
5
|
+
/** ONE line of small stamps that scrolls sideways, no ground — the tray a member picks from */
|
|
6
|
+
strip?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** StampBook — the album page: StampCells three across on an album ground, the free places drawn empty up to `places`. */
|
|
9
|
+
export declare function StampBook({ places, strip, className, children, ...rest }: StampBookProps): import("react").JSX.Element;
|
|
10
|
+
export interface StampCellProps extends Omit<ComponentProps<'span'>, 'onClick'> {
|
|
11
|
+
/** a short line under the stamp — who it is from */
|
|
12
|
+
label?: ReactNode;
|
|
13
|
+
/** the cell is a button: it renders a `<button>` (give the Stamp inside no `onClick`) */
|
|
14
|
+
onClick?: () => void;
|
|
15
|
+
/** with `onClick`: this stamp is in use (stuck on the note) — `aria-pressed`, a check on its corner. Omit it and the cell is a plain button */
|
|
16
|
+
on?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** StampCell — one place of a StampBook: a Stamp (the child) and a short label under it. */
|
|
19
|
+
export declare function StampCell({ label, onClick, on, className, children, ...rest }: StampCellProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children } from 'react';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { Icon } from '../type/Icon.js';
|
|
5
|
+
import { StampPlace } from './Stamp.js';
|
|
6
|
+
/** StampBook — the album page: StampCells three across on an album ground, the free places drawn empty up to `places`. */
|
|
7
|
+
export function StampBook({ places = 0, strip, className, children, ...rest }) {
|
|
8
|
+
const free = Math.max(0, places - Children.count(children));
|
|
9
|
+
return (_jsxs("div", { className: cx('stamp-book', strip && 'stamp-book--strip', className), ...rest, children: [children, Array.from({ length: free }, (_, i) => _jsx(StampPlace, { faint: true, "aria-hidden": "true" }, i))] }));
|
|
10
|
+
}
|
|
11
|
+
/** StampCell — one place of a StampBook: a Stamp (the child) and a short label under it. */
|
|
12
|
+
export function StampCell({ label, onClick, on, className, children, ...rest }) {
|
|
13
|
+
const tap = onClick != null;
|
|
14
|
+
const Box = (tap ? 'button' : 'span');
|
|
15
|
+
const boxProps = (tap ? { type: 'button', onClick, 'aria-pressed': on, ...rest } : rest);
|
|
16
|
+
return (_jsxs(Box, { className: cx('stamp-cell', className), ...boxProps, children: [children, label != null && _jsx("span", { className: "stamp-cell__label", children: label }), on && _jsx("span", { className: "stamp-cell__on", "aria-hidden": "true", children: _jsx(Icon, { name: "check" }) })] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ComponentProps } from 'react';
|
|
2
|
+
/** where a stuck stamp's MIDDLE is, as a share of its surface: 0 = the left / top edge, 1 = the right / bottom edge */
|
|
3
|
+
export interface StampAt {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
export interface StampOnProps extends ComponentProps<'div'> {
|
|
8
|
+
/** CONTROLLED: where the stamp is — with `onAtChange`; the app stores it with the note */
|
|
9
|
+
at?: StampAt;
|
|
10
|
+
/** UNCONTROLLED: where it starts (default the middle) */
|
|
11
|
+
defaultAt?: StampAt;
|
|
12
|
+
/** how far it is turned, in degrees — the app's (a hand never sticks one straight) */
|
|
13
|
+
turn?: number;
|
|
14
|
+
/** the member moved it (a drag let go, an arrow key): the stamp ASKS with the new place. With it the stamp can be dragged; without it the stamp is still — the receiver's view */
|
|
15
|
+
onAtChange?: (at: StampAt) => void;
|
|
16
|
+
/** a draggable stamp's accessible name ("แสตมป์ของต้น") */
|
|
17
|
+
label?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* StampOn — a Stamp (the child) stuck on a surface at the place the member chose. The surface is the nearest positioned box:
|
|
21
|
+
* a Sticky's `stuck`, a postcard's face. `at` is the stamp's middle as a share of that surface, so the note reads the same at any size.
|
|
22
|
+
* With `onAtChange` the member drags it (finger or mouse; the arrow keys move it too): while the finger holds, the stamp writes its
|
|
23
|
+
* own place, and on letting go it ASKS — the app owns `at`. It never leaves the surface.
|
|
24
|
+
*/
|
|
25
|
+
export declare function StampOn({ at, defaultAt, turn, onAtChange, label, className, style, children, ...rest }: StampOnProps): import("react").JSX.Element;
|