@cahyo-dimas/freeday 3.1.0 → 3.2.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 +70 -0
- package/COMPONENTS.md +20 -1
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/USAGE.md +10 -0
- package/adapters/blazor/FdyModal.razor.cs +1 -1
- package/adapters/react/components/FdyModal.tsx +2 -1
- package/adapters/vue/components/FdyModal.vue +4 -1
- package/dist/freeday.bundle.css +77 -7
- package/dist/freeday.css +75 -5
- package/dist/freeday.tokens.css +2 -2
- package/docs/getting-started.md +1 -1
- package/package.json +2 -2
- package/src/components/cfl.css +57 -2
- package/src/components/tabs.css +18 -3
- package/tokens/tokens.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,76 @@ benar. Perubahan seperti itu ditulis di bawah `### Changed: BREAKING (types)`
|
|
|
9
9
|
lama → tipe baru, dan cara menyempitkannya — bukan di bawah `### Added`, betapapun aditifnya dari
|
|
10
10
|
sisi kit.
|
|
11
11
|
|
|
12
|
+
## [3.2.0] - 2026-08-28
|
|
13
|
+
|
|
14
|
+
**Dua cacat yang tak bisa di-screenshot, dan satu komponen yang akhirnya muat di panel 420px.**
|
|
15
|
+
Ketiganya datang dari layar sungguhan. Yang menyatukannya: tak satu pun terlihat di DOM atau di
|
|
16
|
+
stylesheet — yang satu hanya bergerak, yang satu hanya terbaca, yang satu hanya muat atau tidak.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Strip tab tak lagi menggulung vertikal, dan garis aktifnya kini benar-benar 2px** (`#052`, dari
|
|
20
|
+
`IDU_AI_DOC_SAPB1_CLIENT`). Dilaporkan pengguna yang bisa merasakannya tapi tak bisa memotretnya:
|
|
21
|
+
*"panel tab ini menggulung vertikal, susah di-screenshot."* Memang begitu — **1px**.
|
|
22
|
+
`overflow-x:auto` tak bisa meminta satu sumbu saja (CSS Overflow §3: sumbu lain yang `visible`
|
|
23
|
+
ikut jadi `auto`), jadi 1px yang digantungkan `margin-bottom:-1px` milik tab — yang ada justru
|
|
24
|
+
untuk mengangkat underline 2px-nya ke atas garis strip — berubah jadi scrollport sungguhan yang
|
|
25
|
+
memakan gestur trackpad. Yang tak dilaporkan siapa pun, dan hanya piksel yang bisa membedakannya:
|
|
26
|
+
scroll container **memotong** keturunannya di padding box, jadi separuh bawah underline itu ikut
|
|
27
|
+
terpotong. Underline aktif terkirim **1px** selama negative margin itu ada — CSS-nya bilang 2px,
|
|
28
|
+
DOM-nya bilang 2px, layarnya bilang 1. Garis strip kini `box-shadow` inset (dicat di kotaknya,
|
|
29
|
+
bukan di konten yang menggulung), dan tab tak lagi menggantung. **Bagi konsumen yang menyembunyikan
|
|
30
|
+
garis itu:** `border-bottom:0` tak lagi menjangkaunya, pakai `box-shadow:none`. Sapuan ke seluruh
|
|
31
|
+
scroller lain (`.fdy-table-scroll`, `.fdy-nav--horizontal`, `.fdy-carousel__viewport`) bersih —
|
|
32
|
+
`.fdy-tabs__list` satu-satunya.
|
|
33
|
+
- **`--color-text-subtle` gelap naik ke `slate-450`** (`#053` §2, dari
|
|
34
|
+
`IDU_AI_DOC_SAPB1_ADDON_DESKTOP_APP`). 4.02:1 di `--color-surface` gelap, di bawah AA, dan yang
|
|
35
|
+
memakainya bukan cuma app: **placeholder kit sendiri** (`.fdy-datepicker__value--placeholder` dan
|
|
36
|
+
sebelas `.fdy-cal__day.is-outside` dalam satu kalender). Yang menarik, tokennya tak melanggar
|
|
37
|
+
kontraknya — gerbang kontras memang menguji tier ini di **3:1 sebagai non-teks**; yang salah
|
|
38
|
+
adalah kit memakai tier dekoratif untuk teks yang berutang AA. Sekarang **4.88:1** di
|
|
39
|
+
`--color-surface`, yaitu permukaan yang dicat setiap `.fdy-input`, jadi placeholder lolos di tempat
|
|
40
|
+
placeholder benar-benar duduk. Light tak disentuh (sudah 4.69). **AA di ketiga permukaan gelap
|
|
41
|
+
sengaja tidak dikejar**: mencapainya butuh nada sejengkal dari `slate-400`, yang *adalah*
|
|
42
|
+
`--color-text-muted`, dan subtle yang sama dengan muted bukan tier lagi.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- **`FdyCfl` punya layout sempit, tanpa satu pun prop baru** (`#053` §1). Di bawah **30rem**,
|
|
46
|
+
barisnya menata diri sebagai list item: kolom pertama jadi judul, sisanya satu baris detail di
|
|
47
|
+
bawahnya, `<thead>` dilepas. **Container query, bukan media query** — dialog bukan viewport, dan
|
|
48
|
+
komponen yang sama adalah tabel di halaman desktop sekaligus daftar di dalam panel 420px yang
|
|
49
|
+
ditambatkan di SAP Business One. Kontrak datanya tak berubah: `columns` sudah menyebut kolom mana
|
|
50
|
+
yang identitas. Keempat stack dapat sekaligus, karena yang bekerja CSS.
|
|
51
|
+
- **`size="cfl"` di `FdyModal`** (`#053` §4), Vue · React · Blazor. `.fdy-modal--cfl` (46rem) sudah
|
|
52
|
+
ada di CSS sejak lama tapi tak terjangkau dari adapter mana pun; `FdyCfl` memasang kelasnya
|
|
53
|
+
sendiri, jadi celah ini hanya muncul bagi yang membangun dialog sendiri selebar itu. **Bukan
|
|
54
|
+
breaking** meski union publiknya melebar: kebijakan 2.1.0 menyasar tipe di posisi *keluaran*
|
|
55
|
+
(handler yang menerima nilai lebih lebar); ini masukan, dan pemanggil yang mengirim nilai lama
|
|
56
|
+
tetap valid.
|
|
57
|
+
|
|
58
|
+
### Added: guards
|
|
59
|
+
- `browser/tabs-strip.mjs` — nol overflow vertikal, underline 2px terbaca dari **piksel** yang
|
|
60
|
+
benar-benar dicat, dan gulir horizontal yang tetap hidup. Diverifikasi mutasi: dengan aturan 3.1.0
|
|
61
|
+
dikembalikan, 2 dari 3 merah.
|
|
62
|
+
- `browser/cfl-narrow.mjs` — menjalankan `FdyCfl.vue` sungguhan di viewport **420px**, lebar
|
|
63
|
+
konsumen yang melaporkannya, karena container query menanyakan lebar *dialog* dan hanya dialog
|
|
64
|
+
sungguhan yang punya lebar.
|
|
65
|
+
- `test/contrast.test.mjs` dapat pasangan baru khusus placeholder, terpisah dari tier dekoratifnya,
|
|
66
|
+
jadi kedua paruh keputusan itu dijaga: gagal di 4.02 kalau tokennya dikembalikan, dan tetap 3:1
|
|
67
|
+
untuk chevron/separator.
|
|
68
|
+
|
|
69
|
+
### Docs
|
|
70
|
+
- `COMPONENTS.md`: kontrak override garis strip tab, layout sempit `FdyCfl`, dan `size="cfl"`.
|
|
71
|
+
- `USAGE.md`: `.fdy-text-subtle` akhirnya masuk tabel peran teks — tabel itu berhenti di "muted",
|
|
72
|
+
padahal kelasnya publik — beserta kontrak kontrasnya di kedua tema.
|
|
73
|
+
|
|
74
|
+
### Tidak diperbaiki, dan alasannya
|
|
75
|
+
- **`--color-text-muted` 3.94:1 di `--color-surface-raised`** (`#053` §3) **tidak reproduksi.** Di
|
|
76
|
+
`soft`, `--color-surface-raised` *identik* dengan `--color-surface` (7.28:1); di `glass`,
|
|
77
|
+
komposit terburuknya 6.70:1; dan muted tak pernah turun di bawah **5.46:1** di mana pun dalam ramp
|
|
78
|
+
gelap. Angka 3.94 itu sendiri nyata: ia persis `slate-400` di atas `slate-700`, yaitu
|
|
79
|
+
`--color-border-strong` — token border yang tak dipakai komponen mana pun sebagai latar. Item ini
|
|
80
|
+
dibiarkan **open** dengan pertanyaan balik ke pelapor, bukan ditutup sebagai withdrawn.
|
|
81
|
+
|
|
12
82
|
## [3.1.0] - 2026-08-27
|
|
13
83
|
|
|
14
84
|
**Dua cacat lebar yang dilaporkan dari layar sungguhan, dan keduanya tak kelihatan di DOM.** Markup-nya
|
package/COMPONENTS.md
CHANGED
|
@@ -640,6 +640,15 @@ child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
|
640
640
|
## Choose-from-list (CFL) — `data-fdy-cfl`
|
|
641
641
|
> **Typed wrapper: `<FdyCfl>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
642
642
|
|
|
643
|
+
> **Narrow: the results stack, and there is no prop for it** (`#053`). Under a `.fdy-cfl__results`
|
|
644
|
+
> narrower than **30rem** — a docked add-on panel, a phone — the rows lay out as list items instead
|
|
645
|
+
> of table cells: the first column becomes the title, the remaining columns become one detail line
|
|
646
|
+
> under it, and `<thead>` is dropped. It is a **container** query, so what decides is the dialog's
|
|
647
|
+
> width and not the viewport's; the same component is a table on a desktop page and a list inside a
|
|
648
|
+
> 420px panel, with no flag to pass and nothing to keep in sync. Your data contract does not change:
|
|
649
|
+
> `columns`, `display` and `rowKey` already say which column is the identity. If a screen genuinely
|
|
650
|
+
> needs columns at that width, override the `@container` block — do not widen the dialog.
|
|
651
|
+
|
|
643
652
|
### Props — `<FdyCfl>`
|
|
644
653
|
|
|
645
654
|
Four of these are required and carry the whole component: `fetchPage`, `columns`, `display`,
|
|
@@ -1496,6 +1505,16 @@ APG tabs: ←/→, Home/End, roving tabindex. Needs `freeday-tabs.js`.
|
|
|
1496
1505
|
`.fdy-tabs__tab` marks its active state from **`aria-selected="true"` or `aria-current="page"`**,
|
|
1497
1506
|
so the same look serves routed sub-navigation built from plain links. See the note under Nav.
|
|
1498
1507
|
|
|
1508
|
+
> **The strip's line is an inset `box-shadow`, not a `border-bottom`** (`#052`; through 3.1.0 it
|
|
1509
|
+
> was a border, so an app upgrading past that release changes the override with it). To
|
|
1510
|
+
> remove it — a strip inside a header that draws its own rule — override `box-shadow:none`;
|
|
1511
|
+
> `border-bottom:0` no longer reaches it. The swap is not cosmetic: `.fdy-tabs__list` scrolls
|
|
1512
|
+
> horizontally, a scroll container clips its descendants to the padding box, and the tab's
|
|
1513
|
+
> `margin-bottom:-1px` (which lifted its 2px underline onto that border) therefore pushed the
|
|
1514
|
+
> underline's lower half into the clip — the underline painted 1px, and the strip carried 1px of
|
|
1515
|
+
> vertical scroll that ate trackpad gestures. A shadow paints on the box, not on the scrolled
|
|
1516
|
+
> content, so the line needs no overhang to sit under. Guarded in `browser/tabs-strip.mjs`.
|
|
1517
|
+
|
|
1499
1518
|
```html
|
|
1500
1519
|
<div class="fdy-tabs" data-fdy-tabs>
|
|
1501
1520
|
<div class="fdy-tabs__list" role="tablist" aria-label="Invoice detail">
|
|
@@ -1521,7 +1540,7 @@ so the same look serves routed sub-navigation built from plain links. See the no
|
|
|
1521
1540
|
| `open` | `boolean` | Controlled: your state decides. The wrapper cancels the native `cancel` event so the DOM can never disagree with it. |
|
|
1522
1541
|
| `title` | `string` (Vue) · `ReactNode` | Names the dialog through `aria-labelledby`. Vue also offers a `title` slot when the heading needs markup. |
|
|
1523
1542
|
| `onClose` | `() => void` | React. Vue emits `close`. Fires from Escape, a backdrop click and the ×, all three of which exist only under `dismissible`. |
|
|
1524
|
-
| `size?` | `'sm' \| 'md' \| 'lg' \| 'wide'` | Dialog width. |
|
|
1543
|
+
| `size?` | `'sm' \| 'md' \| 'lg' \| 'wide' \| 'cfl'` | Dialog width. `cfl` is the 46rem choose-from-list width; `FdyCfl` applies it itself, so pass it only for a dialog you build at that width. |
|
|
1525
1544
|
| `dismissible?` | `boolean` | Whether Escape, the backdrop and the × can close it. Off means the reader has to take a footer action, which is the point of a blocking confirm. |
|
|
1526
1545
|
| `closeLabel?` | `string` | Accessible name for the × button. Default `Close`. |
|
|
1527
1546
|
| `footer?` | `ReactNode` | React. Vue: the `footer` slot. |
|
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/v3.2.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/v3.2.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
|
@@ -23,10 +23,20 @@ the single biggest cause of "flat grey mush". Pick the role, not the size:
|
|
|
23
23
|
| Card / row title | `.fdy-title-card` (or `.fdy-card__title`) | A title inside a card or list row (`<h3>`). |
|
|
24
24
|
| Body | *(default)* | Running text. |
|
|
25
25
|
| Muted / caption | `.fdy-text-muted`, `.fdy-text-caption` | Secondary text, timestamps, help. |
|
|
26
|
+
| Subtle | `.fdy-text-subtle` | The faintest ink. Placeholders and decoration — chevrons, separators, out-of-month days. **Read the contract below before using it for a sentence.** |
|
|
26
27
|
|
|
27
28
|
Everything else is body. If you reach for a fourth title size, you probably need a section, not a
|
|
28
29
|
font size.
|
|
29
30
|
|
|
31
|
+
**`--color-text-subtle` is not a third body-text tier, and dark is where that bites.** It clears AA
|
|
32
|
+
on `--color-surface` in both themes (light 4.69:1, dark 4.88:1) — which is what `.fdy-input` and
|
|
33
|
+
`.fdy-textarea` paint, so a placeholder is safe. It clears AA on **nothing else**, in either theme:
|
|
34
|
+
`--color-surface-3` reads 4.11:1 in light and 3.66:1 in dark. Nor will it ever: reaching 4.5 there
|
|
35
|
+
needs a tone within a hair of `--color-text-muted`, and a subtle that equals muted is not a tier. So a small uppercase
|
|
36
|
+
label, a card's second line, or anything a reader must actually read on a raised or tinted surface
|
|
37
|
+
takes `--color-text-muted`. `test/contrast.test.mjs` holds both halves of that split, which is why
|
|
38
|
+
the ramp cannot quietly drift into either mistake.
|
|
39
|
+
|
|
30
40
|
|
|
31
41
|
## Borders: two roles, two contrast contracts
|
|
32
42
|
|
|
@@ -19,7 +19,7 @@ public partial class FdyModal
|
|
|
19
19
|
/// <summary>Optional footer (action buttons). Omitted entirely when null.</summary>
|
|
20
20
|
[Parameter] public RenderFragment? FooterContent { get; set; }
|
|
21
21
|
|
|
22
|
-
/// <summary>Size modifier: <c>sm</c> | <c>md</c> | <c>lg</c> | <c>wide</c
|
|
22
|
+
/// <summary>Size modifier: <c>sm</c> | <c>md</c> | <c>lg</c> | <c>wide</c> | <c>cfl</c> (the 46rem choose-from-list width).</summary>
|
|
23
23
|
[Parameter] public string? Size { get; set; }
|
|
24
24
|
|
|
25
25
|
/// <summary>When true (default), Esc and a backdrop click dismiss the dialog and a close
|
|
@@ -13,7 +13,8 @@ export interface FdyModalProps {
|
|
|
13
13
|
open: boolean;
|
|
14
14
|
title: ReactNode;
|
|
15
15
|
onClose: () => void;
|
|
16
|
-
|
|
16
|
+
/* 'cfl' is the choose-from-list width (46rem); see the Vue wrapper for why it was missing. */
|
|
17
|
+
size?: 'sm' | 'md' | 'lg' | 'wide' | 'cfl';
|
|
17
18
|
dismissible?: boolean;
|
|
18
19
|
/** aria-label for the × button. Default 'Close'. */
|
|
19
20
|
closeLabel?: string;
|
|
@@ -16,7 +16,10 @@ import { computed, onMounted, useId, watch, type ComputedRef, type Ref, ref } fr
|
|
|
16
16
|
const props = withDefaults(defineProps<{
|
|
17
17
|
open: boolean;
|
|
18
18
|
title: string;
|
|
19
|
-
|
|
19
|
+
/* 'cfl' is the width the choose-from-list dialog uses (46rem). It was reachable from the CSS
|
|
20
|
+
and from nowhere else until #053: FdyCfl sets the class itself, so the gap only ever showed up
|
|
21
|
+
when an app built its own dialog at that width and had to hand-write the class. */
|
|
22
|
+
size?: 'sm' | 'md' | 'lg' | 'wide' | 'cfl';
|
|
20
23
|
dismissible?: boolean;
|
|
21
24
|
/** aria-label for the × button. Default 'Close'. Blazor has had `CloseLabel` since it
|
|
22
25
|
* shipped; these two hard-coded the string, so a non-English app could not rename it. */
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
--color-surface-raised: var(--color-surface);
|
|
324
324
|
--color-text: var(--slate-100);
|
|
325
325
|
--color-text-muted: var(--slate-400);
|
|
326
|
-
--color-text-subtle: var(--slate-
|
|
326
|
+
--color-text-subtle: var(--slate-450);
|
|
327
327
|
--color-on-brand: var(--slate-0);
|
|
328
328
|
--color-border: var(--slate-800);
|
|
329
329
|
--color-border-strong: var(--slate-700);
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
--color-surface-raised: var(--color-surface);
|
|
388
388
|
--color-text: var(--slate-100);
|
|
389
389
|
--color-text-muted: var(--slate-400);
|
|
390
|
-
--color-text-subtle: var(--slate-
|
|
390
|
+
--color-text-subtle: var(--slate-450);
|
|
391
391
|
--color-on-brand: var(--slate-0);
|
|
392
392
|
--color-border: var(--slate-800);
|
|
393
393
|
--color-border-strong: var(--slate-700);
|
|
@@ -1470,9 +1470,16 @@ a { color: var(--color-primary); }
|
|
|
1470
1470
|
/* Search bar, single leading-icon field; Enter/typing filters the results. */
|
|
1471
1471
|
.fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
1472
1472
|
|
|
1473
|
-
/* Results, dense rows, sticky header, its own scroll region.
|
|
1474
|
-
|
|
1473
|
+
/* Results, dense rows, sticky header, its own scroll region.
|
|
1474
|
+
*
|
|
1475
|
+
* `container-type: inline-size` makes this box the query container for the stacked layout below.
|
|
1476
|
+
* Safe here and nowhere near free in general: a size container stops contributing intrinsic inline
|
|
1477
|
+
* size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
|
|
1478
|
+
* takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
|
|
1479
|
+
* its own content, so it never sized to the table to begin with. */
|
|
1480
|
+
.fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
|
|
1475
1481
|
.fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
|
|
1482
|
+
|
|
1476
1483
|
.fdy-cfl__row{cursor:pointer;}
|
|
1477
1484
|
.fdy-cfl__row:hover{background:var(--color-surface-2);}
|
|
1478
1485
|
.fdy-cfl__row:focus-visible{outline:none;background:var(--color-primary-soft);box-shadow:inset 2px 0 0 var(--color-primary);}
|
|
@@ -1490,6 +1497,54 @@ a { color: var(--color-primary); }
|
|
|
1490
1497
|
.fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
1491
1498
|
.fdy-cfl__actions{display:flex;gap:var(--space-2);}
|
|
1492
1499
|
|
|
1500
|
+
/* Narrow: the same rows as list items, not table cells (#053).
|
|
1501
|
+
*
|
|
1502
|
+
* A 420px add-on panel gives this dialog ~372px. A three- or four-column master-data table in that
|
|
1503
|
+
* space either squeezes every column below legibility or scrolls on both axes, in a dialog whose
|
|
1504
|
+
* whole job is "search, look, click one". So the first cell becomes the title and the rest become
|
|
1505
|
+
* one detail line under it — the shape a picker takes when it is narrow.
|
|
1506
|
+
*
|
|
1507
|
+
* A CONTAINER query, not a media query: the dialog is not the viewport. The same component is wide
|
|
1508
|
+
* on a desktop page and narrow inside a docked panel, and only the container knows which.
|
|
1509
|
+
*
|
|
1510
|
+
* No ARIA roles come with this, and that is a MEASURED decision rather than an omission. The
|
|
1511
|
+
* received wisdom from the responsive-table era is that `display:block` on a table box drops its
|
|
1512
|
+
* implicit role; explicit `role="table"/"row"/"cell"` were written into all four stacks here on
|
|
1513
|
+
* that belief and then taken out again, because Chrome's own accessibility tree says otherwise:
|
|
1514
|
+
* with the rows at `display:flex`, stripping every `role` attribute leaves
|
|
1515
|
+
* `table → row → cell` intact (Chromium 133; only `rowgroup`, which associates nothing, is lost).
|
|
1516
|
+
* Redundant ARIA is what the kit's own rule calls worse than none, so the guard asserts the
|
|
1517
|
+
* OUTCOME — that a stacked row is still a row in the a11y tree — and stays silent about how.
|
|
1518
|
+
* `browser/cfl-narrow.mjs` is where a future engine that does dissolve them shows up as red.
|
|
1519
|
+
*
|
|
1520
|
+
* Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
|
|
1521
|
+
* row that costs a third of the width buys nothing when each row is labelled by its own title. */
|
|
1522
|
+
@container fdy-cfl (max-width: 30rem){
|
|
1523
|
+
.fdy-cfl__results .fdy-table,
|
|
1524
|
+
.fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
|
|
1525
|
+
.fdy-cfl__results .fdy-table thead{display:none;}
|
|
1526
|
+
.fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
1527
|
+
.fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
|
|
1528
|
+
.fdy-cfl__row > td{display:block;padding:0;border:0;}
|
|
1529
|
+
/* The title takes the whole first line, which is what pushes every remaining cell onto the second
|
|
1530
|
+
one — no selector has to know how many columns there are, or which of them is last. */
|
|
1531
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
|
|
1532
|
+
.fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
1533
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
|
|
1534
|
+
/* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
|
|
1535
|
+
.fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
|
|
1536
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
|
|
1537
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
1538
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
|
|
1539
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
|
|
1540
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
|
|
1541
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
|
|
1542
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
|
|
1543
|
+
.fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
|
|
1544
|
+
/* The checkbox keeps its own line position beside the title rather than above it. */
|
|
1545
|
+
.fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1493
1548
|
/* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
|
|
1494
1549
|
* Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */
|
|
1495
1550
|
|
|
@@ -2428,9 +2483,24 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
2428
2483
|
.fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}
|
|
2429
2484
|
|
|
2430
2485
|
/* Freeday. Tabs (WAI-ARIA APG) */
|
|
2431
|
-
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
2432
|
-
|
|
2433
|
-
|
|
2486
|
+
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
2487
|
+
*
|
|
2488
|
+
* The strip's own line is an INSET SHADOW, not a border, and the tab carries no negative margin
|
|
2489
|
+
* (#052). The pair those two replace — `border-bottom` here plus `margin-bottom:-1px` on the tab,
|
|
2490
|
+
* to lift the active tab's 2px underline onto the strip's 1px line so the two read as one — was
|
|
2491
|
+
* broken by the `overflow-x:auto` beside it, in two ways at once:
|
|
2492
|
+
* 1. CSS Overflow §3 gives no way to scroll one axis alone: `overflow-y` computes from `visible`
|
|
2493
|
+
* to `auto` because the other axis is not visible, so the 1px the tab hangs below the content
|
|
2494
|
+
* box became a real scrollport. Measured 47 client / 48 scroll, `scrollTop` settling at 1 —
|
|
2495
|
+
* invisible in a screenshot, and a trackpad gesture over the strip is eaten by it.
|
|
2496
|
+
* 2. A scroll container clips its descendants to the padding box, so the pixel the negative
|
|
2497
|
+
* margin pushed out was clipped away and the strip's own line painted in its place: the
|
|
2498
|
+
* active underline rendered ONE pixel of primary, never the two it asks for.
|
|
2499
|
+
* An inset shadow paints on the box, not on the scrolled content, so the line stays put while the
|
|
2500
|
+
* tabs scroll sideways, and the tab's own border-bottom paints over it — same look, no overflow.
|
|
2501
|
+
* To hide the line, override `box-shadow`, not `border-bottom` (guarded in browser/tabs-strip.mjs). */
|
|
2502
|
+
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);box-shadow:inset 0 calc(var(--bw) * -1) 0 var(--color-border);overflow-x:auto;}
|
|
2503
|
+
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
2434
2504
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
2435
2505
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
2436
2506
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
package/dist/freeday.css
CHANGED
|
@@ -592,9 +592,16 @@ a { color: var(--color-primary); }
|
|
|
592
592
|
/* Search bar, single leading-icon field; Enter/typing filters the results. */
|
|
593
593
|
.fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
594
594
|
|
|
595
|
-
/* Results, dense rows, sticky header, its own scroll region.
|
|
596
|
-
|
|
595
|
+
/* Results, dense rows, sticky header, its own scroll region.
|
|
596
|
+
*
|
|
597
|
+
* `container-type: inline-size` makes this box the query container for the stacked layout below.
|
|
598
|
+
* Safe here and nowhere near free in general: a size container stops contributing intrinsic inline
|
|
599
|
+
* size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
|
|
600
|
+
* takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
|
|
601
|
+
* its own content, so it never sized to the table to begin with. */
|
|
602
|
+
.fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
|
|
597
603
|
.fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
|
|
604
|
+
|
|
598
605
|
.fdy-cfl__row{cursor:pointer;}
|
|
599
606
|
.fdy-cfl__row:hover{background:var(--color-surface-2);}
|
|
600
607
|
.fdy-cfl__row:focus-visible{outline:none;background:var(--color-primary-soft);box-shadow:inset 2px 0 0 var(--color-primary);}
|
|
@@ -612,6 +619,54 @@ a { color: var(--color-primary); }
|
|
|
612
619
|
.fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
613
620
|
.fdy-cfl__actions{display:flex;gap:var(--space-2);}
|
|
614
621
|
|
|
622
|
+
/* Narrow: the same rows as list items, not table cells (#053).
|
|
623
|
+
*
|
|
624
|
+
* A 420px add-on panel gives this dialog ~372px. A three- or four-column master-data table in that
|
|
625
|
+
* space either squeezes every column below legibility or scrolls on both axes, in a dialog whose
|
|
626
|
+
* whole job is "search, look, click one". So the first cell becomes the title and the rest become
|
|
627
|
+
* one detail line under it — the shape a picker takes when it is narrow.
|
|
628
|
+
*
|
|
629
|
+
* A CONTAINER query, not a media query: the dialog is not the viewport. The same component is wide
|
|
630
|
+
* on a desktop page and narrow inside a docked panel, and only the container knows which.
|
|
631
|
+
*
|
|
632
|
+
* No ARIA roles come with this, and that is a MEASURED decision rather than an omission. The
|
|
633
|
+
* received wisdom from the responsive-table era is that `display:block` on a table box drops its
|
|
634
|
+
* implicit role; explicit `role="table"/"row"/"cell"` were written into all four stacks here on
|
|
635
|
+
* that belief and then taken out again, because Chrome's own accessibility tree says otherwise:
|
|
636
|
+
* with the rows at `display:flex`, stripping every `role` attribute leaves
|
|
637
|
+
* `table → row → cell` intact (Chromium 133; only `rowgroup`, which associates nothing, is lost).
|
|
638
|
+
* Redundant ARIA is what the kit's own rule calls worse than none, so the guard asserts the
|
|
639
|
+
* OUTCOME — that a stacked row is still a row in the a11y tree — and stays silent about how.
|
|
640
|
+
* `browser/cfl-narrow.mjs` is where a future engine that does dissolve them shows up as red.
|
|
641
|
+
*
|
|
642
|
+
* Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
|
|
643
|
+
* row that costs a third of the width buys nothing when each row is labelled by its own title. */
|
|
644
|
+
@container fdy-cfl (max-width: 30rem){
|
|
645
|
+
.fdy-cfl__results .fdy-table,
|
|
646
|
+
.fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
|
|
647
|
+
.fdy-cfl__results .fdy-table thead{display:none;}
|
|
648
|
+
.fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
649
|
+
.fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
|
|
650
|
+
.fdy-cfl__row > td{display:block;padding:0;border:0;}
|
|
651
|
+
/* The title takes the whole first line, which is what pushes every remaining cell onto the second
|
|
652
|
+
one — no selector has to know how many columns there are, or which of them is last. */
|
|
653
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
|
|
654
|
+
.fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
655
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
|
|
656
|
+
/* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
|
|
657
|
+
.fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
|
|
658
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
|
|
659
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
660
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
|
|
661
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
|
|
662
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
|
|
663
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
|
|
664
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
|
|
665
|
+
.fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
|
|
666
|
+
/* The checkbox keeps its own line position beside the title rather than above it. */
|
|
667
|
+
.fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
|
|
668
|
+
}
|
|
669
|
+
|
|
615
670
|
/* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
|
|
616
671
|
* Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */
|
|
617
672
|
|
|
@@ -1550,9 +1605,24 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1550
1605
|
.fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}
|
|
1551
1606
|
|
|
1552
1607
|
/* Freeday. Tabs (WAI-ARIA APG) */
|
|
1553
|
-
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
1554
|
-
|
|
1555
|
-
|
|
1608
|
+
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
1609
|
+
*
|
|
1610
|
+
* The strip's own line is an INSET SHADOW, not a border, and the tab carries no negative margin
|
|
1611
|
+
* (#052). The pair those two replace — `border-bottom` here plus `margin-bottom:-1px` on the tab,
|
|
1612
|
+
* to lift the active tab's 2px underline onto the strip's 1px line so the two read as one — was
|
|
1613
|
+
* broken by the `overflow-x:auto` beside it, in two ways at once:
|
|
1614
|
+
* 1. CSS Overflow §3 gives no way to scroll one axis alone: `overflow-y` computes from `visible`
|
|
1615
|
+
* to `auto` because the other axis is not visible, so the 1px the tab hangs below the content
|
|
1616
|
+
* box became a real scrollport. Measured 47 client / 48 scroll, `scrollTop` settling at 1 —
|
|
1617
|
+
* invisible in a screenshot, and a trackpad gesture over the strip is eaten by it.
|
|
1618
|
+
* 2. A scroll container clips its descendants to the padding box, so the pixel the negative
|
|
1619
|
+
* margin pushed out was clipped away and the strip's own line painted in its place: the
|
|
1620
|
+
* active underline rendered ONE pixel of primary, never the two it asks for.
|
|
1621
|
+
* An inset shadow paints on the box, not on the scrolled content, so the line stays put while the
|
|
1622
|
+
* tabs scroll sideways, and the tab's own border-bottom paints over it — same look, no overflow.
|
|
1623
|
+
* To hide the line, override `box-shadow`, not `border-bottom` (guarded in browser/tabs-strip.mjs). */
|
|
1624
|
+
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);box-shadow:inset 0 calc(var(--bw) * -1) 0 var(--color-border);overflow-x:auto;}
|
|
1625
|
+
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1556
1626
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
1557
1627
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
1558
1628
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
package/dist/freeday.tokens.css
CHANGED
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
--color-surface-raised: var(--color-surface);
|
|
323
323
|
--color-text: var(--slate-100);
|
|
324
324
|
--color-text-muted: var(--slate-400);
|
|
325
|
-
--color-text-subtle: var(--slate-
|
|
325
|
+
--color-text-subtle: var(--slate-450);
|
|
326
326
|
--color-on-brand: var(--slate-0);
|
|
327
327
|
--color-border: var(--slate-800);
|
|
328
328
|
--color-border-strong: var(--slate-700);
|
|
@@ -386,7 +386,7 @@
|
|
|
386
386
|
--color-surface-raised: var(--color-surface);
|
|
387
387
|
--color-text: var(--slate-100);
|
|
388
388
|
--color-text-muted: var(--slate-400);
|
|
389
|
-
--color-text-subtle: var(--slate-
|
|
389
|
+
--color-text-subtle: var(--slate-450);
|
|
390
390
|
--color-on-brand: var(--slate-0);
|
|
391
391
|
--color-border: var(--slate-800);
|
|
392
392
|
--color-border-strong: var(--slate-700);
|
package/docs/getting-started.md
CHANGED
|
@@ -191,7 +191,7 @@ live docs also have a copy button per component.
|
|
|
191
191
|
```bash
|
|
192
192
|
npm i @cahyo-dimas/freeday
|
|
193
193
|
```
|
|
194
|
-
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.
|
|
194
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.2.0"` (public npm package). `dist/` is
|
|
195
195
|
committed and published → no build step; `npm ci` runs without auth.
|
|
196
196
|
|
|
197
197
|
### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node tokens/build.mjs",
|
|
94
94
|
"test": "node --test",
|
|
95
|
-
"test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/picker-states.mjs browser/harness-contract.mjs",
|
|
95
|
+
"test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/cfl-narrow.mjs browser/harness-contract.mjs",
|
|
96
96
|
"prepack": "node tokens/build.mjs",
|
|
97
97
|
"version": "node tokens/build.mjs && git add dist",
|
|
98
98
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
|
package/src/components/cfl.css
CHANGED
|
@@ -7,9 +7,16 @@
|
|
|
7
7
|
/* Search bar, single leading-icon field; Enter/typing filters the results. */
|
|
8
8
|
.fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
9
9
|
|
|
10
|
-
/* Results, dense rows, sticky header, its own scroll region.
|
|
11
|
-
|
|
10
|
+
/* Results, dense rows, sticky header, its own scroll region.
|
|
11
|
+
*
|
|
12
|
+
* `container-type: inline-size` makes this box the query container for the stacked layout below.
|
|
13
|
+
* Safe here and nowhere near free in general: a size container stops contributing intrinsic inline
|
|
14
|
+
* size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
|
|
15
|
+
* takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
|
|
16
|
+
* its own content, so it never sized to the table to begin with. */
|
|
17
|
+
.fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
|
|
12
18
|
.fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
|
|
19
|
+
|
|
13
20
|
.fdy-cfl__row{cursor:pointer;}
|
|
14
21
|
.fdy-cfl__row:hover{background:var(--color-surface-2);}
|
|
15
22
|
.fdy-cfl__row:focus-visible{outline:none;background:var(--color-primary-soft);box-shadow:inset 2px 0 0 var(--color-primary);}
|
|
@@ -26,3 +33,51 @@
|
|
|
26
33
|
.fdy-modal--cfl .fdy-modal__footer{justify-content:space-between;}
|
|
27
34
|
.fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
28
35
|
.fdy-cfl__actions{display:flex;gap:var(--space-2);}
|
|
36
|
+
|
|
37
|
+
/* Narrow: the same rows as list items, not table cells (#053).
|
|
38
|
+
*
|
|
39
|
+
* A 420px add-on panel gives this dialog ~372px. A three- or four-column master-data table in that
|
|
40
|
+
* space either squeezes every column below legibility or scrolls on both axes, in a dialog whose
|
|
41
|
+
* whole job is "search, look, click one". So the first cell becomes the title and the rest become
|
|
42
|
+
* one detail line under it — the shape a picker takes when it is narrow.
|
|
43
|
+
*
|
|
44
|
+
* A CONTAINER query, not a media query: the dialog is not the viewport. The same component is wide
|
|
45
|
+
* on a desktop page and narrow inside a docked panel, and only the container knows which.
|
|
46
|
+
*
|
|
47
|
+
* No ARIA roles come with this, and that is a MEASURED decision rather than an omission. The
|
|
48
|
+
* received wisdom from the responsive-table era is that `display:block` on a table box drops its
|
|
49
|
+
* implicit role; explicit `role="table"/"row"/"cell"` were written into all four stacks here on
|
|
50
|
+
* that belief and then taken out again, because Chrome's own accessibility tree says otherwise:
|
|
51
|
+
* with the rows at `display:flex`, stripping every `role` attribute leaves
|
|
52
|
+
* `table → row → cell` intact (Chromium 133; only `rowgroup`, which associates nothing, is lost).
|
|
53
|
+
* Redundant ARIA is what the kit's own rule calls worse than none, so the guard asserts the
|
|
54
|
+
* OUTCOME — that a stacked row is still a row in the a11y tree — and stays silent about how.
|
|
55
|
+
* `browser/cfl-narrow.mjs` is where a future engine that does dissolve them shows up as red.
|
|
56
|
+
*
|
|
57
|
+
* Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
|
|
58
|
+
* row that costs a third of the width buys nothing when each row is labelled by its own title. */
|
|
59
|
+
@container fdy-cfl (max-width: 30rem){
|
|
60
|
+
.fdy-cfl__results .fdy-table,
|
|
61
|
+
.fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
|
|
62
|
+
.fdy-cfl__results .fdy-table thead{display:none;}
|
|
63
|
+
.fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
64
|
+
.fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
|
|
65
|
+
.fdy-cfl__row > td{display:block;padding:0;border:0;}
|
|
66
|
+
/* The title takes the whole first line, which is what pushes every remaining cell onto the second
|
|
67
|
+
one — no selector has to know how many columns there are, or which of them is last. */
|
|
68
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
|
|
69
|
+
.fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
70
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
|
|
71
|
+
/* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
|
|
72
|
+
.fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
|
|
73
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
|
|
74
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
75
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
|
|
76
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
|
|
77
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
|
|
78
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
|
|
79
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
|
|
80
|
+
.fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
|
|
81
|
+
/* The checkbox keeps its own line position beside the title rather than above it. */
|
|
82
|
+
.fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
|
|
83
|
+
}
|
package/src/components/tabs.css
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
/* Freeday. Tabs (WAI-ARIA APG) */
|
|
2
|
-
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
3
|
+
*
|
|
4
|
+
* The strip's own line is an INSET SHADOW, not a border, and the tab carries no negative margin
|
|
5
|
+
* (#052). The pair those two replace — `border-bottom` here plus `margin-bottom:-1px` on the tab,
|
|
6
|
+
* to lift the active tab's 2px underline onto the strip's 1px line so the two read as one — was
|
|
7
|
+
* broken by the `overflow-x:auto` beside it, in two ways at once:
|
|
8
|
+
* 1. CSS Overflow §3 gives no way to scroll one axis alone: `overflow-y` computes from `visible`
|
|
9
|
+
* to `auto` because the other axis is not visible, so the 1px the tab hangs below the content
|
|
10
|
+
* box became a real scrollport. Measured 47 client / 48 scroll, `scrollTop` settling at 1 —
|
|
11
|
+
* invisible in a screenshot, and a trackpad gesture over the strip is eaten by it.
|
|
12
|
+
* 2. A scroll container clips its descendants to the padding box, so the pixel the negative
|
|
13
|
+
* margin pushed out was clipped away and the strip's own line painted in its place: the
|
|
14
|
+
* active underline rendered ONE pixel of primary, never the two it asks for.
|
|
15
|
+
* An inset shadow paints on the box, not on the scrolled content, so the line stays put while the
|
|
16
|
+
* tabs scroll sideways, and the tab's own border-bottom paints over it — same look, no overflow.
|
|
17
|
+
* To hide the line, override `box-shadow`, not `border-bottom` (guarded in browser/tabs-strip.mjs). */
|
|
18
|
+
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);box-shadow:inset 0 calc(var(--bw) * -1) 0 var(--color-border);overflow-x:auto;}
|
|
19
|
+
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
5
20
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
6
21
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
7
22
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
package/tokens/tokens.json
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
|
|
123
123
|
"text": { "$value": "{slate.900}", "$dark": "{slate.100}" },
|
|
124
124
|
"text-muted": { "$value": "{slate.600}", "$dark": "{slate.400}" },
|
|
125
|
-
"text-subtle": { "$value": "{slate.500}", "$dark": "{slate.
|
|
125
|
+
"text-subtle": { "$comment": "The faintest ink, and the ONLY text tier whose dark value is not the light one's mirror (#053). Light slate-500 on white is 4.69:1 and clears AA on its own; the same primitive on the dark surface is 4.02:1, and the tier is exactly what a placeholder uses — the one line telling an operator what an empty field wants. Dark is slate-450 for that reason: 4.88:1 on --color-surface, which is what every .fdy-input paints itself, so the placeholder clears AA where placeholders actually sit. It is still NOT a body-text tier on the darker surfaces (3.66:1 on surface-3); reaching AA there needs slate-400, which is --color-text-muted, and a subtle that equals muted is not a tier. test/contrast.test.mjs asserts both halves of that split.", "$value": "{slate.500}", "$dark": "{slate.450}" },
|
|
126
126
|
"on-brand": { "$value": "{slate.0}", "$dark": "{slate.0}" },
|
|
127
127
|
|
|
128
128
|
"border": { "$value": "{slate.200}", "$dark": "{slate.800}" },
|