@cahyo-dimas/freeday 1.16.0 → 1.17.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 +29 -0
- package/README.id.md +18 -8
- package/README.md +18 -8
- package/dist/freeday-toast.js +22 -1
- package/dist/freeday.bundle.css +27 -0
- package/dist/freeday.css +27 -0
- package/dist/freeday.d.ts +5 -0
- package/dist/freeday.js +22 -1
- package/package.json +1 -1
- package/src/base.css +5 -0
- package/src/components/avatar.css +13 -0
- package/src/components/card.css +4 -0
- package/src/components/skeleton.css +5 -0
- package/src/freeday-toast.js +22 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
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.17.0] — 2026-08-11
|
|
7
|
+
### Added
|
|
8
|
+
- **Avatar identity tones (`--tone-1`…`--tone-8`).** Decorative tints (from the categorical chart
|
|
9
|
+
palette) with a theme-aware, text-leaning foreground, so same-initial avatars — common where many
|
|
10
|
+
names share a prefix — stay distinguishable. Verified WCAG **AA** (≥4.5:1) in light **and** dark,
|
|
11
|
+
guarded by `test/contrast.test.mjs` (which gained a `color-mix()` evaluator). Hash a stable index
|
|
12
|
+
off the full name, not the initials.
|
|
13
|
+
- **Size-matched skeletons (`.fdy-skeleton--avatar` + `--avatar-sm`/`--avatar-lg`).** Reserve the exact
|
|
14
|
+
`.fdy-avatar` box (2 / 2.5 / 3.5rem) so data landing causes no layout shift (a bare `--circle` has no
|
|
15
|
+
dimensions of its own).
|
|
16
|
+
- **`.fdy-card--button`.** When the whole card *is* the control — a real `<button>` picker row, so it is
|
|
17
|
+
keyboard-reachable and announced — this resets the UA button box **without** stripping the card's own
|
|
18
|
+
surface/border.
|
|
19
|
+
- **`.fdy-list-reset`.** Opt-in list reset for consumers who pair Freeday with a utility framework run
|
|
20
|
+
preflight-off (`base.css` is intentionally a *light* reset — it does not strip `ul`/`ol`/`p` margins).
|
|
21
|
+
- **Toast `key` + `Freeday.dismiss()`.** `toast({ …, key })` replaces an existing same-key toast in
|
|
22
|
+
place instead of stacking a duplicate (a burst of identical failures shows one, refreshed);
|
|
23
|
+
`Freeday.dismiss(node | key)` closes one early. `toast()` already returned the element — both are now
|
|
24
|
+
in the `window.Freeday` type declarations.
|
|
25
|
+
### Docs
|
|
26
|
+
- State the scope boundary (components + tokens, **not** layout; pair with a utility framework run
|
|
27
|
+
preflight-off) in the README and getting-started; document that `--space-*`/`--radius-*` are public
|
|
28
|
+
tokens to build a utility theme on, and that `data-density` only re-scales control height.
|
|
29
|
+
- Input docs now lead with `aria-invalid="true"` as the canonical accessible-field pattern (it does
|
|
30
|
+
both the visual state and the one screen readers act on); `.fdy-input--error` is the fallback.
|
|
31
|
+
|
|
32
|
+
Additive; no breaking changes. Gate: `node --test` 22/22 (incl. avatar-tone contrast, both themes) ·
|
|
33
|
+
`typecheck:react` 0 · new CSS/JS browser-verified in real Chrome. From real-app consumption feedback.
|
|
34
|
+
|
|
6
35
|
## [1.16.0] — 2026-08-11
|
|
7
36
|
### Added
|
|
8
37
|
- **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.17.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,14 @@ 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
|
+
> **Scope: komponen + token, bukan layout.** Freeday punya komponen & design token; helper layout
|
|
53
|
+
> cuma `.fdy-hidden` / `.fdy-visually-hidden`. Layout (stack/grid/gap) dari layer-mu sendiri —
|
|
54
|
+
> pasangkan dengan utility framework (Tailwind, UnoCSS…) mode **utilities-only, preflight OFF**
|
|
55
|
+
> (`base.css` Freeday = reset-nya). `base.css` itu reset *ringan* (tak me-reset margin `ul`/`ol`/`p` —
|
|
56
|
+
> pakai `.fdy-list-reset` atau komponen list Freeday), dan skala spacing/radius/durasi adalah custom
|
|
57
|
+
> property publik (`--space-0`…`--space-24`, …) yang bisa jadi dasar theme utility-mu. Detail:
|
|
58
|
+
> [`docs/getting-started.md` §Core concepts](docs/getting-started.md).
|
|
59
|
+
|
|
52
60
|
> `.fdy-btn` **sudah** tombol primary — tak ada modifier `.fdy-btn--primary` terpisah.
|
|
53
61
|
> Modifier untuk varian lain: `--ghost`, `--danger`, `--text`, `--sm`, `--lg`, `--icon`
|
|
54
62
|
> (lihat `docs/index.html`).
|
|
@@ -126,17 +134,19 @@ import { useFreeday } from '@cahyo-dimas/freeday/react';
|
|
|
126
134
|
const root = useRef<HTMLDivElement>(null);
|
|
127
135
|
useFreeday(root); // event fdy-* bubbling → listen di root
|
|
128
136
|
```
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
```razor
|
|
138
|
+
@* Blazor — RCL Freeday.Blazor (net8.0): komponen native typed <FdyX> dengan @bind, 10/10 parity *@
|
|
139
|
+
@using Freeday.Blazor
|
|
140
|
+
<FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl" />
|
|
141
|
+
<FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)" PageSize="10" />
|
|
142
|
+
@* Fallback untuk markup mentah: interop window.FreedayBlazor.initAll / .on *@
|
|
133
143
|
```
|
|
134
144
|
|
|
135
145
|
| Framework | Adapter | Contoh jalan |
|
|
136
146
|
|---|---|---|
|
|
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 (.
|
|
147
|
+
| Vue 3 | `@cahyo-dimas/freeday/vue` (`useFreeday` + komponen `v-model`) | [`examples/vue-faktur/`](examples/vue-faktur/) (`npm install && npm run dev`) |
|
|
148
|
+
| React 19 | `@cahyo-dimas/freeday/react` (`useFreeday` + komponen `value`/`onChange`) | [`examples/react-faktur/`](examples/react-faktur/) (`npm install && npm run dev`) |
|
|
149
|
+
| Blazor WASM (net8.0) | RCL `Freeday.Blazor` (`<FdyX>` + `@bind`) · interop `/blazor` | [`examples/blazor-faktur/`](examples/blazor-faktur/) (`dotnet run`) |
|
|
140
150
|
|
|
141
151
|
Peta library & pola lengkap: [`docs/integrations.md`](docs/integrations.md).
|
|
142
152
|
|
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.17.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,14 @@ 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
|
+
> **Scope: components + tokens, not layout.** Freeday owns components and design tokens; the only
|
|
53
|
+
> layout helpers are `.fdy-hidden` / `.fdy-visually-hidden`. Bring your own layout layer — pair it
|
|
54
|
+
> with a utility framework (Tailwind, UnoCSS…) run **utilities-only with preflight OFF** (Freeday's
|
|
55
|
+
> `base.css` is the reset). `base.css` is a *light* reset (it doesn't strip `ul`/`ol`/`p` margins —
|
|
56
|
+
> use `.fdy-list-reset` or a Freeday list component), and the spacing/radius/duration scales are
|
|
57
|
+
> public custom properties (`--space-0`…`--space-24`, …) you can build your utility theme on. Full
|
|
58
|
+
> notes: [`docs/getting-started.md` §Core concepts](docs/getting-started.md).
|
|
59
|
+
|
|
52
60
|
> `.fdy-btn` is **already** the primary button — there's no separate `.fdy-btn--primary` modifier.
|
|
53
61
|
> Modifiers for other variants: `--ghost`, `--danger`, `--text`, `--sm`, `--lg`, `--icon`
|
|
54
62
|
> (see `docs/index.html`).
|
|
@@ -126,17 +134,19 @@ import { useFreeday } from '@cahyo-dimas/freeday/react';
|
|
|
126
134
|
const root = useRef<HTMLDivElement>(null);
|
|
127
135
|
useFreeday(root); // fdy-* events bubble → listen at the root
|
|
128
136
|
```
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
```razor
|
|
138
|
+
@* Blazor — Freeday.Blazor RCL (net8.0): native typed <FdyX> with @bind, 10/10 parity *@
|
|
139
|
+
@using Freeday.Blazor
|
|
140
|
+
<FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl" />
|
|
141
|
+
<FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)" PageSize="10" />
|
|
142
|
+
@* Fallback for raw markup: window.FreedayBlazor.initAll / .on interop *@
|
|
133
143
|
```
|
|
134
144
|
|
|
135
145
|
| Framework | Adapter | Working example |
|
|
136
146
|
|---|---|---|
|
|
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 (.
|
|
147
|
+
| Vue 3 | `@cahyo-dimas/freeday/vue` (`useFreeday` + `v-model` components) | [`examples/vue-faktur/`](examples/vue-faktur/) (`npm install && npm run dev`) |
|
|
148
|
+
| React 19 | `@cahyo-dimas/freeday/react` (`useFreeday` + `value`/`onChange` components) | [`examples/react-faktur/`](examples/react-faktur/) (`npm install && npm run dev`) |
|
|
149
|
+
| Blazor WASM (net8.0) | `Freeday.Blazor` RCL (`<FdyX>` + `@bind`) · `/blazor` interop | [`examples/blazor-faktur/`](examples/blazor-faktur/) (`dotnet run`) |
|
|
140
150
|
|
|
141
151
|
Full library map and patterns: [`docs/integrations.md`](docs/integrations.md).
|
|
142
152
|
|
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
|
@@ -361,6 +361,11 @@ a { color: var(--color-primary); }
|
|
|
361
361
|
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
|
|
362
362
|
:where(button, input, select, textarea) { font: inherit; }
|
|
363
363
|
.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; }
|
|
364
|
+
/* Opt-in list reset. base.css is intentionally a *light* reset — it does NOT strip list/paragraph
|
|
365
|
+
* margins (see the kit's own list components, which each reset themselves). If you run a utility
|
|
366
|
+
* framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
|
|
367
|
+
* bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
|
|
368
|
+
.fdy-list-reset { list-style: none; margin: 0; padding: 0; }
|
|
364
369
|
@media (prefers-reduced-motion: reduce) {
|
|
365
370
|
*, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
|
|
366
371
|
}
|
|
@@ -524,6 +529,19 @@ a { color: var(--color-primary); }
|
|
|
524
529
|
.fdy-avatar-group{display:inline-flex;}
|
|
525
530
|
.fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
|
|
526
531
|
.fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
|
|
532
|
+
/* Decorative identity tones — a soft tint of a categorical chart colour with a theme-aware,
|
|
533
|
+
* text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
|
|
534
|
+
* test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
|
|
535
|
+
* hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
|
|
536
|
+
.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));}
|
|
537
|
+
.fdy-avatar--tone-1{--_fdy-avatar-tone:var(--chart-1);}
|
|
538
|
+
.fdy-avatar--tone-2{--_fdy-avatar-tone:var(--chart-2);}
|
|
539
|
+
.fdy-avatar--tone-3{--_fdy-avatar-tone:var(--chart-3);}
|
|
540
|
+
.fdy-avatar--tone-4{--_fdy-avatar-tone:var(--chart-4);}
|
|
541
|
+
.fdy-avatar--tone-5{--_fdy-avatar-tone:var(--chart-5);}
|
|
542
|
+
.fdy-avatar--tone-6{--_fdy-avatar-tone:var(--chart-6);}
|
|
543
|
+
.fdy-avatar--tone-7{--_fdy-avatar-tone:var(--chart-7);}
|
|
544
|
+
.fdy-avatar--tone-8{--_fdy-avatar-tone:var(--chart-8);}
|
|
527
545
|
|
|
528
546
|
/* Freeday — Badge */
|
|
529
547
|
.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 +661,10 @@ a { color: var(--color-primary); }
|
|
|
643
661
|
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
|
|
644
662
|
.fdy-card--interactive{cursor:pointer;}
|
|
645
663
|
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
|
|
664
|
+
/* When the whole card IS the control — a real <button> so the row is keyboard-reachable and
|
|
665
|
+
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
666
|
+
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
667
|
+
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
646
668
|
.fdy-card__body{padding:var(--space-5);}
|
|
647
669
|
.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
670
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -1202,6 +1224,11 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1202
1224
|
.fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
|
|
1203
1225
|
.fdy-skeleton--title{height:1.2rem;width:40%;}
|
|
1204
1226
|
.fdy-skeleton--circle{border-radius:var(--radius-full);}
|
|
1227
|
+
/* Size-matched to .fdy-avatar (2.5rem) and its --sm/--lg, so a skeleton reserves the exact avatar
|
|
1228
|
+
* box and data landing causes no layout shift (a plain --circle has no dimensions of its own). */
|
|
1229
|
+
.fdy-skeleton--avatar{width:2.5rem;height:2.5rem;border-radius:var(--radius-full);}
|
|
1230
|
+
.fdy-skeleton--avatar-sm{width:2rem;height:2rem;border-radius:var(--radius-full);}
|
|
1231
|
+
.fdy-skeleton--avatar-lg{width:3.5rem;height:3.5rem;border-radius:var(--radius-full);}
|
|
1205
1232
|
@keyframes fdy-skeleton{to{background-position:-200% 0;}}
|
|
1206
1233
|
@media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
|
|
1207
1234
|
|
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(--chart-1);}
|
|
193
|
+
.fdy-avatar--tone-2{--_fdy-avatar-tone:var(--chart-2);}
|
|
194
|
+
.fdy-avatar--tone-3{--_fdy-avatar-tone:var(--chart-3);}
|
|
195
|
+
.fdy-avatar--tone-4{--_fdy-avatar-tone:var(--chart-4);}
|
|
196
|
+
.fdy-avatar--tone-5{--_fdy-avatar-tone:var(--chart-5);}
|
|
197
|
+
.fdy-avatar--tone-6{--_fdy-avatar-tone:var(--chart-6);}
|
|
198
|
+
.fdy-avatar--tone-7{--_fdy-avatar-tone:var(--chart-7);}
|
|
199
|
+
.fdy-avatar--tone-8{--_fdy-avatar-tone:var(--chart-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,10 @@ 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
|
+
/* When the whole card IS the control — a real <button> so the row is keyboard-reachable and
|
|
320
|
+
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
321
|
+
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
322
|
+
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
301
323
|
.fdy-card__body{padding:var(--space-5);}
|
|
302
324
|
.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
325
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -857,6 +879,11 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
857
879
|
.fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
|
|
858
880
|
.fdy-skeleton--title{height:1.2rem;width:40%;}
|
|
859
881
|
.fdy-skeleton--circle{border-radius:var(--radius-full);}
|
|
882
|
+
/* Size-matched to .fdy-avatar (2.5rem) and its --sm/--lg, so a skeleton reserves the exact avatar
|
|
883
|
+
* box and data landing causes no layout shift (a plain --circle has no dimensions of its own). */
|
|
884
|
+
.fdy-skeleton--avatar{width:2.5rem;height:2.5rem;border-radius:var(--radius-full);}
|
|
885
|
+
.fdy-skeleton--avatar-sm{width:2rem;height:2rem;border-radius:var(--radius-full);}
|
|
886
|
+
.fdy-skeleton--avatar-lg{width:3.5rem;height:3.5rem;border-radius:var(--radius-full);}
|
|
860
887
|
@keyframes fdy-skeleton{to{background-position:-200% 0;}}
|
|
861
888
|
@media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
|
|
862
889
|
|
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/package.json
CHANGED
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(--chart-1);}
|
|
15
|
+
.fdy-avatar--tone-2{--_fdy-avatar-tone:var(--chart-2);}
|
|
16
|
+
.fdy-avatar--tone-3{--_fdy-avatar-tone:var(--chart-3);}
|
|
17
|
+
.fdy-avatar--tone-4{--_fdy-avatar-tone:var(--chart-4);}
|
|
18
|
+
.fdy-avatar--tone-5{--_fdy-avatar-tone:var(--chart-5);}
|
|
19
|
+
.fdy-avatar--tone-6{--_fdy-avatar-tone:var(--chart-6);}
|
|
20
|
+
.fdy-avatar--tone-7{--_fdy-avatar-tone:var(--chart-7);}
|
|
21
|
+
.fdy-avatar--tone-8{--_fdy-avatar-tone:var(--chart-8);}
|
package/src/components/card.css
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
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
|
+
/* When the whole card IS the control — a real <button> so the row is keyboard-reachable and
|
|
7
|
+
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
8
|
+
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
9
|
+
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
6
10
|
.fdy-card__body{padding:var(--space-5);}
|
|
7
11
|
.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
12
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -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
|
})();
|