@cahyo-dimas/freeday 1.29.0 → 1.31.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 +76 -0
- package/COMPONENTS.md +9 -4
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/USAGE.md +22 -0
- package/dist/freeday.bundle.css +30 -7
- package/dist/freeday.css +13 -3
- package/dist/freeday.tokens.css +17 -4
- package/docs/getting-started.md +1 -1
- package/package.json +1 -1
- package/src/components/card.css +2 -2
- package/src/components/composition.css +4 -1
- package/src/components/list.css +7 -0
- package/tokens/tokens.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,82 @@
|
|
|
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.31.0] — 2026-08-18
|
|
7
|
+
Improvement note 002: density was a one-way door.
|
|
8
|
+
### Added
|
|
9
|
+
- **`[data-density="comfortable"]` is now a real rule.** The kit shipped only the `compact` block,
|
|
10
|
+
and its own comment promised per-subtree density — true in one direction only. These are
|
|
11
|
+
inheriting custom properties, so once `<html>` is compact *every* subtree is compact and a
|
|
12
|
+
`comfortable` wrapper matched nothing at all. An app that is dense overall (a back office where
|
|
13
|
+
most screens are data grids) could not opt its shared chrome back out: measured against a sibling
|
|
14
|
+
product, the logo sat 12px from the edge instead of 16 and the avatar 12 instead of 20 — four
|
|
15
|
+
pixels in two places, from `--space-4` and `--control-h`, which reads as sloppiness rather than as
|
|
16
|
+
a token. The workaround, restating the five defaults on a local class, is exactly the copy that
|
|
17
|
+
goes stale when the kit retunes a step.
|
|
18
|
+
Note the kit's own README has been telling people to write `data-density="comfortable"` on `<html>`
|
|
19
|
+
since long before a rule existed for it — harmless there, because it matched the defaults by
|
|
20
|
+
accident, and broken the moment anyone needed it on a wrapper.
|
|
21
|
+
### Notes on the shape of the fix
|
|
22
|
+
- The five values are **derived from the tokens' own defaults** at build time, not written out a
|
|
23
|
+
second time. Same key set by construction, so a token that gains a `$compact` value automatically
|
|
24
|
+
gains its way back out, and a retuned default can never leave the two blocks disagreeing. The
|
|
25
|
+
report's hand-written block was correct in all five values — this only removes the chance for it
|
|
26
|
+
to stop being correct.
|
|
27
|
+
### Added — guards
|
|
28
|
+
- `test/build.test.mjs` asserts the two density blocks cover the *same* tokens, that every
|
|
29
|
+
comfortable value equals the `:root` default, and that none of them equals its compact counterpart
|
|
30
|
+
(a block that resets nothing is as green as one that works).
|
|
31
|
+
- `browser/theme.mjs` measures it in a real engine: a compact root, a `comfortable` island that
|
|
32
|
+
returns to a 40px control and `--space-4: 1rem`, and a `compact` island nested back inside that —
|
|
33
|
+
because the point of the rule is the box that comes out, not the declaration going in.
|
|
34
|
+
|
|
35
|
+
## [1.30.0] — 2026-08-18
|
|
36
|
+
Note 008 (adopting 1.29.0) plus a direct design report: *"the input border looks too dark next to the
|
|
37
|
+
card and button-group borders."*
|
|
38
|
+
### Fixed
|
|
39
|
+
- **The dark theme's contrast has not actually been tested since v1.21.0.** `contrast.test.mjs` looked
|
|
40
|
+
for `:root[data-theme="dark"]`; un-rooting the theme selectors in 1.21.0 changed the emitted
|
|
41
|
+
selector to `[data-theme="dark"]`, so the regex matched nothing, `dark` fell back to the **light**
|
|
42
|
+
values, and every `WCAG contrast — DARK` assertion re-tested the light theme under a dark label —
|
|
43
|
+
54 declarations ignored, all green. Fixed, and the scope now has a guard of its own: a dark scope
|
|
44
|
+
that parses to nothing, or that resolves to the light surface, fails loudly. *No latent dark-theme
|
|
45
|
+
failures were hiding behind it — the theme passes on its own merits.*
|
|
46
|
+
- **`--color-control-border` retuned** to a new ramp step `--slate-450` (`#798295`). It was
|
|
47
|
+
`--slate-500`, the same ink as `--color-text-subtle`: a control **boundary** carrying text-level
|
|
48
|
+
contrast (4.69:1 on surface where WCAG 1.4.11 asks 3:1), which is what makes a form look heavier
|
|
49
|
+
than the cards around it. Now ≈3.9/3.6/3.4 across the three light surfaces. The same step
|
|
50
|
+
*strengthens* the dark theme, which was sitting at **3.02:1** on `surface-3` — 0.02 above the
|
|
51
|
+
floor, and unguarded because of the bug above; it is now 3.66:1.
|
|
52
|
+
- **`.fdy-stat__label` uses `--color-text-muted`.** It spent `--color-text-subtle`, which is gated at
|
|
53
|
+
3.0 *on purpose* (placeholders, dividers, decorative glyphs) and measures 4.41:1 on `surface-2` —
|
|
54
|
+
under AA for text, on the only thing naming the number above it. Reported in 006 §6 and still
|
|
55
|
+
carried by the app at 1.29.0.
|
|
56
|
+
### Added
|
|
57
|
+
- **`.fdy-btn--stretch` now works in a `.fdy-list__row`** (note 008 §1). The pattern existed for
|
|
58
|
+
"one card, one primary action, one escape hatch" and had no equivalent for the same sentence with
|
|
59
|
+
*row* in it — an app rendering both grid and list from one component had to hand-roll the row half.
|
|
60
|
+
The row anchors the overlay itself, opt-in through `:has(.fdy-btn--stretch)` so a plain row is
|
|
61
|
+
untouched and nothing an app pinned inside one starts resolving against a new containing block.
|
|
62
|
+
Measured before the fix: **every** row's overlay covered the whole list and the last one in the DOM
|
|
63
|
+
won, so a click on row one opened row two — under row one's name.
|
|
64
|
+
### Notes on the shape of the fix
|
|
65
|
+
- **Note 008 §2 is not reproducible** and no code shipped for it. With a positive control to prove
|
|
66
|
+
the measurement works (pointing at the card body *does* put the stretched target in `:hover`),
|
|
67
|
+
pointing at the escape hatch leaves it at rest: the hover chain is `HTML > BODY > card > footer >
|
|
68
|
+
hatch`, and the stretched button is a sibling, not an ancestor. The `pointer-events:none` rule the
|
|
69
|
+
report carries fixes something the kit's own composition does not do.
|
|
70
|
+
- The report's contrast figures (4.41 / 4.69) were re-derived from the 1.29 ramp and are exact.
|
|
71
|
+
### Added — guards
|
|
72
|
+
- `contrast.test.mjs`: the control border is now asserted on **all three** surfaces (`surface-3` was
|
|
73
|
+
missing, and it is the worst case that pinned the dark theme to the floor) and at **3.25**, not
|
|
74
|
+
3.0 — a boundary that clears the floor by 0.02 has no headroom, and the margin is what catches a
|
|
75
|
+
revert to the old ink.
|
|
76
|
+
- `css.test.mjs`: a stat label must spend a *text* ink, and a list row hosting a stretched target
|
|
77
|
+
must anchor it — both are class↔token pairings that every token-level assertion passes straight
|
|
78
|
+
through.
|
|
79
|
+
- `browser/card-stretch.mjs`: the list case, asserting **row one** (the point that hides the bug is
|
|
80
|
+
row two, which passes even when the overlay belongs to another row).
|
|
81
|
+
|
|
6
82
|
## [1.29.0] — 2026-08-18
|
|
7
83
|
Improvement note 001 from a second consuming app (a 40-screen back office). Eight findings; six
|
|
8
84
|
executed, one already documented, one left as an owner decision.
|
package/COMPONENTS.md
CHANGED
|
@@ -232,7 +232,8 @@ utility variant that has to agree with the shell must use `nav`; using `md` leav
|
|
|
232
232
|
|
|
233
233
|
Density: `data-density="compact"` works on `<html>` **or any wrapper** — the selector is a bare
|
|
234
234
|
`[data-density="compact"]` over inheriting custom properties, so one screen can be dense while the
|
|
235
|
-
rest of the app is not.
|
|
235
|
+
rest of the app is not. `data-density="comfortable"` is a real rule too, so the reverse also works:
|
|
236
|
+
a compact root with one region opted back out (shared chrome that must match a sibling product).
|
|
236
237
|
|
|
237
238
|
---
|
|
238
239
|
|
|
@@ -962,9 +963,13 @@ while it stays a real `<button>`/`<a>` with real keyboard semantics.
|
|
|
962
963
|
- **Do not add `.fdy-btn--stretch` to more than one control per card** — the last one wins the
|
|
963
964
|
overlap, and which one that is depends on source order.
|
|
964
965
|
- Text under the overlay is no longer selectable; that is the cost of the pattern, not a bug.
|
|
965
|
-
- **Cards
|
|
966
|
-
`.fdy-card
|
|
967
|
-
|
|
966
|
+
- **Cards and list rows.** The overlay anchors to the nearest *positioned* ancestor, and both hosts
|
|
967
|
+
supply one: `.fdy-card` always, `.fdy-list__row` when it contains a stretched target. Everything
|
|
968
|
+
above applies to a row unchanged — same markup, same automatic raise. In any *other* container,
|
|
969
|
+
make the element `position:relative` yourself, or the overlay resolves against whatever is
|
|
970
|
+
positioned further up (in a list that used to be `.fdy-list` itself, so every row's target covered
|
|
971
|
+
the whole list and the last one in the DOM won — a click on row one opened row two).
|
|
972
|
+
A row whose *whole* surface is one control is still better as `.fdy-list__row--button`.
|
|
968
973
|
- `.fdy-btn` nudges itself on `:hover`/`:active`, and a transformed element becomes the containing
|
|
969
974
|
block for its own absolutely positioned descendants — so a hand-rolled `::after{inset:0}` overlay
|
|
970
975
|
**re-anchors to the button mid-gesture** and the click never lands. `--stretch` neutralises those
|
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.31.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`.
|
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.31.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`.
|
package/USAGE.md
CHANGED
|
@@ -27,6 +27,21 @@ the single biggest cause of "flat grey mush". Pick the role, not the size:
|
|
|
27
27
|
Everything else is body. If you reach for a fourth title size, you probably need a section, not a
|
|
28
28
|
font size.
|
|
29
29
|
|
|
30
|
+
|
|
31
|
+
## Borders: two roles, two contrast contracts
|
|
32
|
+
|
|
33
|
+
`--color-border` (and `-muted` / `-strong`) is **decoration** — a card edge, a table rule, a
|
|
34
|
+
separator. Nothing in WCAG requires it to be visible, and it is deliberately faint (≈1.3:1).
|
|
35
|
+
|
|
36
|
+
`--color-control-border` is the **boundary of a control** — input, textarea, checkbox, radio,
|
|
37
|
+
switch, dropzone. WCAG 1.4.11 requires 3:1 against every surface it can sit on, so it is
|
|
38
|
+
necessarily darker than a card edge; that difference is the contract, not an inconsistency.
|
|
39
|
+
|
|
40
|
+
It is tuned to clear that floor with margin (≈3.4–3.9 depending on surface) rather than to be as
|
|
41
|
+
dark as it can be: a boundary that reads as loud as body text makes a form feel heavier than it is.
|
|
42
|
+
Do not "harmonise" a control border down to `--color-border` — that drops a required 3:1 boundary to
|
|
43
|
+
1.3:1.
|
|
44
|
+
|
|
30
45
|
## 2. Spacing rhythm — three gaps, always from the scale
|
|
31
46
|
|
|
32
47
|
Never a loose value; always `var(--space-N)` (a 4px scale). Three rhythms carry most layouts, and the
|
|
@@ -115,6 +130,13 @@ container, a single `<section>`. An app whose two list screens are dense and who
|
|
|
115
130
|
are not should scope it per screen rather than densifying everything. Set it at one level per screen,
|
|
116
131
|
never per component.
|
|
117
132
|
|
|
133
|
+
**And it goes both ways.** `data-density="comfortable"` is a real rule, not just the absence of
|
|
134
|
+
`compact`, so an app that is dense *at the root* can opt one region back out — a header, a toolbar,
|
|
135
|
+
a footer shared with a sibling product that has to line up pixel for pixel. Inheritance only runs
|
|
136
|
+
downhill; without that rule a compact root left every subtree compact and a `comfortable` wrapper
|
|
137
|
+
changed nothing. Do not restate the five values by hand: that copy goes stale the moment the kit
|
|
138
|
+
retunes a step.
|
|
139
|
+
|
|
118
140
|
## 6b. Navigation: one component, two orientations — and never a tab role
|
|
119
141
|
|
|
120
142
|
A navigation link is an `<a class="fdy-nav__item">` marked **`aria-current="page"`**. That does not
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
--slate-200: #e0e3ea;
|
|
19
19
|
--slate-300: #cbd0da;
|
|
20
20
|
--slate-400: #99a1b3;
|
|
21
|
+
--slate-450: #798295;
|
|
21
22
|
--slate-500: #6b7488;
|
|
22
23
|
--slate-600: #4d5568;
|
|
23
24
|
--slate-700: #3a4152;
|
|
@@ -138,7 +139,7 @@
|
|
|
138
139
|
--color-border: var(--slate-200);
|
|
139
140
|
--color-border-strong: var(--slate-300);
|
|
140
141
|
--color-border-muted: var(--slate-100);
|
|
141
|
-
--color-control-border: var(--slate-
|
|
142
|
+
--color-control-border: var(--slate-450);
|
|
142
143
|
--color-switch-thumb: var(--slate-0);
|
|
143
144
|
--color-shadow-inset: rgba(16,14,30,.06);
|
|
144
145
|
--color-inverse-surface: var(--slate-900);
|
|
@@ -209,7 +210,7 @@
|
|
|
209
210
|
--color-border: var(--slate-800);
|
|
210
211
|
--color-border-strong: var(--slate-700);
|
|
211
212
|
--color-border-muted: #1c2130;
|
|
212
|
-
--color-control-border: var(--slate-
|
|
213
|
+
--color-control-border: var(--slate-450);
|
|
213
214
|
--color-inverse-surface: var(--slate-100);
|
|
214
215
|
--color-inverse-text: var(--slate-900);
|
|
215
216
|
--color-success: var(--green-500);
|
|
@@ -272,7 +273,7 @@
|
|
|
272
273
|
--color-border: var(--slate-800);
|
|
273
274
|
--color-border-strong: var(--slate-700);
|
|
274
275
|
--color-border-muted: #1c2130;
|
|
275
|
-
--color-control-border: var(--slate-
|
|
276
|
+
--color-control-border: var(--slate-450);
|
|
276
277
|
--color-inverse-surface: var(--slate-100);
|
|
277
278
|
--color-inverse-text: var(--slate-900);
|
|
278
279
|
--color-success: var(--green-500);
|
|
@@ -328,7 +329,7 @@
|
|
|
328
329
|
--color-border: var(--slate-200);
|
|
329
330
|
--color-border-strong: var(--slate-300);
|
|
330
331
|
--color-border-muted: var(--slate-100);
|
|
331
|
-
--color-control-border: var(--slate-
|
|
332
|
+
--color-control-border: var(--slate-450);
|
|
332
333
|
--color-inverse-surface: var(--slate-900);
|
|
333
334
|
--color-inverse-text: var(--slate-0);
|
|
334
335
|
--color-success: var(--green-600);
|
|
@@ -367,6 +368,18 @@
|
|
|
367
368
|
--space-6: 1.25rem;
|
|
368
369
|
--control-h: 2rem;
|
|
369
370
|
}
|
|
371
|
+
/* ...and the way back out. Inheritance only runs downhill: once the root is compact every subtree is
|
|
372
|
+
* compact, and before this rule existed a data-density="comfortable" wrapper matched nothing — so the
|
|
373
|
+
* comment above was true in one direction only. An app that is dense overall and carries one region
|
|
374
|
+
* of shared chrome (a header, a toolbar, a footer shared with a sibling product) had to restate the
|
|
375
|
+
* defaults by hand, which is exactly the local copy that goes stale when the kit retunes them. */
|
|
376
|
+
[data-density="comfortable"] {
|
|
377
|
+
--space-3: 0.75rem;
|
|
378
|
+
--space-4: 1rem;
|
|
379
|
+
--space-5: 1.25rem;
|
|
380
|
+
--space-6: 1.5rem;
|
|
381
|
+
--control-h: 2.5rem;
|
|
382
|
+
}
|
|
370
383
|
|
|
371
384
|
/* Freeday components — GENERATED by tokens/build.mjs. Do not edit by hand. */
|
|
372
385
|
/* Freeday base layer — reset ringan + default via token */
|
|
@@ -779,13 +792,13 @@ a { color: var(--color-primary); }
|
|
|
779
792
|
because forgetting it fails SILENTLY: the secondary control looks and hovers exactly as before and
|
|
780
793
|
simply never receives the click, which is the very symptom the pattern exists to cure. Matched by
|
|
781
794
|
shape (anything focusable) so a control the kit does not ship yet is covered too. */
|
|
782
|
-
.fdy-card:has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
|
|
795
|
+
:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
|
|
783
796
|
/* The position is a DEFAULT, not an override: z-index needs a positioned box (or a flex/grid item),
|
|
784
797
|
but an app that deliberately pins a control inside the card — a corner dismiss, a favourite star —
|
|
785
798
|
must keep its own `position:absolute`. Wrapped in :where() so this whole selector weighs nothing
|
|
786
799
|
and ANY app rule wins it; measured, because at normal specificity the kit silently dragged an
|
|
787
800
|
absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
|
|
788
|
-
:where(.fdy-card:has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
|
|
801
|
+
:where(:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
|
|
789
802
|
.fdy-card__body{padding:var(--space-5);}
|
|
790
803
|
.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);}
|
|
791
804
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -1043,7 +1056,10 @@ a { color: var(--color-primary); }
|
|
|
1043
1056
|
* single boxed strip, or use .fdy-stats--boxed for shared surface + dividers. */
|
|
1044
1057
|
.fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
|
|
1045
1058
|
.fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
|
|
1046
|
-
|
|
1059
|
+
/* --color-text-muted, not -subtle: this is a LABEL, i.e. text, and -subtle is deliberately gated at
|
|
1060
|
+
3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41 — under AA
|
|
1061
|
+
for text, and a stat label is often the only thing naming the number above it. */
|
|
1062
|
+
.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-muted);}
|
|
1047
1063
|
.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;}
|
|
1048
1064
|
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
1049
1065
|
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
|
@@ -1343,6 +1359,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1343
1359
|
* Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
|
|
1344
1360
|
/* position:relative — containing block for out-of-flow row content (see base.css). */
|
|
1345
1361
|
.fdy-list{position:relative;list-style:none;margin:0;padding:0;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;}
|
|
1362
|
+
/* A row that hosts a stretched target has to be the anchor itself (#008 §1). Without this the
|
|
1363
|
+
overlay resolves against .fdy-list — which IS positioned — so EVERY row's target covers the whole
|
|
1364
|
+
list and the last one in the DOM wins: measured, a click on row one opens row two, under row one's
|
|
1365
|
+
name, which looks exactly like working software. Opt-in via :has(), so a row that doesn't use the
|
|
1366
|
+
pattern is untouched and nothing an app absolutely positions inside a plain row starts resolving
|
|
1367
|
+
against a new containing block. The escape-hatch raise lives in card.css beside the card's. */
|
|
1368
|
+
.fdy-list__row:has(.fdy-btn--stretch){position:relative;}
|
|
1346
1369
|
.fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
|
|
1347
1370
|
/* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
|
|
1348
1371
|
* of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
|
package/dist/freeday.css
CHANGED
|
@@ -409,13 +409,13 @@ a { color: var(--color-primary); }
|
|
|
409
409
|
because forgetting it fails SILENTLY: the secondary control looks and hovers exactly as before and
|
|
410
410
|
simply never receives the click, which is the very symptom the pattern exists to cure. Matched by
|
|
411
411
|
shape (anything focusable) so a control the kit does not ship yet is covered too. */
|
|
412
|
-
.fdy-card:has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
|
|
412
|
+
:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
|
|
413
413
|
/* The position is a DEFAULT, not an override: z-index needs a positioned box (or a flex/grid item),
|
|
414
414
|
but an app that deliberately pins a control inside the card — a corner dismiss, a favourite star —
|
|
415
415
|
must keep its own `position:absolute`. Wrapped in :where() so this whole selector weighs nothing
|
|
416
416
|
and ANY app rule wins it; measured, because at normal specificity the kit silently dragged an
|
|
417
417
|
absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
|
|
418
|
-
:where(.fdy-card:has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
|
|
418
|
+
:where(:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
|
|
419
419
|
.fdy-card__body{padding:var(--space-5);}
|
|
420
420
|
.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);}
|
|
421
421
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -673,7 +673,10 @@ a { color: var(--color-primary); }
|
|
|
673
673
|
* single boxed strip, or use .fdy-stats--boxed for shared surface + dividers. */
|
|
674
674
|
.fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
|
|
675
675
|
.fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
|
|
676
|
-
|
|
676
|
+
/* --color-text-muted, not -subtle: this is a LABEL, i.e. text, and -subtle is deliberately gated at
|
|
677
|
+
3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41 — under AA
|
|
678
|
+
for text, and a stat label is often the only thing naming the number above it. */
|
|
679
|
+
.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-muted);}
|
|
677
680
|
.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;}
|
|
678
681
|
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
679
682
|
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
|
@@ -973,6 +976,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
973
976
|
* Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
|
|
974
977
|
/* position:relative — containing block for out-of-flow row content (see base.css). */
|
|
975
978
|
.fdy-list{position:relative;list-style:none;margin:0;padding:0;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;}
|
|
979
|
+
/* A row that hosts a stretched target has to be the anchor itself (#008 §1). Without this the
|
|
980
|
+
overlay resolves against .fdy-list — which IS positioned — so EVERY row's target covers the whole
|
|
981
|
+
list and the last one in the DOM wins: measured, a click on row one opens row two, under row one's
|
|
982
|
+
name, which looks exactly like working software. Opt-in via :has(), so a row that doesn't use the
|
|
983
|
+
pattern is untouched and nothing an app absolutely positions inside a plain row starts resolving
|
|
984
|
+
against a new containing block. The escape-hatch raise lives in card.css beside the card's. */
|
|
985
|
+
.fdy-list__row:has(.fdy-btn--stretch){position:relative;}
|
|
976
986
|
.fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
|
|
977
987
|
/* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
|
|
978
988
|
* of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
|
package/dist/freeday.tokens.css
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
--slate-200: #e0e3ea;
|
|
18
18
|
--slate-300: #cbd0da;
|
|
19
19
|
--slate-400: #99a1b3;
|
|
20
|
+
--slate-450: #798295;
|
|
20
21
|
--slate-500: #6b7488;
|
|
21
22
|
--slate-600: #4d5568;
|
|
22
23
|
--slate-700: #3a4152;
|
|
@@ -137,7 +138,7 @@
|
|
|
137
138
|
--color-border: var(--slate-200);
|
|
138
139
|
--color-border-strong: var(--slate-300);
|
|
139
140
|
--color-border-muted: var(--slate-100);
|
|
140
|
-
--color-control-border: var(--slate-
|
|
141
|
+
--color-control-border: var(--slate-450);
|
|
141
142
|
--color-switch-thumb: var(--slate-0);
|
|
142
143
|
--color-shadow-inset: rgba(16,14,30,.06);
|
|
143
144
|
--color-inverse-surface: var(--slate-900);
|
|
@@ -208,7 +209,7 @@
|
|
|
208
209
|
--color-border: var(--slate-800);
|
|
209
210
|
--color-border-strong: var(--slate-700);
|
|
210
211
|
--color-border-muted: #1c2130;
|
|
211
|
-
--color-control-border: var(--slate-
|
|
212
|
+
--color-control-border: var(--slate-450);
|
|
212
213
|
--color-inverse-surface: var(--slate-100);
|
|
213
214
|
--color-inverse-text: var(--slate-900);
|
|
214
215
|
--color-success: var(--green-500);
|
|
@@ -271,7 +272,7 @@
|
|
|
271
272
|
--color-border: var(--slate-800);
|
|
272
273
|
--color-border-strong: var(--slate-700);
|
|
273
274
|
--color-border-muted: #1c2130;
|
|
274
|
-
--color-control-border: var(--slate-
|
|
275
|
+
--color-control-border: var(--slate-450);
|
|
275
276
|
--color-inverse-surface: var(--slate-100);
|
|
276
277
|
--color-inverse-text: var(--slate-900);
|
|
277
278
|
--color-success: var(--green-500);
|
|
@@ -327,7 +328,7 @@
|
|
|
327
328
|
--color-border: var(--slate-200);
|
|
328
329
|
--color-border-strong: var(--slate-300);
|
|
329
330
|
--color-border-muted: var(--slate-100);
|
|
330
|
-
--color-control-border: var(--slate-
|
|
331
|
+
--color-control-border: var(--slate-450);
|
|
331
332
|
--color-inverse-surface: var(--slate-900);
|
|
332
333
|
--color-inverse-text: var(--slate-0);
|
|
333
334
|
--color-success: var(--green-600);
|
|
@@ -366,3 +367,15 @@
|
|
|
366
367
|
--space-6: 1.25rem;
|
|
367
368
|
--control-h: 2rem;
|
|
368
369
|
}
|
|
370
|
+
/* ...and the way back out. Inheritance only runs downhill: once the root is compact every subtree is
|
|
371
|
+
* compact, and before this rule existed a data-density="comfortable" wrapper matched nothing — so the
|
|
372
|
+
* comment above was true in one direction only. An app that is dense overall and carries one region
|
|
373
|
+
* of shared chrome (a header, a toolbar, a footer shared with a sibling product) had to restate the
|
|
374
|
+
* defaults by hand, which is exactly the local copy that goes stale when the kit retunes them. */
|
|
375
|
+
[data-density="comfortable"] {
|
|
376
|
+
--space-3: 0.75rem;
|
|
377
|
+
--space-4: 1rem;
|
|
378
|
+
--space-5: 1.25rem;
|
|
379
|
+
--space-6: 1.5rem;
|
|
380
|
+
--control-h: 2.5rem;
|
|
381
|
+
}
|
package/docs/getting-started.md
CHANGED
|
@@ -184,7 +184,7 @@ live docs also have a copy button per component.
|
|
|
184
184
|
```bash
|
|
185
185
|
npm i @cahyo-dimas/freeday
|
|
186
186
|
```
|
|
187
|
-
Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.
|
|
187
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.31.0"` (public npm package). `dist/` is
|
|
188
188
|
committed and published → no build step; `npm ci` runs without auth.
|
|
189
189
|
|
|
190
190
|
### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
|
package/package.json
CHANGED
package/src/components/card.css
CHANGED
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
because forgetting it fails SILENTLY: the secondary control looks and hovers exactly as before and
|
|
25
25
|
simply never receives the click, which is the very symptom the pattern exists to cure. Matched by
|
|
26
26
|
shape (anything focusable) so a control the kit does not ship yet is covered too. */
|
|
27
|
-
.fdy-card:has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
|
|
27
|
+
:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
|
|
28
28
|
/* The position is a DEFAULT, not an override: z-index needs a positioned box (or a flex/grid item),
|
|
29
29
|
but an app that deliberately pins a control inside the card — a corner dismiss, a favourite star —
|
|
30
30
|
must keep its own `position:absolute`. Wrapped in :where() so this whole selector weighs nothing
|
|
31
31
|
and ANY app rule wins it; measured, because at normal specificity the kit silently dragged an
|
|
32
32
|
absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
|
|
33
|
-
:where(.fdy-card:has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
|
|
33
|
+
:where(:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
|
|
34
34
|
.fdy-card__body{padding:var(--space-5);}
|
|
35
35
|
.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);}
|
|
36
36
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
* single boxed strip, or use .fdy-stats--boxed for shared surface + dividers. */
|
|
41
41
|
.fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
|
|
42
42
|
.fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
|
|
43
|
-
|
|
43
|
+
/* --color-text-muted, not -subtle: this is a LABEL, i.e. text, and -subtle is deliberately gated at
|
|
44
|
+
3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41 — under AA
|
|
45
|
+
for text, and a stat label is often the only thing naming the number above it. */
|
|
46
|
+
.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-muted);}
|
|
44
47
|
.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
48
|
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
46
49
|
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
package/src/components/list.css
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
* Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
|
|
11
11
|
/* position:relative — containing block for out-of-flow row content (see base.css). */
|
|
12
12
|
.fdy-list{position:relative;list-style:none;margin:0;padding:0;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;}
|
|
13
|
+
/* A row that hosts a stretched target has to be the anchor itself (#008 §1). Without this the
|
|
14
|
+
overlay resolves against .fdy-list — which IS positioned — so EVERY row's target covers the whole
|
|
15
|
+
list and the last one in the DOM wins: measured, a click on row one opens row two, under row one's
|
|
16
|
+
name, which looks exactly like working software. Opt-in via :has(), so a row that doesn't use the
|
|
17
|
+
pattern is untouched and nothing an app absolutely positions inside a plain row starts resolving
|
|
18
|
+
against a new containing block. The escape-hatch raise lives in card.css beside the card's. */
|
|
19
|
+
.fdy-list__row:has(.fdy-btn--stretch){position:relative;}
|
|
13
20
|
.fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
|
|
14
21
|
/* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
|
|
15
22
|
* of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
|
package/tokens/tokens.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"slate": {
|
|
11
11
|
"0": { "$value": "#ffffff" }, "50": { "$value": "#f7f8fa" },
|
|
12
12
|
"100": { "$value": "#eef0f4" }, "200": { "$value": "#e0e3ea" },
|
|
13
|
-
"300": { "$value": "#cbd0da" }, "400": { "$value": "#99a1b3" },
|
|
13
|
+
"300": { "$value": "#cbd0da" }, "400": { "$value": "#99a1b3" }, "450": { "$value": "#798295" },
|
|
14
14
|
"500": { "$value": "#6b7488" }, "600": { "$value": "#4d5568" },
|
|
15
15
|
"700": { "$value": "#3a4152" }, "800": { "$value": "#262b38" },
|
|
16
16
|
"900": { "$value": "#171b26" }, "950": { "$value": "#0e111a" }
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"border": { "$value": "{slate.200}", "$dark": "{slate.800}" },
|
|
87
87
|
"border-strong": { "$value": "{slate.300}", "$dark": "{slate.700}" },
|
|
88
88
|
"border-muted": { "$value": "{slate.100}", "$dark": "#1c2130" },
|
|
89
|
-
"control-border": { "$value": "{slate.
|
|
89
|
+
"control-border": { "$value": "{slate.450}", "$dark": "{slate.450}" },
|
|
90
90
|
"switch-thumb": { "$value": "{slate.0}" },
|
|
91
91
|
"shadow-inset": { "$value": "rgba(16,14,30,.06)" },
|
|
92
92
|
|