@usableapp/cardds 0.7.88 → 0.7.89

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.
@@ -15,14 +15,21 @@ export interface StampProps extends Omit<ComponentProps<'span'>, 'onClick'> {
15
15
  tilt?: 'left' | 'right';
16
16
  /** the whole stamp is a button: it renders a `<button>` */
17
17
  onClick?: () => void;
18
+ /** the member presses send and the stamp that already stands in the card's head is STRUCK onto its place by itself (shown large and faint, pressed down to its size) — as it comes on, or as this turns true. Use it when a stamp stands where the layout put it (`Card end`, a centre, the album) and sending should strike it; the stamp does not move and it keeps its `tilt`. A stamp laid ON a surface at a place the member chose is `StampOn arrive`. A stamp without `arrive` is not struck. With `onArrived` */
19
+ arrive?: boolean;
20
+ /** an arriving stamp has LANDED (its strike ended; at once where motion is off, and once per arrival) — the app does what follows: sends, closes, sets the card's `leave` */
21
+ onArrived?: () => void;
18
22
  }
19
23
  /**
20
24
  * 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
25
  * Every stamp is ONE size wherever it stands (`--stamp-size`, two taps wide: 96 × 120) — what holds it only places it; a holder narrower than that shows it whole, smaller.
22
26
  * The one small stamp is a stamp stuck on something (`StampOn`). Every inner length follows the stamp's width.
23
27
  * 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).
28
+ * With `arrive` the stamp that stands in its place (the head of a card, `end={<Stamp arrive …/>}`) is struck onto it when the member sends, and `onArrived` tells
29
+ * the app it has landed, so it sends. The strike scales it down from twice its size, so it never moves the layout (it may cover its neighbours for a moment);
30
+ * a `tilt` is kept, the stamp ends at its turn. Afterwards the same stamp stands in the same place with no strike: drop `arrive` or keep it, nothing is struck twice.
24
31
  */
25
- export declare function Stamp({ pictures, tone, name, words, bare, tilt, onClick, className, ...rest }: StampProps): import("react").JSX.Element;
32
+ export declare function Stamp({ pictures, tone, name, words, bare, tilt, onClick, arrive, onArrived, className, onAnimationEnd, ...rest }: StampProps): import("react").JSX.Element;
26
33
  export interface StampPlaceProps extends Omit<ComponentProps<'span'>, 'onClick'> {
27
34
  /** a gap nobody is asked to fill: a paler edge (an album's free places) */
28
35
  faint?: boolean;
@@ -1,16 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  import { pressable } from '../pressable.js';
4
+ import { useStrike } from './useStrike.js';
4
5
  /**
5
6
  * Stamp — a perforated paper stamp, 4:5: the pictures fill the inner frame on a tone, the words sit on the picture's foot.
6
7
  * Every stamp is ONE size wherever it stands (`--stamp-size`, two taps wide: 96 × 120) — what holds it only places it; a holder narrower than that shows it whole, smaller.
7
8
  * The one small stamp is a stamp stuck on something (`StampOn`). Every inner length follows the stamp's width.
8
9
  * 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).
10
+ * With `arrive` the stamp that stands in its place (the head of a card, `end={<Stamp arrive …/>}`) is struck onto it when the member sends, and `onArrived` tells
11
+ * the app it has landed, so it sends. The strike scales it down from twice its size, so it never moves the layout (it may cover its neighbours for a moment);
12
+ * a `tilt` is kept, the stamp ends at its turn. Afterwards the same stamp stands in the same place with no strike: drop `arrive` or keep it, nothing is struck twice.
9
13
  */
10
- export function Stamp({ pictures, tone = 'custard', name, words, bare, tilt, onClick, className, ...rest }) {
14
+ export function Stamp({ pictures, tone = 'custard', name, words, bare, tilt, onClick, arrive, onArrived, className, onAnimationEnd, ...rest }) {
11
15
  const shown = pictures.slice(0, 4);
16
+ const { struck } = useStrike(arrive, onArrived, onAnimationEnd);
12
17
  const { tap, Box, boxProps } = pressable(onClick, 'span', 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 })] }))] }) }) }));
18
+ return (_jsx(Box, { className: cx('stamp', `stamp--${shown.length}`, bare && 'stamp--bare', tilt && `stamp--tilt-${tilt}`, tap && 'stamp--tap', arrive && 'stamp--arrive', className), onAnimationEnd: struck, ...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
19
  }
15
20
  /**
16
21
  * StampPlace — an empty place for a stamp: a 4:5 frame of round dots the size a Stamp is (`--stamp-size`), a word or an Icon inside.
@@ -24,7 +24,7 @@ export interface StampOnProps extends ComponentProps<'div'> {
24
24
  /** the stamp at the stamp's MAIN size, 96 × 120 (`--stamp-size`), to cover what it lies on; the default is the small size, 48 × 60 (`--stamp-sm-size`), which leaves a note's words readable */
25
25
  main?: boolean;
26
26
  /** the stamp sticks ITSELF: as it comes onto the screen it is struck onto its place (shown large, pressed down to its size). Nobody
27
- * picked it or pressed to stick it — the app shows it when its own move (send) begins. With `onArrived` */
27
+ * picked it or pressed to stick it — the app shows it when its own move (send) begins. With `onArrived`. A stamp that already stands in a card's head or a centre, where the layout put it, is `Stamp arrive` instead */
28
28
  arrive?: boolean;
29
29
  /** an arriving stamp has LANDED (its strike ended; at once where motion is off) — the app does what follows: sends, closes */
30
30
  onArrived?: () => void;
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useContext, useEffect, useRef, useState } from 'react';
3
3
  import { cx } from '../cx.js';
4
- import { reducedMotion, useControlled } from '../state.js';
4
+ import { useControlled } from '../state.js';
5
5
  import { Icon } from '../type/Icon.js';
6
6
  import { word } from '../words.js';
7
7
  import { UnderNotes } from './underNotes.js';
8
+ import { useStrike } from './useStrike.js';
8
9
  const MIDDLE = { x: 0.5, y: 0.5 };
9
10
  const STEP = 0.02; // an arrow key's move: a share of the surface
10
11
  const MOVES = { ArrowLeft: [-STEP, 0], ArrowRight: [STEP, 0], ArrowUp: [0, -STEP], ArrowDown: [0, STEP] };
@@ -18,7 +19,6 @@ function within(el, x, y) {
18
19
  const mx = el.offsetWidth / 2 / box.clientWidth, my = el.offsetHeight / 2 / box.clientHeight;
19
20
  return { x: round(clamp(x, mx, 1 - mx)), y: round(clamp(y, my, 1 - my)) };
20
21
  }
21
- const STRIKE = 'strike'; // css/tokens.css — the strike every mark struck onto paper reads
22
22
  const write = (el, at) => { el.style.setProperty('--stamp-on-x', String(at.x)); el.style.setProperty('--stamp-on-y', String(at.y)); };
23
23
  /**
24
24
  * StampOn — a Stamp (the child) stuck on a surface at the place the member chose. The surface is the nearest positioned box:
@@ -46,7 +46,7 @@ export function StampOn({ at, defaultAt = MIDDLE, turn = 0, onAtChange, label, o
46
46
  const grip = useRef(null);
47
47
  const timer = useRef(undefined);
48
48
  const moves = onAtChange != null && !under;
49
- const [arrived, setArrived] = useState(false);
49
+ const { arrived, struck } = useStrike(arrive, onArrived, onAnimationEnd);
50
50
  const removes = onRemove != null && !under && !(arrive && !arrived);
51
51
  const ask = (next) => { if (next.x !== shown.x || next.y !== shown.y)
52
52
  request(next); };
@@ -115,20 +115,6 @@ export function StampOn({ at, defaultAt = MIDDLE, turn = 0, onAtChange, label, o
115
115
  if (!moves)
116
116
  show();
117
117
  };
118
- const landed = useRef(onArrived);
119
- landed.current = onArrived;
120
- const land = () => { setArrived(true); landed.current?.(); };
121
- useEffect(() => { if (!arrive)
122
- setArrived(false);
123
- else if (reducedMotion()) {
124
- setArrived(true);
125
- landed.current?.();
126
- } }, [arrive]); // no strike to wait for
127
- const struck = (e) => {
128
- onAnimationEnd?.(e);
129
- if (arrive && e.target === e.currentTarget && e.animationName === STRIKE)
130
- land();
131
- };
132
118
  const place = { '--stamp-on-x': shown.x, '--stamp-on-y': shown.y, '--stamp-on-turn': turn, ...style };
133
119
  const own = moves || removes ? { tabIndex: 0, role: 'group', 'aria-label': label, onKeyDown: key } : null;
134
120
  const drag = moves ? { onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up } : null;
@@ -0,0 +1,7 @@
1
+ import { type AnimationEvent } from 'react';
2
+ /** a holder that `arrive`s: has its strike ended? Returns `arrived` (false again once `arrive` turns off) and `struck`, the holder's `onAnimationEnd`
3
+ * (it calls `onAnimationEnd` first). `onArrived` comes once per arrival: when the strike ends, at once where motion is off */
4
+ export declare function useStrike<E extends Element>(arrive: boolean | undefined, onArrived: (() => void) | undefined, onAnimationEnd?: (e: AnimationEvent<E>) => void): {
5
+ arrived: boolean;
6
+ struck: (e: AnimationEvent<E>) => void;
7
+ };
@@ -0,0 +1,20 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { reducedMotion, useLatest } from '../state.js';
3
+ const STRIKE = 'strike'; // css/tokens.css — the strike every mark struck onto paper reads
4
+ /** a holder that `arrive`s: has its strike ended? Returns `arrived` (false again once `arrive` turns off) and `struck`, the holder's `onAnimationEnd`
5
+ * (it calls `onAnimationEnd` first). `onArrived` comes once per arrival: when the strike ends, at once where motion is off */
6
+ export function useStrike(arrive, onArrived, onAnimationEnd) {
7
+ const told = useLatest(onArrived);
8
+ const [arrived, setArrived] = useState(false);
9
+ const land = () => { setArrived(true); told.current?.(); };
10
+ useEffect(() => { if (!arrive)
11
+ setArrived(false);
12
+ else if (reducedMotion())
13
+ land(); }, [arrive]); // no strike to wait for
14
+ const struck = (e) => {
15
+ onAnimationEnd?.(e);
16
+ if (arrive && e.target === e.currentTarget && e.animationName === STRIKE)
17
+ land();
18
+ };
19
+ return { arrived, struck };
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usableapp/cardds",
3
- "version": "0.7.88",
3
+ "version": "0.7.89",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",