@usableapp/cardds 0.7.93 → 0.7.94

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.
@@ -1,7 +1,7 @@
1
1
  import type { ComponentProps, ReactNode } from 'react';
2
2
  export interface DayStripProps extends ComponentProps<'div'> {
3
3
  }
4
- /** DayStrip — a week of Days, one selected. */
4
+ /** DayStrip — a week of Days, one selected, today marked. */
5
5
  export declare function DayStrip({ className, ...rest }: DayStripProps): import("react").JSX.Element;
6
6
  export interface DayProps extends Omit<ComponentProps<'button'>, 'name'> {
7
7
  /** weekday name (Thu) */
@@ -12,6 +12,8 @@ export interface DayProps extends Omit<ComponentProps<'button'>, 'name'> {
12
12
  selected?: boolean;
13
13
  /** a mark under the number (something is on) */
14
14
  dot?: boolean;
15
+ /** this day is today (aria-current="date"; the app says which): a light tone, quieter than the selected pill, which wins when it is both */
16
+ today?: boolean;
15
17
  }
16
- /** Day — one day in a DayStrip: name · number · optional dot; the selected one is a filled pill. */
17
- export declare function Day({ name, num, selected, dot, className, ...rest }: DayProps): import("react").JSX.Element;
18
+ /** Day — one day in a DayStrip: name · number · optional dot; the selected one is a filled pill, today a light tone (--day-today-bg / --day-today-ink). */
19
+ export declare function Day({ name, num, selected, dot, today, className, ...rest }: DayProps): import("react").JSX.Element;
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
- /** DayStrip — a week of Days, one selected. */
3
+ /** DayStrip — a week of Days, one selected, today marked. */
4
4
  export function DayStrip({ className, ...rest }) {
5
5
  return _jsx("div", { className: cx('day-strip', className), role: "tablist", ...rest });
6
6
  }
7
- /** Day — one day in a DayStrip: name · number · optional dot; the selected one is a filled pill. */
8
- export function Day({ name, num, selected, dot, className, ...rest }) {
9
- return (_jsxs("button", { type: "button", role: "tab", "aria-selected": selected ? 'true' : undefined, className: cx('day', className), ...rest, children: [_jsx("span", { className: "day__name", children: name }), _jsx("span", { className: "day__num", children: num }), dot && _jsx("span", { className: "day__dot" })] }));
7
+ /** Day — one day in a DayStrip: name · number · optional dot; the selected one is a filled pill, today a light tone (--day-today-bg / --day-today-ink). */
8
+ export function Day({ name, num, selected, dot, today, className, ...rest }) {
9
+ return (_jsxs("button", { type: "button", role: "tab", "aria-selected": selected ? 'true' : undefined, "aria-current": today ? 'date' : undefined, className: cx('day', today && 'day--today', className), ...rest, children: [_jsx("span", { className: "day__name", children: name }), _jsx("span", { className: "day__num", children: num }), dot && _jsx("span", { className: "day__dot" })] }));
10
10
  }
@@ -1,15 +1,21 @@
1
1
  import type { ComponentProps, ReactNode } from 'react';
2
- export interface ComposerProps extends Omit<ComponentProps<'textarea'>, 'className' | 'style'> {
2
+ export interface ComposerProps extends Omit<ComponentProps<'textarea'>, 'className' | 'style' | 'children'> {
3
3
  /** the caption between the two buttons ("Your request is born") */
4
4
  hint?: ReactNode;
5
5
  /** the left button of the bar (an IconBtn — voice) */
6
6
  lead?: ReactNode;
7
7
  /** the right button of the bar (an `IconBtn invert` — send) */
8
8
  send?: ReactNode;
9
+ /** more writing areas under the first, each a `ComposerArea` (the reason under the request), above the bar */
10
+ children?: ReactNode;
9
11
  /** classes for the composer's wrapper, not the textarea */
10
12
  className?: string;
11
13
  /** inline style for the wrapper, not the textarea */
12
14
  style?: React.CSSProperties;
13
15
  }
14
- /** Composer — a big write-in area (display type) with a bottom bar: icon · hint · send. The write-in step's sheet content. */
15
- export declare function Composer({ hint, lead, send, className, style, ...textarea }: ComposerProps): import("react").JSX.Element;
16
+ /** Composer — a big write-in area (display type) with a bottom bar: icon · hint · send. The write-in step's sheet content. A second thing to write in the same card (the reason under the request) is a `ComposerArea` child: the same words, behind a dashed rule. The bar is drawn only when it holds a lead, a hint or a send. */
17
+ export declare function Composer({ hint, lead, send, children, className, style, ...textarea }: ComposerProps): import("react").JSX.Element;
18
+ export interface ComposerAreaProps extends Omit<ComponentProps<'textarea'>, 'className' | 'children'> {
19
+ }
20
+ /** ComposerArea — a further writing area inside a Composer (an optional reason or need under the request): the Composer's own type and look, shorter, with a dashed rule above it that the area draws itself. Controlled like any textarea (value + onChange); give it its own aria-label. */
21
+ export declare function ComposerArea(props: ComposerAreaProps): import("react").JSX.Element;
@@ -1,8 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  import { useStepCursor } from '../sheets/stepCursor.js';
4
- /** Composer — a big write-in area (display type) with a bottom bar: icon · hint · send. The write-in step's sheet content. */
5
- export function Composer({ hint, lead, send, className, style, ...textarea }) {
4
+ /** Composer — a big write-in area (display type) with a bottom bar: icon · hint · send. The write-in step's sheet content. A second thing to write in the same card (the reason under the request) is a `ComposerArea` child: the same words, behind a dashed rule. The bar is drawn only when it holds a lead, a hint or a send. */
5
+ export function Composer({ hint, lead, send, children, className, style, ...textarea }) {
6
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] })] }));
7
+ return (_jsxs("div", { className: cx('composer', className), style: style, children: [_jsx("textarea", { className: "composer__input", ...textarea, ...cursor }), children, (lead != null || hint != null || send != null) && (_jsxs("div", { className: "composer__bar", children: [lead, hint != null && _jsx("span", { className: "composer__hint", children: hint }), send] }))] }));
8
+ }
9
+ /** ComposerArea — a further writing area inside a Composer (an optional reason or need under the request): the Composer's own type and look, shorter, with a dashed rule above it that the area draws itself. Controlled like any textarea (value + onChange); give it its own aria-label. */
10
+ export function ComposerArea(props) {
11
+ const cursor = useStepCursor();
12
+ return _jsx("textarea", { className: "composer__input composer__area", ...props, ...cursor });
8
13
  }
package/dist/index.d.ts CHANGED
@@ -94,6 +94,7 @@ export * from './post/PostBox.js';
94
94
  export * from './media/Quote.js';
95
95
  export * from './media/Postcard.js';
96
96
  export * from './media/MapArea.js';
97
+ export * from './media/MapPick.js';
97
98
  export * from './media/Tile.js';
98
99
  export * from './media/Mosaic.js';
99
100
  export * from './layover/Banner.js';
package/dist/index.js CHANGED
@@ -98,6 +98,7 @@ export * from './post/PostBox.js';
98
98
  export * from './media/Quote.js';
99
99
  export * from './media/Postcard.js';
100
100
  export * from './media/MapArea.js';
101
+ export * from './media/MapPick.js';
101
102
  export * from './media/Tile.js';
102
103
  export * from './media/Mosaic.js';
103
104
  export * from './layover/Banner.js';
@@ -1,5 +1,8 @@
1
1
  import type { ComponentProps, ReactNode, Ref } from 'react';
2
2
  export interface MapAreaProps extends ComponentProps<'div'> {
3
+ /** IN FLOW instead of behind a stage: the map is a box in a card or a sheet's body, the ring and the hint in its own box.
4
+ * The only thing in a SheetBody, it fills the body: the sheet's head stays on top, its foot under it */
5
+ inline?: boolean;
3
6
  /** the words over the map, under the bar's line */
4
7
  hint?: ReactNode;
5
8
  /** the tile provider's credit, bottom right (OSM requires it) — a caption, never the engine's own control */
@@ -8,13 +11,16 @@ export interface MapAreaProps extends ComponentProps<'div'> {
8
11
  ringLabel?: string;
9
12
  /** the ring element — the engine reads its position to know which point is the choice */
10
13
  ringRef?: Ref<HTMLSpanElement>;
11
- /** the map engine's mount — ONE element, filled to the box (the app hands it to Leaflet or the like) */
14
+ /** FIRST, the map engine's mount — ONE element, filled to the box (the app hands it to Leaflet or the like); after it, the
15
+ * places the member may pick (`MapPick`), over the map */
12
16
  children?: ReactNode;
13
17
  }
14
18
  /**
15
- * MapArea — the box a map engine draws in, behind a stage sheet: the child is the engine's mount and fills it;
16
- * before tiles arrive (or offline) the box shows the wireframe street pattern, never a hole. The ring marks the
17
- * rough area being chosen and stays where the CSS puts it (centred in the band a half sheet leaves open) —
18
- * dragging the map underneath is the choice, never a pin; `ringRef` is how the engine learns where that is.
19
+ * MapArea — the box a map engine draws in: the first child is the engine's mount and fills it; before tiles arrive (or
20
+ * offline) the box shows the wireframe street pattern, never a hole. Two places: behind a stage sheet (the default), or
21
+ * `inline`, in flow inside a card or a sheet's body — as a SheetBody's only child it fills the body, so the open sheet
22
+ * becomes the map while its head stays. The ring marks the rough area: behind a stage it is centred in the band a half
23
+ * sheet leaves open, inline at the centre of its own box. Dragging the map underneath is that choice, never a pin;
24
+ * `ringRef` is how the engine learns where that is. Places to pick go after the mount, as `MapPick`s.
19
25
  */
20
- export declare function MapArea({ hint, credit, ringLabel, ringRef, className, children, ...rest }: MapAreaProps): import("react").JSX.Element;
26
+ export declare function MapArea({ inline, hint, credit, ringLabel, ringRef, className, children, ...rest }: MapAreaProps): import("react").JSX.Element;
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /**
4
- * MapArea — the box a map engine draws in, behind a stage sheet: the child is the engine's mount and fills it;
5
- * before tiles arrive (or offline) the box shows the wireframe street pattern, never a hole. The ring marks the
6
- * rough area being chosen and stays where the CSS puts it (centred in the band a half sheet leaves open) —
7
- * dragging the map underneath is the choice, never a pin; `ringRef` is how the engine learns where that is.
4
+ * MapArea — the box a map engine draws in: the first child is the engine's mount and fills it; before tiles arrive (or
5
+ * offline) the box shows the wireframe street pattern, never a hole. Two places: behind a stage sheet (the default), or
6
+ * `inline`, in flow inside a card or a sheet's body — as a SheetBody's only child it fills the body, so the open sheet
7
+ * becomes the map while its head stays. The ring marks the rough area: behind a stage it is centred in the band a half
8
+ * sheet leaves open, inline at the centre of its own box. Dragging the map underneath is that choice, never a pin;
9
+ * `ringRef` is how the engine learns where that is. Places to pick go after the mount, as `MapPick`s.
8
10
  */
9
- export function MapArea({ hint, credit, ringLabel = 'ย่านของคุณโดยประมาณ', ringRef, className, children, ...rest }) {
10
- return (_jsxs("div", { className: cx('map', className), ...rest, children: [children, _jsx("span", { ref: ringRef, className: "map__ring", role: "img", "aria-label": ringLabel }), hint != null && _jsx("span", { className: "map__hint", children: hint }), credit != null && _jsx("span", { className: "map__credit", children: credit })] }));
11
+ export function MapArea({ inline, hint, credit, ringLabel = 'ย่านของคุณโดยประมาณ', ringRef, className, children, ...rest }) {
12
+ return (_jsxs("div", { className: cx('map', inline && 'map--inline', className), ...rest, children: [children, _jsx("span", { ref: ringRef, className: "map__ring", role: "img", "aria-label": ringLabel }), hint != null && _jsx("span", { className: "map__hint", children: hint }), credit != null && _jsx("span", { className: "map__credit", children: credit })] }));
11
13
  }
@@ -0,0 +1,32 @@
1
+ import type { ChangeEventHandler, ComponentProps, ReactNode } from 'react';
2
+ import { type IconName } from '../type/Icon.js';
3
+ export interface MapPickProps extends Omit<ComponentProps<'label'>, 'onChange'> {
4
+ /** the place's short name, under its mark (a library, a village hall): the radio's accessible name */
5
+ children: ReactNode;
6
+ /** the icon inside the mark (store, house…); without one the mark is a plain round point */
7
+ icon?: IconName;
8
+ /** the place's point, from the top of the map's box (a CSS length / percentage): the mark's centre stands on it */
9
+ top: string;
10
+ /** the place's point, from the left of the map's box (a CSS length / percentage) */
11
+ left: string;
12
+ /** the input's name: the marks of one choice share it (that is what makes them one group) */
13
+ name?: string;
14
+ /** what the input reports when this place is picked */
15
+ value?: string;
16
+ /** CONTROLLED: this place is the one picked. The app owns it — pair it with `onChange` */
17
+ checked?: boolean;
18
+ /** UNCONTROLLED: picked on arrival; the input then keeps its own */
19
+ defaultChecked?: boolean;
20
+ /** the native radio's change event: the member picked this place */
21
+ onChange?: ChangeEventHandler<HTMLInputElement>;
22
+ /** this place cannot be picked now */
23
+ disabled?: boolean;
24
+ }
25
+ /**
26
+ * MapPick — a place on a map the member taps to choose it: a small round mark with its short name under it, standing on
27
+ * its point (`top` / `left` in the map's box, as a Callout). Drawn small, so five fit on a phone's map; the tap is the
28
+ * full `--tap` square round the point, and the name takes a tap too. The marks of one choice share a `name` and are one
29
+ * radio group (arrow keys move between them); the picked one fills with accent. It knows nothing of the map or the sheet:
30
+ * it goes in a `MapArea` after the engine's mount, and the app owns which place is picked.
31
+ */
32
+ export declare function MapPick({ children, icon, top, left, name, value, checked, defaultChecked, onChange, disabled, className, style, ...rest }: MapPickProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../cx.js';
3
+ import { Icon } from '../type/Icon.js';
4
+ /**
5
+ * MapPick — a place on a map the member taps to choose it: a small round mark with its short name under it, standing on
6
+ * its point (`top` / `left` in the map's box, as a Callout). Drawn small, so five fit on a phone's map; the tap is the
7
+ * full `--tap` square round the point, and the name takes a tap too. The marks of one choice share a `name` and are one
8
+ * radio group (arrow keys move between them); the picked one fills with accent. It knows nothing of the map or the sheet:
9
+ * it goes in a `MapArea` after the engine's mount, and the app owns which place is picked.
10
+ */
11
+ export function MapPick({ children, icon, top, left, name, value, checked, defaultChecked, onChange, disabled, className, style, ...rest }) {
12
+ return (_jsxs("label", { className: cx('map-pick', className), style: { top, left, ...style }, ...rest, children: [_jsx("input", { type: "radio", name: name, value: value, checked: checked, defaultChecked: defaultChecked, onChange: onChange, disabled: disabled }), _jsx("span", { className: "map-pick__mark", children: icon && _jsx(Icon, { name: icon, size: "xs" }) }), _jsx("span", { className: "map-pick__name", children: children })] }));
13
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  export interface ActionBarProps extends ComponentProps<'div'> {
3
- /** `pager`: previous / next as two equal buttons at the edges (a Link may sit between them) · `tiers`: rows stacked — a control tier above the buttons */
3
+ /** `pager`: previous / next as two equal buttons at the edges — two round IconBtns, or two Btns that name the step they lead to, each at its own size (a Link may sit between them) · `tiers`: rows stacked — a control tier above the buttons */
4
4
  variant?: 'pager' | 'tiers';
5
5
  /** a bare bar for a preview: no position, full width, no side padding (a real bar is a Screen's `bottom`) */
6
6
  inline?: boolean;
@@ -9,13 +9,17 @@ export interface ActionBarProps extends ComponentProps<'div'> {
9
9
  * ActionBar — the screen's ONE move: the Screen's `bottom` (`<Screen fill bottom={<ActionBar…>}>`), the last part of its flex
10
10
  * column, as tall as its rows. Never after `</Screen>`, never positioned by hand.
11
11
  * Plain: one wide primary Btn (the step that commits).
12
- * `pager`: the pager bar — Back + Next as peers, buttons only. The `Pager` (the dots that say where the walk is) is a different thing: it may be the bar's first row (a Walkthrough).
12
+ * `pager`: the pager bar — Back + Next as peers, buttons only: two round `IconBtn`s (`arrow-left` · `invert arrow-right`) at the
13
+ * edges, or two `Btn`s that SAY the step each leads to, the arrow at the round pager's size (`<Btn><Icon name="arrow-left" /> คำขอ</Btn>` ·
14
+ * `<Btn primary>วันเวลา <Icon name="arrow-right" /></Btn>`): each hugs its edge at its own size, open ground between — left and
15
+ * right, never two halves (Lh 2026-10-17). A Link between stays in the middle.
16
+ * A word that commits ("Done") is not a pager button: that step is the plain bar's one wide Btn. The `Pager` (the dots that say where the walk is) is a different thing: it may be the bar's first row (a Walkthrough).
13
17
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
14
18
  * In a `Screen fill` it is a layer over the content's foot; on a scrolling Screen a sticky last row.
15
19
  */
16
20
  export declare function ActionBar({ variant, inline, className, style, ...rest }: ActionBarProps): import("react").JSX.Element;
17
21
  export interface ActionBarTierProps extends ComponentProps<'div'> {
18
- /** the tier that holds the round pager bar (two IconBtns at the edges) */
22
+ /** the tier that holds the pager (two IconBtns at the edges, or two worded Btns, each at its edge at its own size) */
19
23
  pager?: boolean;
20
24
  }
21
25
  /** ActionBarTier — one row inside an `ActionBar variant="tiers"`: a control row, or the pager row. */
@@ -4,7 +4,11 @@ import { cx } from '../cx.js';
4
4
  * ActionBar — the screen's ONE move: the Screen's `bottom` (`<Screen fill bottom={<ActionBar…>}>`), the last part of its flex
5
5
  * column, as tall as its rows. Never after `</Screen>`, never positioned by hand.
6
6
  * Plain: one wide primary Btn (the step that commits).
7
- * `pager`: the pager bar — Back + Next as peers, buttons only. The `Pager` (the dots that say where the walk is) is a different thing: it may be the bar's first row (a Walkthrough).
7
+ * `pager`: the pager bar — Back + Next as peers, buttons only: two round `IconBtn`s (`arrow-left` · `invert arrow-right`) at the
8
+ * edges, or two `Btn`s that SAY the step each leads to, the arrow at the round pager's size (`<Btn><Icon name="arrow-left" /> คำขอ</Btn>` ·
9
+ * `<Btn primary>วันเวลา <Icon name="arrow-right" /></Btn>`): each hugs its edge at its own size, open ground between — left and
10
+ * right, never two halves (Lh 2026-10-17). A Link between stays in the middle.
11
+ * A word that commits ("Done") is not a pager button: that step is the plain bar's one wide Btn. The `Pager` (the dots that say where the walk is) is a different thing: it may be the bar's first row (a Walkthrough).
8
12
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
9
13
  * In a `Screen fill` it is a layer over the content's foot; on a scrolling Screen a sticky last row.
10
14
  */
package/lint/eslint.mjs CHANGED
@@ -18,7 +18,7 @@ const rule = (level, description) => ({
18
18
  };
19
19
  },
20
20
  });
21
- const check = rule('error', 'cardds used through its components: no hand-written cardds class, no native <select>; the bar is the Screen\'s bottom');
21
+ const check = rule('error', 'cardds used through its components: no hand-written cardds class, no native <select>; the bar is the Screen\'s bottom; a card never wraps a card');
22
22
  const advice = rule('warn', 'cardds behaviour kept: a Sheet that can move and slides out; no bar in front of a sheet; the cursor, the control names and the look are cardds\'s and the theme\'s, not the element\'s');
23
23
 
24
24
  const plugin = { meta: { name: '@usableapp/cardds/eslint' }, rules: { check, advice } };
package/lint/rules.mjs CHANGED
@@ -216,5 +216,16 @@ export function checkSource(source, file, { classes = carddsClasses() } = {}) {
216
216
  add('row-label-button', 'warn', m.index, '<Row as="label"> holding a button and no htmlFor: a label presses the first control inside it, so a touch on the row\'s words presses that button, not its Check. Give the Check an id and the row htmlFor={id}.');
217
217
  }
218
218
 
219
+ // 9 · a card never wraps a card (Lh 2026-10-17): a CardList, a CardFeed and a row card stand on the base or in a sheet's body, each card its own boundary.
220
+ // `Card sm` inside a card is left alone: its own doc still names that place (not ruled yet)
221
+ for (const m of text.matchAll(/<Card\b(?![\w.])/g)) {
222
+ const el = elementAt(text, m.index);
223
+ const inner = el.slice(tagAt(text, m.index).length);
224
+ for (const c of inner.matchAll(/<(Card|CardList|CardFeed|FeedCard)\b(?![\w.])/g)) {
225
+ if (c[1] === 'Card' && /\ssm\b(?!\s*=\s*\{\s*false)/.test(tagAt(inner, c.index))) continue;
226
+ add('card-in-card', 'error', m.index + tagAt(text, m.index).length + c.index, `<${c[1]}> inside a <Card>: a card never wraps a card — stand the ${c[1] === 'Card' ? 'card' : c[1]} on the base or in a sheet's body, without a card round it. Rows inside a card are <Rows>/<Row>, which draw no boundary.`);
227
+ }
228
+ }
229
+
219
230
  return findings.sort((a, b) => a.line - b.line);
220
231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usableapp/cardds",
3
- "version": "0.7.93",
3
+ "version": "0.7.94",
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.",