@cahyo-dimas/freeday 1.16.0 → 1.18.0
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 +69 -0
- package/README.id.md +28 -8
- package/README.md +28 -8
- package/USAGE.md +104 -0
- package/adapters/react/components/FdyTable.tsx +11 -0
- package/adapters/vue/components/FdyDrawer.vue +5 -2
- package/adapters/vue/components/FdyModal.vue +5 -2
- package/adapters/vue/components/FdyTable.vue +12 -0
- package/dist/freeday-toast.js +22 -1
- package/dist/freeday.bundle.css +104 -0
- package/dist/freeday.css +92 -0
- package/dist/freeday.d.ts +5 -0
- package/dist/freeday.js +22 -1
- package/dist/freeday.tokens.css +12 -0
- package/package.json +3 -1
- package/src/base.css +5 -0
- package/src/components/avatar.css +13 -0
- package/src/components/card.css +7 -0
- package/src/components/chip.css +12 -0
- package/src/components/composition.css +49 -0
- package/src/components/skeleton.css +5 -0
- package/src/freeday-toast.js +22 -1
- package/tokens/tokens.json +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,75 @@
|
|
|
3
3
|
Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); tiap versi = git tag.
|
|
5
5
|
|
|
6
|
+
## [1.18.0] — 2026-08-11
|
|
7
|
+
### Fixed
|
|
8
|
+
- **`FdyModal` / `FdyDrawer` (Vue) were non-dismissible when `dismissible` was omitted — an
|
|
9
|
+
accessibility defect.** Vue's boolean-cast delivers an omitted Boolean prop as `false`, not
|
|
10
|
+
`undefined`, so the defensive `props.dismissible !== false` evaluated to `false`: no Escape, no
|
|
11
|
+
backdrop close, and (modal) no close button rendered — the opposite of the documented
|
|
12
|
+
`dismissible: true` default. Both now use `withDefaults(…, { dismissible: true })`. React was
|
|
13
|
+
unaffected (it leaves omitted props `undefined`); `FdyCombo`/`FdyDatepicker` use `=== true`, which
|
|
14
|
+
is correct for an intended-`false` default.
|
|
15
|
+
- **`.fdy-card--interactive` hover transform now respects reduced motion** — the `-3px` lift is dropped
|
|
16
|
+
under `prefers-reduced-motion` (the shadow still signals the affordance).
|
|
17
|
+
### Added
|
|
18
|
+
- **Page-composition primitives + type roles (`src/components/composition.css`).** `.fdy-page`,
|
|
19
|
+
`.fdy-page__header`, `.fdy-page-section`, `.fdy-toolbar`, `.fdy-stats`/`.fdy-stat` (a KPI tile that is
|
|
20
|
+
deliberately *not* a card, so a metric strip doesn't become an identical-card grid), and three title
|
|
21
|
+
roles `.fdy-title-page` / `-section` / `-card` (+ `.fdy-eyebrow`, `.fdy-text-muted`/`-caption`). They
|
|
22
|
+
encode *how a page is assembled* so independently-built screens cohere.
|
|
23
|
+
- **`USAGE.md` — the usage doctrine.** Which token/role/shadow to use when: type roles, spacing rhythm,
|
|
24
|
+
elevation, one-primary-per-screen, semantic-vs-categorical colour, density, and the shell-down
|
|
25
|
+
composition order. The line where a component library becomes a design system.
|
|
26
|
+
- **General categorical palette `--tone-1`…`--tone-8`** (a chart-neutral alias of the validated,
|
|
27
|
+
theme-aware `--chart-1`…`8`) + **`.fdy-chip--tone-N`** to match `.fdy-avatar--tone-N` — non-semantic
|
|
28
|
+
category colours (chips, tags, legends), WCAG AA in light & dark (gated).
|
|
29
|
+
- **`FdyTable` surfaces its processed rows** — a `process` event (Vue) / `onProcess` callback (React),
|
|
30
|
+
`{ rows, total }`, in **both** client and server modes — so the same filtered/sorted/paged set can
|
|
31
|
+
drive a responsive card list, a selection summary, or CSV export without re-deriving the pipeline.
|
|
32
|
+
- **`./table-model` export.** The pure `filterRows`/`sortRows`/`paginate`/`cellValue`/… functions
|
|
33
|
+
(`adapters/core/table-model.js`) are now reachable as `@cahyo-dimas/freeday/table-model`, so a consumer
|
|
34
|
+
can pre-compute exactly what `FdyTable` does instead of re-deriving it and risking drift.
|
|
35
|
+
### Changed
|
|
36
|
+
- **`data-density="compact"` now steps the mid-range spacing scale** (`--space-3`…`--space-6`) as well as
|
|
37
|
+
`--control-h`, so Freeday components — and a utility theme built on `var(--space-N)` — actually densify;
|
|
38
|
+
previously it changed only control height.
|
|
39
|
+
- **Docs:** getting-started leads with the `.fdy-app` shell + a loud "load the fonts yourself" step (the
|
|
40
|
+
package names Sora / IBM Plex Sans / JetBrains Mono but bundles none); README links `USAGE.md`.
|
|
41
|
+
|
|
42
|
+
Additive except the two bug fixes (which restore documented behaviour). Gate: `node --test` 22/22 ·
|
|
43
|
+
`typecheck:react` 0 · `test:browser` 6/6 · new CSS browser-verified in real Chrome. From real-app
|
|
44
|
+
consumption feedback (three instalments).
|
|
45
|
+
|
|
46
|
+
## [1.17.0] — 2026-08-11
|
|
47
|
+
### Added
|
|
48
|
+
- **Avatar identity tones (`--tone-1`…`--tone-8`).** Decorative tints (from the categorical chart
|
|
49
|
+
palette) with a theme-aware, text-leaning foreground, so same-initial avatars — common where many
|
|
50
|
+
names share a prefix — stay distinguishable. Verified WCAG **AA** (≥4.5:1) in light **and** dark,
|
|
51
|
+
guarded by `test/contrast.test.mjs` (which gained a `color-mix()` evaluator). Hash a stable index
|
|
52
|
+
off the full name, not the initials.
|
|
53
|
+
- **Size-matched skeletons (`.fdy-skeleton--avatar` + `--avatar-sm`/`--avatar-lg`).** Reserve the exact
|
|
54
|
+
`.fdy-avatar` box (2 / 2.5 / 3.5rem) so data landing causes no layout shift (a bare `--circle` has no
|
|
55
|
+
dimensions of its own).
|
|
56
|
+
- **`.fdy-card--button`.** When the whole card *is* the control — a real `<button>` picker row, so it is
|
|
57
|
+
keyboard-reachable and announced — this resets the UA button box **without** stripping the card's own
|
|
58
|
+
surface/border.
|
|
59
|
+
- **`.fdy-list-reset`.** Opt-in list reset for consumers who pair Freeday with a utility framework run
|
|
60
|
+
preflight-off (`base.css` is intentionally a *light* reset — it does not strip `ul`/`ol`/`p` margins).
|
|
61
|
+
- **Toast `key` + `Freeday.dismiss()`.** `toast({ …, key })` replaces an existing same-key toast in
|
|
62
|
+
place instead of stacking a duplicate (a burst of identical failures shows one, refreshed);
|
|
63
|
+
`Freeday.dismiss(node | key)` closes one early. `toast()` already returned the element — both are now
|
|
64
|
+
in the `window.Freeday` type declarations.
|
|
65
|
+
### Docs
|
|
66
|
+
- State the scope boundary (components + tokens, **not** layout; pair with a utility framework run
|
|
67
|
+
preflight-off) in the README and getting-started; document that `--space-*`/`--radius-*` are public
|
|
68
|
+
tokens to build a utility theme on, and that `data-density` only re-scales control height.
|
|
69
|
+
- Input docs now lead with `aria-invalid="true"` as the canonical accessible-field pattern (it does
|
|
70
|
+
both the visual state and the one screen readers act on); `.fdy-input--error` is the fallback.
|
|
71
|
+
|
|
72
|
+
Additive; no breaking changes. Gate: `node --test` 22/22 (incl. avatar-tone contrast, both themes) ·
|
|
73
|
+
`typecheck:react` 0 · new CSS/JS browser-verified in real Chrome. From real-app consumption feedback.
|
|
74
|
+
|
|
6
75
|
## [1.16.0] — 2026-08-11
|
|
7
76
|
### Added
|
|
8
77
|
- **Native Blazor component library (`adapters/blazor/`) — first release, 10/10 parity.** A Razor
|
package/README.id.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Lebih banyak _free day_ buat dev — UI kit-nya sudah siap pakai.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.18.0)
|
|
9
9
|
|
|
10
10
|
UI KIT yang token-driven & framework-agnostic — satu sumber kebenaran untuk warna, tipografi,
|
|
11
11
|
spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
|
@@ -49,6 +49,24 @@ ter-publish → install tanpa build step; minify diserahkan ke bundler konsumen.
|
|
|
49
49
|
Kelas komponen berprefix `fdy-` (mis. `fdy-btn`, `fdy-card`, `fdy-badge`). Pakai langsung di
|
|
50
50
|
markup framework apa pun — Vue, React, Blazor, atau HTML polos.
|
|
51
51
|
|
|
52
|
+
> **⚠️ Muat font-nya sendiri — paket ini tidak.** Token tipe menamai **Sora** / **IBM Plex Sans** /
|
|
53
|
+
> **JetBrains Mono** tapi Freeday tak membundel file font. Muat (mis. `@import '@fontsource/sora/700.css'`
|
|
54
|
+
> …) atau override `--font-display`/`--font-body`/`--font-mono` — kalau tidak, kit jatuh ke fallback
|
|
55
|
+
> sistem dan terlihat "belum jadi". Detail: [`docs/getting-started.md`](docs/getting-started.md).
|
|
56
|
+
|
|
57
|
+
> **Token/role mana dipakai kapan → [`USAGE.md`](USAGE.md).** Freeday menjaga konsistensi *nilai*;
|
|
58
|
+
> `USAGE.md` = doktrin yang menjaga konsistensi *keputusan* — role tipe (`.fdy-title-page/-section/-card`),
|
|
59
|
+
> ritme spasi, elevasi, satu-primary-per-layar, palet kategorikal `--tone-1…8`, dan primitif komposisi
|
|
60
|
+
> halaman (`.fdy-page`, `.fdy-page-section`, `.fdy-stats`). Tiap app mulai di `.fdy-app`.
|
|
61
|
+
|
|
62
|
+
> **Scope: komponen + token, bukan layout.** Freeday punya komponen & design token; helper layout
|
|
63
|
+
> cuma `.fdy-hidden` / `.fdy-visually-hidden` plus primitif komposisi di atas. Layout grid dari layer-mu sendiri —
|
|
64
|
+
> pasangkan dengan utility framework (Tailwind, UnoCSS…) mode **utilities-only, preflight OFF**
|
|
65
|
+
> (`base.css` Freeday = reset-nya). `base.css` itu reset *ringan* (tak me-reset margin `ul`/`ol`/`p` —
|
|
66
|
+
> pakai `.fdy-list-reset` atau komponen list Freeday), dan skala spacing/radius/durasi adalah custom
|
|
67
|
+
> property publik (`--space-0`…`--space-24`, …) yang bisa jadi dasar theme utility-mu. Detail:
|
|
68
|
+
> [`docs/getting-started.md` §Core concepts](docs/getting-started.md).
|
|
69
|
+
|
|
52
70
|
> `.fdy-btn` **sudah** tombol primary — tak ada modifier `.fdy-btn--primary` terpisah.
|
|
53
71
|
> Modifier untuk varian lain: `--ghost`, `--danger`, `--text`, `--sm`, `--lg`, `--icon`
|
|
54
72
|
> (lihat `docs/index.html`).
|
|
@@ -126,17 +144,19 @@ import { useFreeday } from '@cahyo-dimas/freeday/react';
|
|
|
126
144
|
const root = useRef<HTMLDivElement>(null);
|
|
127
145
|
useFreeday(root); // event fdy-* bubbling → listen di root
|
|
128
146
|
```
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
```razor
|
|
148
|
+
@* Blazor — RCL Freeday.Blazor (net8.0): komponen native typed <FdyX> dengan @bind, 10/10 parity *@
|
|
149
|
+
@using Freeday.Blazor
|
|
150
|
+
<FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl" />
|
|
151
|
+
<FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)" PageSize="10" />
|
|
152
|
+
@* Fallback untuk markup mentah: interop window.FreedayBlazor.initAll / .on *@
|
|
133
153
|
```
|
|
134
154
|
|
|
135
155
|
| Framework | Adapter | Contoh jalan |
|
|
136
156
|
|---|---|---|
|
|
137
|
-
| Vue 3 | `@cahyo-dimas/freeday/vue` | [`examples/vue-faktur/`](examples/vue-faktur/) (`npm install && npm run dev`) |
|
|
138
|
-
| React 19 | `@cahyo-dimas/freeday/react` | [`examples/react-faktur/`](examples/react-faktur/) (`npm install && npm run dev`) |
|
|
139
|
-
| Blazor WASM (.
|
|
157
|
+
| Vue 3 | `@cahyo-dimas/freeday/vue` (`useFreeday` + komponen `v-model`) | [`examples/vue-faktur/`](examples/vue-faktur/) (`npm install && npm run dev`) |
|
|
158
|
+
| React 19 | `@cahyo-dimas/freeday/react` (`useFreeday` + komponen `value`/`onChange`) | [`examples/react-faktur/`](examples/react-faktur/) (`npm install && npm run dev`) |
|
|
159
|
+
| Blazor WASM (net8.0) | RCL `Freeday.Blazor` (`<FdyX>` + `@bind`) · interop `/blazor` | [`examples/blazor-faktur/`](examples/blazor-faktur/) (`dotnet run`) |
|
|
140
160
|
|
|
141
161
|
Peta library & pola lengkap: [`docs/integrations.md`](docs/integrations.md).
|
|
142
162
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **More free days for devs — the UI kit is ready to use.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.18.0)
|
|
9
9
|
|
|
10
10
|
A token-driven, framework-agnostic UI kit — one source of truth for color, typography,
|
|
11
11
|
spacing, and components. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
|
@@ -49,6 +49,24 @@ bundler. Because it's on **public npm**, `npm ci` runs in CI without auth or an
|
|
|
49
49
|
Component classes are prefixed `fdy-` (e.g. `fdy-btn`, `fdy-card`, `fdy-badge`). Use them directly
|
|
50
50
|
in any framework's markup — Vue, React, Blazor, or plain HTML.
|
|
51
51
|
|
|
52
|
+
> **⚠️ Load the fonts — the package doesn't.** The type tokens name **Sora** / **IBM Plex Sans** /
|
|
53
|
+
> **JetBrains Mono** but Freeday bundles no font files. Load them (e.g. `@import '@fontsource/sora/700.css'`
|
|
54
|
+
> …) or override `--font-display`/`--font-body`/`--font-mono` — otherwise the kit renders in the system
|
|
55
|
+
> fallback and looks unfinished. See [`docs/getting-started.md` §Core concepts](docs/getting-started.md).
|
|
56
|
+
|
|
57
|
+
> **Which token/role to use when → [`USAGE.md`](USAGE.md).** Freeday enforces consistent *values*;
|
|
58
|
+
> `USAGE.md` is the doctrine that also makes *decisions* consistent — type roles (`.fdy-title-page/-section/-card`),
|
|
59
|
+
> spacing rhythm, elevation, one-primary-per-screen, the `--tone-1…8` categorical palette, and the page-
|
|
60
|
+
> composition primitives (`.fdy-page`, `.fdy-page-section`, `.fdy-stats`). Every app starts in `.fdy-app`.
|
|
61
|
+
|
|
62
|
+
> **Scope: components + tokens, not layout.** Freeday owns components and design tokens; the only
|
|
63
|
+
> layout helpers are `.fdy-hidden` / `.fdy-visually-hidden` plus the page-composition primitives above.
|
|
64
|
+
> Bring your own grid layer — pair it with a utility framework (Tailwind, UnoCSS…) run **utilities-only
|
|
65
|
+
> with preflight OFF** (Freeday's `base.css` is the reset). `base.css` is a *light* reset (it doesn't
|
|
66
|
+
> strip `ul`/`ol`/`p` margins — use `.fdy-list-reset` or a Freeday list component), and the
|
|
67
|
+
> spacing/radius/duration scales are public custom properties (`--space-0`…`--space-24`, …) you can
|
|
68
|
+
> build your utility theme on. Full notes: [`docs/getting-started.md` §Core concepts](docs/getting-started.md).
|
|
69
|
+
|
|
52
70
|
> `.fdy-btn` is **already** the primary button — there's no separate `.fdy-btn--primary` modifier.
|
|
53
71
|
> Modifiers for other variants: `--ghost`, `--danger`, `--text`, `--sm`, `--lg`, `--icon`
|
|
54
72
|
> (see `docs/index.html`).
|
|
@@ -126,17 +144,19 @@ import { useFreeday } from '@cahyo-dimas/freeday/react';
|
|
|
126
144
|
const root = useRef<HTMLDivElement>(null);
|
|
127
145
|
useFreeday(root); // fdy-* events bubble → listen at the root
|
|
128
146
|
```
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
```razor
|
|
148
|
+
@* Blazor — Freeday.Blazor RCL (net8.0): native typed <FdyX> with @bind, 10/10 parity *@
|
|
149
|
+
@using Freeday.Blazor
|
|
150
|
+
<FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl" />
|
|
151
|
+
<FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)" PageSize="10" />
|
|
152
|
+
@* Fallback for raw markup: window.FreedayBlazor.initAll / .on interop *@
|
|
133
153
|
```
|
|
134
154
|
|
|
135
155
|
| Framework | Adapter | Working example |
|
|
136
156
|
|---|---|---|
|
|
137
|
-
| Vue 3 | `@cahyo-dimas/freeday/vue` | [`examples/vue-faktur/`](examples/vue-faktur/) (`npm install && npm run dev`) |
|
|
138
|
-
| React 19 | `@cahyo-dimas/freeday/react` | [`examples/react-faktur/`](examples/react-faktur/) (`npm install && npm run dev`) |
|
|
139
|
-
| Blazor WASM (.
|
|
157
|
+
| Vue 3 | `@cahyo-dimas/freeday/vue` (`useFreeday` + `v-model` components) | [`examples/vue-faktur/`](examples/vue-faktur/) (`npm install && npm run dev`) |
|
|
158
|
+
| React 19 | `@cahyo-dimas/freeday/react` (`useFreeday` + `value`/`onChange` components) | [`examples/react-faktur/`](examples/react-faktur/) (`npm install && npm run dev`) |
|
|
159
|
+
| Blazor WASM (net8.0) | `Freeday.Blazor` RCL (`<FdyX>` + `@bind`) · `/blazor` interop | [`examples/blazor-faktur/`](examples/blazor-faktur/) (`dotnet run`) |
|
|
140
160
|
|
|
141
161
|
Full library map and patterns: [`docs/integrations.md`](docs/integrations.md).
|
|
142
162
|
|
package/USAGE.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Freeday — Usage doctrine
|
|
2
|
+
|
|
3
|
+
Freeday ships tokens and components. This file ships the **decisions** — which token to use when,
|
|
4
|
+
how a page is assembled, what earns emphasis. A component library enforces consistent *values*; a
|
|
5
|
+
design system also enforces consistent *decisions*. Skim this once before building a screen; it is
|
|
6
|
+
what makes screens built by different people (or different sessions) look like one product.
|
|
7
|
+
|
|
8
|
+
The rules are opinionated on purpose. When one conflicts with a real need, break it deliberately —
|
|
9
|
+
but start here, not from a blank page.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Type roles — three title levels, not eleven
|
|
14
|
+
|
|
15
|
+
Do **not** reuse `.fdy-card__title` for a page title. Collapsing page/section/card into one style is
|
|
16
|
+
the single biggest cause of "flat grey mush". Pick the role, not the size:
|
|
17
|
+
|
|
18
|
+
| Role | Class | When |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Eyebrow | `.fdy-eyebrow` | A small uppercase label above a page/section title. Optional. |
|
|
21
|
+
| Page title | `.fdy-title-page` | One per screen. The `<h1>`. |
|
|
22
|
+
| Section title | `.fdy-title-section` | A region within the page (`<h2>`). |
|
|
23
|
+
| Card / row title | `.fdy-title-card` (or `.fdy-card__title`) | A title inside a card or list row (`<h3>`). |
|
|
24
|
+
| Body | *(default)* | Running text. |
|
|
25
|
+
| Muted / caption | `.fdy-text-muted`, `.fdy-text-caption` | Secondary text, timestamps, help. |
|
|
26
|
+
|
|
27
|
+
Everything else is body. If you reach for a fourth title size, you probably need a section, not a
|
|
28
|
+
font size.
|
|
29
|
+
|
|
30
|
+
## 2. Spacing rhythm — three gaps, always from the scale
|
|
31
|
+
|
|
32
|
+
Never a loose value; always `var(--space-N)` (a 4px scale). Three rhythms carry most layouts, and the
|
|
33
|
+
composition primitives apply them for you:
|
|
34
|
+
|
|
35
|
+
- **Between page sections:** `--space-8` — `.fdy-page` sets this gap between its children.
|
|
36
|
+
- **Within a group** (heading ↔ its body, cards in a list): `--space-4` — `.fdy-page-section` sets it.
|
|
37
|
+
- **Inside a control/card** (label ↔ input, icon ↔ text): `--space-2` / `--space-3`.
|
|
38
|
+
|
|
39
|
+
Reserve `--space-1` for hairline pairs and `--space-10`+ for deliberate breathing room (a hero, an
|
|
40
|
+
empty state). Don't scatter `--space-6` everywhere — a page with one gap value has no rhythm.
|
|
41
|
+
|
|
42
|
+
## 3. Elevation — most surfaces are flat
|
|
43
|
+
|
|
44
|
+
Shadow is a signal, not decoration. Spend it sparingly:
|
|
45
|
+
|
|
46
|
+
- **Flat** (no shadow, border only, or nothing): the default. Page sections, list rows, the app shell.
|
|
47
|
+
- **`--shadow-1`:** a card that is a distinct object you could pick up — a workspace tile, a panel.
|
|
48
|
+
- **`--shadow-2`/`3`:** something that *floats over* the page — a popover, menu, or dropdown.
|
|
49
|
+
- **`--shadow-4`:** a modal / drawer overlay only.
|
|
50
|
+
|
|
51
|
+
If every box on the screen has the same card shadow, none of them read as special — that's the
|
|
52
|
+
"identical card grid" failure. Prefer flat sections with **one** raised element that matters.
|
|
53
|
+
|
|
54
|
+
## 4. Emphasis — exactly one primary per screen
|
|
55
|
+
|
|
56
|
+
`.fdy-btn` is *already* the primary action (there is no `--primary` modifier). Use it **once** per
|
|
57
|
+
screen — the one thing you want the user to do. Everything else is `--ghost` or `--text`. Two primary
|
|
58
|
+
buttons on a screen means neither is. The same rule governs colour fills and `--shadow`: one focal
|
|
59
|
+
point, everything around it quiet.
|
|
60
|
+
|
|
61
|
+
## 5. Colour — semantic is reserved; categorical is `--tone`
|
|
62
|
+
|
|
63
|
+
- **Accent** (`--color-primary`, and `--color-accent` sparingly): interactive + brand. This is your
|
|
64
|
+
one accent hue.
|
|
65
|
+
- **Semantic is reserved** and is *not* your accent: `--color-success` = good only, `--color-warning`
|
|
66
|
+
= caution only, `--color-danger` = destructive/error only. Never decorative. Encode state in a pill
|
|
67
|
+
or chip, not just colour.
|
|
68
|
+
- **Categorical** (`--tone-1` … `--tone-8`, the general alias of the validated chart palette): N
|
|
69
|
+
visually-distinct **non-semantic** colours — avatar tones, category chips, tags, legend swatches.
|
|
70
|
+
Use the modifiers `.fdy-avatar--tone-N` / `.fdy-chip--tone-N` (both stay WCAG AA in light & dark),
|
|
71
|
+
and hash a stable index off the full string so the same category always gets the same colour.
|
|
72
|
+
- **Surfaces:** most backgrounds are `--color-surface`; `--color-surface-2`/`-3` for a recessed area;
|
|
73
|
+
`--color-primary-soft` only when you want a tinted callout, not as a default panel colour.
|
|
74
|
+
|
|
75
|
+
## 6. Density — `compact` for data-dense screens
|
|
76
|
+
|
|
77
|
+
`data-density="compact"` on `<html>` tightens control height **and** the mid-range spacing scale
|
|
78
|
+
(`--space-3`…`--space-6` step down a notch), so cards, toolbars and tables get denser. Use it on
|
|
79
|
+
table-heavy back-office screens; leave `comfortable` (the default) for forms and marketing-adjacent
|
|
80
|
+
pages. Set it once at the app root, not per component.
|
|
81
|
+
|
|
82
|
+
## 7. Assemble the page from the frame down
|
|
83
|
+
|
|
84
|
+
1. **Shell:** every application starts inside **`.fdy-app`** (`__topbar`, `__sidebar`, `__main`,
|
|
85
|
+
`__content`, `__navtoggle`, `__backdrop`). Don't hand-roll a shell from flexbox — the toggle and
|
|
86
|
+
backdrop plumbing are already there. See `docs/getting-started.md`.
|
|
87
|
+
2. **Page:** wrap the screen body in **`.fdy-page`** (vertical section rhythm), opening with a
|
|
88
|
+
**`.fdy-page__header`** (eyebrow + `.fdy-title-page` + `.fdy-page__desc` on the left, the one
|
|
89
|
+
primary action on the right).
|
|
90
|
+
3. **Sections:** each region is a **`.fdy-page-section`** (a `.fdy-title-section` + optional
|
|
91
|
+
`.fdy-toolbar`, then its body).
|
|
92
|
+
4. **KPIs:** a **`.fdy-stats`** grid of **`.fdy-stat`** tiles — deliberately *not* cards, so a metric
|
|
93
|
+
strip doesn't become an identical-card grid. Wrap in `.fdy-stats--boxed` for one shared strip.
|
|
94
|
+
5. **Content:** components (`.fdy-card`, `.fdy-datatable`, `.fdy-chart`, …) go inside sections.
|
|
95
|
+
|
|
96
|
+
Freeday deliberately owns **components + tokens, not layout**. Everything above is layout in the kit's
|
|
97
|
+
own language; for the rest (grids, one-off spacing), pair a utility framework run **utilities-only,
|
|
98
|
+
preflight-off** — and build its theme on `var(--space-N)` so the two systems agree. See
|
|
99
|
+
`docs/getting-started.md` §Core concepts.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
*Layout classes here live in `src/components/composition.css`. If a screen needs a primitive that
|
|
104
|
+
isn't here, it probably belongs here — open an issue rather than re-inventing it per screen.*
|
|
@@ -57,6 +57,10 @@ export interface FdyTableProps<Row extends object> {
|
|
|
57
57
|
rowClass?: (row: Row) => string | undefined;
|
|
58
58
|
/** A row was activated (click, or Enter/Space while the row itself is focused). */
|
|
59
59
|
onRowActivate?: (row: Row) => void;
|
|
60
|
+
/** Called with the processed page of rows (after filter/sort/paginate) plus the total row count —
|
|
61
|
+
* in BOTH modes, whenever they change. Lets a consumer render the SAME processed set elsewhere
|
|
62
|
+
* (a `< md` card list, a "selected" summary, export-to-CSV) without re-deriving the pipeline. */
|
|
63
|
+
onProcess?: (result: { rows: Row[]; total: number }) => void;
|
|
60
64
|
/** Controlled: row keys whose detail is shown as a full-width row beneath them. */
|
|
61
65
|
expandedKeys?: ReadonlyArray<string | number>;
|
|
62
66
|
/** Renders the expandable detail row for an expanded row (React equivalent of Vue's `row-detail` slot). */
|
|
@@ -110,6 +114,13 @@ export function FdyTable<Row extends object>(props: FdyTableProps<Row>): JSX.Ele
|
|
|
110
114
|
if (!serverPaged && internalPageIndex > totalPages - 1) setInternalPageIndex(Math.max(0, totalPages - 1));
|
|
111
115
|
}, [serverPaged, internalPageIndex, totalPages]);
|
|
112
116
|
|
|
117
|
+
// Surface the processed page + total to the parent (both modes), so the same result can drive a
|
|
118
|
+
// responsive card list / summary / export without re-implementing filter/sort/paginate.
|
|
119
|
+
const onProcess = props.onProcess;
|
|
120
|
+
useEffect((): void => {
|
|
121
|
+
onProcess?.({ rows: displayRows, total: totalCount });
|
|
122
|
+
}, [onProcess, displayRows, totalCount]);
|
|
123
|
+
|
|
113
124
|
function ariaSortOf(col: FdyTableColumn<Row>): 'ascending' | 'descending' | undefined {
|
|
114
125
|
if (effectiveSort === null || effectiveSort.key !== col.key) return undefined;
|
|
115
126
|
return effectiveSort.dir === 'asc' ? 'ascending' : 'descending';
|
|
@@ -8,12 +8,15 @@ import { computed, onMounted, useId, watch, type ComputedRef, type Ref, ref } fr
|
|
|
8
8
|
// focus trap, focus restore, top-layer stacking and inert background; `dismissible` (default true)
|
|
9
9
|
// gates Esc + backdrop dismissal.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// dismissible MUST go through withDefaults: Vue's boolean-cast gives an omitted Boolean prop
|
|
12
|
+
// `false`, not `undefined`, so a bare `props.dismissible !== false` would make an un-annotated
|
|
13
|
+
// drawer non-dismissible (no Esc, no backdrop, no close button) — the opposite of the default.
|
|
14
|
+
const props = withDefaults(defineProps<{
|
|
12
15
|
open: boolean;
|
|
13
16
|
title: string;
|
|
14
17
|
side?: 'left' | 'right';
|
|
15
18
|
dismissible?: boolean;
|
|
16
|
-
}>();
|
|
19
|
+
}>(), { dismissible: true });
|
|
17
20
|
|
|
18
21
|
const emit = defineEmits<{
|
|
19
22
|
close: [];
|
|
@@ -10,12 +10,15 @@ import { computed, onMounted, useId, watch, type ComputedRef, type Ref, ref } fr
|
|
|
10
10
|
// Native <dialog> already provides the focus trap, focus restore, top-layer stacking and inert
|
|
11
11
|
// background — the wrapper only avoids breaking them. `dismissible` (default true) gates Esc + backdrop.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// dismissible MUST go through withDefaults: Vue's boolean-cast gives an omitted Boolean prop
|
|
14
|
+
// `false`, not `undefined`, so a bare `props.dismissible !== false` would make an un-annotated
|
|
15
|
+
// modal non-dismissible (no Esc, no backdrop, no close button) — the opposite of the default.
|
|
16
|
+
const props = withDefaults(defineProps<{
|
|
14
17
|
open: boolean;
|
|
15
18
|
title: string;
|
|
16
19
|
size?: 'sm' | 'md' | 'lg' | 'wide';
|
|
17
20
|
dismissible?: boolean;
|
|
18
|
-
}>();
|
|
21
|
+
}>(), { dismissible: true });
|
|
19
22
|
|
|
20
23
|
const emit = defineEmits<{
|
|
21
24
|
close: [];
|
|
@@ -58,6 +58,10 @@ const emit = defineEmits<{
|
|
|
58
58
|
'update:page': [page: FdyPageState];
|
|
59
59
|
/** A row was activated (click, or Enter/Space while the row itself is focused). */
|
|
60
60
|
'row-activate': [row: Row];
|
|
61
|
+
/** The processed page of rows (after filter/sort/paginate) plus the total row count — fires in
|
|
62
|
+
* BOTH modes whenever they change. Lets a consumer render the SAME processed set elsewhere
|
|
63
|
+
* (a `< md` card list, a "selected" summary, export-to-CSV) without re-deriving the pipeline. */
|
|
64
|
+
'process': [result: { rows: Row[]; total: number }];
|
|
61
65
|
}>();
|
|
62
66
|
|
|
63
67
|
const internalSort: Ref<FdySortState | null> = ref(null);
|
|
@@ -125,6 +129,14 @@ watch(totalPages, (tp: number): void => {
|
|
|
125
129
|
if (!serverPaged.value && internalPageIndex.value > tp - 1) internalPageIndex.value = Math.max(0, tp - 1);
|
|
126
130
|
});
|
|
127
131
|
|
|
132
|
+
// Surface the processed page + total to the parent (both modes), so the same result can drive a
|
|
133
|
+
// responsive card list / summary / export without re-implementing filter/sort/paginate.
|
|
134
|
+
watch(
|
|
135
|
+
[displayRows, totalCount],
|
|
136
|
+
(): void => emit('process', { rows: displayRows.value, total: totalCount.value }),
|
|
137
|
+
{ immediate: true },
|
|
138
|
+
);
|
|
139
|
+
|
|
128
140
|
function ariaSortOf(col: FdyTableColumn<Row>): 'ascending' | 'descending' | undefined {
|
|
129
141
|
const s: FdySortState | null = effectiveSort.value;
|
|
130
142
|
if (s === null || s.key !== col.key) return undefined;
|
package/dist/freeday-toast.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
/* Freeday — toast API (optional, zero-dependency).
|
|
2
|
-
* Freeday.toast({ variant, title, message, timeout }) shows a transient
|
|
2
|
+
* Freeday.toast({ variant, title, message, timeout, key }) shows a transient
|
|
3
3
|
* notification in a live region (created on first use). Returns the element.
|
|
4
4
|
* variant: 'success' | 'warning' | 'danger' | 'info' (default: neutral)
|
|
5
5
|
* timeout: ms before auto-dismiss (default 4000; 0 = sticky)
|
|
6
|
+
* key: optional stable id — a new toast with the same key REPLACES the
|
|
7
|
+
* existing one in place instead of stacking a duplicate (e.g. a burst
|
|
8
|
+
* of identical failures shows one toast, refreshed).
|
|
9
|
+
* Freeday.dismiss(nodeOrKey) closes a toast early (pass the returned element or its key).
|
|
6
10
|
* Danger toasts use role="alert"; others role="status".
|
|
7
11
|
*/
|
|
8
12
|
(function () {
|
|
9
13
|
'use strict';
|
|
10
14
|
|
|
11
15
|
var region = null;
|
|
16
|
+
var keyed = {}; // key -> current node, for replace-in-place + dismiss-by-key
|
|
17
|
+
|
|
18
|
+
function forget(node) {
|
|
19
|
+
var k = node && node._fdyKey;
|
|
20
|
+
if (k != null && keyed[k] === node) delete keyed[k];
|
|
21
|
+
}
|
|
12
22
|
|
|
13
23
|
function ensureRegion() {
|
|
14
24
|
if (region && document.body.contains(region)) return region;
|
|
@@ -24,7 +34,9 @@
|
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
function dismiss(toast) {
|
|
37
|
+
if (typeof toast === 'string') toast = keyed[toast]; // dismiss by key
|
|
27
38
|
if (!toast || toast.dataset.leaving === '1') return;
|
|
39
|
+
forget(toast);
|
|
28
40
|
toast.dataset.leaving = '1';
|
|
29
41
|
toast.classList.add('is-leaving');
|
|
30
42
|
var remove = function () { if (toast.parentNode) toast.parentNode.removeChild(toast); };
|
|
@@ -64,6 +76,14 @@
|
|
|
64
76
|
node.appendChild(accent);
|
|
65
77
|
node.appendChild(body);
|
|
66
78
|
node.appendChild(close);
|
|
79
|
+
|
|
80
|
+
// Replace-in-place: a same-key toast removes the previous one so bursts don't stack.
|
|
81
|
+
if (opts.key != null) {
|
|
82
|
+
var prev = keyed[opts.key];
|
|
83
|
+
if (prev && prev.parentNode) prev.parentNode.removeChild(prev);
|
|
84
|
+
node._fdyKey = opts.key;
|
|
85
|
+
keyed[opts.key] = node;
|
|
86
|
+
}
|
|
67
87
|
region.appendChild(node);
|
|
68
88
|
|
|
69
89
|
var timeout = opts.timeout == null ? 4000 : opts.timeout;
|
|
@@ -81,4 +101,5 @@
|
|
|
81
101
|
|
|
82
102
|
window.Freeday = window.Freeday || {};
|
|
83
103
|
window.Freeday.toast = toast;
|
|
104
|
+
window.Freeday.dismiss = dismiss; // dismiss(nodeOrKey)
|
|
84
105
|
})();
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -166,6 +166,14 @@
|
|
|
166
166
|
--chart-8: #e34948;
|
|
167
167
|
--chart-grid: var(--slate-200);
|
|
168
168
|
--chart-tick: var(--slate-400);
|
|
169
|
+
--tone-1: var(--chart-1);
|
|
170
|
+
--tone-2: var(--chart-2);
|
|
171
|
+
--tone-3: var(--chart-3);
|
|
172
|
+
--tone-4: var(--chart-4);
|
|
173
|
+
--tone-5: var(--chart-5);
|
|
174
|
+
--tone-6: var(--chart-6);
|
|
175
|
+
--tone-7: var(--chart-7);
|
|
176
|
+
--tone-8: var(--chart-8);
|
|
169
177
|
--focus-ring: var(--azure-600);
|
|
170
178
|
--focus-ring-width: 2px;
|
|
171
179
|
}
|
|
@@ -340,6 +348,10 @@
|
|
|
340
348
|
--focus-ring: var(--azure-600);
|
|
341
349
|
}
|
|
342
350
|
:root[data-density="compact"] {
|
|
351
|
+
--space-3: 0.625rem;
|
|
352
|
+
--space-4: 0.75rem;
|
|
353
|
+
--space-5: 1rem;
|
|
354
|
+
--space-6: 1.25rem;
|
|
343
355
|
--control-h: 2rem;
|
|
344
356
|
}
|
|
345
357
|
|
|
@@ -361,6 +373,11 @@ a { color: var(--color-primary); }
|
|
|
361
373
|
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
|
|
362
374
|
:where(button, input, select, textarea) { font: inherit; }
|
|
363
375
|
.fdy-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
376
|
+
/* Opt-in list reset. base.css is intentionally a *light* reset — it does NOT strip list/paragraph
|
|
377
|
+
* margins (see the kit's own list components, which each reset themselves). If you run a utility
|
|
378
|
+
* framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
|
|
379
|
+
* bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
|
|
380
|
+
.fdy-list-reset { list-style: none; margin: 0; padding: 0; }
|
|
364
381
|
@media (prefers-reduced-motion: reduce) {
|
|
365
382
|
*, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
|
|
366
383
|
}
|
|
@@ -524,6 +541,19 @@ a { color: var(--color-primary); }
|
|
|
524
541
|
.fdy-avatar-group{display:inline-flex;}
|
|
525
542
|
.fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
|
|
526
543
|
.fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
|
|
544
|
+
/* Decorative identity tones — a soft tint of a categorical chart colour with a theme-aware,
|
|
545
|
+
* text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
|
|
546
|
+
* test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
|
|
547
|
+
* hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
|
|
548
|
+
.fdy-avatar--tone-1,.fdy-avatar--tone-2,.fdy-avatar--tone-3,.fdy-avatar--tone-4,.fdy-avatar--tone-5,.fdy-avatar--tone-6,.fdy-avatar--tone-7,.fdy-avatar--tone-8{background:color-mix(in srgb,var(--_fdy-avatar-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-avatar-tone) 50%,var(--color-text));}
|
|
549
|
+
.fdy-avatar--tone-1{--_fdy-avatar-tone:var(--tone-1);}
|
|
550
|
+
.fdy-avatar--tone-2{--_fdy-avatar-tone:var(--tone-2);}
|
|
551
|
+
.fdy-avatar--tone-3{--_fdy-avatar-tone:var(--tone-3);}
|
|
552
|
+
.fdy-avatar--tone-4{--_fdy-avatar-tone:var(--tone-4);}
|
|
553
|
+
.fdy-avatar--tone-5{--_fdy-avatar-tone:var(--tone-5);}
|
|
554
|
+
.fdy-avatar--tone-6{--_fdy-avatar-tone:var(--tone-6);}
|
|
555
|
+
.fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
|
|
556
|
+
.fdy-avatar--tone-8{--_fdy-avatar-tone:var(--tone-8);}
|
|
527
557
|
|
|
528
558
|
/* Freeday — Badge */
|
|
529
559
|
.fdy-badge{display:inline-flex;align-items:center;gap:var(--space-1);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;padding:.35em .7em;border-radius:var(--radius-full);color:var(--color-primary-strong);background:var(--color-primary-soft);border:var(--bw) solid color-mix(in srgb,var(--color-primary) 22%,transparent);box-shadow:0 1px 1.5px rgba(16,14,30,.05);}
|
|
@@ -643,6 +673,13 @@ a { color: var(--color-primary); }
|
|
|
643
673
|
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
|
|
644
674
|
.fdy-card--interactive{cursor:pointer;}
|
|
645
675
|
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
|
|
676
|
+
/* The global reduced-motion reset kills the ANIMATION; drop the -3px shift too so the card
|
|
677
|
+
* doesn't jump. The shadow still communicates the affordance. */
|
|
678
|
+
@media (prefers-reduced-motion:reduce){.fdy-card--interactive:hover{transform:none;}}
|
|
679
|
+
/* When the whole card IS the control — a real <button> so the row is keyboard-reachable and
|
|
680
|
+
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
681
|
+
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
682
|
+
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
646
683
|
.fdy-card__body{padding:var(--space-5);}
|
|
647
684
|
.fdy-card__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0 0 var(--space-1);}
|
|
648
685
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -794,6 +831,18 @@ a { color: var(--color-primary); }
|
|
|
794
831
|
/* Freeday — Chip (tag / removable filter) */
|
|
795
832
|
.fdy-chip{display:inline-flex;align-items:center;gap:var(--space-2);height:1.75rem;padding:0 var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--weight-medium);color:var(--color-text);background:var(--color-surface-3);border:var(--bw) solid var(--color-border);}
|
|
796
833
|
.fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
|
|
834
|
+
/* Categorical tone chips — non-semantic category tags / legend swatches, from the general
|
|
835
|
+
* --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
|
|
836
|
+
* gated by test/contrast.test.mjs). */
|
|
837
|
+
.fdy-chip--tone-1,.fdy-chip--tone-2,.fdy-chip--tone-3,.fdy-chip--tone-4,.fdy-chip--tone-5,.fdy-chip--tone-6,.fdy-chip--tone-7,.fdy-chip--tone-8{background:color-mix(in srgb,var(--_fdy-chip-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-chip-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-chip-tone) 30%,var(--color-border));}
|
|
838
|
+
.fdy-chip--tone-1{--_fdy-chip-tone:var(--tone-1);}
|
|
839
|
+
.fdy-chip--tone-2{--_fdy-chip-tone:var(--tone-2);}
|
|
840
|
+
.fdy-chip--tone-3{--_fdy-chip-tone:var(--tone-3);}
|
|
841
|
+
.fdy-chip--tone-4{--_fdy-chip-tone:var(--tone-4);}
|
|
842
|
+
.fdy-chip--tone-5{--_fdy-chip-tone:var(--tone-5);}
|
|
843
|
+
.fdy-chip--tone-6{--_fdy-chip-tone:var(--tone-6);}
|
|
844
|
+
.fdy-chip--tone-7{--_fdy-chip-tone:var(--tone-7);}
|
|
845
|
+
.fdy-chip--tone-8{--_fdy-chip-tone:var(--tone-8);}
|
|
797
846
|
.fdy-chip__remove{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-right:calc(var(--space-1)*-1);border:0;padding:0;border-radius:var(--radius-full);background:transparent;color:currentColor;opacity:.6;cursor:pointer;font-size:var(--text-sm);line-height:1;}
|
|
798
847
|
.fdy-chip__remove:hover{opacity:1;background:color-mix(in srgb,currentColor 16%,transparent);}
|
|
799
848
|
.fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
|
|
@@ -843,6 +892,56 @@ a { color: var(--color-primary); }
|
|
|
843
892
|
@media (prefers-reduced-motion:no-preference){.fdy-combo__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
|
|
844
893
|
@keyframes fdy-combo-in{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
|
|
845
894
|
|
|
895
|
+
/* Freeday — Page composition & type roles.
|
|
896
|
+
*
|
|
897
|
+
* The kit stops at the component; these primitives encode *how a page is assembled* so screens
|
|
898
|
+
* built by different people cohere. They carry the usage doctrine (see USAGE.md) in markup: a
|
|
899
|
+
* named type role gets used, a doc gets skimmed. Everything here is token-driven layout — no new
|
|
900
|
+
* colours, no new shadows. Pair with .fdy-app (the shell) and the components inside.
|
|
901
|
+
*/
|
|
902
|
+
|
|
903
|
+
/* ---- Type roles (three title levels + supporting text) -------------------------------------
|
|
904
|
+
* One role per level of hierarchy. Reach for these instead of re-using .fdy-card__title for a
|
|
905
|
+
* page title (the mistake that collapses three levels into one grey mush). */
|
|
906
|
+
.fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-subtle);}
|
|
907
|
+
.fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
908
|
+
.fdy-title-section{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-snug);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
909
|
+
.fdy-title-card{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0;}
|
|
910
|
+
.fdy-text-muted{color:var(--color-text-muted);}
|
|
911
|
+
.fdy-text-subtle{color:var(--color-text-subtle);}
|
|
912
|
+
.fdy-text-caption{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;}
|
|
913
|
+
|
|
914
|
+
/* ---- Page: the vertical stack of sections, with the section rhythm between them ------------- */
|
|
915
|
+
.fdy-page{display:flex;flex-direction:column;gap:var(--space-8);}
|
|
916
|
+
|
|
917
|
+
/* ---- Page header: eyebrow + title + description on the left, primary action(s) on the right -- */
|
|
918
|
+
.fdy-page__header{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--space-4);flex-wrap:wrap;}
|
|
919
|
+
.fdy-page__heading{display:flex;flex-direction:column;gap:var(--space-2);min-width:0;}
|
|
920
|
+
.fdy-page__desc{font-size:var(--text-base);color:var(--color-text-muted);margin:0;max-width:62ch;line-height:var(--leading-normal);}
|
|
921
|
+
.fdy-page__actions{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
|
|
922
|
+
|
|
923
|
+
/* ---- Page section: a heading (+ optional toolbar) and its body, with the intra-group rhythm --- */
|
|
924
|
+
.fdy-page-section{display:flex;flex-direction:column;gap:var(--space-4);}
|
|
925
|
+
.fdy-page-section__head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;}
|
|
926
|
+
|
|
927
|
+
/* ---- Toolbar: a horizontal control row (search + actions), wraps cleanly ---------------------- */
|
|
928
|
+
.fdy-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;}
|
|
929
|
+
.fdy-toolbar__spacer{flex:1;}
|
|
930
|
+
.fdy-toolbar__search{max-width:18rem;flex:1 1 12rem;}
|
|
931
|
+
|
|
932
|
+
/* ---- Stat tile: a KPI metric that is deliberately NOT a card, so a KPI strip doesn't become an
|
|
933
|
+
* identical-white-card grid. Flat by default; wrap the .fdy-stats grid in one card if you want a
|
|
934
|
+
* single boxed strip, or use .fdy-stats--boxed for shared surface + dividers. */
|
|
935
|
+
.fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
|
|
936
|
+
.fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
|
|
937
|
+
.fdy-stat__label{font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-subtle);}
|
|
938
|
+
.fdy-stat__value{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:1;color:var(--color-text);font-variant-numeric:tabular-nums;}
|
|
939
|
+
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
940
|
+
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
|
941
|
+
.fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
|
|
942
|
+
.fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
|
|
943
|
+
.fdy-stats--boxed .fdy-stat:last-child{border-right:0;}
|
|
944
|
+
|
|
846
945
|
/* Freeday — Date picker (input-styled trigger + calendar popover).
|
|
847
946
|
* Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
|
|
848
947
|
.fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
|
|
@@ -1202,6 +1301,11 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1202
1301
|
.fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
|
|
1203
1302
|
.fdy-skeleton--title{height:1.2rem;width:40%;}
|
|
1204
1303
|
.fdy-skeleton--circle{border-radius:var(--radius-full);}
|
|
1304
|
+
/* Size-matched to .fdy-avatar (2.5rem) and its --sm/--lg, so a skeleton reserves the exact avatar
|
|
1305
|
+
* box and data landing causes no layout shift (a plain --circle has no dimensions of its own). */
|
|
1306
|
+
.fdy-skeleton--avatar{width:2.5rem;height:2.5rem;border-radius:var(--radius-full);}
|
|
1307
|
+
.fdy-skeleton--avatar-sm{width:2rem;height:2rem;border-radius:var(--radius-full);}
|
|
1308
|
+
.fdy-skeleton--avatar-lg{width:3.5rem;height:3.5rem;border-radius:var(--radius-full);}
|
|
1205
1309
|
@keyframes fdy-skeleton{to{background-position:-200% 0;}}
|
|
1206
1310
|
@media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
|
|
1207
1311
|
|
package/dist/freeday.css
CHANGED
|
@@ -16,6 +16,11 @@ a { color: var(--color-primary); }
|
|
|
16
16
|
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
|
|
17
17
|
:where(button, input, select, textarea) { font: inherit; }
|
|
18
18
|
.fdy-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
19
|
+
/* Opt-in list reset. base.css is intentionally a *light* reset — it does NOT strip list/paragraph
|
|
20
|
+
* margins (see the kit's own list components, which each reset themselves). If you run a utility
|
|
21
|
+
* framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
|
|
22
|
+
* bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
|
|
23
|
+
.fdy-list-reset { list-style: none; margin: 0; padding: 0; }
|
|
19
24
|
@media (prefers-reduced-motion: reduce) {
|
|
20
25
|
*, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
|
|
21
26
|
}
|
|
@@ -179,6 +184,19 @@ a { color: var(--color-primary); }
|
|
|
179
184
|
.fdy-avatar-group{display:inline-flex;}
|
|
180
185
|
.fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
|
|
181
186
|
.fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
|
|
187
|
+
/* Decorative identity tones — a soft tint of a categorical chart colour with a theme-aware,
|
|
188
|
+
* text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
|
|
189
|
+
* test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
|
|
190
|
+
* hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
|
|
191
|
+
.fdy-avatar--tone-1,.fdy-avatar--tone-2,.fdy-avatar--tone-3,.fdy-avatar--tone-4,.fdy-avatar--tone-5,.fdy-avatar--tone-6,.fdy-avatar--tone-7,.fdy-avatar--tone-8{background:color-mix(in srgb,var(--_fdy-avatar-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-avatar-tone) 50%,var(--color-text));}
|
|
192
|
+
.fdy-avatar--tone-1{--_fdy-avatar-tone:var(--tone-1);}
|
|
193
|
+
.fdy-avatar--tone-2{--_fdy-avatar-tone:var(--tone-2);}
|
|
194
|
+
.fdy-avatar--tone-3{--_fdy-avatar-tone:var(--tone-3);}
|
|
195
|
+
.fdy-avatar--tone-4{--_fdy-avatar-tone:var(--tone-4);}
|
|
196
|
+
.fdy-avatar--tone-5{--_fdy-avatar-tone:var(--tone-5);}
|
|
197
|
+
.fdy-avatar--tone-6{--_fdy-avatar-tone:var(--tone-6);}
|
|
198
|
+
.fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
|
|
199
|
+
.fdy-avatar--tone-8{--_fdy-avatar-tone:var(--tone-8);}
|
|
182
200
|
|
|
183
201
|
/* Freeday — Badge */
|
|
184
202
|
.fdy-badge{display:inline-flex;align-items:center;gap:var(--space-1);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;padding:.35em .7em;border-radius:var(--radius-full);color:var(--color-primary-strong);background:var(--color-primary-soft);border:var(--bw) solid color-mix(in srgb,var(--color-primary) 22%,transparent);box-shadow:0 1px 1.5px rgba(16,14,30,.05);}
|
|
@@ -298,6 +316,13 @@ a { color: var(--color-primary); }
|
|
|
298
316
|
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
|
|
299
317
|
.fdy-card--interactive{cursor:pointer;}
|
|
300
318
|
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
|
|
319
|
+
/* The global reduced-motion reset kills the ANIMATION; drop the -3px shift too so the card
|
|
320
|
+
* doesn't jump. The shadow still communicates the affordance. */
|
|
321
|
+
@media (prefers-reduced-motion:reduce){.fdy-card--interactive:hover{transform:none;}}
|
|
322
|
+
/* When the whole card IS the control — a real <button> so the row is keyboard-reachable and
|
|
323
|
+
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
324
|
+
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
325
|
+
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
301
326
|
.fdy-card__body{padding:var(--space-5);}
|
|
302
327
|
.fdy-card__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0 0 var(--space-1);}
|
|
303
328
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -449,6 +474,18 @@ a { color: var(--color-primary); }
|
|
|
449
474
|
/* Freeday — Chip (tag / removable filter) */
|
|
450
475
|
.fdy-chip{display:inline-flex;align-items:center;gap:var(--space-2);height:1.75rem;padding:0 var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--weight-medium);color:var(--color-text);background:var(--color-surface-3);border:var(--bw) solid var(--color-border);}
|
|
451
476
|
.fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
|
|
477
|
+
/* Categorical tone chips — non-semantic category tags / legend swatches, from the general
|
|
478
|
+
* --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
|
|
479
|
+
* gated by test/contrast.test.mjs). */
|
|
480
|
+
.fdy-chip--tone-1,.fdy-chip--tone-2,.fdy-chip--tone-3,.fdy-chip--tone-4,.fdy-chip--tone-5,.fdy-chip--tone-6,.fdy-chip--tone-7,.fdy-chip--tone-8{background:color-mix(in srgb,var(--_fdy-chip-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-chip-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-chip-tone) 30%,var(--color-border));}
|
|
481
|
+
.fdy-chip--tone-1{--_fdy-chip-tone:var(--tone-1);}
|
|
482
|
+
.fdy-chip--tone-2{--_fdy-chip-tone:var(--tone-2);}
|
|
483
|
+
.fdy-chip--tone-3{--_fdy-chip-tone:var(--tone-3);}
|
|
484
|
+
.fdy-chip--tone-4{--_fdy-chip-tone:var(--tone-4);}
|
|
485
|
+
.fdy-chip--tone-5{--_fdy-chip-tone:var(--tone-5);}
|
|
486
|
+
.fdy-chip--tone-6{--_fdy-chip-tone:var(--tone-6);}
|
|
487
|
+
.fdy-chip--tone-7{--_fdy-chip-tone:var(--tone-7);}
|
|
488
|
+
.fdy-chip--tone-8{--_fdy-chip-tone:var(--tone-8);}
|
|
452
489
|
.fdy-chip__remove{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-right:calc(var(--space-1)*-1);border:0;padding:0;border-radius:var(--radius-full);background:transparent;color:currentColor;opacity:.6;cursor:pointer;font-size:var(--text-sm);line-height:1;}
|
|
453
490
|
.fdy-chip__remove:hover{opacity:1;background:color-mix(in srgb,currentColor 16%,transparent);}
|
|
454
491
|
.fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
|
|
@@ -498,6 +535,56 @@ a { color: var(--color-primary); }
|
|
|
498
535
|
@media (prefers-reduced-motion:no-preference){.fdy-combo__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
|
|
499
536
|
@keyframes fdy-combo-in{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
|
|
500
537
|
|
|
538
|
+
/* Freeday — Page composition & type roles.
|
|
539
|
+
*
|
|
540
|
+
* The kit stops at the component; these primitives encode *how a page is assembled* so screens
|
|
541
|
+
* built by different people cohere. They carry the usage doctrine (see USAGE.md) in markup: a
|
|
542
|
+
* named type role gets used, a doc gets skimmed. Everything here is token-driven layout — no new
|
|
543
|
+
* colours, no new shadows. Pair with .fdy-app (the shell) and the components inside.
|
|
544
|
+
*/
|
|
545
|
+
|
|
546
|
+
/* ---- Type roles (three title levels + supporting text) -------------------------------------
|
|
547
|
+
* One role per level of hierarchy. Reach for these instead of re-using .fdy-card__title for a
|
|
548
|
+
* page title (the mistake that collapses three levels into one grey mush). */
|
|
549
|
+
.fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-subtle);}
|
|
550
|
+
.fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
551
|
+
.fdy-title-section{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-snug);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
552
|
+
.fdy-title-card{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0;}
|
|
553
|
+
.fdy-text-muted{color:var(--color-text-muted);}
|
|
554
|
+
.fdy-text-subtle{color:var(--color-text-subtle);}
|
|
555
|
+
.fdy-text-caption{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;}
|
|
556
|
+
|
|
557
|
+
/* ---- Page: the vertical stack of sections, with the section rhythm between them ------------- */
|
|
558
|
+
.fdy-page{display:flex;flex-direction:column;gap:var(--space-8);}
|
|
559
|
+
|
|
560
|
+
/* ---- Page header: eyebrow + title + description on the left, primary action(s) on the right -- */
|
|
561
|
+
.fdy-page__header{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--space-4);flex-wrap:wrap;}
|
|
562
|
+
.fdy-page__heading{display:flex;flex-direction:column;gap:var(--space-2);min-width:0;}
|
|
563
|
+
.fdy-page__desc{font-size:var(--text-base);color:var(--color-text-muted);margin:0;max-width:62ch;line-height:var(--leading-normal);}
|
|
564
|
+
.fdy-page__actions{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
|
|
565
|
+
|
|
566
|
+
/* ---- Page section: a heading (+ optional toolbar) and its body, with the intra-group rhythm --- */
|
|
567
|
+
.fdy-page-section{display:flex;flex-direction:column;gap:var(--space-4);}
|
|
568
|
+
.fdy-page-section__head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;}
|
|
569
|
+
|
|
570
|
+
/* ---- Toolbar: a horizontal control row (search + actions), wraps cleanly ---------------------- */
|
|
571
|
+
.fdy-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;}
|
|
572
|
+
.fdy-toolbar__spacer{flex:1;}
|
|
573
|
+
.fdy-toolbar__search{max-width:18rem;flex:1 1 12rem;}
|
|
574
|
+
|
|
575
|
+
/* ---- Stat tile: a KPI metric that is deliberately NOT a card, so a KPI strip doesn't become an
|
|
576
|
+
* identical-white-card grid. Flat by default; wrap the .fdy-stats grid in one card if you want a
|
|
577
|
+
* single boxed strip, or use .fdy-stats--boxed for shared surface + dividers. */
|
|
578
|
+
.fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
|
|
579
|
+
.fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
|
|
580
|
+
.fdy-stat__label{font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-subtle);}
|
|
581
|
+
.fdy-stat__value{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:1;color:var(--color-text);font-variant-numeric:tabular-nums;}
|
|
582
|
+
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
583
|
+
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
|
584
|
+
.fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
|
|
585
|
+
.fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
|
|
586
|
+
.fdy-stats--boxed .fdy-stat:last-child{border-right:0;}
|
|
587
|
+
|
|
501
588
|
/* Freeday — Date picker (input-styled trigger + calendar popover).
|
|
502
589
|
* Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
|
|
503
590
|
.fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
|
|
@@ -857,6 +944,11 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
857
944
|
.fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
|
|
858
945
|
.fdy-skeleton--title{height:1.2rem;width:40%;}
|
|
859
946
|
.fdy-skeleton--circle{border-radius:var(--radius-full);}
|
|
947
|
+
/* Size-matched to .fdy-avatar (2.5rem) and its --sm/--lg, so a skeleton reserves the exact avatar
|
|
948
|
+
* box and data landing causes no layout shift (a plain --circle has no dimensions of its own). */
|
|
949
|
+
.fdy-skeleton--avatar{width:2.5rem;height:2.5rem;border-radius:var(--radius-full);}
|
|
950
|
+
.fdy-skeleton--avatar-sm{width:2rem;height:2rem;border-radius:var(--radius-full);}
|
|
951
|
+
.fdy-skeleton--avatar-lg{width:3.5rem;height:3.5rem;border-radius:var(--radius-full);}
|
|
860
952
|
@keyframes fdy-skeleton{to{background-position:-200% 0;}}
|
|
861
953
|
@media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
|
|
862
954
|
|
package/dist/freeday.d.ts
CHANGED
|
@@ -10,11 +10,16 @@ export interface FreedayToastOptions {
|
|
|
10
10
|
message?: string;
|
|
11
11
|
/** Milliseconds before auto-dismiss. Default 4000; 0 = sticky. */
|
|
12
12
|
timeout?: number;
|
|
13
|
+
/** Stable id: a new toast with the same key replaces the existing one in place instead of
|
|
14
|
+
* stacking a duplicate (e.g. a burst of identical failures shows one, refreshed). */
|
|
15
|
+
key?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export interface FreedayGlobal {
|
|
16
19
|
/** Show a transient toast in a live region (created on first use). Returns the toast element. */
|
|
17
20
|
toast(opts?: FreedayToastOptions): HTMLElement;
|
|
21
|
+
/** Dismiss a toast early — pass the element returned by toast(), or a key string. */
|
|
22
|
+
dismiss(target: HTMLElement | string): void;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
declare global {
|
package/dist/freeday.js
CHANGED
|
@@ -3319,16 +3319,26 @@
|
|
|
3319
3319
|
})();
|
|
3320
3320
|
|
|
3321
3321
|
/* Freeday — toast API (optional, zero-dependency).
|
|
3322
|
-
* Freeday.toast({ variant, title, message, timeout }) shows a transient
|
|
3322
|
+
* Freeday.toast({ variant, title, message, timeout, key }) shows a transient
|
|
3323
3323
|
* notification in a live region (created on first use). Returns the element.
|
|
3324
3324
|
* variant: 'success' | 'warning' | 'danger' | 'info' (default: neutral)
|
|
3325
3325
|
* timeout: ms before auto-dismiss (default 4000; 0 = sticky)
|
|
3326
|
+
* key: optional stable id — a new toast with the same key REPLACES the
|
|
3327
|
+
* existing one in place instead of stacking a duplicate (e.g. a burst
|
|
3328
|
+
* of identical failures shows one toast, refreshed).
|
|
3329
|
+
* Freeday.dismiss(nodeOrKey) closes a toast early (pass the returned element or its key).
|
|
3326
3330
|
* Danger toasts use role="alert"; others role="status".
|
|
3327
3331
|
*/
|
|
3328
3332
|
(function () {
|
|
3329
3333
|
'use strict';
|
|
3330
3334
|
|
|
3331
3335
|
var region = null;
|
|
3336
|
+
var keyed = {}; // key -> current node, for replace-in-place + dismiss-by-key
|
|
3337
|
+
|
|
3338
|
+
function forget(node) {
|
|
3339
|
+
var k = node && node._fdyKey;
|
|
3340
|
+
if (k != null && keyed[k] === node) delete keyed[k];
|
|
3341
|
+
}
|
|
3332
3342
|
|
|
3333
3343
|
function ensureRegion() {
|
|
3334
3344
|
if (region && document.body.contains(region)) return region;
|
|
@@ -3344,7 +3354,9 @@
|
|
|
3344
3354
|
}
|
|
3345
3355
|
|
|
3346
3356
|
function dismiss(toast) {
|
|
3357
|
+
if (typeof toast === 'string') toast = keyed[toast]; // dismiss by key
|
|
3347
3358
|
if (!toast || toast.dataset.leaving === '1') return;
|
|
3359
|
+
forget(toast);
|
|
3348
3360
|
toast.dataset.leaving = '1';
|
|
3349
3361
|
toast.classList.add('is-leaving');
|
|
3350
3362
|
var remove = function () { if (toast.parentNode) toast.parentNode.removeChild(toast); };
|
|
@@ -3384,6 +3396,14 @@
|
|
|
3384
3396
|
node.appendChild(accent);
|
|
3385
3397
|
node.appendChild(body);
|
|
3386
3398
|
node.appendChild(close);
|
|
3399
|
+
|
|
3400
|
+
// Replace-in-place: a same-key toast removes the previous one so bursts don't stack.
|
|
3401
|
+
if (opts.key != null) {
|
|
3402
|
+
var prev = keyed[opts.key];
|
|
3403
|
+
if (prev && prev.parentNode) prev.parentNode.removeChild(prev);
|
|
3404
|
+
node._fdyKey = opts.key;
|
|
3405
|
+
keyed[opts.key] = node;
|
|
3406
|
+
}
|
|
3387
3407
|
region.appendChild(node);
|
|
3388
3408
|
|
|
3389
3409
|
var timeout = opts.timeout == null ? 4000 : opts.timeout;
|
|
@@ -3401,6 +3421,7 @@
|
|
|
3401
3421
|
|
|
3402
3422
|
window.Freeday = window.Freeday || {};
|
|
3403
3423
|
window.Freeday.toast = toast;
|
|
3424
|
+
window.Freeday.dismiss = dismiss; // dismiss(nodeOrKey)
|
|
3404
3425
|
})();
|
|
3405
3426
|
|
|
3406
3427
|
/* Freeday — tree checkbox cascade (optional, zero-dependency).
|
package/dist/freeday.tokens.css
CHANGED
|
@@ -165,6 +165,14 @@
|
|
|
165
165
|
--chart-8: #e34948;
|
|
166
166
|
--chart-grid: var(--slate-200);
|
|
167
167
|
--chart-tick: var(--slate-400);
|
|
168
|
+
--tone-1: var(--chart-1);
|
|
169
|
+
--tone-2: var(--chart-2);
|
|
170
|
+
--tone-3: var(--chart-3);
|
|
171
|
+
--tone-4: var(--chart-4);
|
|
172
|
+
--tone-5: var(--chart-5);
|
|
173
|
+
--tone-6: var(--chart-6);
|
|
174
|
+
--tone-7: var(--chart-7);
|
|
175
|
+
--tone-8: var(--chart-8);
|
|
168
176
|
--focus-ring: var(--azure-600);
|
|
169
177
|
--focus-ring-width: 2px;
|
|
170
178
|
}
|
|
@@ -339,5 +347,9 @@
|
|
|
339
347
|
--focus-ring: var(--azure-600);
|
|
340
348
|
}
|
|
341
349
|
:root[data-density="compact"] {
|
|
350
|
+
--space-3: 0.625rem;
|
|
351
|
+
--space-4: 0.75rem;
|
|
352
|
+
--space-5: 1rem;
|
|
353
|
+
--space-6: 1.25rem;
|
|
342
354
|
--control-h: 2rem;
|
|
343
355
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"tokens/breakpoints.d.ts",
|
|
29
29
|
"README.md",
|
|
30
30
|
"README.id.md",
|
|
31
|
+
"USAGE.md",
|
|
31
32
|
"CHANGELOG.md"
|
|
32
33
|
],
|
|
33
34
|
"exports": {
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"./css/components": { "types": "./dist/asset.d.ts", "default": "./dist/freeday.css" },
|
|
37
38
|
"./tokens": { "types": "./dist/asset.d.ts", "default": "./dist/freeday.tokens.css" },
|
|
38
39
|
"./breakpoints": { "types": "./tokens/breakpoints.d.ts", "default": "./tokens/breakpoints.mjs" },
|
|
40
|
+
"./table-model": { "types": "./adapters/core/table-model.d.ts", "default": "./adapters/core/table-model.js" },
|
|
39
41
|
"./enhancers/*": { "types": "./dist/asset.d.ts", "default": "./dist/freeday-*.js" },
|
|
40
42
|
"./vue": {
|
|
41
43
|
"types": "./adapters/vue/index.d.ts",
|
package/src/base.css
CHANGED
|
@@ -15,6 +15,11 @@ a { color: var(--color-primary); }
|
|
|
15
15
|
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
|
|
16
16
|
:where(button, input, select, textarea) { font: inherit; }
|
|
17
17
|
.fdy-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
18
|
+
/* Opt-in list reset. base.css is intentionally a *light* reset — it does NOT strip list/paragraph
|
|
19
|
+
* margins (see the kit's own list components, which each reset themselves). If you run a utility
|
|
20
|
+
* framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
|
|
21
|
+
* bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
|
|
22
|
+
.fdy-list-reset { list-style: none; margin: 0; padding: 0; }
|
|
18
23
|
@media (prefers-reduced-motion: reduce) {
|
|
19
24
|
*, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
|
|
20
25
|
}
|
|
@@ -6,3 +6,16 @@
|
|
|
6
6
|
.fdy-avatar-group{display:inline-flex;}
|
|
7
7
|
.fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
|
|
8
8
|
.fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
|
|
9
|
+
/* Decorative identity tones — a soft tint of a categorical chart colour with a theme-aware,
|
|
10
|
+
* text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
|
|
11
|
+
* test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
|
|
12
|
+
* hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
|
|
13
|
+
.fdy-avatar--tone-1,.fdy-avatar--tone-2,.fdy-avatar--tone-3,.fdy-avatar--tone-4,.fdy-avatar--tone-5,.fdy-avatar--tone-6,.fdy-avatar--tone-7,.fdy-avatar--tone-8{background:color-mix(in srgb,var(--_fdy-avatar-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-avatar-tone) 50%,var(--color-text));}
|
|
14
|
+
.fdy-avatar--tone-1{--_fdy-avatar-tone:var(--tone-1);}
|
|
15
|
+
.fdy-avatar--tone-2{--_fdy-avatar-tone:var(--tone-2);}
|
|
16
|
+
.fdy-avatar--tone-3{--_fdy-avatar-tone:var(--tone-3);}
|
|
17
|
+
.fdy-avatar--tone-4{--_fdy-avatar-tone:var(--tone-4);}
|
|
18
|
+
.fdy-avatar--tone-5{--_fdy-avatar-tone:var(--tone-5);}
|
|
19
|
+
.fdy-avatar--tone-6{--_fdy-avatar-tone:var(--tone-6);}
|
|
20
|
+
.fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
|
|
21
|
+
.fdy-avatar--tone-8{--_fdy-avatar-tone:var(--tone-8);}
|
package/src/components/card.css
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
|
|
4
4
|
.fdy-card--interactive{cursor:pointer;}
|
|
5
5
|
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
|
|
6
|
+
/* The global reduced-motion reset kills the ANIMATION; drop the -3px shift too so the card
|
|
7
|
+
* doesn't jump. The shadow still communicates the affordance. */
|
|
8
|
+
@media (prefers-reduced-motion:reduce){.fdy-card--interactive:hover{transform:none;}}
|
|
9
|
+
/* When the whole card IS the control — a real <button> so the row is keyboard-reachable and
|
|
10
|
+
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
11
|
+
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
12
|
+
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
6
13
|
.fdy-card__body{padding:var(--space-5);}
|
|
7
14
|
.fdy-card__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0 0 var(--space-1);}
|
|
8
15
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
package/src/components/chip.css
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/* Freeday — Chip (tag / removable filter) */
|
|
2
2
|
.fdy-chip{display:inline-flex;align-items:center;gap:var(--space-2);height:1.75rem;padding:0 var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--weight-medium);color:var(--color-text);background:var(--color-surface-3);border:var(--bw) solid var(--color-border);}
|
|
3
3
|
.fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
|
|
4
|
+
/* Categorical tone chips — non-semantic category tags / legend swatches, from the general
|
|
5
|
+
* --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
|
|
6
|
+
* gated by test/contrast.test.mjs). */
|
|
7
|
+
.fdy-chip--tone-1,.fdy-chip--tone-2,.fdy-chip--tone-3,.fdy-chip--tone-4,.fdy-chip--tone-5,.fdy-chip--tone-6,.fdy-chip--tone-7,.fdy-chip--tone-8{background:color-mix(in srgb,var(--_fdy-chip-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-chip-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-chip-tone) 30%,var(--color-border));}
|
|
8
|
+
.fdy-chip--tone-1{--_fdy-chip-tone:var(--tone-1);}
|
|
9
|
+
.fdy-chip--tone-2{--_fdy-chip-tone:var(--tone-2);}
|
|
10
|
+
.fdy-chip--tone-3{--_fdy-chip-tone:var(--tone-3);}
|
|
11
|
+
.fdy-chip--tone-4{--_fdy-chip-tone:var(--tone-4);}
|
|
12
|
+
.fdy-chip--tone-5{--_fdy-chip-tone:var(--tone-5);}
|
|
13
|
+
.fdy-chip--tone-6{--_fdy-chip-tone:var(--tone-6);}
|
|
14
|
+
.fdy-chip--tone-7{--_fdy-chip-tone:var(--tone-7);}
|
|
15
|
+
.fdy-chip--tone-8{--_fdy-chip-tone:var(--tone-8);}
|
|
4
16
|
.fdy-chip__remove{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-right:calc(var(--space-1)*-1);border:0;padding:0;border-radius:var(--radius-full);background:transparent;color:currentColor;opacity:.6;cursor:pointer;font-size:var(--text-sm);line-height:1;}
|
|
5
17
|
.fdy-chip__remove:hover{opacity:1;background:color-mix(in srgb,currentColor 16%,transparent);}
|
|
6
18
|
.fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* Freeday — Page composition & type roles.
|
|
2
|
+
*
|
|
3
|
+
* The kit stops at the component; these primitives encode *how a page is assembled* so screens
|
|
4
|
+
* built by different people cohere. They carry the usage doctrine (see USAGE.md) in markup: a
|
|
5
|
+
* named type role gets used, a doc gets skimmed. Everything here is token-driven layout — no new
|
|
6
|
+
* colours, no new shadows. Pair with .fdy-app (the shell) and the components inside.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* ---- Type roles (three title levels + supporting text) -------------------------------------
|
|
10
|
+
* One role per level of hierarchy. Reach for these instead of re-using .fdy-card__title for a
|
|
11
|
+
* page title (the mistake that collapses three levels into one grey mush). */
|
|
12
|
+
.fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-subtle);}
|
|
13
|
+
.fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
14
|
+
.fdy-title-section{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-snug);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
15
|
+
.fdy-title-card{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0;}
|
|
16
|
+
.fdy-text-muted{color:var(--color-text-muted);}
|
|
17
|
+
.fdy-text-subtle{color:var(--color-text-subtle);}
|
|
18
|
+
.fdy-text-caption{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;}
|
|
19
|
+
|
|
20
|
+
/* ---- Page: the vertical stack of sections, with the section rhythm between them ------------- */
|
|
21
|
+
.fdy-page{display:flex;flex-direction:column;gap:var(--space-8);}
|
|
22
|
+
|
|
23
|
+
/* ---- Page header: eyebrow + title + description on the left, primary action(s) on the right -- */
|
|
24
|
+
.fdy-page__header{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--space-4);flex-wrap:wrap;}
|
|
25
|
+
.fdy-page__heading{display:flex;flex-direction:column;gap:var(--space-2);min-width:0;}
|
|
26
|
+
.fdy-page__desc{font-size:var(--text-base);color:var(--color-text-muted);margin:0;max-width:62ch;line-height:var(--leading-normal);}
|
|
27
|
+
.fdy-page__actions{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
|
|
28
|
+
|
|
29
|
+
/* ---- Page section: a heading (+ optional toolbar) and its body, with the intra-group rhythm --- */
|
|
30
|
+
.fdy-page-section{display:flex;flex-direction:column;gap:var(--space-4);}
|
|
31
|
+
.fdy-page-section__head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;}
|
|
32
|
+
|
|
33
|
+
/* ---- Toolbar: a horizontal control row (search + actions), wraps cleanly ---------------------- */
|
|
34
|
+
.fdy-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;}
|
|
35
|
+
.fdy-toolbar__spacer{flex:1;}
|
|
36
|
+
.fdy-toolbar__search{max-width:18rem;flex:1 1 12rem;}
|
|
37
|
+
|
|
38
|
+
/* ---- Stat tile: a KPI metric that is deliberately NOT a card, so a KPI strip doesn't become an
|
|
39
|
+
* identical-white-card grid. Flat by default; wrap the .fdy-stats grid in one card if you want a
|
|
40
|
+
* single boxed strip, or use .fdy-stats--boxed for shared surface + dividers. */
|
|
41
|
+
.fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
|
|
42
|
+
.fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
|
|
43
|
+
.fdy-stat__label{font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-subtle);}
|
|
44
|
+
.fdy-stat__value{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:1;color:var(--color-text);font-variant-numeric:tabular-nums;}
|
|
45
|
+
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
46
|
+
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
|
47
|
+
.fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
|
|
48
|
+
.fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
|
|
49
|
+
.fdy-stats--boxed .fdy-stat:last-child{border-right:0;}
|
|
@@ -3,5 +3,10 @@
|
|
|
3
3
|
.fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
|
|
4
4
|
.fdy-skeleton--title{height:1.2rem;width:40%;}
|
|
5
5
|
.fdy-skeleton--circle{border-radius:var(--radius-full);}
|
|
6
|
+
/* Size-matched to .fdy-avatar (2.5rem) and its --sm/--lg, so a skeleton reserves the exact avatar
|
|
7
|
+
* box and data landing causes no layout shift (a plain --circle has no dimensions of its own). */
|
|
8
|
+
.fdy-skeleton--avatar{width:2.5rem;height:2.5rem;border-radius:var(--radius-full);}
|
|
9
|
+
.fdy-skeleton--avatar-sm{width:2rem;height:2rem;border-radius:var(--radius-full);}
|
|
10
|
+
.fdy-skeleton--avatar-lg{width:3.5rem;height:3.5rem;border-radius:var(--radius-full);}
|
|
6
11
|
@keyframes fdy-skeleton{to{background-position:-200% 0;}}
|
|
7
12
|
@media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
|
package/src/freeday-toast.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
/* Freeday — toast API (optional, zero-dependency).
|
|
2
|
-
* Freeday.toast({ variant, title, message, timeout }) shows a transient
|
|
2
|
+
* Freeday.toast({ variant, title, message, timeout, key }) shows a transient
|
|
3
3
|
* notification in a live region (created on first use). Returns the element.
|
|
4
4
|
* variant: 'success' | 'warning' | 'danger' | 'info' (default: neutral)
|
|
5
5
|
* timeout: ms before auto-dismiss (default 4000; 0 = sticky)
|
|
6
|
+
* key: optional stable id — a new toast with the same key REPLACES the
|
|
7
|
+
* existing one in place instead of stacking a duplicate (e.g. a burst
|
|
8
|
+
* of identical failures shows one toast, refreshed).
|
|
9
|
+
* Freeday.dismiss(nodeOrKey) closes a toast early (pass the returned element or its key).
|
|
6
10
|
* Danger toasts use role="alert"; others role="status".
|
|
7
11
|
*/
|
|
8
12
|
(function () {
|
|
9
13
|
'use strict';
|
|
10
14
|
|
|
11
15
|
var region = null;
|
|
16
|
+
var keyed = {}; // key -> current node, for replace-in-place + dismiss-by-key
|
|
17
|
+
|
|
18
|
+
function forget(node) {
|
|
19
|
+
var k = node && node._fdyKey;
|
|
20
|
+
if (k != null && keyed[k] === node) delete keyed[k];
|
|
21
|
+
}
|
|
12
22
|
|
|
13
23
|
function ensureRegion() {
|
|
14
24
|
if (region && document.body.contains(region)) return region;
|
|
@@ -24,7 +34,9 @@
|
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
function dismiss(toast) {
|
|
37
|
+
if (typeof toast === 'string') toast = keyed[toast]; // dismiss by key
|
|
27
38
|
if (!toast || toast.dataset.leaving === '1') return;
|
|
39
|
+
forget(toast);
|
|
28
40
|
toast.dataset.leaving = '1';
|
|
29
41
|
toast.classList.add('is-leaving');
|
|
30
42
|
var remove = function () { if (toast.parentNode) toast.parentNode.removeChild(toast); };
|
|
@@ -64,6 +76,14 @@
|
|
|
64
76
|
node.appendChild(accent);
|
|
65
77
|
node.appendChild(body);
|
|
66
78
|
node.appendChild(close);
|
|
79
|
+
|
|
80
|
+
// Replace-in-place: a same-key toast removes the previous one so bursts don't stack.
|
|
81
|
+
if (opts.key != null) {
|
|
82
|
+
var prev = keyed[opts.key];
|
|
83
|
+
if (prev && prev.parentNode) prev.parentNode.removeChild(prev);
|
|
84
|
+
node._fdyKey = opts.key;
|
|
85
|
+
keyed[opts.key] = node;
|
|
86
|
+
}
|
|
67
87
|
region.appendChild(node);
|
|
68
88
|
|
|
69
89
|
var timeout = opts.timeout == null ? 4000 : opts.timeout;
|
|
@@ -81,4 +101,5 @@
|
|
|
81
101
|
|
|
82
102
|
window.Freeday = window.Freeday || {};
|
|
83
103
|
window.Freeday.toast = toast;
|
|
104
|
+
window.Freeday.dismiss = dismiss; // dismiss(nodeOrKey)
|
|
84
105
|
})();
|
package/tokens/tokens.json
CHANGED
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"weight": { "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" } },
|
|
43
43
|
|
|
44
44
|
"space": {
|
|
45
|
-
"0": { "$value": "0" }, "1": { "$value": "0.25rem" }, "2": { "$value": "0.5rem" }, "3": { "$value": "0.75rem" },
|
|
46
|
-
"4": { "$value": "1rem" }, "5": { "$value": "1.25rem" }, "6": { "$value": "1.5rem" }, "8": { "$value": "2rem" },
|
|
45
|
+
"0": { "$value": "0" }, "1": { "$value": "0.25rem" }, "2": { "$value": "0.5rem" }, "3": { "$value": "0.75rem", "$compact": "0.625rem" },
|
|
46
|
+
"4": { "$value": "1rem", "$compact": "0.75rem" }, "5": { "$value": "1.25rem", "$compact": "1rem" }, "6": { "$value": "1.5rem", "$compact": "1.25rem" }, "8": { "$value": "2rem" },
|
|
47
47
|
"10": { "$value": "2.5rem" }, "12": { "$value": "3rem" }, "16": { "$value": "4rem" }, "20": { "$value": "5rem" }, "24": { "$value": "6rem" }
|
|
48
48
|
},
|
|
49
49
|
"radius": { "xs": { "$value": "3px" }, "sm": { "$value": "4px" }, "md": { "$value": "6px" }, "lg": { "$value": "10px" }, "xl": { "$value": "14px" }, "full": { "$value": "999px" } },
|
|
@@ -118,6 +118,18 @@
|
|
|
118
118
|
"tick": { "$value": "{slate.400}", "$dark": "{slate.600}" }
|
|
119
119
|
},
|
|
120
120
|
|
|
121
|
+
"_comment_tone": "General categorical palette — the same 8 validated hues as chart, aliased under a chart-neutral name for avatars, chips, tags, legend swatches (theme-follows via chart's own $dark).",
|
|
122
|
+
"tone": {
|
|
123
|
+
"1": { "$value": "{chart.1}" },
|
|
124
|
+
"2": { "$value": "{chart.2}" },
|
|
125
|
+
"3": { "$value": "{chart.3}" },
|
|
126
|
+
"4": { "$value": "{chart.4}" },
|
|
127
|
+
"5": { "$value": "{chart.5}" },
|
|
128
|
+
"6": { "$value": "{chart.6}" },
|
|
129
|
+
"7": { "$value": "{chart.7}" },
|
|
130
|
+
"8": { "$value": "{chart.8}" }
|
|
131
|
+
},
|
|
132
|
+
|
|
121
133
|
"focus-ring": { "$value": "{azure.600}", "$dark": "{azure.400}" },
|
|
122
134
|
"focus-ring-width": { "$value": "2px" }
|
|
123
135
|
}
|