@usableapp/cardds 0.7.3 → 0.7.5
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/css/choice.css +1 -1
- package/css/forms.css +12 -0
- package/css/sheet.css +40 -0
- package/css/tokens.css +1 -0
- package/dist/cardds.css +54 -1
- package/dist/forms/Field.d.ts +15 -2
- package/dist/forms/Field.js +28 -2
- package/dist/forms/FormNote.d.ts +7 -0
- package/dist/forms/FormNote.js +11 -0
- package/dist/forms/Pin.d.ts +11 -2
- package/dist/forms/Pin.js +54 -3
- package/dist/forms/check.d.ts +23 -0
- package/dist/forms/check.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sheets/Sheet.js +19 -2
- package/dist/sheets/SheetSteps.d.ts +47 -0
- package/dist/sheets/SheetSteps.js +244 -0
- package/dist/type/icons.d.ts +1 -1
- package/dist/type/icons.js +2 -2
- package/icons.svg +2 -0
- package/package.json +1 -1
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.5 — 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Errors in a form (new)
|
|
8
|
+
- `--signal-error` (#c62828) — the ONE colour the wireframe has; always with an icon and words, never the colour alone.
|
|
9
|
+
A theme may set it.
|
|
10
|
+
- `Field error` — the edge turns red, `aria-invalid`, and the field takes the focus with ALL its text selected (type
|
|
11
|
+
straight over it). `Pin error` — every cell red (edge + digit), the wrong digits stay in sight, the cursor goes back to
|
|
12
|
+
the first cell; the next digit starts a new code.
|
|
13
|
+
- `SheetStep error` / `busy` — ONE line under the step's button: an alert icon + words (`role="alert"`), or a turning
|
|
14
|
+
ring + words ("sending…", the step's buttons take no taps). The words are the app's.
|
|
15
|
+
- FORMAT checks on the client (never content — content errors come from the app through `error`): `Field format="email"
|
|
16
|
+
| "tel"`, `digits={n}`, `required`; `Pin match={firstPin}`; `formatError="…"` = the app's words. Checked when a field is
|
|
17
|
+
left and when the step's button is pressed — never while typing; a failing one holds the press back (the app's onClick
|
|
18
|
+
is not called). A Pin that does not match shows red for 3 s, then the steps go back one (`onAtChange(at, 'mismatch')` —
|
|
19
|
+
clear both codes there).
|
|
20
|
+
- Icons `circle-alert`, `loader-circle`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `Pin` — a typed digit is coloured words (`--pin-cell-filled-ink`, default the accent), no filled pill.
|
|
24
|
+
- `SheetSteps` — an ACCORDION: the old step folds while the new one opens, then the done pile scrolls (smoothly) so
|
|
25
|
+
2½ done rows show above the open step; a step change never drops a phone's keyboard (the cursor waits in a holder
|
|
26
|
+
field while the step folds); step titles at the label size (`--sheet-step-title-font`); a step stays ✓ when the
|
|
27
|
+
member goes back; `finish` is always shown, disabled until every step is done or skipped; the steps never close the
|
|
28
|
+
sheet — when `at` reaches the count, the app sets its Sheet `away`.
|
|
29
|
+
- `Sheet` — on arrival the cursor goes to a `[data-autofocus]` inside it (SheetSteps marks its open step's first field),
|
|
30
|
+
else the handle, once the sheet has finished sliding in.
|
|
31
|
+
|
|
32
|
+
## 0.7.4 — 2026-09-24
|
|
33
|
+
|
|
34
|
+
### New
|
|
35
|
+
- `SheetSteps` / `SheetStep` — one task taken in steps INSIDE one sheet (phone · code · PIN · PIN again · face · notices),
|
|
36
|
+
in a `SheetBody`. The done steps pile on top, one short line each (title · `summary` · ✓); the current step is open
|
|
37
|
+
under them, its form from the top at the form's own spacing; the next steps wait in a pile at the bottom (under the
|
|
38
|
+
keyboard when it is up). A thin rule over every step. Controlled: `at` + `onAtChange(at, 'tap' | 'swipe' | 'skip')`, or
|
|
39
|
+
`defaultAt`; the APP moves `at` when a step's work is really done; `at = count` = all done — the last step's own action
|
|
40
|
+
sends, or pass `finish` (a `Btn`) to stand under the steps on the last one. `reversible` = a tap on a done / next step
|
|
41
|
+
or an up/down swipe moves; without it only forward. `SheetStep skippable skipLabel="…"` = a small link that moves on.
|
|
42
|
+
The cursor stands in each new step's first field; with the keyboard up the field's middle is brought to eye level
|
|
43
|
+
(`--screen-centre` of what shows) and the done pile glides out of the way and back. Knobs `--sheet-steps-row-h`,
|
|
44
|
+
`--sheet-step-border`. Stories: `/SheetSteps` (Forward · Reversible · Start · Controlled · Finish · Long).
|
|
45
|
+
|
|
5
46
|
## 0.7.3 — 2026-09-24
|
|
6
47
|
|
|
7
48
|
### New
|
package/css/choice.css
CHANGED
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
font: var(--pin-cell-font, var(--type-stat));
|
|
206
206
|
font-variant-numeric: tabular-nums;
|
|
207
207
|
}
|
|
208
|
-
.pin__cell:not(:placeholder-shown) {
|
|
208
|
+
.pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
|
|
209
209
|
/* the cell the cursor waits in (Lh 2026-09-22): NO outline ring — it sat outside the pill and got cropped by the sheet's
|
|
210
210
|
edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
|
|
211
211
|
is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
|
package/css/forms.css
CHANGED
|
@@ -65,3 +65,15 @@
|
|
|
65
65
|
line-height: 1;
|
|
66
66
|
}
|
|
67
67
|
.add-row__plus .icon { width: var(--icon-xs); height: var(--icon-xs); }
|
|
68
|
+
|
|
69
|
+
/* ---- a wrong value, and the line that says so (Lh 2026-09-24) ----
|
|
70
|
+
--signal-error is the one colour the wireframe has; it never speaks alone — the line carries an icon and words.
|
|
71
|
+
<p class="form-note form-note--error" role="alert"><svg class="icon icon--xs">…</svg>Not a phone number</p>
|
|
72
|
+
<p class="form-note form-note--busy" role="status"><svg …/>Sending the code…</p> (the ring turns) */
|
|
73
|
+
.field.field--error, [data-palette] .field.field--error { border-color: var(--field-error-border, var(--signal-error)); }
|
|
74
|
+
.pin.pin--error .pin__cell, .pin.pin--error .pin__cell:focus { border-color: var(--pin-error-border, var(--signal-error)); color: var(--pin-error-ink, var(--signal-error)); } /* ALL the cells, edge and digit — the one the cursor waits in too (Lh 2026-09-24); the wrong digits stay in red until the member types again */
|
|
75
|
+
.form-note { display: flex; align-items: center; justify-content: center; gap: var(--sp-2); margin: 0; font: var(--form-note-font, var(--type-caption)); color: var(--form-note-ink, var(--card-muted)); text-align: center; }
|
|
76
|
+
.form-note > .icon { flex: none; }
|
|
77
|
+
.form-note--error { color: var(--form-note-error-ink, var(--signal-error)); }
|
|
78
|
+
@media (prefers-reduced-motion: no-preference) { .form-note--busy > .icon { animation: form-note-turn 0.9s linear infinite; } }
|
|
79
|
+
@keyframes form-note-turn { to { rotate: 1turn; } }
|
package/css/sheet.css
CHANGED
|
@@ -371,3 +371,43 @@
|
|
|
371
371
|
z-index: 1;
|
|
372
372
|
}
|
|
373
373
|
.sheet-stage--dim .sheet { z-index: 2; }
|
|
374
|
+
|
|
375
|
+
/* ---- sheet steps: one task in steps inside one sheet (Lh 2026-09-24) ----
|
|
376
|
+
<div class="sheet-steps"> › <ol class="sheet-steps__track"> of <li class="sheet-step sheet-step--done|now|next">. The done steps
|
|
377
|
+
PILE UP on top, ALL of them, each folded to one short line (title · summary · ✓) — when the keyboard leaves too little room the
|
|
378
|
+
body scrolls them up only as far as the field needs; the current step is open under them — its title, then its form at
|
|
379
|
+
the form's own spacing, from the top (never spread or centred: the keyboard needs the room under it); the next steps wait in a pile at the
|
|
380
|
+
bottom of the body, each its title line. Every step has a thin rule on top. A step opens and folds as a 1fr ↔ 0fr grid row, so the pile rises and
|
|
381
|
+
falls — no height is written. Nothing fades: done and next read at full ink. */
|
|
382
|
+
.sheet__body:has(> .sheet-steps) { display: flex; flex-direction: column; }
|
|
383
|
+
.sheet-steps { flex-shrink: 0; min-height: calc(100% + var(--kb, 0px)); /* the keyboard's height more: the pile of next steps stays under the keyboard, it never rises with it */ display: flex; flex-direction: column; touch-action: pan-y; }
|
|
384
|
+
.sheet-steps__holder { position: absolute; top: 0; left: 0; width: 1px; height: 1px; padding: 0; border: 0; opacity: 0; pointer-events: none; font-size: 16px; } /* holds the cursor (and the phone's keyboard) while a step folds; 16px so iOS never zooms to it */
|
|
385
|
+
.sheet-steps { position: relative; }
|
|
386
|
+
.sheet-steps__track { list-style: none; margin: 0; padding: 0; flex: 1; display: flex; flex-direction: column; }
|
|
387
|
+
|
|
388
|
+
.sheet-step {
|
|
389
|
+
position: relative; min-width: 0;
|
|
390
|
+
display: grid; grid-template-rows: auto 0fr; align-content: start; /* mid-fold the rows sum to less than the box: the spare room stays BELOW, never spread into the title row (Lh: it looked like a spring) */
|
|
391
|
+
border-top: var(--rule-w) solid var(--sheet-step-border, var(--hairline));
|
|
392
|
+
flex: 0 0 auto;
|
|
393
|
+
transition: grid-template-rows var(--motion-sheet) ease, flex-grow var(--motion-sheet) ease;
|
|
394
|
+
}
|
|
395
|
+
/* the open step GROWS into the room left, so the steps not done yet wait in a pile at the bottom — and a move is an ACCORDION:
|
|
396
|
+
in one beat the step that was open folds while the new one unfolds (flex-grow and the 1fr row both ease), the piles slide
|
|
397
|
+
with them, nothing jumps (Lh 2026-09-24) */
|
|
398
|
+
.sheet-step--now { grid-template-rows: auto 1fr; flex-grow: 1; }
|
|
399
|
+
.sheet-step__head { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--sheet-steps-row-h, var(--tap-sm)); }
|
|
400
|
+
.sheet-step__title { font: var(--sheet-step-title-font, var(--type-label)); margin: 0; flex: 0 1 auto; min-width: 0; outline: none; /* the label size, not a body title: a pile of rows must stay small (Lh 2026-09-24) */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
401
|
+
.sheet-step__summary { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
402
|
+
.sheet-step__check { margin-inline-start: auto; }
|
|
403
|
+
.sheet-step__body { min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
|
|
404
|
+
.sheet-step__inner { display: grid; gap: var(--card-gap); padding-bottom: var(--card-gap); }
|
|
405
|
+
.sheet-step__inner > * { min-width: 0; }
|
|
406
|
+
.sheet-step__skip { justify-self: center; }
|
|
407
|
+
.sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s linear var(--motion-sheet); }
|
|
408
|
+
.sheet-step__go { position: absolute; inset: 0; margin: 0; padding: 0; border: 0; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
|
409
|
+
/* the last step's finish, when the app gives one: under the steps */
|
|
410
|
+
.sheet-steps__finish { display: grid; padding-top: var(--card-gap); }
|
|
411
|
+
@media (prefers-reduced-motion: reduce) { .sheet-step { transition: none; } }
|
|
412
|
+
/* the step waiting for its answer: its buttons take no taps (the app guards its own handler too) */
|
|
413
|
+
.sheet-step--busy .btn { pointer-events: none; opacity: 0.5; }
|
package/css/tokens.css
CHANGED
|
@@ -187,6 +187,7 @@
|
|
|
187
187
|
--neg: #3d3d3d;
|
|
188
188
|
--dot-a: var(--color-brand, #141414);
|
|
189
189
|
--dot-b: #8a8a8a;
|
|
190
|
+
--signal-error: #c62828; /* the ONE colour in the wireframe (Lh 2026-09-24): something went wrong — a field's edge, the words that say what. Always with an icon and words, never the colour alone */
|
|
190
191
|
|
|
191
192
|
/* derived tints — read the element's own ink, so they work on any slot */
|
|
192
193
|
--outline: var(--action-border); /* control edges (palette.css softens) */
|
package/dist/cardds.css
CHANGED
|
@@ -228,6 +228,7 @@
|
|
|
228
228
|
--neg: #3d3d3d;
|
|
229
229
|
--dot-a: var(--color-brand, #141414);
|
|
230
230
|
--dot-b: #8a8a8a;
|
|
231
|
+
--signal-error: #c62828; /* the ONE colour in the wireframe (Lh 2026-09-24): something went wrong — a field's edge, the words that say what. Always with an icon and words, never the colour alone */
|
|
231
232
|
|
|
232
233
|
/* derived tints — read the element's own ink, so they work on any slot */
|
|
233
234
|
--outline: var(--action-border); /* control edges (palette.css softens) */
|
|
@@ -1548,6 +1549,46 @@ button { font: inherit; cursor: pointer; }
|
|
|
1548
1549
|
}
|
|
1549
1550
|
.sheet-stage--dim .sheet { z-index: 2; }
|
|
1550
1551
|
|
|
1552
|
+
/* ---- sheet steps: one task in steps inside one sheet (Lh 2026-09-24) ----
|
|
1553
|
+
<div class="sheet-steps"> › <ol class="sheet-steps__track"> of <li class="sheet-step sheet-step--done|now|next">. The done steps
|
|
1554
|
+
PILE UP on top, ALL of them, each folded to one short line (title · summary · ✓) — when the keyboard leaves too little room the
|
|
1555
|
+
body scrolls them up only as far as the field needs; the current step is open under them — its title, then its form at
|
|
1556
|
+
the form's own spacing, from the top (never spread or centred: the keyboard needs the room under it); the next steps wait in a pile at the
|
|
1557
|
+
bottom of the body, each its title line. Every step has a thin rule on top. A step opens and folds as a 1fr ↔ 0fr grid row, so the pile rises and
|
|
1558
|
+
falls — no height is written. Nothing fades: done and next read at full ink. */
|
|
1559
|
+
.sheet__body:has(> .sheet-steps) { display: flex; flex-direction: column; }
|
|
1560
|
+
.sheet-steps { flex-shrink: 0; min-height: calc(100% + var(--kb, 0px)); /* the keyboard's height more: the pile of next steps stays under the keyboard, it never rises with it */ display: flex; flex-direction: column; touch-action: pan-y; }
|
|
1561
|
+
.sheet-steps__holder { position: absolute; top: 0; left: 0; width: 1px; height: 1px; padding: 0; border: 0; opacity: 0; pointer-events: none; font-size: 16px; } /* holds the cursor (and the phone's keyboard) while a step folds; 16px so iOS never zooms to it */
|
|
1562
|
+
.sheet-steps { position: relative; }
|
|
1563
|
+
.sheet-steps__track { list-style: none; margin: 0; padding: 0; flex: 1; display: flex; flex-direction: column; }
|
|
1564
|
+
|
|
1565
|
+
.sheet-step {
|
|
1566
|
+
position: relative; min-width: 0;
|
|
1567
|
+
display: grid; grid-template-rows: auto 0fr; align-content: start; /* mid-fold the rows sum to less than the box: the spare room stays BELOW, never spread into the title row (Lh: it looked like a spring) */
|
|
1568
|
+
border-top: var(--rule-w) solid var(--sheet-step-border, var(--hairline));
|
|
1569
|
+
flex: 0 0 auto;
|
|
1570
|
+
transition: grid-template-rows var(--motion-sheet) ease, flex-grow var(--motion-sheet) ease;
|
|
1571
|
+
}
|
|
1572
|
+
/* the open step GROWS into the room left, so the steps not done yet wait in a pile at the bottom — and a move is an ACCORDION:
|
|
1573
|
+
in one beat the step that was open folds while the new one unfolds (flex-grow and the 1fr row both ease), the piles slide
|
|
1574
|
+
with them, nothing jumps (Lh 2026-09-24) */
|
|
1575
|
+
.sheet-step--now { grid-template-rows: auto 1fr; flex-grow: 1; }
|
|
1576
|
+
.sheet-step__head { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--sheet-steps-row-h, var(--tap-sm)); }
|
|
1577
|
+
.sheet-step__title { font: var(--sheet-step-title-font, var(--type-label)); margin: 0; flex: 0 1 auto; min-width: 0; outline: none; /* the label size, not a body title: a pile of rows must stay small (Lh 2026-09-24) */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1578
|
+
.sheet-step__summary { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1579
|
+
.sheet-step__check { margin-inline-start: auto; }
|
|
1580
|
+
.sheet-step__body { min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
|
|
1581
|
+
.sheet-step__inner { display: grid; gap: var(--card-gap); padding-bottom: var(--card-gap); }
|
|
1582
|
+
.sheet-step__inner > * { min-width: 0; }
|
|
1583
|
+
.sheet-step__skip { justify-self: center; }
|
|
1584
|
+
.sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s linear var(--motion-sheet); }
|
|
1585
|
+
.sheet-step__go { position: absolute; inset: 0; margin: 0; padding: 0; border: 0; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
|
1586
|
+
/* the last step's finish, when the app gives one: under the steps */
|
|
1587
|
+
.sheet-steps__finish { display: grid; padding-top: var(--card-gap); }
|
|
1588
|
+
@media (prefers-reduced-motion: reduce) { .sheet-step { transition: none; } }
|
|
1589
|
+
/* the step waiting for its answer: its buttons take no taps (the app guards its own handler too) */
|
|
1590
|
+
.sheet-step--busy .btn { pointer-events: none; opacity: 0.5; }
|
|
1591
|
+
|
|
1551
1592
|
/* ---- css/step.css — .step: one screen, one task — card, optional sheet, bar outside ---- */
|
|
1552
1593
|
/* ============================================================
|
|
1553
1594
|
cardds/step.css — .step: one screen, one task.
|
|
@@ -1778,6 +1819,18 @@ button { font: inherit; cursor: pointer; }
|
|
|
1778
1819
|
}
|
|
1779
1820
|
.add-row__plus .icon { width: var(--icon-xs); height: var(--icon-xs); }
|
|
1780
1821
|
|
|
1822
|
+
/* ---- a wrong value, and the line that says so (Lh 2026-09-24) ----
|
|
1823
|
+
--signal-error is the one colour the wireframe has; it never speaks alone — the line carries an icon and words.
|
|
1824
|
+
<p class="form-note form-note--error" role="alert"><svg class="icon icon--xs">…</svg>Not a phone number</p>
|
|
1825
|
+
<p class="form-note form-note--busy" role="status"><svg …/>Sending the code…</p> (the ring turns) */
|
|
1826
|
+
.field.field--error, [data-palette] .field.field--error { border-color: var(--field-error-border, var(--signal-error)); }
|
|
1827
|
+
.pin.pin--error .pin__cell, .pin.pin--error .pin__cell:focus { border-color: var(--pin-error-border, var(--signal-error)); color: var(--pin-error-ink, var(--signal-error)); } /* ALL the cells, edge and digit — the one the cursor waits in too (Lh 2026-09-24); the wrong digits stay in red until the member types again */
|
|
1828
|
+
.form-note { display: flex; align-items: center; justify-content: center; gap: var(--sp-2); margin: 0; font: var(--form-note-font, var(--type-caption)); color: var(--form-note-ink, var(--card-muted)); text-align: center; }
|
|
1829
|
+
.form-note > .icon { flex: none; }
|
|
1830
|
+
.form-note--error { color: var(--form-note-error-ink, var(--signal-error)); }
|
|
1831
|
+
@media (prefers-reduced-motion: no-preference) { .form-note--busy > .icon { animation: form-note-turn 0.9s linear infinite; } }
|
|
1832
|
+
@keyframes form-note-turn { to { rotate: 1turn; } }
|
|
1833
|
+
|
|
1781
1834
|
/* ---- css/journey.css — route, tile badge, note row ---- */
|
|
1782
1835
|
/* ============================================================
|
|
1783
1836
|
cardds/journey.css — trip/status primitives inside cards:
|
|
@@ -2742,7 +2795,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2742
2795
|
font: var(--pin-cell-font, var(--type-stat));
|
|
2743
2796
|
font-variant-numeric: tabular-nums;
|
|
2744
2797
|
}
|
|
2745
|
-
.pin__cell:not(:placeholder-shown) {
|
|
2798
|
+
.pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
|
|
2746
2799
|
/* the cell the cursor waits in (Lh 2026-09-22): NO outline ring — it sat outside the pill and got cropped by the sheet's
|
|
2747
2800
|
edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
|
|
2748
2801
|
is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
|
package/dist/forms/Field.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
2
|
import { type IconName } from '../type/Icon.js';
|
|
3
3
|
export interface FieldProps extends Omit<ComponentProps<'input'>, 'className' | 'style'> {
|
|
4
4
|
/** a leading icon (phone-call, plane, calendar-days) */
|
|
@@ -7,6 +7,19 @@ export interface FieldProps extends Omit<ComponentProps<'input'>, 'className' |
|
|
|
7
7
|
action?: ReactNode;
|
|
8
8
|
/** the accessible name when the placeholder is not enough */
|
|
9
9
|
label?: string;
|
|
10
|
+
/** the value is wrong: the edge turns `--signal-error`, the input says `aria-invalid`, and the moment it comes (or changes) the
|
|
11
|
+
* field takes the focus with ALL its text selected — the member types the right one straight over it. The words saying what
|
|
12
|
+
* is wrong are the step's (`SheetStep error`), under its button; any truthy value marks the field. */
|
|
13
|
+
error?: unknown;
|
|
14
|
+
/** a FORMAT check on the client (never whether the content is right — that is `error`, from the app): `email`, or `tel`
|
|
15
|
+
* (digits, spaces, - + ( ) only). Checked when the member LEAVES the field and when the step's button is pressed, never
|
|
16
|
+
* while typing; a wrong one is marked like `error`, and the step's button does not go on until it is fixed. */
|
|
17
|
+
format?: 'email' | 'tel';
|
|
18
|
+
/** the exact number of digits it must hold (a phone number: 10) — a format check like `format` */
|
|
19
|
+
digits?: number;
|
|
20
|
+
/** the words when the format check fails — shown under the step's button (the app's words: cardds has none). `required`
|
|
21
|
+
* (the attribute) makes an empty field fail when the button is pressed. */
|
|
22
|
+
formatError?: ReactNode;
|
|
10
23
|
className?: string;
|
|
11
24
|
style?: React.CSSProperties;
|
|
12
25
|
}
|
|
@@ -14,4 +27,4 @@ export interface FieldProps extends Omit<ComponentProps<'input'>, 'className' |
|
|
|
14
27
|
* Field — the flat outlined input: the LABEL IS THE PLACEHOLDER. 48px tall (56px with an action inside).
|
|
15
28
|
* Fields belong in a Sheet (what the member acts on), 1–3 per step, never in a reading card.
|
|
16
29
|
*/
|
|
17
|
-
export declare function Field({ icon, action, label, className, style, ...input }: FieldProps): import("react").JSX.Element;
|
|
30
|
+
export declare function Field({ icon, action, label, error, format, digits, formatError, className, style, onBlur, onInput, ...input }: FieldProps): import("react").JSX.Element;
|
package/dist/forms/Field.js
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { formatOk, useCheck } from './check.js';
|
|
2
4
|
import { cx } from '../cx.js';
|
|
3
5
|
import { Icon } from '../type/Icon.js';
|
|
4
6
|
/**
|
|
5
7
|
* Field — the flat outlined input: the LABEL IS THE PLACEHOLDER. 48px tall (56px with an action inside).
|
|
6
8
|
* Fields belong in a Sheet (what the member acts on), 1–3 per step, never in a reading card.
|
|
7
9
|
*/
|
|
8
|
-
export function Field({ icon, action, label, className, style, ...input }) {
|
|
9
|
-
|
|
10
|
+
export function Field({ icon, action, label, error, format, digits, formatError, className, style, onBlur, onInput, ...input }) {
|
|
11
|
+
const ref = useRef(null);
|
|
12
|
+
const [bad, setBad] = useState(false); // the format check failed, and has not been fixed since
|
|
13
|
+
const wrong = (error != null && error !== false && error !== '') || bad;
|
|
14
|
+
const checked = format != null || digits != null || input.required;
|
|
15
|
+
const words = formatError ?? ''; // no words given: the field is still marked, the step's line stays empty
|
|
16
|
+
const verdict = (strict) => {
|
|
17
|
+
const v = ref.current?.value ?? '';
|
|
18
|
+
if (!checked || (!v.trim() && !(strict && input.required)))
|
|
19
|
+
return null; // an untouched empty field fails only when required, on the press
|
|
20
|
+
return formatOk(v, format, digits) && (v.trim() || !input.required) ? null : words;
|
|
21
|
+
};
|
|
22
|
+
const { say } = useCheck((strict) => { const w = verdict(strict); if (w != null)
|
|
23
|
+
setBad(true); return w; }, () => { ref.current?.focus(); ref.current?.select(); });
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (wrong) {
|
|
26
|
+
ref.current?.focus();
|
|
27
|
+
ref.current?.select();
|
|
28
|
+
}
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- when the app's error comes or changes
|
|
30
|
+
}, [error]);
|
|
31
|
+
return (_jsxs("label", { className: cx('field', action != null && 'field--action', wrong && 'field--error', className), style: style, children: [icon && _jsx(Icon, { name: icon, size: "sm" }), _jsx("input", { ref: ref, "aria-label": label, "aria-invalid": wrong || undefined, ...input, onBlur: (e) => { onBlur?.(e); if (!checked)
|
|
32
|
+
return; const w = verdict(false); setBad(w != null); say(w); }, onInput: (e) => { onInput?.(e); if (bad && verdict(false) == null) {
|
|
33
|
+
setBad(false);
|
|
34
|
+
say(null);
|
|
35
|
+
} } }), action] }));
|
|
10
36
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/** the one line that says how a form stands — `error` (an alert icon + words, read out at once) or `busy` (a turning ring +
|
|
3
|
+
* words, "sending the code…"). Internal: a SheetStep draws it under its button. */
|
|
4
|
+
export declare function FormNote({ error, busy }: {
|
|
5
|
+
error?: ReactNode;
|
|
6
|
+
busy?: ReactNode;
|
|
7
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from '../type/Icon.js';
|
|
3
|
+
/** the one line that says how a form stands — `error` (an alert icon + words, read out at once) or `busy` (a turning ring +
|
|
4
|
+
* words, "sending the code…"). Internal: a SheetStep draws it under its button. */
|
|
5
|
+
export function FormNote({ error, busy }) {
|
|
6
|
+
if (error != null && error !== false)
|
|
7
|
+
return _jsxs("p", { className: "form-note form-note--error", role: "alert", children: [_jsx(Icon, { name: "circle-alert", size: "xs" }), error] });
|
|
8
|
+
if (busy != null && busy !== false)
|
|
9
|
+
return _jsxs("p", { className: "form-note form-note--busy", role: "status", children: [_jsx(Icon, { name: "loader-circle", size: "xs" }), busy] });
|
|
10
|
+
return null;
|
|
11
|
+
}
|
package/dist/forms/Pin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentProps } from 'react';
|
|
1
|
+
import { type ReactNode, type ComponentProps } from 'react';
|
|
2
2
|
export interface PinProps extends Omit<ComponentProps<'div'>, 'onChange' | 'defaultValue'> {
|
|
3
3
|
/** how many digits (4 or 6) */
|
|
4
4
|
length?: number;
|
|
@@ -10,6 +10,15 @@ export interface PinProps extends Omit<ComponentProps<'div'>, 'onChange' | 'defa
|
|
|
10
10
|
label?: string;
|
|
11
11
|
/** the code so far, after every change; `complete` once every cell holds a digit */
|
|
12
12
|
onChange?: (code: string, complete: boolean) => void;
|
|
13
|
+
/** the code is wrong: the digits stay in sight in `--signal-error` (words, no fill) with red edges, the cursor goes back to the
|
|
14
|
+
* first cell, and the next digit typed starts a new code. The words are the step's (`SheetStep error`). */
|
|
15
|
+
error?: unknown;
|
|
16
|
+
/** a FORMAT check on the client: the code it must equal (the first PIN, when this one confirms it). Once every cell is filled
|
|
17
|
+
* and it differs, the Pin turns red with its digits in sight, the step shows `formatError` under it for three seconds and then
|
|
18
|
+
* steps BACK one step (`onAtChange(at - 1, 'mismatch')` — the app clears both codes there). */
|
|
19
|
+
match?: string;
|
|
20
|
+
/** the words when a check fails: the code does not match, or the step's button was pressed with cells still empty */
|
|
21
|
+
formatError?: ReactNode;
|
|
13
22
|
/** focus the first empty cell on mount */
|
|
14
23
|
autoFocus?: boolean;
|
|
15
24
|
}
|
|
@@ -19,4 +28,4 @@ export interface PinProps extends Omit<ComponentProps<'div'>, 'onChange' | 'defa
|
|
|
19
28
|
* arrows walk the cells, a pasted code fills them from the one it landed in; only digits stay. The first cell takes the SMS
|
|
20
29
|
* one-time-code autofill. The code is a VALUE, controlled like an input: `value` + `onChange`, or `defaultValue`.
|
|
21
30
|
*/
|
|
22
|
-
export declare function Pin({ length, value, defaultValue, label, onChange, autoFocus, className, ...rest }: PinProps): import("react").JSX.Element;
|
|
31
|
+
export declare function Pin({ length, value, defaultValue, label, onChange, autoFocus, error, match, formatError, className, ...rest }: PinProps): import("react").JSX.Element;
|
package/dist/forms/Pin.js
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
|
+
import { useCheck } from './check.js';
|
|
4
5
|
/**
|
|
5
6
|
* Pin — big code entry, one digit per pill; a filled cell turns accent. Lives in a Sheet.
|
|
6
7
|
* Its own behaviour, inside it: typing a digit moves to the next cell, Backspace in an empty cell moves back and clears, the
|
|
7
8
|
* arrows walk the cells, a pasted code fills them from the one it landed in; only digits stay. The first cell takes the SMS
|
|
8
9
|
* one-time-code autofill. The code is a VALUE, controlled like an input: `value` + `onChange`, or `defaultValue`.
|
|
9
10
|
*/
|
|
10
|
-
export function Pin({ length = 6, value, defaultValue = '', label = 'Digit', onChange, autoFocus, className, ...rest }) {
|
|
11
|
+
export function Pin({ length = 6, value, defaultValue = '', label = 'Digit', onChange, autoFocus, error, match, formatError, className, ...rest }) {
|
|
11
12
|
const ref = useRef(null);
|
|
12
13
|
const [inner, setInner] = useState(defaultValue);
|
|
13
14
|
const code = (value ?? inner).replace(/\D/g, '').slice(0, length);
|
|
14
15
|
const cell = (i) => ref.current?.querySelectorAll('.pin__cell')[i];
|
|
15
16
|
const set = (next) => {
|
|
16
17
|
next = next.slice(0, length);
|
|
18
|
+
if (bad) {
|
|
19
|
+
setBad(false);
|
|
20
|
+
say(null);
|
|
21
|
+
}
|
|
22
|
+
if (match != null && next.length === length && next !== match) {
|
|
23
|
+
setBad(true);
|
|
24
|
+
say(words);
|
|
25
|
+
back();
|
|
26
|
+
restart.current = true;
|
|
27
|
+
}
|
|
17
28
|
if (value === undefined)
|
|
18
29
|
setInner(next);
|
|
19
30
|
if (next !== code)
|
|
@@ -24,8 +35,47 @@ export function Pin({ length = 6, value, defaultValue = '', label = 'Digit', onC
|
|
|
24
35
|
cell(Math.min(code.length, length - 1))?.focus();
|
|
25
36
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- on mount only
|
|
26
37
|
}, [autoFocus]);
|
|
38
|
+
const [bad, setBad] = useState(false); // a format check failed, and nothing was typed since
|
|
39
|
+
const words = formatError ?? '';
|
|
40
|
+
const { say, back } = useCheck((strict) => {
|
|
41
|
+
if (strict && code.length < length) {
|
|
42
|
+
setBad(true);
|
|
43
|
+
return words;
|
|
44
|
+
}
|
|
45
|
+
if (match != null && code.length === length && code !== match) {
|
|
46
|
+
setBad(true);
|
|
47
|
+
return words;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}, () => cell(Math.min(code.length, length - 1))?.focus());
|
|
51
|
+
const wrong = (error != null && error !== false && error !== '') || bad;
|
|
52
|
+
const restart = useRef(false);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!wrong)
|
|
55
|
+
return;
|
|
56
|
+
restart.current = true; // the wrong code stays in sight (red); the next digit typed starts a new one from the first cell
|
|
57
|
+
cell(0)?.focus();
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- each new error, once
|
|
59
|
+
}, [wrong, error]);
|
|
60
|
+
/* the app emptied the code (a new try: it went back after a mismatch): the red goes with it */
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (code || !bad)
|
|
63
|
+
return;
|
|
64
|
+
setBad(false);
|
|
65
|
+
say(null);
|
|
66
|
+
restart.current = false;
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- when the code is emptied from outside
|
|
68
|
+
}, [code]);
|
|
27
69
|
const input = (i) => (e) => {
|
|
28
70
|
const digits = e.target.value.replace(/\D/g, '');
|
|
71
|
+
if (restart.current) {
|
|
72
|
+
restart.current = false;
|
|
73
|
+
if (digits) {
|
|
74
|
+
set(digits.slice(-1));
|
|
75
|
+
cell(1)?.focus();
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
} // a new try
|
|
29
79
|
const at = Math.min(i, code.length); // a code has no gaps: a digit typed further along lands in the first empty cell
|
|
30
80
|
if (!digits) {
|
|
31
81
|
set(code.slice(0, at) + code.slice(at + 1));
|
|
@@ -49,8 +99,9 @@ export function Pin({ length = 6, value, defaultValue = '', label = 'Digit', onC
|
|
|
49
99
|
cell(i + 1)?.focus();
|
|
50
100
|
}
|
|
51
101
|
};
|
|
52
|
-
return (_jsx("div", { ref: ref, className: cx('pin', className), ...rest, children: Array.from({ length }, (_, i) => (
|
|
102
|
+
return (_jsx("div", { ref: ref, className: cx('pin', wrong && 'pin--error', className), ...rest, children: Array.from({ length }, (_, i) => (
|
|
53
103
|
// no maxLength: the browser would cut a pasted code before this ever saw it. A cell holds one digit — typing into a full one
|
|
54
104
|
// replaces it, so its digit is selected whenever the cell takes the focus
|
|
55
|
-
_jsx("input", { className: "pin__cell", inputMode: "numeric", pattern: "[0-9]*", placeholder: " ", value: code[i] ?? '', "aria-label": `${label} ${i + 1}`, autoComplete: i === 0 ? 'one-time-code' : 'off', onChange: input(i), onKeyDown: key(i), onFocus: (e) =>
|
|
105
|
+
_jsx("input", { className: "pin__cell", inputMode: "numeric", pattern: "[0-9]*", placeholder: " ", value: code[i] ?? '', "aria-label": `${label} ${i + 1}`, "aria-invalid": wrong || undefined, autoComplete: i === 0 ? 'one-time-code' : 'off', onChange: input(i), onKeyDown: key(i), onFocus: (e) => { if (!restart.current)
|
|
106
|
+
e.currentTarget.select(); } }, i))) }));
|
|
56
107
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type Check = {
|
|
3
|
+
/** the words when it fails now, else null. `strict` = the button was pressed: an empty control fails too */
|
|
4
|
+
check: (strict: boolean) => ReactNode | null;
|
|
5
|
+
/** take the focus, ready to be fixed */
|
|
6
|
+
focus: () => void;
|
|
7
|
+
};
|
|
8
|
+
export type CheckGroup = {
|
|
9
|
+
add: (c: Check) => () => void;
|
|
10
|
+
/** a control's own verdict changed (it was left, or it was fixed): its words, or null */
|
|
11
|
+
say: (c: Check, words: ReactNode | null) => void;
|
|
12
|
+
/** a check that can only be failed by going back (the second PIN does not match the first): the group shows the words for a
|
|
13
|
+
* moment, then steps back */
|
|
14
|
+
back: () => void;
|
|
15
|
+
};
|
|
16
|
+
export declare const CheckContext: import("react").Context<CheckGroup | null>;
|
|
17
|
+
/** join the group around (if any) with this control's check; the latest check function is always the one asked */
|
|
18
|
+
export declare function useCheck(check: Check['check'], focus: Check['focus']): {
|
|
19
|
+
say: (words: ReactNode | null) => void | undefined;
|
|
20
|
+
back: () => void | undefined;
|
|
21
|
+
};
|
|
22
|
+
/** a Field's format: `email`, `tel` (digits, spaces, - + ( ) only) and/or an exact count of `digits` */
|
|
23
|
+
export declare function formatOk(value: string, format?: 'email' | 'tel', digits?: number): boolean;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useRef } from 'react';
|
|
2
|
+
export const CheckContext = createContext(null);
|
|
3
|
+
/** join the group around (if any) with this control's check; the latest check function is always the one asked */
|
|
4
|
+
export function useCheck(check, focus) {
|
|
5
|
+
const group = useContext(CheckContext);
|
|
6
|
+
const latest = useRef({ check, focus });
|
|
7
|
+
latest.current.check = check;
|
|
8
|
+
latest.current.focus = focus;
|
|
9
|
+
const self = useRef({ check: (s) => latest.current.check(s), focus: () => latest.current.focus() }).current;
|
|
10
|
+
useEffect(() => group?.add(self), [group, self]);
|
|
11
|
+
return { say: (words) => group?.say(self, words), back: () => group?.back() };
|
|
12
|
+
}
|
|
13
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
14
|
+
/** a Field's format: `email`, `tel` (digits, spaces, - + ( ) only) and/or an exact count of `digits` */
|
|
15
|
+
export function formatOk(value, format, digits) {
|
|
16
|
+
const v = value.trim();
|
|
17
|
+
if (format === 'email' && !EMAIL.test(v))
|
|
18
|
+
return false;
|
|
19
|
+
if (format === 'tel' && !/^[\d\s\-+()]*$/.test(v))
|
|
20
|
+
return false;
|
|
21
|
+
if (digits != null && v.replace(/\D/g, '').length !== digits)
|
|
22
|
+
return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from './sheets/SheetHead.js';
|
|
|
27
27
|
export * from './sheets/SheetBody.js';
|
|
28
28
|
export * from './sheets/SheetStage.js';
|
|
29
29
|
export * from './sheets/Step.js';
|
|
30
|
+
export * from './sheets/SheetSteps.js';
|
|
30
31
|
export * from './sheets/Drawer.js';
|
|
31
32
|
export * from './actions/Btn.js';
|
|
32
33
|
export * from './actions/IconBtn.js';
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export * from './sheets/SheetHead.js';
|
|
|
31
31
|
export * from './sheets/SheetBody.js';
|
|
32
32
|
export * from './sheets/SheetStage.js';
|
|
33
33
|
export * from './sheets/Step.js';
|
|
34
|
+
export * from './sheets/SheetSteps.js';
|
|
34
35
|
export * from './sheets/Drawer.js';
|
|
35
36
|
export * from './actions/Btn.js';
|
|
36
37
|
export * from './actions/IconBtn.js';
|
package/dist/sheets/Sheet.js
CHANGED
|
@@ -39,8 +39,25 @@ export function Sheet({ state, defaultState = 'half', onStateChange, states, han
|
|
|
39
39
|
useEffect(() => {
|
|
40
40
|
if (current !== 'peek' && current !== 'away')
|
|
41
41
|
home.current = current;
|
|
42
|
-
if (was.current === 'away' && current !== 'away')
|
|
43
|
-
|
|
42
|
+
if (was.current === 'away' && current !== 'away') { // it just came onto the screen: the cursor goes to what is marked to take it first (`data-autofocus` — a SheetSteps marks its open step's first field), else the handle, for the keyboard user
|
|
43
|
+
// …once it has finished sliding in (Lh 2026-09-24): the keyboard must not come up while the sheet still moves
|
|
44
|
+
const el = ref.current;
|
|
45
|
+
let done = false;
|
|
46
|
+
const land = () => {
|
|
47
|
+
if (done)
|
|
48
|
+
return;
|
|
49
|
+
done = true;
|
|
50
|
+
el?.removeEventListener('transitionend', onEnd);
|
|
51
|
+
const first = el?.querySelector('[data-autofocus]:not([inert] *)');
|
|
52
|
+
(first ?? handleRef.current)?.focus({ preventScroll: true });
|
|
53
|
+
};
|
|
54
|
+
const onEnd = (e) => { if (e.target === el && e.propertyName === 'translate')
|
|
55
|
+
land(); };
|
|
56
|
+
el?.addEventListener('transitionend', onEnd);
|
|
57
|
+
const t = setTimeout(land, 600); // no transition (reduced motion): land anyway
|
|
58
|
+
was.current = current;
|
|
59
|
+
return () => { clearTimeout(t); el?.removeEventListener('transitionend', onEnd); };
|
|
60
|
+
}
|
|
44
61
|
was.current = current;
|
|
45
62
|
}, [current]);
|
|
46
63
|
/* geometry, read once per gesture — never stored: how far it has slid, and where each state rests, in px from its top at full */
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
/** what asked for the move: a `tap` on another step, a `swipe` across the steps, the step's `skip` link, a `mismatch` (a Pin's
|
|
3
|
+
* `match` failed: back one step — clear the codes there) */
|
|
4
|
+
export type SheetStepsReason = 'tap' | 'swipe' | 'skip' | 'mismatch';
|
|
5
|
+
export interface SheetStepsProps extends ComponentProps<'div'> {
|
|
6
|
+
/** optional: a `Btn` that finishes, under the steps — always in sight, `disabled` until EVERY step is done (its work, or
|
|
7
|
+
* skipped: `at` has reached `count`). None = the task ends when `at` reaches `count` — the last step's own action, or its skip. */
|
|
8
|
+
finish?: ReactNode;
|
|
9
|
+
/** CONTROLLED: the step the member is on (0-based). Steps before it are done, after it are next. The APP moves it — when a step's
|
|
10
|
+
* work is really finished (the code checked by the server, the two PINs match), `setAt(at + 1)`; after the last step the app sends. `at = count` = every step done —
|
|
11
|
+
* the app then closes its Sheet (`state="away"`, a beat after the fold) and says so on the page: the steps never close it themselves. */
|
|
12
|
+
at?: number;
|
|
13
|
+
/** UNCONTROLLED: the step it starts on (default 0) */
|
|
14
|
+
defaultAt?: number;
|
|
15
|
+
/** the steps ASK to move — a tap on another step, a swipe, a skip. Controlled: set `at` to it (or don't). */
|
|
16
|
+
onAtChange?: (at: number, reason: SheetStepsReason) => void;
|
|
17
|
+
/** true: the member may go back and forth — a tap on a done or next step opens it, an up/down swipe moves one step.
|
|
18
|
+
* false (default): only forward — the done steps stay in the pile, never opened again (a swipe up passes only a `skippable` step). */
|
|
19
|
+
reversible?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* SheetSteps — one task taken in steps INSIDE one sheet (phone · code · PIN · PIN again · face · notices). The done steps pile up
|
|
23
|
+
* on top, each one line (title · summary · ✓); the current step is open under them, its form at the form's own spacing; the next
|
|
24
|
+
* steps wait below as their titles. A thin rule over every step. The last step's own action sends, or a `finish` stands under the
|
|
25
|
+
* steps on the last one. Goes in a `SheetBody`.
|
|
26
|
+
* The sheet may be dragged down to peek and back — the body keeps where it was scrolled.
|
|
27
|
+
*/
|
|
28
|
+
export declare function SheetSteps({ at, defaultAt, onAtChange, reversible, finish, className, style, children, onFocus, onBlur, onPointerDown, onPointerUp, onPointerCancel, ...rest }: SheetStepsProps): import("react").JSX.Element;
|
|
29
|
+
export interface SheetStepProps extends Omit<ComponentProps<'li'>, 'title'> {
|
|
30
|
+
/** the step's name — always shown: over the open step, on the folded line of a done one */
|
|
31
|
+
title: ReactNode;
|
|
32
|
+
/** the done step's one-line answer ("081-xxx-4567", "set") — shown on its folded line */
|
|
33
|
+
summary?: ReactNode;
|
|
34
|
+
/** the member may pass this step without doing it: a small link at its end moves on (reason `skip`) */
|
|
35
|
+
skippable?: boolean;
|
|
36
|
+
/** the skip link's words (default "Skip") */
|
|
37
|
+
skipLabel?: ReactNode;
|
|
38
|
+
/** what went wrong, in words — one line UNDER the step's button (an alert icon, `--signal-error`, read out at once): the number
|
|
39
|
+
* is not a phone number, no signal, the code could not be sent, the two PINs differ. The step stays where it is; to send the
|
|
40
|
+
* member back, the app sets `at` (and gives THAT step the error). Mark the wrong control with its own `error` (Field, Pin). */
|
|
41
|
+
error?: ReactNode;
|
|
42
|
+
/** the step is waiting for the answer — one line under the button: a turning ring + these words ("sending the code…"); the
|
|
43
|
+
* step's buttons take no taps meanwhile. The app moves `at` when the answer is good, or sets `error`. */
|
|
44
|
+
busy?: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
/** SheetStep — one step of a SheetSteps: its title, then what the member does (a Field, a Pin, a Toggle, the step's own Btn). */
|
|
47
|
+
export declare function SheetStep({ title, summary, skippable, skipLabel, error, busy, className, children, ...rest }: SheetStepProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, cloneElement, createContext, isValidElement, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { Icon } from '../type/Icon.js';
|
|
5
|
+
import { Link } from '../actions/Link.js';
|
|
6
|
+
import { FormNote } from '../forms/FormNote.js';
|
|
7
|
+
import { CheckContext } from '../forms/check.js';
|
|
8
|
+
const BACK_MS = 3000; // a PIN that does not match: its red digits and words stay this long, then the step goes back
|
|
9
|
+
const MOVE_MS = 350; // = --motion-sheet: the accordion's beat
|
|
10
|
+
const GLIDE_MS = 220; // the pile's slide out of the keyboard's way and back: quick, but the eye follows it
|
|
11
|
+
const SWIPE_REM = 3; // an up/down move longer than this (and mostly up/down) is a swipe
|
|
12
|
+
const StepCtx = createContext(null);
|
|
13
|
+
/**
|
|
14
|
+
* SheetSteps — one task taken in steps INSIDE one sheet (phone · code · PIN · PIN again · face · notices). The done steps pile up
|
|
15
|
+
* on top, each one line (title · summary · ✓); the current step is open under them, its form at the form's own spacing; the next
|
|
16
|
+
* steps wait below as their titles. A thin rule over every step. The last step's own action sends, or a `finish` stands under the
|
|
17
|
+
* steps on the last one. Goes in a `SheetBody`.
|
|
18
|
+
* The sheet may be dragged down to peek and back — the body keeps where it was scrolled.
|
|
19
|
+
*/
|
|
20
|
+
export function SheetSteps({ at, defaultAt = 0, onAtChange, reversible = false, finish, className, style, children, onFocus, onBlur, onPointerDown, onPointerUp, onPointerCancel, ...rest }) {
|
|
21
|
+
const [inner, setInner] = useState(defaultAt);
|
|
22
|
+
const current = at ?? inner;
|
|
23
|
+
const steps = Children.toArray(children).filter(isValidElement);
|
|
24
|
+
const count = steps.length;
|
|
25
|
+
/* how far the member has come: a step before it was DONE (its work, or skipped) — it stays done when they go back to look */
|
|
26
|
+
const [furthest, setFurthest] = useState(current);
|
|
27
|
+
if (current > furthest)
|
|
28
|
+
setFurthest(current);
|
|
29
|
+
const reach = Math.max(furthest, current);
|
|
30
|
+
const ref = useRef(null);
|
|
31
|
+
const first = useRef(true);
|
|
32
|
+
const holderRef = useRef(null);
|
|
33
|
+
const moving = useRef(0); // until then a step change is playing its beats: focus moves do not scroll
|
|
34
|
+
const shown = useRef(current);
|
|
35
|
+
if (shown.current !== current) {
|
|
36
|
+
shown.current = current;
|
|
37
|
+
moving.current = performance.now() + MOVE_MS + 100;
|
|
38
|
+
} // from the very render that changes it: the old field's blur and the keyboard's jump come in that same commit
|
|
39
|
+
const press = useRef(null);
|
|
40
|
+
const go = (i, reason) => {
|
|
41
|
+
if (i === current || i < 0 || i > count)
|
|
42
|
+
return;
|
|
43
|
+
if (at === undefined)
|
|
44
|
+
setInner(i);
|
|
45
|
+
onAtChange?.(i, reason);
|
|
46
|
+
};
|
|
47
|
+
/* how far the body is scrolled when a step opens or a field in it takes the keyboard: the TWO latest done rows always show
|
|
48
|
+
above the open step, and half of the one before — older ones go up out of sight, one row at a time, so what was done never seems to vanish (Lh
|
|
49
|
+
2026-09-24: moving the pile further startled; with two rows or fewer nothing moves) */
|
|
50
|
+
const settle = () => {
|
|
51
|
+
const list = ref.current, body = list?.closest('.sheet__body'), now = list?.querySelector('.sheet-step--now');
|
|
52
|
+
if (!list || !body || !now)
|
|
53
|
+
return;
|
|
54
|
+
const above = [];
|
|
55
|
+
for (let el = now.previousElementSibling; el; el = el.previousElementSibling)
|
|
56
|
+
above.unshift(el);
|
|
57
|
+
const bTop = body.getBoundingClientRect().top, at = (el) => body.scrollTop + el.getBoundingClientRect().top - bTop;
|
|
58
|
+
const third = above.length > 2 ? above[above.length - 3] : null; // 2½ rows show (Lh 2026-09-24): the two latest whole, the one before cut in half — a sign there is more above
|
|
59
|
+
let to = third ? at(third) + third.getBoundingClientRect().height / 2 : 0;
|
|
60
|
+
/* …unless the field with the focus would still be under the cut (a short phone, a big text size): then up as far as it needs —
|
|
61
|
+
but never past the LATEST done row, which always stays in sight */
|
|
62
|
+
const focused = document.activeElement instanceof HTMLElement && now.contains(document.activeElement) ? document.activeElement : null;
|
|
63
|
+
if (focused) {
|
|
64
|
+
const vv = window.visualViewport;
|
|
65
|
+
const cut = Math.min(body.getBoundingClientRect().bottom - (parseFloat(getComputedStyle(body).scrollPaddingBottom) || 0), vv ? vv.offsetTop + vv.height : Infinity);
|
|
66
|
+
const under = (focused.closest('.field, .pin') ?? focused).getBoundingClientRect().bottom + (body.scrollTop - to) - cut; // where its bottom will be once scrolled to `to`
|
|
67
|
+
const most = above.length ? at(above[above.length - 1]) : 0;
|
|
68
|
+
if (under > 0)
|
|
69
|
+
to = Math.min(to + under, Math.max(most, to));
|
|
70
|
+
}
|
|
71
|
+
if (Math.abs(to - body.scrollTop) <= 1)
|
|
72
|
+
return;
|
|
73
|
+
// the pile SCROLLS there, animated (Lh 2026-09-24) — the browser's own smooth scroll; its frames are not replayed as jumps
|
|
74
|
+
const smooth = !matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
75
|
+
own.current = performance.now() + (smooth ? 700 : 0);
|
|
76
|
+
body.scrollTo({ top: to, behavior: smooth ? 'smooth' : 'auto' });
|
|
77
|
+
};
|
|
78
|
+
/* the step changed, in TWO beats one right after the other: (1) the old step folds while the new one unfolds — nothing else
|
|
79
|
+
moves; (2) then the pile settles (the two latest done rows in sight), gliding. The cursor goes into the new step at once
|
|
80
|
+
(so a phone's keyboard stays up) but without scrolling. Not on arrival — the body keeps its scroll. */
|
|
81
|
+
/* the open step's first field is the one to take the cursor when the sheet comes onto the screen (Sheet reads `data-autofocus`) */
|
|
82
|
+
useLayoutEffect(() => {
|
|
83
|
+
ref.current?.querySelectorAll('[data-autofocus]').forEach((el) => el.removeAttribute('data-autofocus'));
|
|
84
|
+
ref.current?.querySelector('.sheet-step--now .sheet-step__inner :is(input:not([type=checkbox], [type=radio], [type=hidden]), textarea)')?.setAttribute('data-autofocus', '');
|
|
85
|
+
});
|
|
86
|
+
useLayoutEffect(() => {
|
|
87
|
+
if (first.current) {
|
|
88
|
+
first.current = false;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// ONE THING AT A TIME (Lh 2026-09-24 — the old field and the new one fought over the cursor and the pile bounced):
|
|
92
|
+
// (1) the old field lets go — blur, at once; (2) the fold plays alone; (3) then the cursor goes into the new step and the
|
|
93
|
+
// pile settles, together, right after the fold. `moving` (set by the render) holds every jump until then.
|
|
94
|
+
// The cursor waits in a HOLDER while the fold plays — an invisible text field of the steps' own: the phone's keyboard stays up
|
|
95
|
+
// (a blur would drop it and the next focus bring it back — Lh 2026-09-24), and it takes the keyboard type of the field to come
|
|
96
|
+
const at = document.activeElement, holder = holderRef.current;
|
|
97
|
+
const nextField = ref.current?.querySelector('.sheet-step--now .sheet-step__inner :is(input:not([type=checkbox], [type=radio], [type=hidden]), textarea)');
|
|
98
|
+
if (at instanceof HTMLElement && ref.current?.contains(at)) {
|
|
99
|
+
if (holder && nextField && at.matches('input, textarea')) {
|
|
100
|
+
holder.inputMode = nextField.inputMode || 'text';
|
|
101
|
+
holder.value = '';
|
|
102
|
+
holder.focus({ preventScroll: true });
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
at.blur();
|
|
106
|
+
}
|
|
107
|
+
const t = setTimeout(() => {
|
|
108
|
+
const now = ref.current?.querySelector('.sheet-step--now');
|
|
109
|
+
// the cursor stands by: the step's first field takes the focus (the keyboard comes with it); a step with no field — its
|
|
110
|
+
// title, so a screen reader hears where it is
|
|
111
|
+
(now?.querySelector('.sheet-step__inner :is(input:not([type=checkbox], [type=radio], [type=hidden]), textarea)') ?? now?.querySelector('.sheet-step__title'))?.focus({ preventScroll: true });
|
|
112
|
+
glide.current = performance.now() + 900;
|
|
113
|
+
settle();
|
|
114
|
+
}, MOVE_MS);
|
|
115
|
+
return () => clearTimeout(t);
|
|
116
|
+
}, [current]);
|
|
117
|
+
/* the keyboard comes and goes: the body's scroll JUMPS (the Sheet's reveal, the browser's own, ours below, the box growing back).
|
|
118
|
+
For a short while after a field in the steps gains or loses the focus, every such jump is played as a quick slide (FLIP: the steps
|
|
119
|
+
are set back by the jump, then eased to rest) so the eye can follow the pile moving out of the way and back. */
|
|
120
|
+
const glide = useRef(0), last = useRef(0), own = useRef(0); // own: our smooth scroll is playing — not a jump to replay
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
const el = ref.current, body = el?.closest('.sheet__body');
|
|
123
|
+
if (!el || !body)
|
|
124
|
+
return;
|
|
125
|
+
last.current = body.scrollTop;
|
|
126
|
+
const onScroll = () => {
|
|
127
|
+
const d = body.scrollTop - last.current;
|
|
128
|
+
last.current = body.scrollTop;
|
|
129
|
+
if (!d || performance.now() > glide.current || performance.now() < own.current || matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
130
|
+
return;
|
|
131
|
+
const from = parseFloat(el.style.translate.split(' ')[1] || '0') || 0;
|
|
132
|
+
el.style.transition = 'none';
|
|
133
|
+
el.style.translate = `0 ${from + d}px`;
|
|
134
|
+
void el.offsetHeight; // the set-back lands before the ease
|
|
135
|
+
const hold = Math.max(0, moving.current - 100 - performance.now()); // during a step change: held until the fold is done (beat 2)
|
|
136
|
+
el.style.transition = `translate ${GLIDE_MS}ms ease-out ${hold}ms`;
|
|
137
|
+
el.style.translate = '0 0';
|
|
138
|
+
};
|
|
139
|
+
const clean = () => { el.style.removeProperty('transition'); el.style.removeProperty('translate'); };
|
|
140
|
+
body.addEventListener('scroll', onScroll);
|
|
141
|
+
el.addEventListener('transitionend', clean);
|
|
142
|
+
return () => { body.removeEventListener('scroll', onScroll); el.removeEventListener('transitionend', clean); };
|
|
143
|
+
}, []);
|
|
144
|
+
/* a field in the current step took the focus: once the keyboard is up (the Sheet has climbed), settle the pile — the two latest
|
|
145
|
+
done rows in sight. When the focus leaves the fields (the keyboard goes), the pile comes back down. Both moves glide. */
|
|
146
|
+
const typing = (t) => t instanceof HTMLElement && t.matches('input:not([type=checkbox], [type=radio], [type=range]), textarea');
|
|
147
|
+
const focusIn = (e) => {
|
|
148
|
+
onFocus?.(e);
|
|
149
|
+
glide.current = performance.now() + 900;
|
|
150
|
+
const target = e.target;
|
|
151
|
+
if (!target.closest('.sheet-step--now') || !typing(target) || performance.now() < moving.current - 100)
|
|
152
|
+
return; // a step change settles on its own beat
|
|
153
|
+
setTimeout(settle, 300); // after the keyboard and the sheet's own climb
|
|
154
|
+
};
|
|
155
|
+
const focusOut = (e) => {
|
|
156
|
+
onBlur?.(e);
|
|
157
|
+
if (!typing(e.target) || performance.now() < moving.current)
|
|
158
|
+
return; // the old step's field losing the cursor to the new one
|
|
159
|
+
glide.current = performance.now() + 900;
|
|
160
|
+
const body = ref.current?.closest('.sheet__body');
|
|
161
|
+
setTimeout(() => { if (!typing(document.activeElement) && body)
|
|
162
|
+
body.scrollTo({ top: 0 }); }, 0); // Tab to the next field keeps it up
|
|
163
|
+
};
|
|
164
|
+
/* an up/down swipe turns the wheel one step — unless it started in content that can still scroll that way */
|
|
165
|
+
const down = (e) => {
|
|
166
|
+
onPointerDown?.(e);
|
|
167
|
+
press.current = e.isPrimary && !e.target.closest('input, textarea, select, .pin, .picker, .slider') ? { x: e.clientX, y: e.clientY } : null;
|
|
168
|
+
};
|
|
169
|
+
const up = (e) => {
|
|
170
|
+
onPointerUp?.(e);
|
|
171
|
+
const p = press.current;
|
|
172
|
+
press.current = null;
|
|
173
|
+
if (!p)
|
|
174
|
+
return;
|
|
175
|
+
const dx = e.clientX - p.x, dy = e.clientY - p.y;
|
|
176
|
+
if (Math.abs(dy) < SWIPE_REM * parseFloat(getComputedStyle(document.documentElement).fontSize) || Math.abs(dy) < 2 * Math.abs(dx))
|
|
177
|
+
return;
|
|
178
|
+
const body = e.target.closest('.sheet-step__body');
|
|
179
|
+
if (body && (dy < 0 ? body.scrollTop + body.clientHeight < body.scrollHeight - 1 : body.scrollTop > 0))
|
|
180
|
+
return; // the content's own scroll
|
|
181
|
+
const to = current + (dy < 0 ? 1 : -1);
|
|
182
|
+
if (reversible && to < count)
|
|
183
|
+
go(to, 'swipe'); // a swipe never passes the last step — only its own action or its skip does
|
|
184
|
+
else if (dy < 0 && ref.current?.querySelector('.sheet-step--now[data-skippable]'))
|
|
185
|
+
go(to, 'skip'); // forward only, and only past a step that may be skipped
|
|
186
|
+
};
|
|
187
|
+
return (_jsxs("div", { ref: ref, className: cx('sheet-steps', !reversible && 'sheet-steps--locked', className), style: style, onFocus: focusIn, onBlur: focusOut, onPointerDown: down, onPointerUp: up, onPointerCancel: (e) => { onPointerCancel?.(e); press.current = null; }, ...rest, children: [_jsx("input", { ref: holderRef, className: "sheet-steps__holder", "aria-hidden": true, tabIndex: -1, autoComplete: "off", onInput: (e) => { e.currentTarget.value = ''; } }), _jsx("ol", { className: "sheet-steps__track", children: steps.map((step, i) => (_jsx(StepCtx.Provider, { value: { i, at: current, furthest: reach, count, reversible, go }, children: step }, step.key ?? i))) }), finish != null && ( // always in sight; it takes taps only once every step is done (or skipped)
|
|
188
|
+
_jsx("div", { className: "sheet-steps__finish", children: isValidElement(finish) ? cloneElement(finish, { disabled: reach < count || finish.props.disabled }) : finish }))] }));
|
|
189
|
+
}
|
|
190
|
+
/** SheetStep — one step of a SheetSteps: its title, then what the member does (a Field, a Pin, a Toggle, the step's own Btn). */
|
|
191
|
+
export function SheetStep({ title, summary, skippable, skipLabel = 'Skip', error, busy, className, children, ...rest }) {
|
|
192
|
+
const ctx = useContext(StepCtx);
|
|
193
|
+
if (!ctx)
|
|
194
|
+
throw new Error('SheetStep goes inside a SheetSteps');
|
|
195
|
+
const { i, at, furthest, count, reversible, go } = ctx;
|
|
196
|
+
const phase = i === at ? 'now' : i < furthest ? 'done' : 'next';
|
|
197
|
+
const off = phase !== 'now';
|
|
198
|
+
/* the step is the FORM GROUP of the format checks inside it (Field format/digits/required, Pin match): the words of the first
|
|
199
|
+
control that fails show under the button; a press on a button of the step checks them all first and holds the press back
|
|
200
|
+
when one fails (the app's onClick never hears it); a Pin that does not match shows its words for three seconds, then steps back */
|
|
201
|
+
const goRef = useRef(go);
|
|
202
|
+
goRef.current = go;
|
|
203
|
+
const [note, setNote] = useState(null);
|
|
204
|
+
const checks = useRef(new Map());
|
|
205
|
+
const first = () => { for (const w of checks.current.values())
|
|
206
|
+
if (w != null)
|
|
207
|
+
return w; return null; };
|
|
208
|
+
const group = useMemo(() => ({
|
|
209
|
+
add: (c) => { checks.current.set(c, null); return () => { checks.current.delete(c); setNote(first()); }; },
|
|
210
|
+
say: (c, w) => { checks.current.set(c, w); setNote(first()); },
|
|
211
|
+
back: () => setTimeout(() => goRef.current(i - 1, 'mismatch'), BACK_MS),
|
|
212
|
+
}), [i]);
|
|
213
|
+
useEffect(() => { if (off) {
|
|
214
|
+
for (const c of checks.current.keys())
|
|
215
|
+
checks.current.set(c, null);
|
|
216
|
+
setNote(null);
|
|
217
|
+
} }, [off]);
|
|
218
|
+
/* a press on the step's button keeps the cursor where it is: the button still clicks, but the field keeps the focus, so a
|
|
219
|
+
phone's keyboard does not drop and come back (the pile jumped up and down with it — Lh 2026-09-24) */
|
|
220
|
+
const keepCursor = (e) => {
|
|
221
|
+
const at = document.activeElement;
|
|
222
|
+
if (e.target.closest('.btn') && at instanceof HTMLElement && at.matches('input, textarea') && e.currentTarget.contains(at))
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
};
|
|
225
|
+
const press = (e) => {
|
|
226
|
+
const btn = e.target.closest('.btn');
|
|
227
|
+
if (!btn || btn.disabled)
|
|
228
|
+
return;
|
|
229
|
+
let bad = null;
|
|
230
|
+
for (const c of checks.current.keys()) {
|
|
231
|
+
const w = c.check(true);
|
|
232
|
+
checks.current.set(c, w);
|
|
233
|
+
if (w != null && !bad)
|
|
234
|
+
bad = c;
|
|
235
|
+
}
|
|
236
|
+
setNote(first());
|
|
237
|
+
if (bad) {
|
|
238
|
+
e.stopPropagation();
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
bad.focus();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
return (_jsxs("li", { className: cx('sheet-step', `sheet-step--${phase}`, busy != null && busy !== false && 'sheet-step--busy', className), "aria-current": phase === 'now' ? 'step' : undefined, "aria-busy": busy != null && busy !== false ? true : undefined, "data-skippable": skippable || undefined, ...rest, children: [_jsxs("div", { className: "sheet-step__head", inert: off || undefined, children: [_jsx("h3", { className: "sheet-step__title", tabIndex: -1, children: title }), phase === 'done' && summary != null && _jsx("span", { className: "sheet-step__summary t-caption t-muted", children: summary }), phase === 'done' && _jsx(Icon, { name: "check", size: "xs", className: "sheet-step__check" })] }), _jsx("div", { className: "sheet-step__body", children: _jsxs("div", { className: "sheet-step__inner", inert: off || undefined, onClickCapture: press, onPointerDown: keepCursor, children: [_jsx(CheckContext.Provider, { value: group, children: children }), _jsx(FormNote, { error: error ?? note, busy: busy }), skippable && _jsx(Link, { sm: true, muted: true, className: "sheet-step__skip", onClick: () => go(i + 1, 'skip'), children: skipLabel })] }) }), reversible && off && (_jsx("button", { type: "button", className: "sheet-step__go", "aria-label": typeof title === 'string' ? title : `${i + 1} / ${count}`, onClick: () => go(i, 'tap') }))] }));
|
|
244
|
+
}
|
package/dist/type/icons.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type IconName = "arrow-left" | "arrow-right" | "arrow-up" | "arrow-up-right" | "bell" | "briefcase" | "calendar-days" | "car" | "check" | "chevron-down" | "chevron-left" | "chevron-right" | "chevron-up" | "circle-help" | "clock" | "droplet" | "ellipsis" | "file-text" | "gift" | "handshake" | "heart" | "hourglass" | "house" | "id-card" | "image" | "layout-grid" | "lightbulb" | "log-out" | "luggage" | "map" | "megaphone" | "menu" | "message-circle" | "mic" | "music" | "package" | "pencil" | "percent" | "phone-call" | "plane" | "play" | "plus" | "search" | "share" | "shopping-bag" | "siren" | "sliders" | "star" | "store" | "sun" | "thermometer" | "ticket" | "trash" | "truck" | "upload" | "user" | "users" | "wallet" | "x";
|
|
1
|
+
export type IconName = "arrow-left" | "arrow-right" | "arrow-up" | "arrow-up-right" | "bell" | "briefcase" | "calendar-days" | "car" | "check" | "chevron-down" | "chevron-left" | "chevron-right" | "chevron-up" | "circle-alert" | "circle-help" | "clock" | "droplet" | "ellipsis" | "file-text" | "gift" | "handshake" | "heart" | "hourglass" | "house" | "id-card" | "image" | "layout-grid" | "lightbulb" | "loader-circle" | "log-out" | "luggage" | "map" | "megaphone" | "menu" | "message-circle" | "mic" | "music" | "package" | "pencil" | "percent" | "phone-call" | "plane" | "play" | "plus" | "search" | "share" | "shopping-bag" | "siren" | "sliders" | "star" | "store" | "sun" | "thermometer" | "ticket" | "trash" | "truck" | "upload" | "user" | "users" | "wallet" | "x";
|
|
2
2
|
export declare const ICON_NAMES: readonly IconName[];
|
|
3
3
|
export declare const ICONS: Record<IconName, {
|
|
4
4
|
attrs: Record<string, string>;
|
package/dist/type/icons.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const ICON_NAMES = ["arrow-left", "arrow-right", "arrow-up", "arrow-up-right", "bell", "briefcase", "calendar-days", "car", "check", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "circle-help", "clock", "droplet", "ellipsis", "file-text", "gift", "handshake", "heart", "hourglass", "house", "id-card", "image", "layout-grid", "lightbulb", "log-out", "luggage", "map", "megaphone", "menu", "message-circle", "mic", "music", "package", "pencil", "percent", "phone-call", "plane", "play", "plus", "search", "share", "shopping-bag", "siren", "sliders", "star", "store", "sun", "thermometer", "ticket", "trash", "truck", "upload", "user", "users", "wallet", "x"];
|
|
2
|
-
export const ICONS = { "arrow-left": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m12 19-7-7 7-7\" /><path d=\"M19 12H5\" />" }, "arrow-right": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M5 12h14\"/><path d=\"m12 5 7 7-7 7\"/>" }, "arrow-up": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m5 12 7-7 7 7\"/><path d=\"M12 19V5\"/>" }, "arrow-up-right": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M7 7h10v10\" /><path d=\"M7 17 17 7\" />" }, "bell": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M10.268 21a2 2 0 0 0 3.464 0\" /><path d=\"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\" />" }, "briefcase": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16\" /><rect width=\"20\" height=\"14\" x=\"2\" y=\"6\" rx=\"2\" />" }, "calendar-days": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M8 2v4\" /><path d=\"M16 2v4\" /><rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" /><path d=\"M3 10h18\" /><path d=\"M8 14h.01\" /><path d=\"M12 14h.01\" /><path d=\"M16 14h.01\" /><path d=\"M8 18h.01\" /><path d=\"M12 18h.01\" /><path d=\"M16 18h.01\" />" }, "car": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2\"/><circle cx=\"7\" cy=\"17\" r=\"2\"/><path d=\"M9 17h6\"/><circle cx=\"17\" cy=\"17\" r=\"2\"/>" }, "check": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M20 6 9 17l-5-5\"/>" }, "chevron-down": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m6 9 6 6 6-6\" />" }, "chevron-left": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m15 18-6-6 6-6\"/>" }, "chevron-right": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m9 18 6-6-6-6\"/>" }, "chevron-up": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m18 15-6-6-6 6\"/>" }, "circle-help": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><path d=\"M12 17h.01\"/>" }, "clock": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M12 6v6l4 2\" /><circle cx=\"12\" cy=\"12\" r=\"10\" />" }, "droplet": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z\"/>" }, "ellipsis": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>" }, "file-text": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/><path d=\"M10 9H8\"/><path d=\"M16 13H8\"/><path d=\"M16 17H8\"/>" }, "gift": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<rect x=\"3\" y=\"8\" width=\"18\" height=\"4\" rx=\"1\"/><path d=\"M12 8v13\"/><path d=\"M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7\"/><path d=\"M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5\"/>" }, "handshake": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m11 17 2 2a1 1 0 1 0 3-3\"/><path d=\"m14 14 2.5 2.5a1 1 0 1 0 3-3l-3.88-3.88a3 3 0 0 0-4.24 0l-.88.88a1 1 0 1 1-3-3l2.81-2.81a5.79 5.79 0 0 1 7.06-.87l.47.28a2 2 0 0 0 1.42.25L21 4\"/><path d=\"m21 3 1 11h-2\"/><path d=\"M3 3 2 14l6.5 6.5a1 1 0 1 0 3-3\"/><path d=\"M3 4h8\"/>" }, "heart": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z\" />" }, "hourglass": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M5 22h14\"/><path d=\"M5 2h14\"/><path d=\"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22\"/><path d=\"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2\"/>" }, "house": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"/><path d=\"M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/>" }, "id-card": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 10h2\"/><path d=\"M16 14h2\"/><path d=\"M6.17 15a3 3 0 0 1 5.66 0\"/><circle cx=\"9\" cy=\"11\" r=\"2\"/><rect x=\"2\" y=\"5\" width=\"20\" height=\"14\" rx=\"2\"/>" }, "image": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/>" }, "layout-grid": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\"/>" }, "lightbulb": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1.3.5 2.6 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/>" }, "log-out": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" x2=\"9\" y1=\"12\" y2=\"12\"/>" }, "luggage": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M6 20a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2\" /><path d=\"M8 18V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v14\" /><path d=\"M10 20h4\" /><circle cx=\"16\" cy=\"20\" r=\"2\" /><circle cx=\"8\" cy=\"20\" r=\"2\" />" }, "map": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z\" /><path d=\"M15 5.764v15\" /><path d=\"M9 3.236v15\" />" }, "megaphone": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m3 11 18-5v12L3 14v-3z\"/><path d=\"M11.6 16.8a3 3 0 1 1-5.8-1.6\"/>" }, "menu": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M4 12h16\" /><path d=\"M4 18h16\" /><path d=\"M4 6h16\" />" }, "message-circle": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\"/>" }, "mic": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>" }, "music": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M9 18V5l12-2v13\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><circle cx=\"18\" cy=\"16\" r=\"3\"/>" }, "package": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m7.5 4.27 9 5.15\"/><path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>" }, "pencil": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"/><path d=\"m15 5 4 4\"/>" }, "percent": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<line x1=\"19\" x2=\"5\" y1=\"5\" y2=\"19\" /><circle cx=\"6.5\" cy=\"6.5\" r=\"2.5\" /><circle cx=\"17.5\" cy=\"17.5\" r=\"2.5\" />" }, "phone-call": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M13 2a9 9 0 0 1 9 9\" /><path d=\"M13 6a5 5 0 0 1 5 5\" /><path d=\"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384\" />" }, "plane": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z\" />" }, "play": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<polygon points=\"6 3 20 12 6 21 6 3\"/>" }, "plus": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M5 12h14\" /><path d=\"M12 5v14\" />" }, "search": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>" }, "share": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/><polyline points=\"16 6 12 2 8 6\"/><line x1=\"12\" x2=\"12\" y1=\"2\" y2=\"15\"/>" }, "shopping-bag": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 10a4 4 0 0 1-8 0\" /><path d=\"M3.103 6.034h17.794\" /><path d=\"M3.4 5.467a2 2 0 0 0-.4 1.2V20a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.667a2 2 0 0 0-.4-1.2l-2-2.667A2 2 0 0 0 17 2H7a2 2 0 0 0-1.6.8z\" />" }, "siren": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M7 18v-6a5 5 0 1 1 10 0v6\" /><path d=\"M5 21a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2z\" /><path d=\"M21 12h1\" /><path d=\"M18.5 4.5 18 5\" /><path d=\"M2 12h1\" /><path d=\"M12 2v1\" /><path d=\"m4.929 4.929.707.707\" /><path d=\"M12 12v6\" />" }, "sliders": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<line x1=\"21\" x2=\"14\" y1=\"4\" y2=\"4\"/><line x1=\"10\" x2=\"3\" y1=\"4\" y2=\"4\"/><line x1=\"21\" x2=\"12\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"16\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"3\" y1=\"20\" y2=\"20\"/><line x1=\"14\" x2=\"14\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"10\" y2=\"14\"/><line x1=\"16\" x2=\"16\" y1=\"18\" y2=\"22\"/>" }, "star": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"/>" }, "store": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7\" /><path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\" /><path d=\"M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4\" /><path d=\"M2 7h20\" /><path d=\"M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7\" />" }, "sun": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/>" }, "thermometer": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z\"/>" }, "ticket": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z\" /><path d=\"M13 5v2\" /><path d=\"M13 17v2\" /><path d=\"M13 11v2\" />" }, "trash": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/>" }, "truck": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2\"/><path d=\"M15 18H9\"/><path d=\"M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14\"/><circle cx=\"17\" cy=\"18\" r=\"2\"/><circle cx=\"7\" cy=\"18\" r=\"2\"/>" }, "upload": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"15\"/>" }, "user": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>" }, "users": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/>" }, "wallet": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1\"/><path d=\"M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4\"/>" }, "x": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />" } };
|
|
1
|
+
export const ICON_NAMES = ["arrow-left", "arrow-right", "arrow-up", "arrow-up-right", "bell", "briefcase", "calendar-days", "car", "check", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "circle-alert", "circle-help", "clock", "droplet", "ellipsis", "file-text", "gift", "handshake", "heart", "hourglass", "house", "id-card", "image", "layout-grid", "lightbulb", "loader-circle", "log-out", "luggage", "map", "megaphone", "menu", "message-circle", "mic", "music", "package", "pencil", "percent", "phone-call", "plane", "play", "plus", "search", "share", "shopping-bag", "siren", "sliders", "star", "store", "sun", "thermometer", "ticket", "trash", "truck", "upload", "user", "users", "wallet", "x"];
|
|
2
|
+
export const ICONS = { "arrow-left": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m12 19-7-7 7-7\" /><path d=\"M19 12H5\" />" }, "arrow-right": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M5 12h14\"/><path d=\"m12 5 7 7-7 7\"/>" }, "arrow-up": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m5 12 7-7 7 7\"/><path d=\"M12 19V5\"/>" }, "arrow-up-right": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M7 7h10v10\" /><path d=\"M7 17 17 7\" />" }, "bell": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M10.268 21a2 2 0 0 0 3.464 0\" /><path d=\"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\" />" }, "briefcase": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16\" /><rect width=\"20\" height=\"14\" x=\"2\" y=\"6\" rx=\"2\" />" }, "calendar-days": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M8 2v4\" /><path d=\"M16 2v4\" /><rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" /><path d=\"M3 10h18\" /><path d=\"M8 14h.01\" /><path d=\"M12 14h.01\" /><path d=\"M16 14h.01\" /><path d=\"M8 18h.01\" /><path d=\"M12 18h.01\" /><path d=\"M16 18h.01\" />" }, "car": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2\"/><circle cx=\"7\" cy=\"17\" r=\"2\"/><path d=\"M9 17h6\"/><circle cx=\"17\" cy=\"17\" r=\"2\"/>" }, "check": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M20 6 9 17l-5-5\"/>" }, "chevron-down": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m6 9 6 6 6-6\" />" }, "chevron-left": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m15 18-6-6 6-6\"/>" }, "chevron-right": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m9 18 6-6-6-6\"/>" }, "chevron-up": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m18 15-6-6-6 6\"/>" }, "circle-alert": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"12\" cy=\"12\" r=\"10\" /><line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" /><line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />" }, "circle-help": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><path d=\"M12 17h.01\"/>" }, "clock": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M12 6v6l4 2\" /><circle cx=\"12\" cy=\"12\" r=\"10\" />" }, "droplet": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z\"/>" }, "ellipsis": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>" }, "file-text": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/><path d=\"M10 9H8\"/><path d=\"M16 13H8\"/><path d=\"M16 17H8\"/>" }, "gift": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<rect x=\"3\" y=\"8\" width=\"18\" height=\"4\" rx=\"1\"/><path d=\"M12 8v13\"/><path d=\"M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7\"/><path d=\"M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5\"/>" }, "handshake": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m11 17 2 2a1 1 0 1 0 3-3\"/><path d=\"m14 14 2.5 2.5a1 1 0 1 0 3-3l-3.88-3.88a3 3 0 0 0-4.24 0l-.88.88a1 1 0 1 1-3-3l2.81-2.81a5.79 5.79 0 0 1 7.06-.87l.47.28a2 2 0 0 0 1.42.25L21 4\"/><path d=\"m21 3 1 11h-2\"/><path d=\"M3 3 2 14l6.5 6.5a1 1 0 1 0 3-3\"/><path d=\"M3 4h8\"/>" }, "heart": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z\" />" }, "hourglass": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M5 22h14\"/><path d=\"M5 2h14\"/><path d=\"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22\"/><path d=\"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2\"/>" }, "house": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"/><path d=\"M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/>" }, "id-card": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 10h2\"/><path d=\"M16 14h2\"/><path d=\"M6.17 15a3 3 0 0 1 5.66 0\"/><circle cx=\"9\" cy=\"11\" r=\"2\"/><rect x=\"2\" y=\"5\" width=\"20\" height=\"14\" rx=\"2\"/>" }, "image": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/>" }, "layout-grid": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\"/>" }, "lightbulb": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1.3.5 2.6 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/>" }, "loader-circle": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />" }, "log-out": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" x2=\"9\" y1=\"12\" y2=\"12\"/>" }, "luggage": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M6 20a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2\" /><path d=\"M8 18V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v14\" /><path d=\"M10 20h4\" /><circle cx=\"16\" cy=\"20\" r=\"2\" /><circle cx=\"8\" cy=\"20\" r=\"2\" />" }, "map": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z\" /><path d=\"M15 5.764v15\" /><path d=\"M9 3.236v15\" />" }, "megaphone": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m3 11 18-5v12L3 14v-3z\"/><path d=\"M11.6 16.8a3 3 0 1 1-5.8-1.6\"/>" }, "menu": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M4 12h16\" /><path d=\"M4 18h16\" /><path d=\"M4 6h16\" />" }, "message-circle": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\"/>" }, "mic": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/>" }, "music": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M9 18V5l12-2v13\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><circle cx=\"18\" cy=\"16\" r=\"3\"/>" }, "package": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m7.5 4.27 9 5.15\"/><path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>" }, "pencil": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"/><path d=\"m15 5 4 4\"/>" }, "percent": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<line x1=\"19\" x2=\"5\" y1=\"5\" y2=\"19\" /><circle cx=\"6.5\" cy=\"6.5\" r=\"2.5\" /><circle cx=\"17.5\" cy=\"17.5\" r=\"2.5\" />" }, "phone-call": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M13 2a9 9 0 0 1 9 9\" /><path d=\"M13 6a5 5 0 0 1 5 5\" /><path d=\"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384\" />" }, "plane": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z\" />" }, "play": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<polygon points=\"6 3 20 12 6 21 6 3\"/>" }, "plus": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M5 12h14\" /><path d=\"M12 5v14\" />" }, "search": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>" }, "share": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/><polyline points=\"16 6 12 2 8 6\"/><line x1=\"12\" x2=\"12\" y1=\"2\" y2=\"15\"/>" }, "shopping-bag": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 10a4 4 0 0 1-8 0\" /><path d=\"M3.103 6.034h17.794\" /><path d=\"M3.4 5.467a2 2 0 0 0-.4 1.2V20a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.667a2 2 0 0 0-.4-1.2l-2-2.667A2 2 0 0 0 17 2H7a2 2 0 0 0-1.6.8z\" />" }, "siren": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M7 18v-6a5 5 0 1 1 10 0v6\" /><path d=\"M5 21a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2z\" /><path d=\"M21 12h1\" /><path d=\"M18.5 4.5 18 5\" /><path d=\"M2 12h1\" /><path d=\"M12 2v1\" /><path d=\"m4.929 4.929.707.707\" /><path d=\"M12 12v6\" />" }, "sliders": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<line x1=\"21\" x2=\"14\" y1=\"4\" y2=\"4\"/><line x1=\"10\" x2=\"3\" y1=\"4\" y2=\"4\"/><line x1=\"21\" x2=\"12\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"16\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"3\" y1=\"20\" y2=\"20\"/><line x1=\"14\" x2=\"14\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"10\" y2=\"14\"/><line x1=\"16\" x2=\"16\" y1=\"18\" y2=\"22\"/>" }, "star": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"/>" }, "store": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7\" /><path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\" /><path d=\"M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4\" /><path d=\"M2 7h20\" /><path d=\"M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7\" />" }, "sun": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/>" }, "thermometer": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z\"/>" }, "ticket": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z\" /><path d=\"M13 5v2\" /><path d=\"M13 17v2\" /><path d=\"M13 11v2\" />" }, "trash": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/>" }, "truck": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2\"/><path d=\"M15 18H9\"/><path d=\"M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14\"/><circle cx=\"17\" cy=\"18\" r=\"2\"/><circle cx=\"7\" cy=\"18\" r=\"2\"/>" }, "upload": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"15\"/>" }, "user": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>" }, "users": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/>" }, "wallet": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1\"/><path d=\"M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4\"/>" }, "x": { "attrs": { "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "strokeWidth": "2", "strokeLinecap": "round", "strokeLinejoin": "round" }, "body": "<path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />" } };
|
package/icons.svg
CHANGED
|
@@ -60,4 +60,6 @@
|
|
|
60
60
|
<symbol id="gift" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="8" width="18" height="4" rx="1"/><path d="M12 8v13"/><path d="M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7"/><path d="M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5"/></symbol>
|
|
61
61
|
<symbol id="id-card" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 10h2"/><path d="M16 14h2"/><path d="M6.17 15a3 3 0 0 1 5.66 0"/><circle cx="9" cy="11" r="2"/><rect x="2" y="5" width="20" height="14" rx="2"/></symbol>
|
|
62
62
|
<symbol id="handshake" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m11 17 2 2a1 1 0 1 0 3-3"/><path d="m14 14 2.5 2.5a1 1 0 1 0 3-3l-3.88-3.88a3 3 0 0 0-4.24 0l-.88.88a1 1 0 1 1-3-3l2.81-2.81a5.79 5.79 0 0 1 7.06-.87l.47.28a2 2 0 0 0 1.42.25L21 4"/><path d="m21 3 1 11h-2"/><path d="M3 3 2 14l6.5 6.5a1 1 0 1 0 3-3"/><path d="M3 4h8"/></symbol>
|
|
63
|
+
<symbol id="circle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <line x1="12" x2="12" y1="8" y2="12" /> <line x1="12" x2="12.01" y1="16" y2="16" /></symbol>
|
|
64
|
+
<symbol id="loader-circle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></symbol>
|
|
63
65
|
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
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.",
|