@usableapp/cardds 0.1.3 → 0.1.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/README.md +43 -40
- package/cardds.js +47 -10
- package/css/layover.css +67 -22
- package/css/sheet.css +10 -4
- package/css/step.css +14 -5
- package/css/tokens.css +1 -0
- package/dist/actions/Btn.js +1 -1
- package/dist/actions/BtnRow.js +1 -1
- package/dist/actions/Chip.js +2 -2
- package/dist/actions/Dot.js +1 -1
- package/dist/actions/Dropdown.d.ts +1 -1
- package/dist/actions/Dropdown.js +2 -2
- package/dist/actions/Fab.d.ts +1 -1
- package/dist/actions/Fab.js +2 -2
- package/dist/actions/IconBtn.d.ts +1 -1
- package/dist/actions/IconBtn.js +3 -3
- package/dist/actions/Link.d.ts +1 -1
- package/dist/actions/Link.js +2 -2
- package/dist/actions/Segment.js +1 -1
- package/dist/cardds.css +92 -31
- package/dist/cards/Card.js +1 -1
- package/dist/cards/CardFoot.js +1 -1
- package/dist/cards/CardHead.js +1 -1
- package/dist/cards/CardParts.js +1 -1
- package/dist/cards/Placeholder.js +1 -1
- package/dist/cards/Stat.js +1 -1
- package/dist/choice/Calendar.js +2 -2
- package/dist/choice/Check.js +1 -1
- package/dist/choice/DayStrip.js +1 -1
- package/dist/choice/Mood.js +1 -1
- package/dist/choice/Slider.js +1 -1
- package/dist/forms/AddRow.js +2 -2
- package/dist/forms/Composer.js +1 -1
- package/dist/forms/Field.d.ts +1 -1
- package/dist/forms/Field.js +2 -2
- package/dist/forms/FileBtn.js +1 -1
- package/dist/forms/Pin.js +1 -1
- package/dist/index.d.ts +71 -71
- package/dist/index.js +71 -71
- package/dist/journey/NoteRow.d.ts +1 -1
- package/dist/journey/NoteRow.js +2 -2
- package/dist/journey/Route.d.ts +1 -1
- package/dist/journey/Route.js +2 -2
- package/dist/journey/TileBadge.js +1 -1
- package/dist/layover/Banner.js +1 -1
- package/dist/layover/Callout.js +1 -1
- package/dist/layover/Deck.js +1 -1
- package/dist/lists/Kv.d.ts +1 -1
- package/dist/lists/Kv.js +2 -2
- package/dist/lists/Legend.js +2 -2
- package/dist/lists/Row.d.ts +1 -1
- package/dist/lists/Row.js +2 -2
- package/dist/lists/Timeline.js +1 -1
- package/dist/media/MapArea.js +1 -1
- package/dist/media/Mosaic.js +1 -1
- package/dist/media/Postcard.js +1 -1
- package/dist/media/Quote.js +1 -1
- package/dist/media/Tile.d.ts +1 -1
- package/dist/media/Tile.js +2 -2
- package/dist/numbers/Badge.js +1 -1
- package/dist/numbers/Band.js +1 -1
- package/dist/numbers/Bars.js +1 -1
- package/dist/numbers/DotGrid.js +1 -1
- package/dist/numbers/Picker.js +1 -1
- package/dist/numbers/Ring.js +1 -1
- package/dist/numbers/Track.js +1 -1
- package/dist/people/Avatar.d.ts +1 -1
- package/dist/people/Avatar.js +2 -2
- package/dist/people/AvatarPick.js +1 -1
- package/dist/scaffold/ActionBar.js +1 -1
- package/dist/scaffold/AppBar.js +1 -1
- package/dist/scaffold/Centre.js +1 -1
- package/dist/scaffold/EmptyState.js +1 -1
- package/dist/scaffold/FilterRow.js +1 -1
- package/dist/scaffold/FloatBar.js +1 -1
- package/dist/scaffold/PagerAt.js +1 -1
- package/dist/scaffold/Screen.js +1 -1
- package/dist/scaffold/Section.js +1 -1
- package/dist/scaffold/TopBar.js +1 -1
- package/dist/sheets/CardBack.js +2 -2
- package/dist/sheets/Drawer.d.ts +7 -3
- package/dist/sheets/Drawer.js +9 -5
- package/dist/sheets/Sheet.d.ts +3 -3
- package/dist/sheets/Sheet.js +1 -1
- package/dist/sheets/SheetBody.js +1 -1
- package/dist/sheets/SheetHead.js +1 -1
- package/dist/sheets/SheetStack.js +1 -1
- package/dist/sheets/SheetStage.d.ts +1 -1
- package/dist/sheets/SheetStage.js +1 -1
- package/dist/sheets/Step.d.ts +3 -4
- package/dist/sheets/Step.js +5 -5
- package/dist/type/Icon.d.ts +1 -1
- package/dist/type/Icon.js +2 -2
- package/dist/type/Text.js +1 -1
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Card-first mobile design system, **React-first**: `src/` is the component
|
|
4
4
|
library — one thin component per pattern, emitting exactly the markup the CSS
|
|
5
|
-
documents
|
|
6
|
-
screens of the Claude Design handoff, as real routes). The CSS (`css/*.css`)
|
|
5
|
+
documents. The CSS (`css/*.css`)
|
|
7
6
|
stays the only truth: a component never styles anything, it only picks classes
|
|
8
7
|
from props. The CSS also works alone, with no build, for a host that wants
|
|
9
|
-
class names rather than components
|
|
10
|
-
|
|
8
|
+
class names rather than components. The demo is `/gallery` (`npm run dev`):
|
|
9
|
+
every component's stories, live, searchable; `tests/fixtures/` holds the
|
|
10
|
+
HTML pages the acceptance tests drive (not a demo — nobody reads them).
|
|
11
11
|
|
|
12
12
|
Repo: **https://github.com/everysundays/cardds** (private — this is the
|
|
13
13
|
source of truth; every consuming project vendors a copy from here, never
|
|
@@ -23,10 +23,9 @@ licences (`fonts/LICENSES.md`).
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
npm install
|
|
26
|
-
npm run dev # the
|
|
26
|
+
npm run dev # the gallery on http://localhost:5174 — every story, searchable
|
|
27
27
|
npm run build # the library: icons → src/type/icons.ts · tsc → dist/ (ESM + .d.ts) · dist/cardds.css (flattened)
|
|
28
|
-
npm
|
|
29
|
-
npm test # Playwright: the step/sheet/centre geometry on demo/, the app's screens and the four handoff upgrades
|
|
28
|
+
npm test # Playwright: the step/sheet/centre geometry on tests/fixtures/, the app's screens and the four handoff upgrades
|
|
30
29
|
```
|
|
31
30
|
|
|
32
31
|
```tsx
|
|
@@ -42,12 +41,13 @@ the code), a card heading that needs more than two lines squeezing its type
|
|
|
42
41
|
(see *Anatomy*), and the `.dropdown` writing its picked value back. `Icon`
|
|
43
42
|
inlines the 56 symbols of `icons.svg` at build time, so no sprite file ships.
|
|
44
43
|
|
|
45
|
-
**The
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
**The gallery** (`gallery/`): Vite + React Router, `cardds` aliased to `src/`
|
|
45
|
+
so the gallery and the package share one truth with no build between them.
|
|
46
|
+
`/` lists every story of every component, `/:Name` one component. (The
|
|
47
|
+
Commons Time Bank app that used to live here — twelve screens from
|
|
48
|
+
`docs/handoff/commons-time-bank-SPEC.md` — was dropped on 2026-09-16; nobody
|
|
49
|
+
looked at it, the gallery is the demo. The type scale is the `Text · Scale`
|
|
50
|
+
story.)
|
|
51
51
|
|
|
52
52
|
**CSS only** (no React, no build):
|
|
53
53
|
|
|
@@ -62,14 +62,12 @@ popover) but won't update its label, a stack still displays but won't open, a
|
|
|
62
62
|
sheet renders every state but only moves when a class changes, a pin is six
|
|
63
63
|
plain inputs.
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
of `tests/step.spec.js` and `tests/centre.spec.js`; `tests/app.spec.js` drives
|
|
72
|
-
the app. Nothing here is verified by eye.
|
|
65
|
+
The one place to look is `/gallery` (`npm run dev` → `http://localhost:5174/gallery`):
|
|
66
|
+
every `.design-sync/previews/<Name>.tsx` story, live, grouped by `src/<group>/`,
|
|
67
|
+
searchable, in a 375×812 phone cell, with a palette switch and an inspect
|
|
68
|
+
switch (box outlines + dimensions on hover). `tests/fixtures/*.html` (ex
|
|
69
|
+
`demo/`) are the pages `tests/step.spec.js` and `tests/centre.spec.js` measure;
|
|
70
|
+
`tests/gallery.spec.js` drives the gallery. Nothing here is verified by eye.
|
|
73
71
|
|
|
74
72
|
### In another project
|
|
75
73
|
|
|
@@ -187,19 +185,19 @@ A step is one screen, one task (see *Anatomy*). It is the other component
|
|
|
187
185
|
whose markup has rules, because the sheet's box is the step itself:
|
|
188
186
|
|
|
189
187
|
```html
|
|
190
|
-
<!-- reads:
|
|
188
|
+
<!-- reads: the words only -->
|
|
191
189
|
<section class="step">
|
|
192
|
-
<article class="
|
|
193
|
-
<div class="card__head"><
|
|
190
|
+
<article class="step__card">
|
|
191
|
+
<div class="card__head"><span class="t-overline t-muted">ขั้นที่ 2 จาก 4</span><h2 class="t-h2">…</h2></div>
|
|
194
192
|
<p class="t-body">…</p>
|
|
195
193
|
</article>
|
|
196
194
|
</section>
|
|
197
195
|
<div class="action-bar action-bar--pager">…</div>
|
|
198
196
|
|
|
199
|
-
<!-- acts:
|
|
197
|
+
<!-- acts: the words + a sheet -->
|
|
200
198
|
<section class="step">
|
|
201
|
-
<article class="
|
|
202
|
-
<article class="card sheet sheet--half" data-sheet-states="peek half full">
|
|
199
|
+
<article class="step__card">…the words…</article>
|
|
200
|
+
<article class="card sheet sheet--half" data-sheet-states="peek half 3q full">
|
|
203
201
|
<button class="sheet__handle" type="button" aria-label="ปรับความสูง"></button>
|
|
204
202
|
<div class="card__head"><h2 class="t-h2">…</h2><span class="chip">…</span></div>
|
|
205
203
|
<div class="sheet__body">
|
|
@@ -221,8 +219,10 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
221
219
|
card takes the inset back as its side margins — so the card and the bar's
|
|
222
220
|
buttons stand on the inset line, and the sheet runs edge to edge like the
|
|
223
221
|
bar and like a sheet in a `.sheet-stage`.
|
|
224
|
-
- **`.step__card` first, `.sheet` last.** The
|
|
225
|
-
|
|
222
|
+
- **`.step__card` first, `.sheet` last.** The block carries the step's words
|
|
223
|
+
ON THE BASE — no frame, no area colour (it was a `.card` until 2026-09-16;
|
|
224
|
+
Lh: a step reads like a page, not a card in a page). It keeps a card's
|
|
225
|
+
rhythm: a `.card__head`, `--card-gap` between things. The sheet is
|
|
226
226
|
**handle · head · body**: the handle, then a `.card__head` title row (that
|
|
227
227
|
row is what `peek` shows — heading left, the contextual thing right), then
|
|
228
228
|
`.sheet__body` with what the member acts on. The handle and the head are
|
|
@@ -311,12 +311,12 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
311
311
|
| `css/choice.css` | `.check`, `.toggle`, `.chip--toggle`, `.chip-grid` + `.chip--pick`, `.day-strip`, `.calendar`, `.mood`, `.pin`, `.composer`, `.slider` | choice controls |
|
|
312
312
|
| `css/lists.css` | `.row` / `.rows` (frameless rows in a card), `.kv`, `.kv-grid`, `.timeline`, `.legend`, `.link` | lists inside cards |
|
|
313
313
|
| `css/media.css` | `.card--cover`, `.quote`, `.tile-grid` + `.tile`, `.card--fold`, `.mosaic`, `.wave` | media & display |
|
|
314
|
-
| `css/layover.css` | `.
|
|
314
|
+
| `css/layover.css` | `.modal` (the ask: `__lift` + `__drawer`, closes on any drawer button → `cardds:modal`), `.menu` (popover), `.banner` (details), `.float-bar`, `.callout`, `.chip--bubble`, `.deck` | cards over content |
|
|
315
315
|
| `icons.svg` | Lucide sprite (ISC), 56 minimal stroke icons | add symbols as needed |
|
|
316
316
|
| `src/` → `dist/` | the React library: `src/<group>/<Name>.tsx`, one thin component per pattern, JSDoc = the rule; built by `npm run build` (tsc) into `dist/` with `.d.ts`; `src/type/icons.ts` is generated from `icons.svg` | when adding a component |
|
|
317
|
-
| `
|
|
318
|
-
| `
|
|
319
|
-
| `docs/handoff/` | the Claude Design handoff spec the app was
|
|
317
|
+
| `gallery/` | the gallery site: `npm run dev` → `/` every story, `/:Name` one component (`gallery/src/Gallery.tsx` globs `.design-sync/previews/`) | the demo |
|
|
318
|
+
| `tests/fixtures/` | the HTML pages (`index`, `sheet`, `stack`, `elements`) the geometry tests measure — ex `demo/`, not a demo | when a geometry test needs a new case |
|
|
319
|
+
| `docs/handoff/` | the Claude Design handoff spec (the Commons Time Bank app built from it was dropped 2026-09-16) | reference |
|
|
320
320
|
| `scripts/` | `build-icons.mjs` (icons.svg → `src/type/icons.ts`), `flatten-css.mjs` (`cardds.css` + imports → `dist/cardds.css`) | rarely |
|
|
321
321
|
| `docs/guides/` | `building-with-cardds.md` — the anatomy, the step, the two sheet systems and the vocabulary in React terms; shipped to Claude Design as its guidelines | when the contract changes |
|
|
322
322
|
| `cardds.js` | the behaviours (delegated, class/property toggles only — everything renders without it): dropdown value sync, `.sheet-stack--tap` open/close (tolerates one wrapper per sheet; close reads the tapped sheet), `.pin` auto-advance / Backspace / paste + `cardds:pin`, the sheet handle (tap / drag-snap / Enter / Space), the focus and keyboard raises (`--kb`), `cardds:sheet` | rarely |
|
|
@@ -356,7 +356,7 @@ Every screen is the same four things, and every card the same three:
|
|
|
356
356
|
- **Two sheet systems, one material.** *Sheet single* (`.sheet`, `sheet.css`)
|
|
357
357
|
is ONE sheet the member acts in, whatever it holds: a short entry in front
|
|
358
358
|
of a step's card, a place on a map, a payment on a dim page — in a `.step`
|
|
359
|
-
or a `.sheet-stage`, and its state is how far it slid (peek / half / full).
|
|
359
|
+
or a `.sheet-stage`, and its state is how far it slid (peek / half / 3q / full).
|
|
360
360
|
*Sheet stack* (`.sheet-stack`, `stack.css`) is MANY sheets, each a main
|
|
361
361
|
section of the app, stacked like paper at the middle of the home screen:
|
|
362
362
|
the menu. Heads peek, a tap brings one sheet to the front, back returns it;
|
|
@@ -365,8 +365,8 @@ Every screen is the same four things, and every card the same three:
|
|
|
365
365
|
sheet is `.card.sheet` — and both obey rule 8: rigid, full height, moved
|
|
366
366
|
by `translate` only. (The stack was first named "card stack": wrong, and
|
|
367
367
|
retired.) Demos: `sheet.html`, `stack.html`.
|
|
368
|
-
- **A step is one screen, one task: `.step`.** What the member reads is
|
|
369
|
-
|
|
368
|
+
- **A step is one screen, one task: `.step`.** What the member reads is
|
|
369
|
+
words on the base (`.step__card` — the name stayed, the frame went); what the member does, if the step asks for it, is a
|
|
370
370
|
`.sheet` in front of it; the screen's one move is `.action-bar`, outside
|
|
371
371
|
the step, below it. A step where the member only READS is the card alone.
|
|
372
372
|
A step where the member ACTS (types, picks, shoots a photo, records) adds
|
|
@@ -469,9 +469,12 @@ Demo: the *skeleton* block at the top of `elements.html`.
|
|
|
469
469
|
8. **A single sheet is rigid material, like a sheet in the stack.** It is always the full
|
|
470
470
|
height of the box that holds it (`inset: 0`, set once, never written
|
|
471
471
|
again) and a state is nothing but how far that full card slid:
|
|
472
|
-
`sheet--full` slid nowhere, `sheet--
|
|
473
|
-
|
|
474
|
-
|
|
472
|
+
`sheet--full` slid nowhere, `sheet--3q` slid down by `--sheet-3q` (a
|
|
473
|
+
quarter of itself — three quarters show), `sheet--half` by `--sheet-half`
|
|
474
|
+
(50%), `sheet--peek` slid down until only `--sheet-peek` shows — the
|
|
475
|
+
handle and the title row. A tap on the handle toggles between the sheet's
|
|
476
|
+
own state and peek (never a step-by-step climb, Lh 2026-09-16); a drag
|
|
477
|
+
snaps to any listed state, and where it lands is what a tap returns to. `translate` is the only animated property
|
|
475
478
|
(`--motion-sheet`), and nothing toggles height, display or z-index for a
|
|
476
479
|
state — the stack's rule, inherited. The box clips whatever slid below its
|
|
477
480
|
bottom edge, so a sheet at any height is still a card: rounded top, a
|
|
@@ -484,7 +487,7 @@ Demo: the *skeleton* block at the top of `elements.html`.
|
|
|
484
487
|
padding. Two boxes hold sheets and the states
|
|
485
488
|
mean the same in both: `.step` (an app screen, the bar outside — Anatomy)
|
|
486
489
|
and `.sheet-stage` (a page-level overlay: a sheet over a map, the dim page
|
|
487
|
-
sheet `--dim` + `--raised`, the ask
|
|
490
|
+
sheet `--dim` + `--raised`, the ask `--ask` + `.modal` = `__lift` + `__drawer`).
|
|
488
491
|
A stage keeps the top bar's line free (`--_top`), so `--full` stops under
|
|
489
492
|
`--header-h` and keeps its corners. Both sheet tokens are published — a
|
|
490
493
|
sheet with a taller head retunes `--sheet-peek` — and registered in
|
package/cardds.js
CHANGED
|
@@ -177,8 +177,12 @@ function carddsCloseStack(stack, card) {
|
|
|
177
177
|
without it; this adds the handle's click and drag, the focus raise, the
|
|
178
178
|
keyboard room, and one event. Delegated: any sheet on the page, no ids.
|
|
179
179
|
|
|
180
|
-
- click / Enter / Space on .sheet__handle
|
|
181
|
-
|
|
180
|
+
- click / Enter / Space on .sheet__handle TOGGLES the sheet between its
|
|
181
|
+
own state and peek (Lh 2026-09-16 — never a step-by-step climb). "Its
|
|
182
|
+
own state" is the last non-peek state it rested in (authored, dragged
|
|
183
|
+
to, or set), kept in data-sheet-home
|
|
184
|
+
- data-sheet-states (default "peek half 3q full") lists the states a
|
|
185
|
+
drag snaps to
|
|
182
186
|
- drag on the handle writes the offset to --_drag (the CSS adds it to the
|
|
183
187
|
state's translate) and on release snaps to the nearest state; a move
|
|
184
188
|
shorter than --sp-2 is a tap. The snap is the one place the sheet's
|
|
@@ -194,7 +198,7 @@ function carddsCloseStack(stack, card) {
|
|
|
194
198
|
|
|
195
199
|
carddsSheetSet(sheet, state) is the one entry point; an app that sets
|
|
196
200
|
the state itself calls it rather than swapping classes, so listeners hear. */
|
|
197
|
-
const CARDDS_SHEET_STATES = ['peek', 'half', 'full'];
|
|
201
|
+
const CARDDS_SHEET_STATES = ['peek', 'half', '3q', 'full'];
|
|
198
202
|
const CARDDS_SHEET_TAP_REM = 0.5; /* = --sp-2: a move shorter than this is a tap, not a drag */
|
|
199
203
|
|
|
200
204
|
function carddsSheetStates(sheet) {
|
|
@@ -210,6 +214,10 @@ function carddsSheetSet(sheet, state) {
|
|
|
210
214
|
if (!CARDDS_SHEET_STATES.includes(state)) return;
|
|
211
215
|
const was = carddsSheetState(sheet);
|
|
212
216
|
CARDDS_SHEET_STATES.forEach(s => sheet.classList.toggle('sheet--' + s, s === state));
|
|
217
|
+
/* where a tap from peek returns to: the non-peek state it is going to, or the
|
|
218
|
+
one it is leaving (an authored state never passed through here before) */
|
|
219
|
+
const home = state !== 'peek' ? state : was !== 'peek' ? was : null;
|
|
220
|
+
if (home) sheet.dataset.sheetHome = home;
|
|
213
221
|
if (was !== state) sheet.dispatchEvent(new CustomEvent('cardds:sheet', { bubbles: true, detail: { state } }));
|
|
214
222
|
}
|
|
215
223
|
|
|
@@ -224,10 +232,19 @@ document.addEventListener('click', e => {
|
|
|
224
232
|
if (handle === carddsSheetSkipClick) { carddsSheetSkipClick = null; return; }
|
|
225
233
|
const sheet = handle.closest('.sheet');
|
|
226
234
|
if (!sheet) return;
|
|
227
|
-
|
|
228
|
-
carddsSheetSet(sheet, states[(states.indexOf(carddsSheetState(sheet)) + 1) % states.length]);
|
|
235
|
+
carddsSheetSet(sheet, carddsSheetState(sheet) === 'peek' ? carddsSheetHome(sheet) : 'peek');
|
|
229
236
|
});
|
|
230
237
|
|
|
238
|
+
/* the state a tap from peek returns to: the last non-peek state it rested in,
|
|
239
|
+
else the authored one, else the first non-peek state it snaps to */
|
|
240
|
+
function carddsSheetHome(sheet) {
|
|
241
|
+
const states = carddsSheetStates(sheet);
|
|
242
|
+
const home = sheet.dataset.sheetHome;
|
|
243
|
+
if (home && states.includes(home)) return home;
|
|
244
|
+
const now = carddsSheetState(sheet);
|
|
245
|
+
return now !== 'peek' ? now : states.find(s => s !== 'peek') ?? 'half';
|
|
246
|
+
}
|
|
247
|
+
|
|
231
248
|
/* how far the sheet has slid, in px: its rendered top against its resting top
|
|
232
249
|
at full (the box's padding edge + its own offset). Read from geometry, not
|
|
233
250
|
from the computed translate — that keeps percentages and calcs as text. */
|
|
@@ -243,6 +260,7 @@ function carddsSheetStops(sheet) {
|
|
|
243
260
|
return {
|
|
244
261
|
full: 0,
|
|
245
262
|
half: height * (parseFloat(cs.getPropertyValue('--sheet-half')) || 0), // a share of the height
|
|
263
|
+
'3q': height * (parseFloat(cs.getPropertyValue('--sheet-3q')) || 0),
|
|
246
264
|
peek: height - (parseFloat(cs.getPropertyValue('--sheet-peek')) || 0), // a length, resolved
|
|
247
265
|
};
|
|
248
266
|
}
|
|
@@ -299,18 +317,17 @@ document.addEventListener('pointerup', carddsSheetRelease);
|
|
|
299
317
|
document.addEventListener('pointercancel', carddsSheetRelease);
|
|
300
318
|
|
|
301
319
|
/* focus in a peeked sheet: a control under the cut cannot be used, so raise
|
|
302
|
-
the sheet first — to
|
|
303
|
-
has no room for the control under the pinned head (a
|
|
304
|
-
large text size). The browser's own reveal scrolled the body against the
|
|
320
|
+
the sheet first — to its own state (the one a tap returns to) at least,
|
|
321
|
+
further if that one has no room for the control under the pinned head (a
|
|
322
|
+
small phone at a large text size). The browser's own reveal scrolled the body against the
|
|
305
323
|
peek strip before this fired; start over from the top and let the reveal,
|
|
306
324
|
with the raised state's scroll-padding, do the least. */
|
|
307
325
|
document.addEventListener('focusin', e => {
|
|
308
326
|
const sheet = e.target.closest?.('.sheet');
|
|
309
327
|
if (!sheet || !sheet.classList.contains('sheet--peek') || e.target.closest('.sheet__handle')) return;
|
|
310
|
-
const states = carddsSheetStates(sheet);
|
|
311
328
|
const body = sheet.querySelector(':scope > .sheet__body');
|
|
312
329
|
if (body) body.scrollTop = 0;
|
|
313
|
-
carddsSheetReveal(e.target,
|
|
330
|
+
carddsSheetReveal(e.target, carddsSheetHome(sheet));
|
|
314
331
|
});
|
|
315
332
|
|
|
316
333
|
/* --kb: the height of the viewport an on-screen keyboard covers. iOS keeps
|
|
@@ -377,3 +394,23 @@ document.addEventListener('scroll', e => {
|
|
|
377
394
|
clearTimeout(carddsScrollTimers.get(t));
|
|
378
395
|
carddsScrollTimers.set(t, setTimeout(() => t.classList.remove('is-scrolling'), 700));
|
|
379
396
|
}, true);
|
|
397
|
+
|
|
398
|
+
/* ---- Modal (layover.css, .sheet-stage--ask): a click on any button in the
|
|
399
|
+
drawer closes the modal by playing its open animations backwards —
|
|
400
|
+
Animation.reverse() on every animation in the card, the same keyframes, no
|
|
401
|
+
closing keyframes to keep in step — then fires "cardds:modal" on the stage
|
|
402
|
+
(bubbles, detail.answer = the button's data-answer) so the app removes the
|
|
403
|
+
stage. Reduced motion (no animations): the event fires at once. */
|
|
404
|
+
document.addEventListener('click', e => {
|
|
405
|
+
const btn = e.target.closest('.modal__drawer button');
|
|
406
|
+
if (!btn) return;
|
|
407
|
+
const stage = btn.closest('.sheet-stage--ask');
|
|
408
|
+
const modal = btn.closest('.modal');
|
|
409
|
+
if (!stage || !modal || stage.classList.contains('is-closing')) return;
|
|
410
|
+
stage.classList.add('is-closing');
|
|
411
|
+
const anims = modal.getAnimations({ subtree: true });
|
|
412
|
+
anims.forEach(a => a.reverse());
|
|
413
|
+
Promise.all(anims.map(a => a.finished)).then(() => {
|
|
414
|
+
stage.dispatchEvent(new CustomEvent('cardds:modal', { bubbles: true, detail: { answer: btn.dataset.answer ?? null } }));
|
|
415
|
+
});
|
|
416
|
+
});
|
package/css/layover.css
CHANGED
|
@@ -1,48 +1,93 @@
|
|
|
1
1
|
/* ============================================================
|
|
2
2
|
cardds/layover.css — cards that lay OVER other content:
|
|
3
|
-
ask
|
|
3
|
+
Modal (ask) · popover menu · expanding banner · float bar ·
|
|
4
4
|
callout · swipe deck. The sheet (peek / half / full / page)
|
|
5
5
|
lives in sheet.css; these compose with .sheet-stage.
|
|
6
6
|
============================================================ */
|
|
7
7
|
|
|
8
|
-
/* ---- ask
|
|
8
|
+
/* ---- ask: the Modal — the case drops in, its drawer opens below (Lh 2026-09-16: ONE card, two parts)
|
|
9
9
|
<div class="sheet-stage sheet-stage--ask">
|
|
10
|
-
<article class="card
|
|
11
|
-
|
|
12
|
-
<div class="
|
|
13
|
-
|
|
10
|
+
<article class="card modal">
|
|
11
|
+
<div class="modal__lift">…the card's content (a head, rows, a timeline)…</div>
|
|
12
|
+
<div class="modal__drawer"><h2 class="t-h2">Take the case?</h2><p class="t-caption">This cannot be undone.</p>
|
|
13
|
+
<div class="btn-trio">…</div>
|
|
14
|
+
</div>
|
|
15
|
+
</article>
|
|
16
|
+
</div>
|
|
17
|
+
.modal is the card — no border (the shadow is its edge on the scrim);
|
|
18
|
+
.modal__lift the light part that holds the thing being asked about (it
|
|
19
|
+
scrolls when tall); .modal__drawer the dark end that asks. The ground is
|
|
20
|
+
the page under a scrim (like --dim). Two beats (2 × --motion-sheet) when
|
|
21
|
+
the stage appears, and the same two backwards when it closes:
|
|
22
|
+
1. the card drops in from above the stage to the screen's centre — its
|
|
23
|
+
drawer still inside it, height 0: nothing of the ask shows yet
|
|
24
|
+
2. the drawer comes out of the card's bottom edge while the lift moves
|
|
25
|
+
up, both at once: the card grows from its centre, which never moves
|
|
26
|
+
(the stage centres it; a growing card keeps its middle where it is)
|
|
27
|
+
Closing: a click on any button in the drawer plays the same animations in
|
|
28
|
+
reverse (cardds.js: Animation.reverse() on each — nothing duplicated), then
|
|
29
|
+
fires "cardds:modal" on the .sheet-stage (bubbles, detail.answer from the
|
|
30
|
+
button's data-answer) for the app to remove the stage.
|
|
31
|
+
Reduced motion: the finished state, no beats; closing fires at once. */
|
|
14
32
|
.sheet-stage--ask {
|
|
15
33
|
--drawer-h: 14rem;
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
display: grid;
|
|
35
|
+
align-content: center; /* the card at the screen's centre — and kept there as it grows */
|
|
36
|
+
padding-inline: var(--screen-pad);
|
|
18
37
|
}
|
|
19
|
-
.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
.sheet-stage--ask::after { content: ""; position: absolute; inset: 0; background: var(--scrim); } /* the page dimmed */
|
|
39
|
+
.sheet-stage--ask > .sheet-stage__bg { filter: blur(var(--sp-2)); }
|
|
40
|
+
.modal {
|
|
41
|
+
position: relative;
|
|
42
|
+
z-index: 1; /* over the scrim */
|
|
43
|
+
max-height: calc(100cqh - 2 * var(--sp-5)); /* cqh: the stage (a size container) — a % here would be of the grid area, which is the card itself */
|
|
44
|
+
padding: 0; /* the parts pad themselves */
|
|
45
|
+
border: 0; /* no frame on the modal (Lh 2026-09-16) — its edge is the shadow */
|
|
46
|
+
display: grid;
|
|
47
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
48
|
+
overflow: clip;
|
|
26
49
|
box-shadow: var(--shadow-sheet);
|
|
50
|
+
}
|
|
51
|
+
.modal__lift {
|
|
52
|
+
padding: var(--card-pad);
|
|
53
|
+
display: grid;
|
|
54
|
+
gap: var(--card-gap);
|
|
27
55
|
align-content: start;
|
|
56
|
+
min-height: 0;
|
|
57
|
+
overflow-y: auto; /* tall content scrolls inside; the drawer stays */
|
|
58
|
+
overscroll-behavior: contain;
|
|
28
59
|
}
|
|
29
|
-
.
|
|
30
|
-
position: absolute;
|
|
31
|
-
inset-inline: 0;
|
|
32
|
-
bottom: 0;
|
|
60
|
+
.modal__drawer {
|
|
33
61
|
height: var(--drawer-h);
|
|
62
|
+
box-sizing: border-box;
|
|
34
63
|
padding: var(--sp-5);
|
|
64
|
+
overflow: clip;
|
|
35
65
|
display: grid;
|
|
36
66
|
align-content: center;
|
|
37
67
|
justify-items: center;
|
|
38
68
|
gap: var(--sp-1);
|
|
39
69
|
text-align: center;
|
|
70
|
+
background: var(--card-3-bg);
|
|
71
|
+
color: var(--card-3-ink);
|
|
40
72
|
--accent: var(--card-3-ink); /* the one solid button inverts on the dark drawer */
|
|
41
73
|
--on-accent: var(--card-3-bg);
|
|
42
74
|
}
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
.
|
|
75
|
+
.modal__drawer .t-caption { opacity: 0.7; }
|
|
76
|
+
.modal__drawer .icon-btn { background: transparent; color: inherit; border-color: var(--ink-rail); }
|
|
77
|
+
.modal__drawer .icon-btn--invert { background: var(--accent); color: var(--on-accent); border-color: transparent; width: var(--tap-lg); height: var(--tap-lg); }
|
|
78
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
79
|
+
.modal { animation: modal-drop calc(2 * var(--motion-sheet)) ease both; }
|
|
80
|
+
.modal__drawer { animation: modal-open calc(2 * var(--motion-sheet)) ease both; }
|
|
81
|
+
@keyframes modal-drop {
|
|
82
|
+
0% { translate: 0 -100cqh; } /* above the stage */
|
|
83
|
+
50%, 100% { translate: 0 0; } /* 1. at the centre — and it stays: beat 2 is the drawer's */
|
|
84
|
+
}
|
|
85
|
+
@keyframes modal-open {
|
|
86
|
+
0%, 50% { height: 0; padding-block: 0; } /* inside the card */
|
|
87
|
+
100% { height: var(--drawer-h); padding-block: var(--sp-5); } /* 2. out below, the lift up, together */
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
46
91
|
.btn-trio { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
|
|
47
92
|
.btn-trio__dots {
|
|
48
93
|
width: var(--sp-5);
|
package/css/sheet.css
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/* ============================================================
|
|
2
2
|
cardds/sheet.css — sheet single: one card as a bottom sheet.
|
|
3
|
-
|
|
3
|
+
Four states: .sheet--peek · .sheet--half · .sheet--3q · .sheet--full
|
|
4
4
|
|
|
5
5
|
A sheet is rigid material, exactly like a sheet in .sheet-stack--tap
|
|
6
6
|
(stack.css): it is ALWAYS the full height of the box that holds it —
|
|
7
7
|
set once by inset: 0, never written again, never transitioned — and
|
|
8
8
|
a state is nothing but how far that full card has slid up. Peek is
|
|
9
9
|
a full sheet that slid down until only its handle and title show;
|
|
10
|
-
half is one that slid down by half of itself;
|
|
11
|
-
not slid at all. The box clips whatever slid below its bottom edge,
|
|
10
|
+
half is one that slid down by half of itself; 3q (three quarters,
|
|
11
|
+
Lh 2026-09-16) by a quarter; full is one that has not slid at all. The box clips whatever slid below its bottom edge,
|
|
12
12
|
so a sheet at any height is still a card: rounded top, a straight
|
|
13
13
|
cut at the box's edge, its content scrolling inside.
|
|
14
14
|
|
|
@@ -28,7 +28,11 @@
|
|
|
28
28
|
page. Many sheets stacked as the app's menu are the other system,
|
|
29
29
|
.sheet-stack (stack.css): the same material, its own class and file.
|
|
30
30
|
|
|
31
|
-
<article class="card sheet sheet--half" data-sheet-states="peek half full">
|
|
31
|
+
<article class="card sheet sheet--half" data-sheet-states="peek half 3q full">
|
|
32
|
+
A tap on the handle TOGGLES the sheet between its own state and peek
|
|
33
|
+
(Lh 2026-09-16) — never a step-by-step climb; a drag snaps to any of
|
|
34
|
+
the listed states, and the state it lands on becomes the one a tap
|
|
35
|
+
returns to.
|
|
32
36
|
<button class="sheet__handle" type="button" aria-label="…"></button>
|
|
33
37
|
<div class="card__head"><h2 class="t-h2">…</h2>…</div>
|
|
34
38
|
<div class="sheet__body">…controls, or small content cards…</div>
|
|
@@ -51,6 +55,7 @@
|
|
|
51
55
|
retune it: .my-sheet { --sheet-peek: … } */
|
|
52
56
|
--sheet-peek: calc(var(--card-pad) + var(--_handle) + 2 * var(--card-gap) + var(--tap));
|
|
53
57
|
--sheet-half: 0.5; /* a share of the sheet's own height */
|
|
58
|
+
--sheet-3q: 0.25; /* three quarters showing: slid by a quarter */
|
|
54
59
|
/* the sheet's own height, seen from inside it: the box it slides in is the
|
|
55
60
|
size container (.step, .sheet-stage), so 100cqh is that box, less what
|
|
56
61
|
the sheet gives up at its top (--_top) and bottom (--_lift). The sheet
|
|
@@ -81,6 +86,7 @@
|
|
|
81
86
|
}
|
|
82
87
|
.sheet--full { --_y: 0%; --_cover: 0rem; }
|
|
83
88
|
.sheet--half { --_y: calc(var(--sheet-half) * 100%); --_cover: calc(var(--sheet-half) * var(--_box)); }
|
|
89
|
+
.sheet--3q { --_y: calc(var(--sheet-3q) * 100%); --_cover: calc(var(--sheet-3q) * var(--_box)); }
|
|
84
90
|
.sheet--peek { --_y: calc(100% - var(--sheet-peek)); --_cover: calc(var(--_box) - var(--sheet-peek)); }
|
|
85
91
|
.sheet.is-dragging { transition: none; } /* follows the finger; the snap gets the ease back */
|
|
86
92
|
|
package/css/step.css
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* ============================================================
|
|
2
2
|
cardds/step.css — .step: one screen, one task.
|
|
3
|
-
What the member reads (a card), what the member does when the
|
|
3
|
+
What the member reads (words on the base — not a card, Lh 2026-09-16), what the member does when the
|
|
4
4
|
step asks for it (a sheet, sheet.css), and the screen's one
|
|
5
5
|
move (.action-bar, actions.css) OUTSIDE the step, below it.
|
|
6
6
|
|
|
7
7
|
<section class="step">
|
|
8
|
-
<article class="
|
|
8
|
+
<article class="step__card">…the words…</article> ← first
|
|
9
9
|
<article class="card sheet sheet--half">…the controls…</article> ← last, optional
|
|
10
10
|
</section>
|
|
11
11
|
<div class="action-bar">…</div>
|
|
@@ -59,10 +59,19 @@
|
|
|
59
59
|
frame — the bar is fixed to the viewport wherever it sits. */
|
|
60
60
|
:has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
|
|
61
61
|
|
|
62
|
-
/* the reading
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
/* the reading block: the words on the base — no frame, no area colour (it was
|
|
63
|
+
a .card until 2026-09-16; Lh: a step reads like a page, not a card in a
|
|
64
|
+
page). It keeps a card's inner rhythm (a head, --card-gap between things,
|
|
65
|
+
the tokens .card publishes for the head and .bleed) and the flex rule: it
|
|
66
|
+
hugs its content, and when the column runs out (a short phone, large text)
|
|
67
|
+
it shrinks and scrolls inside itself instead of pushing the bar or the sheet */
|
|
65
68
|
.step__card {
|
|
69
|
+
--card-pad: 0rem; /* published for .bleed: nothing to cancel */
|
|
70
|
+
--card-gap: var(--sp-4);
|
|
71
|
+
display: grid;
|
|
72
|
+
gap: var(--card-gap);
|
|
73
|
+
align-content: start;
|
|
74
|
+
color: var(--on-surface);
|
|
66
75
|
flex: 0 1 auto;
|
|
67
76
|
min-height: 0;
|
|
68
77
|
margin: var(--_lead, 0) var(--screen-pad) 0; /* the screen's gap, moved in here from above the step; the inset, taken back from the bleed */
|
package/css/tokens.css
CHANGED
|
@@ -176,3 +176,4 @@
|
|
|
176
176
|
============================================================ */
|
|
177
177
|
@property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
178
178
|
@property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
|
|
179
|
+
@property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
|
package/dist/actions/Btn.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createElement } from 'react';
|
|
2
|
-
import { cx } from '../cx';
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
3
|
/**
|
|
4
4
|
* Btn — the pill button (48px+ tall). Plain by default; `primary` for the one move; `quiet` on tinted cards.
|
|
5
5
|
* In a CardFoot up to four share the line; in an ActionBar it takes the width. An Icon xs may lead or trail the label.
|
package/dist/actions/BtnRow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cx } from '../cx';
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
3
|
/** BtnRow — twin CTAs inside a card's content sharing the width (Log time · Offer). In a foot use CardFoot instead. */
|
|
4
4
|
export function BtnRow({ className, ...rest }) {
|
|
5
5
|
return _jsx("div", { className: cx('btn-row', className), ...rest });
|
package/dist/actions/Chip.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createElement } from 'react';
|
|
3
|
-
import { cx } from '../cx';
|
|
4
|
-
import { Dot } from './Dot';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { Dot } from './Dot.js';
|
|
5
5
|
/** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `on` when it is the live one. For pickers use ChipToggle / ChipPick. */
|
|
6
6
|
export function Chip({ on, dot, bubble, as, href, className, children, ...rest }) {
|
|
7
7
|
const el = as ?? (href ? 'a' : 'span');
|
package/dist/actions/Dot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cx } from '../cx';
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
3
|
/** Dot — the signal dot in chips, legends and row metas. */
|
|
4
4
|
export function Dot({ b, className, ...rest }) {
|
|
5
5
|
return _jsx("span", { className: cx('dot', b && 'dot--b', className), ...rest });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
-
import { type IconName } from '../type/Icon';
|
|
2
|
+
import { type IconName } from '../type/Icon.js';
|
|
3
3
|
export interface DropdownProps extends ComponentPropsWithoutRef<'div'> {
|
|
4
4
|
/** unique id — the popover menu's id the trigger targets */
|
|
5
5
|
id: string;
|
package/dist/actions/Dropdown.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cx } from '../cx';
|
|
3
|
-
import { Icon } from '../type/Icon';
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
import { Icon } from '../type/Icon.js';
|
|
4
4
|
/**
|
|
5
5
|
* Dropdown — THE select: a pill trigger + a Menu popover anchored under it (native popover: light dismiss, Esc).
|
|
6
6
|
* Never a native `<select>`. Children are MenuItems; cardds.js copies the picked item's label into the pill
|
package/dist/actions/Fab.d.ts
CHANGED
package/dist/actions/Fab.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cx } from '../cx';
|
|
3
|
-
import { Icon } from '../type/Icon';
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
import { Icon } from '../type/Icon.js';
|
|
4
4
|
/** Fab — the square floating action at the bottom-right of a stage or screen (above the bar). */
|
|
5
5
|
export function Fab({ label, icon = 'plus', invert = true, className, children, ...rest }) {
|
|
6
6
|
return (_jsx("button", { type: "button", "aria-label": label, className: cx('icon-btn fab', invert && 'icon-btn--invert', className), ...rest, children: children ?? _jsx(Icon, { name: icon }) }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
|
2
|
-
import { type IconName } from '../type/Icon';
|
|
2
|
+
import { type IconName } from '../type/Icon.js';
|
|
3
3
|
export interface IconBtnProps extends ComponentPropsWithoutRef<'button'> {
|
|
4
4
|
/** the accessible name — required, the button has no text */
|
|
5
5
|
label: string;
|
package/dist/actions/IconBtn.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createElement } from 'react';
|
|
3
|
-
import { Menu } from './Dropdown';
|
|
4
|
-
import { cx } from '../cx';
|
|
5
|
-
import { Icon } from '../type/Icon';
|
|
3
|
+
import { Menu } from './Dropdown.js';
|
|
4
|
+
import { cx } from '../cx.js';
|
|
5
|
+
import { Icon } from '../type/Icon.js';
|
|
6
6
|
/** IconBtn — the round icon button: `sm` in heads and bars, full size in rows and composers, `invert` for the one filled move.
|
|
7
7
|
* With `menu` it gives the member options the way a Dropdown does — an actions Menu (edit, share, remove…) for the card or sheet it sits in. */
|
|
8
8
|
export function IconBtn({ label, icon, sm, invert, href, menu, menuId, menuStart, className, type, children, ...rest }) {
|
package/dist/actions/Link.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
-
import { type IconName } from '../type/Icon';
|
|
2
|
+
import { type IconName } from '../type/Icon.js';
|
|
3
3
|
export interface LinkProps extends ComponentPropsWithoutRef<'a'> {
|
|
4
4
|
/** muted ink (Skip, "didn't get the code?") */
|
|
5
5
|
muted?: boolean;
|
package/dist/actions/Link.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createElement } from 'react';
|
|
3
|
-
import { cx } from '../cx';
|
|
4
|
-
import { Icon } from '../type/Icon';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { Icon } from '../type/Icon.js';
|
|
5
5
|
/** Link — an inline text action big enough to tap (40px): "See all ↗" in a card head, "Skip" between pager buttons. */
|
|
6
6
|
export function Link({ muted, icon, href, className, children, ...rest }) {
|
|
7
7
|
return createElement(href ? 'a' : 'button', { href, type: href ? undefined : 'button', className: cx('link', muted && 'link--muted', className), ...rest }, children, icon && _jsx(Icon, { name: icon, size: "xs" }));
|
package/dist/actions/Segment.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cx } from '../cx';
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
3
|
/** Segment — a tab strip / day picker whose items share the width; children are SegmentItems. */
|
|
4
4
|
export function Segment({ className, ...rest }) {
|
|
5
5
|
return _jsx("div", { className: cx('segment', className), role: "tablist", ...rest });
|