@usableapp/cardds 0.7.73 → 0.7.75
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 +41 -0
- package/README.md +13 -10
- package/bin/cardds.mjs +4 -2
- package/css/layover.css +5 -1
- package/css/lists.css +1 -1
- package/dist/actions/{ActionNote.d.ts → BarNote.d.ts} +3 -3
- package/dist/actions/{ActionNote.js → BarNote.js} +2 -2
- package/dist/actions/BtnGroup.d.ts +11 -0
- package/dist/actions/{Segment.js → BtnGroup.js} +4 -4
- package/dist/actions/{WaitLink.d.ts → Countdown.d.ts} +3 -3
- package/dist/actions/{WaitLink.js → Countdown.js} +2 -2
- package/dist/actions/Link.d.ts +2 -2
- package/dist/cardds.css +6 -2
- package/dist/cards/Card.d.ts +1 -1
- package/dist/cards/CardParts.d.ts +1 -1
- package/dist/cards/CardParts.js +1 -1
- package/dist/catalog.json +1 -1
- package/dist/choice/Calendar.d.ts +2 -2
- package/dist/index.d.ts +7 -7
- package/dist/index.js +7 -7
- package/dist/journey/{TileBadge.d.ts → JourneyBadge.d.ts} +3 -3
- package/dist/journey/{TileBadge.js → JourneyBadge.js} +2 -2
- package/dist/numbers/NumberBand.d.ts +18 -0
- package/dist/numbers/{Band.js → NumberBand.js} +4 -4
- package/dist/post/PostLetter.d.ts +5 -5
- package/dist/scaffold/ActionBar.d.ts +2 -2
- package/dist/scaffold/ActionBar.js +1 -1
- package/dist/scaffold/Centre.d.ts +3 -3
- package/dist/scaffold/Centre.js +3 -3
- package/dist/scaffold/Page.d.ts +1 -1
- package/dist/scaffold/Page.js +1 -1
- package/dist/scaffold/Pager.d.ts +11 -0
- package/dist/scaffold/Pager.js +6 -0
- package/dist/scaffold/Panes.d.ts +2 -2
- package/dist/scaffold/Panes.js +3 -3
- package/dist/scaffold/Screen.d.ts +1 -1
- package/dist/scaffold/Screen.js +1 -1
- package/dist/sheets/{Drawer.d.ts → Modal.d.ts} +13 -13
- package/dist/sheets/{Drawer.js → Modal.js} +10 -10
- package/dist/sheets/Sheet.d.ts +2 -1
- package/dist/sheets/Sheet.js +1 -0
- package/dist/sheets/SheetStack.d.ts +1 -0
- package/dist/sheets/SheetStack.js +1 -0
- package/dist/sheets/SheetSteps.d.ts +2 -2
- package/dist/sheets/SheetSteps.js +1 -1
- package/dist/sheets/StackSheet.d.ts +2 -2
- package/dist/sheets/StackSheet.js +1 -1
- package/dist/templates/CallSheet.d.ts +2 -2
- package/dist/templates/Home.d.ts +1 -1
- package/dist/templates/Walkthrough.d.ts +4 -4
- package/dist/templates/Walkthrough.js +2 -2
- package/lib/catalog.mjs +21 -2
- package/lib/guide.mjs +16 -0
- package/lib/mcp.mjs +3 -2
- package/lint/eslint.mjs +2 -2
- package/lint/rules.mjs +75 -2
- package/package.json +1 -1
- package/dist/actions/Segment.d.ts +0 -11
- package/dist/numbers/Band.d.ts +0 -18
- package/dist/scaffold/PagerAt.d.ts +0 -11
- package/dist/scaffold/PagerAt.js +0 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
> For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
|
|
4
4
|
|
|
5
|
+
## 0.7.75 — 2026-10-09
|
|
6
|
+
|
|
7
|
+
### Changed — ten names say what the component is for (BREAKING: the old names are gone)
|
|
8
|
+
A name that said a shape now says a function (Lh 2026-10-08). The CSS classes and every knob are UNCHANGED, so a project's `theme.css` and its `--segment-*` / `--band-*` / `--wait-link-*` knobs keep working; only the import changes. Nothing of the old names is exported any more — update the import or the build fails (Lh 2026-10-09: a clean cut, no alias).
|
|
9
|
+
|
|
10
|
+
| was | is | what it is for |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `Segment`, `SegmentItem` | `BtnGroup`, `BtnGroupItem` | a panel of buttons, one on at a time (a grouped button; "segment" is kept for a divider of content) |
|
|
13
|
+
| `WaitLink` | `Countdown` | a link that counts down and takes no tap until the time has passed |
|
|
14
|
+
| `PagerAt` | `Pager` | the dots that say where one is in a walk (the bar is still `ActionBar variant="pager"`) |
|
|
15
|
+
| `TileBadge` | `JourneyBadge` | the small badge on a journey's tile |
|
|
16
|
+
| `Band`, `BandStack` | `NumberBand`, `NumberBandStack` | one number as a band; several stacked |
|
|
17
|
+
| `Lift` | `ModalBody` | the part of a Modal that lifts: what is shown above the question |
|
|
18
|
+
| `Drawer` | `ModalAsk` | the question and its answers at the Modal's foot (`ModalAsk light` as before) |
|
|
19
|
+
| `ActionNote` | `BarNote` | words standing where the bar's button would be |
|
|
20
|
+
|
|
21
|
+
Their Props types follow (`BtnGroupProps`, `ModalAskProps`…). `Centre`, `CtaPack`, `ActionBarTier`, `BtnTrio`, `IconBtnGroup` keep their names.
|
|
22
|
+
|
|
23
|
+
### Changed — fewer stories, one per prop axis
|
|
24
|
+
Ten gallery stories that differed from a sibling only by a tone, a side or an on/off are folded into that sibling (values of one axis stand side by side in one story): `Mood/Default` (was Picked + Unpicked), `Sticky/Looks` (was Tones + Flat + Tilt), `AvatarPick/Inset` (+ OnDark), `Modal/Confirm` (+ ConfirmLight), `Sheet/Half` (+ Look), `Field/Plain` (+ WithIcon); `StickyWelcome/WelcomeRight`, `SheetStack/ClosedOpened` and `SheetStack/StackBackBackOfStack` are gone. 419 → 409 stories; the catalog's `scaffold` ids change with them. The rendered html of every other story is byte-identical.
|
|
25
|
+
|
|
26
|
+
## 0.7.74 — 2026-10-08
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **The guide: the few rules no checker sees, in one short text** (Lh 2026-10-08) — `npx cardds guide` prints it, and `npx cardds mcp` hands the same text to every session as the server's instructions. It says WHEN cardds applies in the words of the work (a prototype, a poc, a mock-up, ขึ้นหน้า… — a screen, a sheet, a sticky note, a stamp, a postcard), the order (find → scaffold or get → write → check), and five rules: take cardds from outside and never edit it; adjust by theme → knobs → props; every component is controlled like an input; import, never copy markup; mobile only. Before → after: these lived in a personal skill file outside the repo → they ship with the version you installed.
|
|
30
|
+
- **Four more rules in `npx cardds check`** (and the ESLint plugin):
|
|
31
|
+
- `focus-by-hand` (warning) — an `autoFocus` or a `.focus()` in a file with a `Sheet`, `SheetStep`, `SheetStack`, `Home`, `Modal` or `CallSheet`: cardds places the cursor itself. Remove it; `data-autofocus` on a field picks that field.
|
|
32
|
+
- `control-name-repeat` (warning) — `handle="…"`, `backLabel="…"`, `nextLabel="…"`, `prevLabel="…"`, `skipLabel="…"` (and `PostLetter`'s) written as a plain string: cardds names these controls in the page's language (`<html lang="th">`). Drop it unless the words differ; another language is `setWords(lang, {…})` once.
|
|
33
|
+
- `native-select` (error) — a `<select>`: use `Dropdown`.
|
|
34
|
+
- `raw-value` (warning) — a raw colour or `px` length in a `style` prop: set it in `theme.css`, by a knob, or by a prop. A knob in a style (`'--tile-badge-ink': '#c00'`) is not flagged.
|
|
35
|
+
A deliberate case keeps `// cardds-ignore-next-line` with its reason, as before. Expect new warnings on the next `npm run cardds:check`.
|
|
36
|
+
- **`--menu-max-h`** — the tallest a popover `Menu` / `Dropdown` panel grows (default 60% of the screen, less its margins).
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
- **`npx cardds check` reads a component by its real name under an import alias** — `import { Home as HomeScreen }` then `<HomeScreen backLabel="กลับ">` passed every rule that looks for a tag; it is checked now.
|
|
40
|
+
- **`npx cardds scaffold <id>` leaves out a name cardds already gives its control** — the starting file no longer carries `backLabel="กลับ"` / `handle="…"` from the gallery's story. `npx cardds get` still shows the story as written.
|
|
41
|
+
- The JSDoc of `Sheet`, `SheetStep`, `StackSheet`, `SheetStack`, `Modal` says that cardds places the cursor, and each cardds-named control prop says it is named by cardds — what `npx cardds get <Name>` returns.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- **A long `Menu` / `Dropdown` ran off the foot of the screen** (timebank, 2026-10-08) — twenty items made a panel 979px tall on an 812px screen, the last names out of reach. Before → after: the panel grew with its items → it stops at `--menu-max-h` and scrolls inside. Nothing to change in an app.
|
|
45
|
+
|
|
5
46
|
## 0.7.73 — 2026-10-08
|
|
6
47
|
|
|
7
48
|
### Added
|
package/README.md
CHANGED
|
@@ -149,15 +149,18 @@ npx cardds scaffold <id> # a whole file from a screen: tsx (the code) or
|
|
|
149
149
|
npx cardds get <Name> [--html] # one component: its role, props (a ReactNode prop is a slot) + variations with code
|
|
150
150
|
npx cardds list # every name by what it is for: one · group-brief · group-detail, the show-only first, then by what the member can do
|
|
151
151
|
npx cardds check <dir> # the rules, before a screen is called done
|
|
152
|
+
npx cardds guide # the few rules no checker sees, in one short text
|
|
152
153
|
```
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
`list` · `get` · `find` · `scaffold` take `--json` (the function's result) and `--root <path>` (a cardds checkout: rebuilt first, so never stale). The same
|
|
155
156
|
five answers over MCP — `list_components` · `get_component` · `find_components` · `scaffold_screen` · `check_source`:
|
|
156
157
|
|
|
157
158
|
```json
|
|
158
159
|
{ "mcpServers": { "cardds": { "command": "npx", "args": ["cardds", "mcp"] } } }
|
|
159
160
|
```
|
|
160
161
|
|
|
162
|
+
The MCP server hands a session the same text as `npx cardds guide` as its instructions, when it starts.
|
|
163
|
+
|
|
161
164
|
A scaffolded tsx may import `./_story` or `./_*Art`: story glue, not in the package — replace or drop it.
|
|
162
165
|
|
|
163
166
|
### React adapter — how a component is written
|
|
@@ -236,16 +239,16 @@ of `Screen`:
|
|
|
236
239
|
|---|---|---|
|
|
237
240
|
| **top** | `top` | `TopBar` · `AppBar` · `"lead"` (no bar: the content starts `--screen-lead`, 15% of the screen, down) |
|
|
238
241
|
| **content** | children | a `Step` (`Pages` · `Page` · `Page` + `Sheet`) · `Centre` · `SheetStack` … |
|
|
239
|
-
| **bottom** | `bottom` | `ActionBar` (rows: `controls` · the one `cta` row — the pager ← → or one wide button · a `ctaLink`) · `FloatBar` · nothing |
|
|
242
|
+
| **bottom** | `bottom` | `ActionBar` (rows: `controls` · the one `cta` row — the pager bar ← → or one wide button · a `ctaLink`) · `FloatBar` · nothing |
|
|
240
243
|
|
|
241
244
|
```tsx
|
|
242
245
|
<Screen fill top="lead" bottom={<ActionBar variant="pager">…</ActionBar>}>
|
|
243
|
-
<Step><Pages>…</Pages><
|
|
246
|
+
<Step><Pages>…</Pages><Pager at={1} of={3} /></Step>
|
|
244
247
|
</Screen>
|
|
245
248
|
```
|
|
246
249
|
|
|
247
250
|
- **Top and content are one flex column; the bottom is a LAYER** (Lh 2026-09-23). In `Screen fill` the content runs to the
|
|
248
|
-
screen's bottom edge; the bottom (`ActionBar`, with the walk's `
|
|
251
|
+
screen's bottom edge; the bottom (`ActionBar`, with the walk's `Pager` as its first row) lies over it at the foot and
|
|
249
252
|
covers what is under it — on purpose, nothing is added to the content to make room. The keyboard lifts the layer (`--kb`).
|
|
250
253
|
- **The bar is INSIDE the screen** — `bottom`, never a sibling after `</Screen>`, never pinned with a hand-set `position`.
|
|
251
254
|
A `FloatBar` in `bottom` floats the same way.
|
|
@@ -336,7 +339,7 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
336
339
|
needs to: the body's bottom padding is the part that slid under (`--_cover`), so the last row comes up to
|
|
337
340
|
the button at half and 3q as it does at full.
|
|
338
341
|
- **Layers: the base, the sheet over it, the top bar** (Lh 2026-09-20). The `.action-bar` belongs to the
|
|
339
|
-
BASE — the move of a page of words (a pager, or one wide button). It is never in front of a sheet. A
|
|
342
|
+
BASE — the move of a page of words (a pager bar, or one wide button). It is never in front of a sheet. A
|
|
340
343
|
screen with a sheet finishes ON the sheet: a `.card__foot`, the sheet's last child — ONE row, the page's
|
|
341
344
|
conclusion. The foot stands on the screen's bottom edge at half, 3q and full alike (a layer as tall as
|
|
342
345
|
the sheet, slid up by the share the sheet slid down — `sheet.css`), and is gone at peek. Nothing inside
|
|
@@ -401,19 +404,19 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
401
404
|
above the centre (2 × (40% − the title bar)): a card taller than that is
|
|
402
405
|
not a centred card — it is a plain scrolling `.screen` (the detail view).
|
|
403
406
|
- **A small group at the same centre** — an empty state; a card with its quiet
|
|
404
|
-
second button and the
|
|
407
|
+
second button and the `Pager` dots under it — is `.screen__centre` (React: `Centre`), a block in the fill
|
|
405
408
|
screen after the topbar. Same placement and cap; it runs edge to edge with
|
|
406
409
|
the inset as padding, and stacks its children at the
|
|
407
410
|
screen's gap. Never hand-lay `top: 40%` on a screen.
|
|
408
411
|
- **The bar is the screen's bottom.** Plain `.action-bar` for one move; with a
|
|
409
412
|
control that belongs to the screen (a slider), `.action-bar--tiers` stacks a
|
|
410
413
|
control tier above the buttons (it publishes the taller `--bar-reserve` the centred card still reads for its cap).
|
|
411
|
-
- **The round pager** is `.action-bar--pager` (or an `.action-bar__tier--pager`)
|
|
414
|
+
- **The round pager bar** is `.action-bar--pager` (or an `.action-bar__tier--pager`)
|
|
412
415
|
holding two `.icon-btn`s and nothing else: they stand at the two edges. The
|
|
413
416
|
step that commits still goes back to one wide button (rule 3).
|
|
414
417
|
- **Page that moves** (Lh 2026-09-20) — two ways, never both on one screen:
|
|
415
418
|
- *Pages of words:* `.step > .pages > .page` (one `aria-current="step"`, the rest `inert`) — every page
|
|
416
|
-
one height, the round pager turns them (`slideTo()`), the words slide like a page. No sheet there.
|
|
419
|
+
one height, the round pager bar turns them (`slideTo()`), the words slide like a page. No sheet there.
|
|
417
420
|
ONE SCREEN TO THE NEXT (a route change) slides the WHOLE screen the same way: `slideScreen(() => navigate(to), 'next' | 'back')`,
|
|
418
421
|
and `installScreenSlide()` once at app start for the back button (story `Step · WalksOn`). `data-slide="off"` on a Screen = no slide.
|
|
419
422
|
ONE OUTCOME, DRAWN ONCE (Lh 2026-10-01): a screen that can come out more than one way (signed in or not, a list or
|
|
@@ -446,7 +449,7 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
446
449
|
it scroll clear.
|
|
447
450
|
**Never a form taken apart** — one sheet per field, a "send" left on the base: a form lives whole in ONE sheet.
|
|
448
451
|
Fixture `tests/fixtures/calls.html`, tests `tests/calls.spec.js`, stories `Step · Walks` / `Step · Calls`.
|
|
449
|
-
- **A pager turns pages of words, never a screen with a sheet** (Lh 2026-09-20). A
|
|
452
|
+
- **A pager bar turns pages of words, never a screen with a sheet** (Lh 2026-09-20). A
|
|
450
453
|
sheet means this screen has work that cannot be walked past: no ← →, no bar at all — the sheet's
|
|
451
454
|
own foot finishes it. (Guidance — not enforced.)
|
|
452
455
|
- A screen is **top · content · bottom** on the base (*Screen: top · content · bottom*). The base is the
|
|
@@ -697,7 +700,7 @@ question and hunts for its answer. Rules 11–13 serve that:
|
|
|
697
700
|
not filled with more content; a new edge is made instead (line the buttons
|
|
698
701
|
up with the avatar).
|
|
699
702
|
12. **Show, don't explain.** Where a known picture carries it — an avatar for a
|
|
700
|
-
person, an icon, a `Ring`/`Track`/`
|
|
703
|
+
person, an icon, a `Ring`/`Track`/`NumberBand`, a `Chip` for a category — use it
|
|
701
704
|
instead of words. Never add a label, a caption line, a comparison or a
|
|
702
705
|
tooltip to explain what the picture already says (the retired overline was
|
|
703
706
|
that mistake). Long lists are differentiated, not spaced out: group them
|
package/bin/cardds.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// `npx cardds init` — set a project up the ONE way (Lh 2026-09-29): the project's theme.css, the check script, the ESLint rules.
|
|
3
3
|
// `npx cardds check [dir…]` — the rules of lint/rules.mjs over the project's source; exit 1 on an error.
|
|
4
|
+
// `npx cardds guide` — the few rules no checker sees, as text (the MCP server hands the same text over as its instructions).
|
|
4
5
|
// `npx cardds list | get <Name> | find [words…] [--represents a,b] [--shape s] [--interact k|none] | scaffold [id]` — the component catalog (lib/catalog.mjs) for an AI session; `npx cardds mcp` serves the same over MCP.
|
|
5
6
|
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, copyFileSync } from 'node:fs';
|
|
6
7
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { checkSource } from '../lint/rules.mjs';
|
|
9
10
|
import * as catalog from '../lib/catalog.mjs';
|
|
11
|
+
import { GUIDE } from '../lib/guide.mjs';
|
|
10
12
|
|
|
11
13
|
const PKG = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
14
|
const [cmd, ...args] = process.argv.slice(2);
|
|
@@ -123,6 +125,6 @@ function ask(cmd, argv) {
|
|
|
123
125
|
return 0;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
const code = cmd === 'check' ? check(args) : cmd === 'init' ? init() : ['list', 'get', 'find', 'scaffold', 'mcp'].includes(cmd) ? await ask(cmd, args)
|
|
127
|
-
: (console.log('usage: cardds init | check [dir…] | list | get <Name> [--html] | find [words…] [--represents a,b] [--shape s] [--interact k|none] | scaffold [id] [--html [--css <href>]] | mcp (list get find scaffold mcp: --root <path> --json)'), cmd ? 2 : 0);
|
|
128
|
+
const code = cmd === 'check' ? check(args) : cmd === 'init' ? init() : cmd === 'guide' ? (process.stdout.write(GUIDE), 0) : ['list', 'get', 'find', 'scaffold', 'mcp'].includes(cmd) ? await ask(cmd, args)
|
|
129
|
+
: (console.log('usage: cardds init | guide | check [dir…] | list | get <Name> [--html] | find [words…] [--represents a,b] [--shape s] [--interact k|none] | scaffold [id] [--html [--css <href>]] | mcp (list get find scaffold mcp: --root <path> --json)'), cmd ? 2 : 0);
|
|
128
130
|
process.exitCode = code; // not process.exit: a big answer piped to a reader is cut off by an exit before it is flushed
|
package/css/layover.css
CHANGED
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
--accent: var(--card-ink);
|
|
80
80
|
--on-accent: var(--card-bg);
|
|
81
81
|
}
|
|
82
|
-
/* the CONFIRM: a Modal with no
|
|
82
|
+
/* the CONFIRM: a Modal with no ModalBody: the ModalAsk IS the card, as tall as its words (no fixed band, no second beat) */
|
|
83
83
|
.modal:not(:has(> .modal__lift)) > .modal__drawer { height: auto; padding-block: var(--modal-confirm-pad-y, var(--sp-6)); animation: none; }
|
|
84
84
|
.modal__drawer .t-caption { opacity: var(--dim); }
|
|
85
85
|
.modal__drawer > h2:focus { outline: none; } /* the question takes the cursor when the modal arrives (for a screen reader, the keyboard's way in) — it is not a control: no ring */
|
|
@@ -128,6 +128,10 @@
|
|
|
128
128
|
padding: var(--menu-pad);
|
|
129
129
|
min-width: 12rem;
|
|
130
130
|
align-content: start; /* rows keep their own height: WebKit stretches a popover grid's rows to the whole room under its anchor (iOS 26.5, 2026-10-08) */
|
|
131
|
+
--_menu-max: min(60dvh, calc(100dvh - 2 * var(--sp-5)));
|
|
132
|
+
max-height: var(--menu-max-h, var(--_menu-max)); /* a long list (twenty neighbours) scrolls inside the panel — it ran off the screen's foot, the last names out of reach (timebank, 2026-10-08) */
|
|
133
|
+
overflow-y: auto;
|
|
134
|
+
overscroll-behavior: contain;
|
|
131
135
|
background: var(--menu-bg, var(--action-bg));
|
|
132
136
|
color: var(--menu-ink, var(--action-ink));
|
|
133
137
|
box-shadow: var(--menu-shadow, var(--shadow-float));
|
package/css/lists.css
CHANGED
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
.link--sm { position: relative; font-size: var(--link-sm-font-size, var(--fs-2xs)); min-height: var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)); }
|
|
202
202
|
.link--sm::after { content: ""; position: absolute; inset: calc((var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)) - var(--tap-sm)) / 2) 0; }
|
|
203
203
|
.link--muted { color: var(--link-muted-ink, var(--card-muted)); }
|
|
204
|
-
.link:disabled { color: var(--link-disabled-ink, var(--card-muted)); cursor: default; } /* inactive = muted (a
|
|
204
|
+
.link:disabled { color: var(--link-disabled-ink, var(--card-muted)); cursor: default; } /* inactive = muted (a Countdown) */
|
|
205
205
|
.link--wait { font-variant-numeric: tabular-nums; } /* the countdown's digits keep their width: the words do not jiggle each second */
|
|
206
206
|
.card--3 .link--muted { color: var(--card-3-muted); }
|
|
207
207
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
|
-
export interface
|
|
2
|
+
export interface BarNoteProps extends ComponentProps<'div'> {
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* BarNote — the words that stand WHERE THE MAIN BUTTON WOULD BE when there is nothing to press: a block exactly the main button's
|
|
6
6
|
* size (full width, `--btn-h` tall), lying on the ground — no fill, no border, no rounding — holding one line of muted words, an
|
|
7
7
|
* inline `Icon` allowed ("กดที่ <Icon name="share" /> ด้านล่างนี้" — the member does the thing elsewhere). It does nothing on a tap.
|
|
8
8
|
* A member of the BOTTOM: a `CardFoot`'s row or an `ActionBar`'s, in place of the button, never beside one; a `link` still goes under
|
|
9
9
|
* it (CardFoot `link`, ActionBar `ctaLink`). One fixed shape — never styled per use (Lh 2026-09-24).
|
|
10
10
|
*/
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function BarNote({ className, children, ...rest }: BarNoteProps): import("react").JSX.Element;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* BarNote — the words that stand WHERE THE MAIN BUTTON WOULD BE when there is nothing to press: a block exactly the main button's
|
|
5
5
|
* size (full width, `--btn-h` tall), lying on the ground — no fill, no border, no rounding — holding one line of muted words, an
|
|
6
6
|
* inline `Icon` allowed ("กดที่ <Icon name="share" /> ด้านล่างนี้" — the member does the thing elsewhere). It does nothing on a tap.
|
|
7
7
|
* A member of the BOTTOM: a `CardFoot`'s row or an `ActionBar`'s, in place of the button, never beside one; a `link` still goes under
|
|
8
8
|
* it (CardFoot `link`, ActionBar `ctaLink`). One fixed shape — never styled per use (Lh 2026-09-24).
|
|
9
9
|
*/
|
|
10
|
-
export function
|
|
10
|
+
export function BarNote({ className, children, ...rest }) {
|
|
11
11
|
return _jsx("div", { role: "note", className: cx('action-note', className), ...rest, children: _jsx("span", { className: "action-note__words", children: children }) });
|
|
12
12
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export interface BtnGroupProps extends ComponentProps<'div'> {
|
|
3
|
+
}
|
|
4
|
+
/** BtnGroup — a panel of buttons, one on at a time (a tab strip, a day picker): its items share the width; children are BtnGroupItems. */
|
|
5
|
+
export declare function BtnGroup({ className, ...rest }: BtnGroupProps): import("react").JSX.Element;
|
|
6
|
+
export interface BtnGroupItemProps extends ComponentProps<'button'> {
|
|
7
|
+
/** the selected tab (filled) */
|
|
8
|
+
selected?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** BtnGroupItem — one tab in a BtnGroup. */
|
|
11
|
+
export declare function BtnGroupItem({ selected, className, ...rest }: BtnGroupItemProps): import("react").JSX.Element;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
|
-
/**
|
|
4
|
-
export function
|
|
3
|
+
/** BtnGroup — a panel of buttons, one on at a time (a tab strip, a day picker): its items share the width; children are BtnGroupItems. */
|
|
4
|
+
export function BtnGroup({ className, ...rest }) {
|
|
5
5
|
return _jsx("div", { className: cx('segment', className), role: "tablist", ...rest });
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
export function
|
|
7
|
+
/** BtnGroupItem — one tab in a BtnGroup. */
|
|
8
|
+
export function BtnGroupItem({ selected, className, ...rest }) {
|
|
9
9
|
return _jsx("button", { type: "button", role: "tab", "aria-selected": selected ? 'true' : undefined, className: cx('segment__item', className), ...rest });
|
|
10
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type LinkProps } from './Link.js';
|
|
3
|
-
export interface
|
|
3
|
+
export interface CountdownProps extends Omit<LinkProps, 'href'> {
|
|
4
4
|
/** CONTROLLED: when it may be pressed again (a time in ms, `Date.now()` based). Before that it counts down and takes no taps;
|
|
5
5
|
* null / past = ready. The APP owns the rule — 30 s, +30 s each time — and sets a new `until` in its onClick. */
|
|
6
6
|
until?: number | null;
|
|
@@ -8,7 +8,7 @@ export interface WaitLinkProps extends Omit<LinkProps, 'href'> {
|
|
|
8
8
|
waiting?: (left: string) => ReactNode;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Countdown — a Link that waits: "ขอรหัสใหม่ได้ใน 0:27", counting down each second, then "ขอรหัสใหม่" to press (a new OTP, a retry).
|
|
12
12
|
* A link, not a button: it is the step's second way, under its control; in a SheetStep it stands centred on its own row.
|
|
13
13
|
*/
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function Countdown({ until, waiting, children, className, disabled, ...rest }: CountdownProps): import("react").JSX.Element;
|
|
@@ -3,10 +3,10 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
4
|
import { Link } from './Link.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Countdown — a Link that waits: "ขอรหัสใหม่ได้ใน 0:27", counting down each second, then "ขอรหัสใหม่" to press (a new OTP, a retry).
|
|
7
7
|
* A link, not a button: it is the step's second way, under its control; in a SheetStep it stands centred on its own row.
|
|
8
8
|
*/
|
|
9
|
-
export function
|
|
9
|
+
export function Countdown({ until, waiting, children, className, disabled, ...rest }) {
|
|
10
10
|
const [now, setNow] = useState(() => Date.now());
|
|
11
11
|
const left = until != null ? Math.ceil((until - now) / 1000) : 0;
|
|
12
12
|
useEffect(() => {
|
package/dist/actions/Link.d.ts
CHANGED
|
@@ -9,12 +9,12 @@ export interface LinkProps extends ComponentProps<'a'> {
|
|
|
9
9
|
sm?: boolean;
|
|
10
10
|
/** without an href it renders as a button */
|
|
11
11
|
href?: string;
|
|
12
|
-
/** a button link that takes no taps now (reads inactive: muted) — `
|
|
12
|
+
/** a button link that takes no taps now (reads inactive: muted) — `Countdown` while it counts down */
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
}
|
|
15
15
|
/** Link — an inline text action big enough to tap (40px): "See all ↗" in a card head, "Skip" between pager buttons. */
|
|
16
16
|
export declare function Link({ muted, sm, icon, href, className, children, ...rest }: LinkProps): import("react").DetailedReactHTMLElement<{
|
|
17
|
-
/** a button link that takes no taps now (reads inactive: muted) — `
|
|
17
|
+
/** a button link that takes no taps now (reads inactive: muted) — `Countdown` while it counts down */
|
|
18
18
|
disabled?: boolean;
|
|
19
19
|
ref?: import("react").Ref<HTMLAnchorElement> | undefined;
|
|
20
20
|
key?: import("react").Key | null | undefined;
|
package/dist/cardds.css
CHANGED
|
@@ -3662,7 +3662,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3662
3662
|
.link--sm { position: relative; font-size: var(--link-sm-font-size, var(--fs-2xs)); min-height: var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)); }
|
|
3663
3663
|
.link--sm::after { content: ""; position: absolute; inset: calc((var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)) - var(--tap-sm)) / 2) 0; }
|
|
3664
3664
|
.link--muted { color: var(--link-muted-ink, var(--card-muted)); }
|
|
3665
|
-
.link:disabled { color: var(--link-disabled-ink, var(--card-muted)); cursor: default; } /* inactive = muted (a
|
|
3665
|
+
.link:disabled { color: var(--link-disabled-ink, var(--card-muted)); cursor: default; } /* inactive = muted (a Countdown) */
|
|
3666
3666
|
.link--wait { font-variant-numeric: tabular-nums; } /* the countdown's digits keep their width: the words do not jiggle each second */
|
|
3667
3667
|
.card--3 .link--muted { color: var(--card-3-muted); }
|
|
3668
3668
|
|
|
@@ -4785,7 +4785,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
4785
4785
|
--accent: var(--card-ink);
|
|
4786
4786
|
--on-accent: var(--card-bg);
|
|
4787
4787
|
}
|
|
4788
|
-
/* the CONFIRM: a Modal with no
|
|
4788
|
+
/* the CONFIRM: a Modal with no ModalBody: the ModalAsk IS the card, as tall as its words (no fixed band, no second beat) */
|
|
4789
4789
|
.modal:not(:has(> .modal__lift)) > .modal__drawer { height: auto; padding-block: var(--modal-confirm-pad-y, var(--sp-6)); animation: none; }
|
|
4790
4790
|
.modal__drawer .t-caption { opacity: var(--dim); }
|
|
4791
4791
|
.modal__drawer > h2:focus { outline: none; } /* the question takes the cursor when the modal arrives (for a screen reader, the keyboard's way in) — it is not a control: no ring */
|
|
@@ -4834,6 +4834,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
4834
4834
|
padding: var(--menu-pad);
|
|
4835
4835
|
min-width: 12rem;
|
|
4836
4836
|
align-content: start; /* rows keep their own height: WebKit stretches a popover grid's rows to the whole room under its anchor (iOS 26.5, 2026-10-08) */
|
|
4837
|
+
--_menu-max: min(60dvh, calc(100dvh - 2 * var(--sp-5)));
|
|
4838
|
+
max-height: var(--menu-max-h, var(--_menu-max)); /* a long list (twenty neighbours) scrolls inside the panel — it ran off the screen's foot, the last names out of reach (timebank, 2026-10-08) */
|
|
4839
|
+
overflow-y: auto;
|
|
4840
|
+
overscroll-behavior: contain;
|
|
4837
4841
|
background: var(--menu-bg, var(--action-bg));
|
|
4838
4842
|
color: var(--menu-ink, var(--action-ink));
|
|
4839
4843
|
box-shadow: var(--menu-shadow, var(--shadow-float));
|
package/dist/cards/Card.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface CardProps extends ComponentProps<'article'> {
|
|
|
19
19
|
fold?: boolean;
|
|
20
20
|
/** one card at the human centre of a `Screen fill` (the introduction screens) */
|
|
21
21
|
centre?: boolean;
|
|
22
|
-
/** a data band: label · giant number · footnote — see
|
|
22
|
+
/** a data band: label · giant number · footnote — see NumberBand */
|
|
23
23
|
band?: boolean;
|
|
24
24
|
/** the element: article (default), a when it links, label when it wraps an input, div/section */
|
|
25
25
|
as?: 'article' | 'div' | 'a' | 'label' | 'section' | 'li';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
2
|
export interface CardMediaProps extends ComponentProps<'div'> {
|
|
3
3
|
}
|
|
4
|
-
/** CardMedia — rounded media inside a card (a Placeholder or img); a corner
|
|
4
|
+
/** CardMedia — rounded media inside a card (a Placeholder or img); a corner JourneyBadge pins to it. */
|
|
5
5
|
export declare function CardMedia({ className, ...rest }: CardMediaProps): import("react").JSX.Element;
|
|
6
6
|
export interface CardBodyProps extends ComponentProps<'div'> {
|
|
7
7
|
}
|
package/dist/cards/CardParts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
|
-
/** CardMedia — rounded media inside a card (a Placeholder or img); a corner
|
|
3
|
+
/** CardMedia — rounded media inside a card (a Placeholder or img); a corner JourneyBadge pins to it. */
|
|
4
4
|
export function CardMedia({ className, ...rest }) {
|
|
5
5
|
return _jsx("div", { className: cx('card__media', className), ...rest });
|
|
6
6
|
}
|