@cahyo-dimas/freeday 3.4.0 → 3.6.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 +127 -0
- package/COMPONENTS.md +278 -12
- package/README.id.md +9 -5
- package/README.md +9 -5
- package/adapters/blazor/FdyDateRange.razor.cs +10 -0
- package/adapters/blazor/FdyTable.razor +6 -2
- package/adapters/blazor/FdyTable.razor.cs +21 -0
- package/adapters/blazor/TableTypes.cs +4 -0
- package/adapters/react/components/FdyDateRange.tsx +8 -2
- package/adapters/react/components/FdyTable.tsx +24 -3
- package/adapters/react/index.d.ts +1 -1
- package/adapters/vue/components/FdyDateRange.vue +11 -5
- package/adapters/vue/components/FdyTable.vue +18 -2
- package/dist/freeday-confirm.js +101 -0
- package/dist/freeday-copy.js +58 -0
- package/dist/freeday-datepicker.js +17 -2
- package/dist/freeday-form.js +30 -1
- package/dist/freeday-search.js +77 -0
- package/dist/freeday.bundle.css +98 -8
- package/dist/freeday.css +98 -8
- package/dist/freeday.d.ts +17 -2
- package/dist/freeday.js +286 -3
- package/dist/freeday.media.css +27 -0
- package/docs/agent-onboarding.md +10 -0
- package/docs/getting-started.md +1 -1
- package/package.json +6 -2
- package/src/components/avatar.css +2 -0
- package/src/components/breakpoints.css +7 -1
- package/src/components/card.css +7 -0
- package/src/components/composition.css +36 -0
- package/src/components/description-list.css +11 -1
- package/src/components/filterbar.css +8 -1
- package/src/components/selection.css +6 -0
- package/src/components/states.css +7 -0
- package/src/components/table.css +14 -5
- package/tokens/breakpoints.d.ts +4 -0
- package/tokens/breakpoints.mjs +22 -8
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,133 @@ 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.6.0] - 2026-10-22
|
|
13
|
+
|
|
14
|
+
**Tiga belas hal yang dibangun ulang sebuah app back-office karena kit belum punya.** App-nya tidak
|
|
15
|
+
memakai Freeday (Vue + PrimeVue), jadi yang diangkat adalah polanya, bukan komponennya: setiap
|
|
16
|
+
kandidat dibandingkan dengan kit, dan hanya yang generik dan benar-benar belum ada yang masuk
|
|
17
|
+
(`#059`–`#071`). Satu di antaranya cacat yang kit punya juga: submit yang ditolak memfokus field
|
|
18
|
+
yang tak terlihat. Satu lagi mengubah tampilan setiap tabel, jadi dikirim sebagai opt-in dan
|
|
19
|
+
dijadwalkan jadi default di 4.0 (NEXT-UP #16). Tidak ada tipe publik yang menyempit dan tidak ada
|
|
20
|
+
tampilan yang berubah tanpa diminta, jadi ini rilis minor.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **`Freeday.confirm()`** (`#060`). Pertanyaan ya/tidak sebelum aksi yang tak bisa diulang, tanpa
|
|
24
|
+
merakit dialog sendiri. Mengembalikan `Promise<boolean>`: `true` saat accept, `false` saat reject,
|
|
25
|
+
Escape atau klik backdrop. `details[]` tampil sebagai daftar di bawah pesan, untuk konfirmasi yang
|
|
26
|
+
harus menyebut temuannya. Fokus awal di tombol reject, teks dipasang sebagai teks (bukan HTML), dan
|
|
27
|
+
kedua tombol punya hook `data-fdy-confirm-accept` / `-reject` untuk test. Tipe ada di
|
|
28
|
+
`freeday.d.ts`; Blazor memanggilnya lewat `JS.InvokeAsync<bool>`.
|
|
29
|
+
- **`[data-fdy-search]`** (`#061`, menjawab NEXT-UP #10). `.fdy-input-group` yang mencari saat
|
|
30
|
+
pengetik berhenti sejenak (`data-fdy-search-delay`, default 400 ms), langsung saat Enter, dan
|
|
31
|
+
punya tombol clear. Ketiga jalur berakhir di satu event `fdy-search {term}`. Clear menulis lewat
|
|
32
|
+
setter native dan memicu `input` native, jadi `v-model`, `onChange` React dan `@bind` Blazor ikut.
|
|
33
|
+
- **`[data-fdy-copy]`** (`#062`). Tombol yang menyalin nilainya sendiri lalu memberi toast. Tanpa
|
|
34
|
+
Clipboard API (http) pembaca diberi tahu untuk menyalin manual. Listener-nya didelegasikan di
|
|
35
|
+
`document`, jadi tombol yang dirender belakangan tak perlu hidrasi.
|
|
36
|
+
- Ketiganya dijaga `browser/confirm-search-copy.mjs` (7 spec), dan tiap spec diverifikasi dengan
|
|
37
|
+
mutasi: delapan mutasi pada kode baru, delapan merah.
|
|
38
|
+
- **`settled` pada date range** (`#063`). Vue `@settled`, React `onSettled`, Blazor `Settled`,
|
|
39
|
+
jalur mentah `fdy-daterange-settled`. Terpancar hanya saat kedua ujung terisi atau keduanya
|
|
40
|
+
kosong. Sebelumnya setiap stack memancarkan per ujung, jadi list yang reload saat berubah memuat
|
|
41
|
+
data dengan tanggal awal saja lalu memuat lagi saat tanggal akhir dipilih. `change` dan `v-model`
|
|
42
|
+
tidak berubah. Dijaga `browser/daterange-settled.mjs` (4 spec) + `FdyDateRangeTests` (2 bUnit),
|
|
43
|
+
lima mutasi, lima merah.
|
|
44
|
+
- **`.fdy-dl--grid`** (`#066`). Grid fakta: label di atas nilai, kolom sebanyak yang muat di lebar
|
|
45
|
+
list itu SENDIRI, maksimal tiga. Diukur dari elemen, bukan jendela, jadi markup yang sama tiga
|
|
46
|
+
kolom di area utama dan satu kolom di rail sempit tanpa modifier. Semua `.fdy-dl dd` kini
|
|
47
|
+
`overflow-wrap:anywhere`, jadi email atau nomor panjang tidak lagi tercetak di atas kolom sebelah.
|
|
48
|
+
- **`.fdy-card__head` + `__action`** (`#070`). Header kartu: tanda, judul yang di-truncate, dan
|
|
49
|
+
satu aksi di kanan, dengan garis selebar kartu. Tandanya `.fdy-avatar --sm --square --tone-N`.
|
|
50
|
+
- **`.fdy-avatar--square`** untuk tanda yang bukan orang (logo perusahaan, chip ikon kartu).
|
|
51
|
+
- **Hint state switch** (`#068`). Dua baris `[data-fdy-when="on|off"]` di satu `.fdy-help`; kit
|
|
52
|
+
menampilkan yang cocok dengan state sekarang, tanpa JS. `aria-describedby` menunjuk ke
|
|
53
|
+
pembungkusnya, jadi pembaca layar hanya mendengar baris yang tampil (diverifikasi lewat deskripsi
|
|
54
|
+
aksesibel yang dihitung Chrome).
|
|
55
|
+
- **`.fdy-state--compact`** (`#069`) untuk state di dalam kartu kecil.
|
|
56
|
+
- **Empty state `FdyTable` tahu kenapa ia kosong** (`#069`). Slot Vue `empty` dan fungsi React
|
|
57
|
+
`empty` menerima `{ filtered, clearFilters }`; Blazor punya `EmptyTemplate`
|
|
58
|
+
(`RenderFragment<FdyTableEmptyContext>`). "Tidak ada yang cocok" bisa menawarkan Clear, "belum
|
|
59
|
+
ada data" menawarkan Create.
|
|
60
|
+
- **`.fdy-page__columns`** (`#065`, menutup "Form master-detail / 2 kolom" yang ditunda di
|
|
61
|
+
NEXT-UP). Rail `__lead` / `__aside` di samping `__main`; lebar dipilih dari rail mana yang ada
|
|
62
|
+
(8/4, 3/9, 3/6/3). Saat menumpuk, rail ringkasan didahulukan. Mulai `md` rail sticky dan
|
|
63
|
+
scroll sendiri, jadi total dan status tetap terlihat dan rail yang lebih tinggi dari layar tidak
|
|
64
|
+
menggantung. Diadaptasi dari layout yang di app asalnya menahan seluruh halaman; di kit shell-nya
|
|
65
|
+
tetap scroll halaman, jadi tidak ada consumer yang dipaksa mengubah layout-nya. Dijaga
|
|
66
|
+
`browser/page-columns.mjs` (5 spec, 6 mutasi, 6 merah).
|
|
67
|
+
- **`.fdy-table--hover-actionable`** (`#064`), prop `hoverActionable` / `HoverActionable` di ketiga
|
|
68
|
+
adapter. Hanya baris yang melakukan sesuatu (`rowActivatable`) yang ber-hover (baris terpilih tetap berwarna terpilih);
|
|
69
|
+
baris inert kembali ke warna istirahatnya sendiri, termasuk stripe, dan sel beku tetap opak.
|
|
70
|
+
Opt-in di 3.x supaya tidak ada tabel yang berubah; direncanakan jadi default di 4.0. Hover dasar
|
|
71
|
+
kini membaca `--_fdy-row-hover` (privat), tampilannya tidak berubah. Dijaga
|
|
72
|
+
`browser/table-hover.mjs` (4 spec, 5 mutasi, 5 merah) + `FdyTableHoverTests` (2 bUnit).
|
|
73
|
+
- **Docs: breadcrumb** (`#071 §8`). `aria-current="page"` hanya untuk crumb yang memang halaman ini;
|
|
74
|
+
trail yang berhenti di induknya (form record yang crumb terakhirnya list) membiarkan crumb itu
|
|
75
|
+
tetap link.
|
|
76
|
+
- **Docs: Record actions** (`#067`). Urutan tetap untuk aksi form di `.fdy-page__actions`:
|
|
77
|
+
Find | New, maksimal dua aksi inline, sisanya di menu More dengan urutan deklarasi yang sama dan
|
|
78
|
+
warna danger yang tetap terbawa, Save terakhir dan satu-satunya tombol solid.
|
|
79
|
+
- Dijaga `browser/display-parts.mjs` (4), `browser/table-empty.mjs` (2) dan `FdyTableEmptyTests`
|
|
80
|
+
(2 bUnit). Setiap perilaku baru diverifikasi dengan mutasi (13 mutasi, 13 merah).
|
|
81
|
+
### Fixed
|
|
82
|
+
- **Date range jalur mentah: ujung yang dihapus kini diumumkan.** Tanggal awal yang melewati
|
|
83
|
+
tanggal akhir mengosongkan tanggal akhir tanpa event apa pun, jadi `To` di Blazor (yang hanya
|
|
84
|
+
mendengar pilihan) tetap memegang tanggal yang sudah tidak ada. Pengosongan itu sekarang
|
|
85
|
+
memancarkan `fdy-datepicker-change` dengan `value: ''`.
|
|
86
|
+
- **Submit yang ditolak kini memperlihatkan field yang ditolaknya** (`#059`). `freeday-form.js`
|
|
87
|
+
memanggil `focus()` pada kontrol invalid pertama dan berhenti di situ. Kalau kontrol itu tidak
|
|
88
|
+
dirender (di dalam `<details>` tertutup, di tab yang tak dipilih, di panel stepper yang sudah
|
|
89
|
+
dilewati), `focus()` tidak melakukan apa pun dan tidak melapor apa pun: field memerah di tempat
|
|
90
|
+
yang tak terlihat, dan tombol Save terbaca mati. Sekarang penghalangnya dibuka dulu lewat kontrol
|
|
91
|
+
milik komponennya sendiri, supaya state komponen itu tetap cocok dengan yang tampil: `<details>`
|
|
92
|
+
di-`open`, tab-nya diklik, tombol step-nya diklik. Step yang **belum dicapai** sengaja tidak
|
|
93
|
+
dilompati, karena itu sama dengan melewati guard `fdy-step-before-change`. Dijaga
|
|
94
|
+
`browser/form-reveal.mjs` (5 spec); empat di antaranya merah sebelum perbaikan.
|
|
95
|
+
|
|
96
|
+
## [3.5.0] - 2026-09-15
|
|
97
|
+
|
|
98
|
+
**Sebuah angka yang hanya bisa ditemukan dengan membaca stylesheet kit.** `#058` (dari
|
|
99
|
+
`IDU_AI_DOC_SAPB1_CLIENT` dan `IDU_AI_DOC_SAAS`, satu sapuan atas dua konsol) datang dari audit yang
|
|
100
|
+
hasilnya nyaris bersih: tipografi, spasi dan warna tak menyisakan satu pun nilai mentah. Yang tersisa
|
|
101
|
+
cuma media query — dan di situlah dua hal yang sama-sama berakar pada satu kenyataan CSS: `@media`
|
|
102
|
+
tak bisa membaca custom property.
|
|
103
|
+
|
|
104
|
+
Akibatnya angka breakpoint kit hanya hidup sebagai literal, app mengetik ulang literal itu, dan
|
|
105
|
+
literal yang diketik ulang hanyut. Dua konsol itu menuliskan satu lebar yang sama dengan tiga cara
|
|
106
|
+
(`640px`, `40rem`, `45rem`) untuk satu maksud yang sama.
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
- **`@cahyo-dimas/freeday/media`** — berkas `@custom-media` (`dist/freeday.media.css`), jawaban
|
|
110
|
+
sisi-CSS untuk apa yang selama ini hanya dijawab `tokens/breakpoints.mjs` di sisi JS. App menulis
|
|
111
|
+
`@media (--fdy-below-sm)` atau `@media (--fdy-filterbar-stacked)`, bukan literal yang diketik
|
|
112
|
+
ulang, dan karenanya tak bisa hanyut.
|
|
113
|
+
Isinya `--fdy-below-*` / `--fdy-from-*` untuk ramp (`sm`·`md`·`lg`·`xl`), plus `--fdy-nav-drawer`,
|
|
114
|
+
`--fdy-nav-static` dan `--fdy-filterbar-stacked` untuk dua lebar yang bukan anak tangga ramp.
|
|
115
|
+
**Opt-in, dan tak mengubah apa pun bagi yang tidak memakainya:** `@custom-media` adalah transform
|
|
116
|
+
PostCSS (`postcss-custom-media`, sudah termasuk di `postcss-preset-env`), jadi app ber-Vite
|
|
117
|
+
mendapatkannya cuma-cuma dan app tanpa PostCSS tetap menulis literal seperti kemarin. Berkas ini
|
|
118
|
+
**tidak** ikut `freeday.css` maupun `freeday.bundle.css` — at-rule waktu-build tak punya urusan di
|
|
119
|
+
stylesheet yang dimuat tiap app — dan tak membawa satu rule pun, jadi meng-import-nya tak berbiaya
|
|
120
|
+
saat runtime.
|
|
121
|
+
Di-**generate** dari objek yang sama yang diekspor sisi JS, jadi kedua paruh skala itu tak mungkin
|
|
122
|
+
berbeda pendapat; yang menjaganya agar tetap sama dengan CSS yang dideskripsikannya adalah dua tes
|
|
123
|
+
baru di `test/docs.test.mjs`, diverifikasi dengan mutasi.
|
|
124
|
+
- **`breakpoints.filterbar` (640)** — lebar tempat `.fdy-filterbar` menumpuk fieldnya jadi selebar
|
|
125
|
+
penuh, kini bernama, bukan cuma tertulis di `filterbar.css:45`.
|
|
126
|
+
|
|
127
|
+
### Changed
|
|
128
|
+
- **640 tetap 640, tapi berhenti jadi angka siluman.** Ia bukan anak tangga ramp (`sm` = 600) dan
|
|
129
|
+
sengaja tidak dipindahkan: 640 adalah lebar telepon-lanskap yang nyata, dan menggesernya akan
|
|
130
|
+
me-reflow setiap filter bar di setiap app. Yang salah bukan angkanya, melainkan bahwa satu-satunya
|
|
131
|
+
cara menemukannya adalah membaca stylesheet kit lalu mempercayai sumber di atas dokumentasi.
|
|
132
|
+
Menyelaraskan rule app ke `sm` — hal yang wajar dilakukan dari dokumentasi saja — merusak
|
|
133
|
+
600–640px: fieldnya sudah ditumpuk kit sementara app masih membatasi lebar field `--w-grow`-nya,
|
|
134
|
+
sehingga field itu terlihat lebih sempit dari tetangganya di tumpukan.
|
|
135
|
+
Header `breakpoints.css`, komentar `filterbar.css`, `COMPONENTS.md` dan kedua README kini menyebut
|
|
136
|
+
`nav` **dan** `filterbar` sebagai dua lebar yang bukan ramp, lengkap dengan pita px yang rusak
|
|
137
|
+
kalau dibulatkan ke anak tangga terdekat.
|
|
138
|
+
|
|
12
139
|
## [3.4.0] - 2026-09-07
|
|
13
140
|
|
|
14
141
|
**Tiga hal yang hanya terlihat setelah sebuah app bertemu telepon sungguhan.** `#057` (dari
|
package/COMPONENTS.md
CHANGED
|
@@ -77,7 +77,7 @@ element is fine. Events are bubbling
|
|
|
77
77
|
| `data-fdy-autocomplete` | `freeday-autocomplete.js` | `FreedayAutocomplete` | `fdy-autocomplete-select` |
|
|
78
78
|
| `data-fdy-cascade` | `freeday-cascade.js` | `FreedayCascade` | `fdy-cascade-change` |
|
|
79
79
|
| `data-fdy-cfl` | `freeday-cfl.js` | `FreedayCfl` | `fdy-cfl-select` |
|
|
80
|
-
| `data-fdy-datepicker`, `data-fdy-daterange` | `freeday-datepicker.js` | `FreedayDatepicker` | `fdy-datepicker-change` |
|
|
80
|
+
| `data-fdy-datepicker`, `data-fdy-daterange` | `freeday-datepicker.js` | `FreedayDatepicker` | `fdy-datepicker-change`, `fdy-daterange-settled` |
|
|
81
81
|
| `data-fdy-timepicker` | `freeday-timepicker.js` | `FreedayTimepicker` | `fdy-time-select` |
|
|
82
82
|
| `data-fdy-datetimepicker` | `freeday-datetime.js` | `FreedayDatetime` | `fdy-datetime-change` |
|
|
83
83
|
| `data-fdy-table` | `freeday-table.js` | `FreedayTable` | `fdy-table-change` |
|
|
@@ -93,9 +93,12 @@ element is fine. Events are bubbling
|
|
|
93
93
|
| `data-fdy-rating` | `freeday-rating.js` | `FreedayRating` | — (native `change`) |
|
|
94
94
|
| `data-fdy-slider` | `freeday-slider.js` | `FreedaySlider` | — (native `input`) |
|
|
95
95
|
| `data-fdy-number` | `freeday-number.js` | `FreedayNumber` | — (native `input`, `change`) |
|
|
96
|
+
| `data-fdy-search` | `freeday-search.js` | `FreedaySearch` | `fdy-search` (+ native `input` on clear) |
|
|
97
|
+
| `data-fdy-copy="value"` (delegated, no hydration) | `freeday-copy.js` | `FreedayCopy` | `fdy-copy` |
|
|
96
98
|
| `data-fdy-drawer="id"` | `freeday-drawer.js` | `FreedayDrawer` | — |
|
|
97
99
|
| `data-fdy-chart="type"` | `freeday-chart.js` | `FreedayChart` | — (`.update(el)` to repaint) |
|
|
98
100
|
| — (imperative) | `freeday-toast.js` | `Freeday.toast()` / `Freeday.dismiss()` | — |
|
|
101
|
+
| — (imperative, returns `Promise<boolean>`) | `freeday-confirm.js` | `Freeday.confirm()` | — |
|
|
99
102
|
| — (stamps `<html data-breakpoint>`) | `freeday-breakpoint.js` | `FreedayBreakpoint` | `fdy-breakpoint-change` |
|
|
100
103
|
| — (internal positioning) | `freeday-popover.js` | `FreedayPopover` | — |
|
|
101
104
|
|
|
@@ -289,6 +292,78 @@ carry the doctrine in markup.
|
|
|
289
292
|
</div>
|
|
290
293
|
```
|
|
291
294
|
|
|
295
|
+
### Document columns — `.fdy-page__columns`
|
|
296
|
+
|
|
297
|
+
A record or form page with side rails: `__lead` on the left for the record's identity (avatar,
|
|
298
|
+
number, status), `__main` for the sections, `__aside` on the right for the summary (totals, what
|
|
299
|
+
happens next). The column widths follow which rails are **present**, so no screen picks them:
|
|
300
|
+
|
|
301
|
+
| Children | From `md` | From `lg` |
|
|
302
|
+
|---|---|---|
|
|
303
|
+
| `__main` | one column | one column |
|
|
304
|
+
| `__main` + `__aside` | 8 / 4 | 8 / 4 |
|
|
305
|
+
| `__lead` + `__main` | 3 / 9 | 3 / 9 |
|
|
306
|
+
| `__lead` + `__main` + `__aside` | rails stacked on the left, main on the right | 3 / 6 / 3 |
|
|
307
|
+
|
|
308
|
+
- Below `md` everything stacks, and **the summary rail comes first**. Last, it would sit under
|
|
309
|
+
every section, and the button in it with it.
|
|
310
|
+
- From `md` each rail is **sticky and scrolls on its own**: the total and the status stay in view
|
|
311
|
+
while `__main` scrolls with the page, and a rail taller than the screen scrolls inside itself
|
|
312
|
+
instead of hanging its end off the bottom. The exception is both rails sharing the left column
|
|
313
|
+
(between `md` and `lg`): two sticky rails in one column would slide over each other, so there they
|
|
314
|
+
scroll with the page.
|
|
315
|
+
- Inside `.fdy-app` the rails stick below the topbar. Set `--fdy-page-sticky-top` on the columns or
|
|
316
|
+
an ancestor if your header is taller.
|
|
317
|
+
- A rail's box starts `--space-1` early on every side, room for a focus ring that a scrolling box
|
|
318
|
+
would otherwise clip. Its content still lines up with `__main`.
|
|
319
|
+
- Put `.fdy-dl--grid` in a rail as is: it measures the rail, not the window, so it stays one column.
|
|
320
|
+
|
|
321
|
+
```html
|
|
322
|
+
<div class="fdy-page__columns">
|
|
323
|
+
<aside class="fdy-page__lead" aria-label="Record">…</aside>
|
|
324
|
+
<div class="fdy-page__main">…sections…</div>
|
|
325
|
+
<aside class="fdy-page__aside" aria-label="Summary">…</aside>
|
|
326
|
+
</div>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Record actions — a form's `.fdy-page__actions`
|
|
330
|
+
|
|
331
|
+
A document or master form carries more than one button: its own actions (Print, Send, Cancel
|
|
332
|
+
document, Copy from…) and Save. Without a rule the row grows with every module, reflows at tablet
|
|
333
|
+
width and pushes Save off the end, and two screens with the same actions order them differently.
|
|
334
|
+
The kit's rule, in this fixed order:
|
|
335
|
+
|
|
336
|
+
1. **Find | New** as one `.fdy-btn-group`, ghost. Hide New on the create screen itself: a button
|
|
337
|
+
that navigates to the screen you are on appears to do nothing.
|
|
338
|
+
2. **At most two** of the form's own actions inline, ghost, in the order they are declared.
|
|
339
|
+
3. **More**: an `.fdy-menu` holding every other action, in the **same declared order**. A
|
|
340
|
+
destructive item keeps `.fdy-menu__item--danger`: an action that loses its colour on the way
|
|
341
|
+
into a menu is one the reader no longer recognises. Keep each action's test hook the same
|
|
342
|
+
whether it is inline or in the menu.
|
|
343
|
+
4. **Save**, last, the **only** solid button on the row. Never green: green says something already
|
|
344
|
+
succeeded.
|
|
345
|
+
|
|
346
|
+
```html
|
|
347
|
+
<div class="fdy-page__actions">
|
|
348
|
+
<div class="fdy-btn-group" role="group" aria-label="Records">
|
|
349
|
+
<a class="fdy-btn fdy-btn--ghost" href="/orders">Find</a>
|
|
350
|
+
<a class="fdy-btn fdy-btn--ghost" href="/orders/new">New</a>
|
|
351
|
+
</div>
|
|
352
|
+
<button class="fdy-btn fdy-btn--ghost" type="button">Print</button>
|
|
353
|
+
<button class="fdy-btn fdy-btn--ghost" type="button">Send</button>
|
|
354
|
+
<div class="fdy-menu-wrap" data-fdy-menu>
|
|
355
|
+
<button class="fdy-btn fdy-btn--ghost" type="button" aria-haspopup="menu" aria-expanded="false">More</button>
|
|
356
|
+
<ul class="fdy-menu fdy-menu--end" role="menu" hidden>
|
|
357
|
+
<li role="none"><button class="fdy-menu__item" role="menuitem">Copy from quotation</button></li>
|
|
358
|
+
<li role="none"><button class="fdy-menu__item fdy-menu__item--danger" role="menuitem">Cancel document</button></li>
|
|
359
|
+
</ul>
|
|
360
|
+
</div>
|
|
361
|
+
<button class="fdy-btn" type="submit" form="order-form">Save</button>
|
|
362
|
+
</div>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Ask before a destructive action with `Freeday.confirm({ tone: 'danger', … })`.
|
|
366
|
+
|
|
292
367
|
## Type roles & text
|
|
293
368
|
One role per level of hierarchy. Never re-use a card title for a page title.
|
|
294
369
|
|
|
@@ -313,10 +388,33 @@ Responsive display: `.fdy-hide-below-sm|md|lg` · `.fdy-hide-above-sm|md|lg`.
|
|
|
313
388
|
Breakpoints (min-width): `sm` 600 · `md` 960 · `lg` 1280 · `xl` 1920, importable as
|
|
314
389
|
`import { breakpoints } from '@cahyo-dimas/freeday/breakpoints'`.
|
|
315
390
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
391
|
+
**Two widths in that object are not ramp steps**, and each is the one to use when your rule has to
|
|
392
|
+
fire with a kit rule. Aligning either to the nearest ramp step leaves a band of broken widths, in
|
|
393
|
+
which the kit has already switched and your app has not:
|
|
394
|
+
|
|
395
|
+
| | px | Governs | Aligning to the ramp instead breaks |
|
|
396
|
+
|---|---|---|---|
|
|
397
|
+
| `breakpoints.nav` | 721 | `.fdy-app` switches the sidebar from off-canvas drawer to static column — and `.fdy-app__main`'s padding step and `.fdy-bottomnav` ride the same width | 721–959px, if you use `md`: sidebar already static while your script still treats it as an overlay |
|
|
398
|
+
| `breakpoints.filterbar` | 640 | `.fdy-filterbar` stacks every field full-width | 600–640px, if you use `sm`: fields already stacked while your rule still caps the `--w-grow` one, so it sits visibly narrower than the fields under it |
|
|
399
|
+
|
|
400
|
+
**In CSS, import the breakpoints instead of retyping them.** `@media` cannot read a custom property,
|
|
401
|
+
so there is no `--breakpoint-*` token and never will be; `@cahyo-dimas/freeday/media` answers it the
|
|
402
|
+
only way CSS can, as a `@custom-media` sheet:
|
|
403
|
+
|
|
404
|
+
```css
|
|
405
|
+
@import '@cahyo-dimas/freeday/media'; /* once, above the rules that use it */
|
|
406
|
+
|
|
407
|
+
@media (--fdy-filterbar-stacked) { .page-filters > .fdy-field--w-grow { max-width: none } }
|
|
408
|
+
@media (--fdy-nav-drawer) { .page-banner { padding-inline: var(--space-5) } }
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Declared: `--fdy-below-sm|md|lg|xl` and `--fdy-from-sm|md|lg|xl` (the ramp, matching the utilities
|
|
412
|
+
above), plus `--fdy-nav-drawer` / `--fdy-nav-static` and `--fdy-filterbar-stacked` for the two
|
|
413
|
+
widths in the table. It is **opt-in**: `@custom-media` is a PostCSS transform
|
|
414
|
+
(`postcss-custom-media`, included in `postcss-preset-env`), so an app that runs PostCSS — anything on
|
|
415
|
+
Vite already does — gets it, and an app that does not keeps writing literals exactly as before. The
|
|
416
|
+
sheet carries no rules, so importing it costs nothing at runtime, and it is deliberately not part of
|
|
417
|
+
`freeday.css` or `freeday.bundle.css`.
|
|
320
418
|
|
|
321
419
|
**Visual style: `data-style="soft | glass"`.** `soft` is the default and the look the kit has always
|
|
322
420
|
had; `glass` frosts the **raised** surfaces — card, modal, drawer, menu, appbar, the app shell's
|
|
@@ -490,7 +588,7 @@ cannot guarantee.
|
|
|
490
588
|
|
|
491
589
|
| Type | What the UA still draws | What to do |
|
|
492
590
|
|---|---|---|
|
|
493
|
-
| `search` | WebKit's clear (×) button, in OS colours | **Left alone on purpose** — it is the only way to empty the field. Want it themed
|
|
591
|
+
| `search` | WebKit's clear (×) button, in OS colours | **Left alone on purpose** — it is the only way to empty the field. Want it themed, and want clearing to search again? Use [`data-fdy-search`](#search-box--data-fdy-search), which is `type="text"` with a clear button of its own. |
|
|
494
592
|
| `date` `time` `datetime-local` | the picker indicator glyph, and the whole native picker | Use `.fdy-datepicker` / `.fdy-timepicker` / `.fdy-datetimepicker` instead — the kit ships its own. |
|
|
495
593
|
|
|
496
594
|
`number` used to be a third: the UA spin buttons are unthemeable, so `.fdy-input[type="number"]`
|
|
@@ -534,6 +632,31 @@ the shared border, focus ring and error promotion. Needs `freeday-number.js`.
|
|
|
534
632
|
`aria-label`, so pointer and browse-mode users still get a named control.
|
|
535
633
|
- `type="button"` is required. Inside a `<form>`, a bare `<button>` submits it.
|
|
536
634
|
|
|
635
|
+
## Search box — `[data-fdy-search]`
|
|
636
|
+
The search box above a list: it searches when the reader pauses typing, at once on Enter, and has a
|
|
637
|
+
clear button that brings every row back. **Not a new block.** It is an `.fdy-input-group`, like the
|
|
638
|
+
number field. Needs `freeday-search.js`.
|
|
639
|
+
|
|
640
|
+
```html
|
|
641
|
+
<div class="fdy-input-group" data-fdy-search>
|
|
642
|
+
<span class="fdy-input-group__addon fdy-input-group__addon--icon"><!-- search svg --></span>
|
|
643
|
+
<input class="fdy-input" type="text" aria-label="Search orders">
|
|
644
|
+
<button type="button" class="fdy-input-group__btn" data-fdy-search-clear aria-label="Clear search">×</button>
|
|
645
|
+
</div>
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
- **One event for all three paths.** The group emits bubbling `fdy-search` with `detail.term`
|
|
649
|
+
(trimmed) after the pause, on Enter, and on clear. `""` means "show everything again". Listen to
|
|
650
|
+
that and nothing else.
|
|
651
|
+
- The pause is `data-fdy-search-delay` in ms on the group, default `400`. A new keystroke restarts
|
|
652
|
+
it; Enter and clear send at once and cancel what was pending.
|
|
653
|
+
- `type="text"`, not `search`: WebKit's own × is unthemed, and emptying a field with it never
|
|
654
|
+
searched again on a list that only listened for Enter.
|
|
655
|
+
- The clear button is yours, and so is its `aria-label`. The enhancer hides it while the box is
|
|
656
|
+
empty. Clearing writes through the native value setter and fires a native `input`, so `v-model`,
|
|
657
|
+
a React `onChange` and Blazor `@bind` see the empty value, then puts focus back in the box.
|
|
658
|
+
- Enter is `preventDefault`ed, so a search box inside a `<form>` does not submit it.
|
|
659
|
+
|
|
537
660
|
## Checkbox · radio · switch
|
|
538
661
|
Native inputs, styled. `.fdy-check` · `.fdy-radio` · `.fdy-switch` on the wrapping `<label>`;
|
|
539
662
|
`.fdy-checkbox` on a bare `<input type="checkbox">` used inside tables/trees.
|
|
@@ -543,6 +666,23 @@ Native inputs, styled. `.fdy-check` · `.fdy-radio` · `.fdy-switch` on the wrap
|
|
|
543
666
|
<label class="fdy-switch"><input type="checkbox"> Enable notifications</label>
|
|
544
667
|
```
|
|
545
668
|
|
|
669
|
+
**A state hint** says what the switch's **current** state means, for a label that does not explain
|
|
670
|
+
"on" by itself ("Approval" on a notification rule). Two lines in one `.fdy-help`, and the kit
|
|
671
|
+
shows the one that matches. Point `aria-describedby` at the **wrapper**, not at the two lines: a
|
|
672
|
+
hidden child of a referenced element is skipped, so a screen reader hears only the line on screen,
|
|
673
|
+
while each line referenced directly would be read even when hidden. Zero JS; works for
|
|
674
|
+
`.fdy-check` too. Needs a `.fdy-field` around both.
|
|
675
|
+
|
|
676
|
+
```html
|
|
677
|
+
<div class="fdy-field">
|
|
678
|
+
<label class="fdy-switch"><input type="checkbox" aria-describedby="appr-hint"> Approval</label>
|
|
679
|
+
<div class="fdy-help" id="appr-hint">
|
|
680
|
+
<span data-fdy-when="on">Each change waits for a supervisor.</span>
|
|
681
|
+
<span data-fdy-when="off">Changes apply at once; nobody reviews them.</span>
|
|
682
|
+
</div>
|
|
683
|
+
</div>
|
|
684
|
+
```
|
|
685
|
+
|
|
546
686
|
## Select / combobox — `.fdy-combo`
|
|
547
687
|
> **Typed wrapper: `<FdyCombo>`**. 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).
|
|
548
688
|
|
|
@@ -834,7 +974,8 @@ page's `<html lang>`), `clearable` and `clearLabel` (this path builds no clear b
|
|
|
834
974
|
| Prop | Type | What it does |
|
|
835
975
|
|---|---|---|
|
|
836
976
|
| `modelValue` · `value` | `DateRangeValue` = `{ start, end }`, each `string \| null` | Both ends at once, so the pair can never be half-applied. |
|
|
837
|
-
| `onChange` | `(value: DateRangeValue) => void` | React. Vue emits `update:modelValue` and `change`. |
|
|
977
|
+
| `onChange` | `(value: DateRangeValue) => void` | React. Vue emits `update:modelValue` and `change`. Fires on **each** end, which is right for a binding and wrong for a reload. |
|
|
978
|
+
| `onSettled?` | `(value: DateRangeValue) => void` | React. Vue emits `settled`, Blazor `Settled` (`SettledRange(From, To)`), the raw path `fdy-daterange-settled` `{ from, to }` on the group. Fires only when **both ends are chosen or both are empty**, so a list that reloads on it never filters on half a range. |
|
|
838
979
|
| `min?` · `max?` | `string` | ISO bounds for both ends. The end can never precede the start. |
|
|
839
980
|
| `locale?` | `string` | As on the single picker. |
|
|
840
981
|
| `startPlaceholder?` · `endPlaceholder?` | `string` | One per field. |
|
|
@@ -851,7 +992,9 @@ The enhancer builds everything.
|
|
|
851
992
|
`__panel`; calendar internals `.fdy-cal__head` `__nav` `__title` `__grid` (+`--months` `--years`)
|
|
852
993
|
`__dow` `__day` `__month` `__year`
|
|
853
994
|
- Range: wrap two pickers in `.fdy-daterange` + `data-fdy-daterange` (`role="group"`), children
|
|
854
|
-
get `data-role="from"` / `"to"`; separator `.fdy-daterange__sep`. The end can't precede the start
|
|
995
|
+
get `data-role="from"` / `"to"`; separator `.fdy-daterange__sep`. The end can't precede the start:
|
|
996
|
+
a start picked past the end clears the end, and that clear fires `fdy-datepicker-change` with
|
|
997
|
+
`value: ''` like a pick. Reload on `fdy-daterange-settled`, not on each change.
|
|
855
998
|
**Typed wrapper: `<FdyDateRange>`** (Vue/React/Blazor). Use it in those stacks.
|
|
856
999
|
**Getting to a distant date.** The title is a **button**, and it drills up one level each press:
|
|
857
1000
|
day grid → 12-cell month grid (`.fdy-cal__grid--months` of `.fdy-cal__month`) → 12-cell year grid
|
|
@@ -959,6 +1102,13 @@ blur/input. Needs `freeday-form.js`.
|
|
|
959
1102
|
- Per-control messages: `data-fdy-msg-required`, `-type`, `-minlength`, `-min`, `-mismatch`, …
|
|
960
1103
|
- Cross-field: `data-fdy-match="#otherFieldId"`
|
|
961
1104
|
- Emits `fdy-form-valid` / `fdy-form-invalid`.
|
|
1105
|
+
- **A refused field is revealed before it is focused**, because `focus()` on a control that is not
|
|
1106
|
+
rendered does nothing. A closed `<details>` around it (accordion, tree, nav group) is opened, an
|
|
1107
|
+
unselected tab is selected by clicking it, and a `.fdy-step-panel` left behind is returned to by
|
|
1108
|
+
clicking its step. A step **not yet reached** is never jumped to, since that would skip its
|
|
1109
|
+
`fdy-step-before-change` guard: put the submit where every refusable field is already reachable,
|
|
1110
|
+
or mark the step `is-error` from `fdy-form-invalid`. Anything else hiding the field (your own
|
|
1111
|
+
`hidden`, a closed drawer) stays as you left it.
|
|
962
1112
|
|
|
963
1113
|
## Password & mask
|
|
964
1114
|
`data-fdy-password` on `<input type="password">` adds a reveal toggle (reusing the input-group
|
|
@@ -1057,6 +1207,15 @@ hover and selection are ordered so the later state always wins. Override the col
|
|
|
1057
1207
|
Tier-3 token **`--fdy-table-stripe`** (set it on any ancestor; the rule reads it as a `var()`
|
|
1058
1208
|
fallback, so your value wins without a specificity fight). Frozen columns stay striped.
|
|
1059
1209
|
|
|
1210
|
+
**`--hover-actionable`** keeps the hover tint for rows that do something
|
|
1211
|
+
(`.fdy-table__row--activatable`) and gives every other row none. A selected row keeps its selected
|
|
1212
|
+
colour either way. A tinted row
|
|
1213
|
+
with no click handler promises a target that is not there: people click it, nothing happens, and
|
|
1214
|
+
the button they needed was elsewhere in the row. Striping is what keeps the eye on a line; hover
|
|
1215
|
+
says "target". An inert row hovers back to its own resting colour, stripe included, and a frozen
|
|
1216
|
+
cell to its opaque surface. Opt-in in 3.x so no existing table changes; planned as the default
|
|
1217
|
+
for 4.0.
|
|
1218
|
+
|
|
1060
1219
|
```html
|
|
1061
1220
|
<div class="fdy-table-wrap">
|
|
1062
1221
|
<table class="fdy-table">
|
|
@@ -1164,12 +1323,13 @@ provide the state prop for a concern and you own that concern instead. React nam
|
|
|
1164
1323
|
| `pageSizes?` · `onPageSizeChange?` | `readonly number[]` · `(size) => void` | Offer a rows-per-page control. Server mode reports the pick through the page event (read its `size`); client mode applies it itself and reports it too, so it works with nothing wired. |
|
|
1165
1324
|
| `pager?` | `boolean` | Default true. Turn it off to withhold the table's own footer when the screen renders one. |
|
|
1166
1325
|
| `loading?` | `boolean` | Show the in-table loading state. |
|
|
1167
|
-
| `emptyText?` · `empty?` | `string` · `ReactNode` | The empty state as text, or as markup (React `empty`, Vue's `empty` slot). |
|
|
1326
|
+
| `emptyText?` · `empty?` | `string` · `ReactNode \| (ctx) => ReactNode` | The empty state as text, or as markup (React `empty`, Vue's `empty` slot). As a React function or through the Vue slot's props it receives `{ filtered, clearFilters }`: whether an active column filter is what emptied the table, and how to clear every filter (through `onFiltersChange` / `update:filters` when they are controlled). "No rows match" wants a Clear button, "nothing here yet" a Create one. |
|
|
1168
1327
|
| `ariaLabel?` | `string` | Names the table for assistive tech. |
|
|
1169
1328
|
| `rowActivatable?` · `onRowActivate?` | `boolean` · `(row) => void` | Rows become focusable and activate on click, Enter or Space. |
|
|
1170
1329
|
| `rowClass?` | `(row: Row) => string \| undefined` | Per-row class hook, e.g. marking the selected row. |
|
|
1171
1330
|
| `expandedKeys?` · `renderRowDetail?` | `ReadonlyArray<string \| number>` · `(row) => ReactNode` | Controlled expansion: these keys get a full-width detail row. Vue uses the `row-detail` slot. |
|
|
1172
1331
|
| `striped?` | `boolean` | Zebra-stripe the body rows (`.fdy-table--striped`). |
|
|
1332
|
+
| `hoverActionable?` | `boolean` | Hover only the rows that do something (`rowActivatable`), `.fdy-table--hover-actionable`. A tint on a row with no handler promises a target that is not there. Inert rows keep their resting colour, stripe included. Off by default so existing tables do not change; planned as the default for 4.0. |
|
|
1173
1333
|
| `selectable?` | `boolean` | Render the checkbox column and the bulk bar. |
|
|
1174
1334
|
| `selectedKeys?` · `onSelectedKeysChange?` | `ReadonlyArray<string \| number>` · `(keys) => void` | Controlled selection, as `rowKey` values — the same identity `expandedKeys` uses, because a key survives the re-fetch that replaces every row object. Omit `selectedKeys` and the column still works, keeping the selection internally. The callback fires in **both** modes, so a screen can watch the selection without owning it. Vue emits `update:selectedKeys`; Blazor binds `@bind-SelectedKeys`. |
|
|
1175
1335
|
| `bulkActions?` | `ReactNode` | React. Content in the bulk bar beside the count; Vue uses the `bulk-actions` slot (scoped: `keys`, `clear`), Blazor `BulkContent`. |
|
|
@@ -1181,7 +1341,8 @@ provide the state prop for a concern and you own that concern instead. React nam
|
|
|
1181
1341
|
|
|
1182
1342
|
Blazor matches this surface (`LoadingText` and `EmptyContent` in place of `emptyText`/`empty`,
|
|
1183
1343
|
`Toolbar` and `RowDetail` as `RenderFragment`s) and is the one adapter that also exposes
|
|
1184
|
-
`FiltersChanged`.
|
|
1344
|
+
`FiltersChanged`. Its filter-aware empty state is `EmptyTemplate`, a
|
|
1345
|
+
`RenderFragment<FdyTableEmptyContext>` (`Filtered`, `ClearFilters`) that wins over `EmptyContent`.
|
|
1185
1346
|
|
|
1186
1347
|
**Select-all ticks the current page, not the whole result set.** A header checkbox that silently
|
|
1187
1348
|
selects rows the reader cannot see is how a bulk delete goes wrong, so it acts on the rows on
|
|
@@ -1309,6 +1470,11 @@ Empty / error placeholder for a data area. `--danger` for failures. Parts: `__ic
|
|
|
1309
1470
|
(`aria-hidden="true"`) `__title` `__text` `__actions`. Put it inside the card or table area it
|
|
1310
1471
|
replaces.
|
|
1311
1472
|
|
|
1473
|
+
`--compact` is the same state inside a small card or dashboard widget: icon and text on one line,
|
|
1474
|
+
`__actions` pushed to the end, no `__title`. Give a failed load its own `--compact --danger` line
|
|
1475
|
+
rather than rendering zero: a widget that shows 0 for a request that failed reads as "nothing to
|
|
1476
|
+
do", the opposite of what happened.
|
|
1477
|
+
|
|
1312
1478
|
## Charts — `data-fdy-chart`
|
|
1313
1479
|
> **Typed wrapper: `<FdyChart>`**. data props in all three (`type` + `values` / `series`); it repaints on data change, so `FreedayChart.update(el)` is only for the raw path below.
|
|
1314
1480
|
|
|
@@ -1410,12 +1576,55 @@ responsive `.fdy-datatable` should become below `md`, rather than a stack of `.f
|
|
|
1410
1576
|
|
|
1411
1577
|
## Description list — `.fdy-dl`
|
|
1412
1578
|
Key–value detail view: `<dl class="fdy-dl">` with `<dt>`/`<dd>` pairs. `--rows` for a row-per-pair
|
|
1413
|
-
layout.
|
|
1579
|
+
layout. Every `dd` wraps a long unbreakable value (an email, an order code, a 13-digit phone) inside
|
|
1580
|
+
its column instead of printing over the next one.
|
|
1581
|
+
|
|
1582
|
+
**`--grid` is the facts panel:** caption above value, each pair wrapped in a `<div>` (valid inside
|
|
1583
|
+
`<dl>`), in as many columns as the list's **own** width holds, never more than three. It is sized
|
|
1584
|
+
by the element, not the window, so the same markup is three columns in a wide main column and one
|
|
1585
|
+
column in a narrow side rail, with no modifier. `--fdy-dl-col-min` (default `14rem`) is the
|
|
1586
|
+
narrowest a column may get.
|
|
1587
|
+
|
|
1588
|
+
```html
|
|
1589
|
+
<dl class="fdy-dl fdy-dl--grid">
|
|
1590
|
+
<div><dt>Customer</dt><dd>PT Sumber Makmur</dd></div>
|
|
1591
|
+
<div><dt>Email</dt><dd>procurement@sumbermakmur.co.id</dd></div>
|
|
1592
|
+
<div><dt>Phone</dt><dd class="fdy-mono">0812345678901</dd></div>
|
|
1593
|
+
</dl>
|
|
1594
|
+
```
|
|
1595
|
+
|
|
1596
|
+
A fact is not a field: use this for read-only values, `.fdy-form-grid` only where something can be
|
|
1597
|
+
typed.
|
|
1414
1598
|
|
|
1415
1599
|
---
|
|
1416
1600
|
|
|
1417
1601
|
# Feedback
|
|
1418
1602
|
|
|
1603
|
+
## Copy a value — `[data-fdy-copy]`
|
|
1604
|
+
A button that copies a value the reader takes somewhere else: an external document number, an
|
|
1605
|
+
event ID. Needs `freeday-copy.js`; uses `Freeday.toast()` for its feedback.
|
|
1606
|
+
|
|
1607
|
+
```html
|
|
1608
|
+
<dd>
|
|
1609
|
+
<span class="fdy-mono">INV-2026-00042</span>
|
|
1610
|
+
<button type="button" class="fdy-btn fdy-btn--ghost fdy-btn--icon fdy-btn--sm"
|
|
1611
|
+
data-fdy-copy="INV-2026-00042" aria-label="Copy invoice number"><!-- copy svg --></button>
|
|
1612
|
+
</dd>
|
|
1613
|
+
```
|
|
1614
|
+
|
|
1615
|
+
- **The value is the attribute.** Write it from the same variable as the visible text; a table row
|
|
1616
|
+
needs no ids to find its own cell. An empty value copies nothing, so render the button only when
|
|
1617
|
+
there is something to copy.
|
|
1618
|
+
- One delegated listener on the document: a button rendered later needs no hydration.
|
|
1619
|
+
- The toast says `Copied.` on success. Without a Clipboard API (plain `http`, an old browser) or
|
|
1620
|
+
when the write is refused, a sticky danger toast tells the reader to copy by hand. A copy that
|
|
1621
|
+
silently does nothing is the one outcome it never produces. Override both strings per button
|
|
1622
|
+
with `data-fdy-text-copied` / `data-fdy-text-failed`.
|
|
1623
|
+
- Emits bubbling `fdy-copy` with `detail.value` and `detail.ok`.
|
|
1624
|
+
- Name the button for its value (`Copy invoice number`); an icon alone has no name.
|
|
1625
|
+
- Inside a clickable table row the click still reaches the row's handler. Stop it there if copying
|
|
1626
|
+
should not also open the row.
|
|
1627
|
+
|
|
1419
1628
|
## Alert — `.fdy-alert`
|
|
1420
1629
|
Inline message. Modifiers `--info` `--success` `--warning` `--danger`. Parts `__icon` `__body`
|
|
1421
1630
|
`__title` `__text` `__close`. The danger variant takes `role="alert"`.
|
|
@@ -1453,6 +1662,35 @@ something is in the top layer too. Where the Popover API is unavailable (before
|
|
|
1453
1662
|
Safari 17 / Firefox 125) the region falls back to a fixed block at `z-index: 200`, which cannot
|
|
1454
1663
|
clear an open modal, the message is still announced to a screen reader.
|
|
1455
1664
|
|
|
1665
|
+
## Confirm — `Freeday.confirm()`
|
|
1666
|
+
A yes/no question before an action that cannot be taken back. Imperative, like the toast. Needs
|
|
1667
|
+
`freeday-confirm.js`; renders a `.fdy-modal--sm`.
|
|
1668
|
+
|
|
1669
|
+
```js
|
|
1670
|
+
const yes = await Freeday.confirm({
|
|
1671
|
+
title: 'Delete order?',
|
|
1672
|
+
message: 'Order SO-1042 will be deleted permanently.',
|
|
1673
|
+
details: ['2 payments are deleted with it'], // optional, shown as a list
|
|
1674
|
+
acceptText: 'Delete order', rejectText: 'Keep', // name the action
|
|
1675
|
+
tone: 'danger', // 'primary' (default) | 'danger'
|
|
1676
|
+
})
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
- Resolves `true` on accept, `false` on reject, **Escape, or a click on the backdrop**. One dialog
|
|
1680
|
+
gives one answer.
|
|
1681
|
+
- `details` is for a confirm that has to name its findings. Five findings folded into `message`
|
|
1682
|
+
read as one long sentence.
|
|
1683
|
+
- Focus starts on the reject button, so an Enter that was meant for the page behind cannot
|
|
1684
|
+
confirm. Focus returns to the opener when it closes, and the dialog leaves the DOM.
|
|
1685
|
+
- Text is set as text, never parsed as HTML, so a record name can go into `message` as is.
|
|
1686
|
+
- The buttons carry `data-fdy-confirm-accept` / `data-fdy-confirm-reject`. A test needs them: an
|
|
1687
|
+
accept labelled "Delete" shares its accessible name with every row's own Delete button.
|
|
1688
|
+
- Defaults are `Confirm` / `Cancel`; pass `acceptText` / `rejectText` instead of relying on them.
|
|
1689
|
+
- Vue and React call it as is. Blazor awaits it through interop:
|
|
1690
|
+
`await JS.InvokeAsync<bool>("Freeday.confirm", new { title = "Delete order?", tone = "danger" })`.
|
|
1691
|
+
- An "unsaved changes" guard is three lines on top of it, in your router's leave hook:
|
|
1692
|
+
`return isDirty() ? Freeday.confirm({ … }) : true`.
|
|
1693
|
+
|
|
1456
1694
|
## Tooltip — `.fdy-tooltip`
|
|
1457
1695
|
Hover/focus only, never the sole carrier of information. Wrap in `.fdy-tooltip-wrap`; the trigger
|
|
1458
1696
|
gets `aria-describedby` → the `.fdy-tooltip[role="tooltip"]` `id`.
|
|
@@ -1587,6 +1825,12 @@ for primary navigation (see Nav above).
|
|
|
1587
1825
|
`.fdy-breadcrumb__item` (last one `aria-current="page"`, no link), `.fdy-breadcrumb__link`, and
|
|
1588
1826
|
`.fdy-breadcrumb__sep` marked `aria-hidden="true"`.
|
|
1589
1827
|
|
|
1828
|
+
`aria-current="page"` belongs to the crumb that **is** this page, not to whichever crumb is last. A
|
|
1829
|
+
trail that stops at the parent (a record form whose last crumb is its list) keeps that crumb a
|
|
1830
|
+
link with no `aria-current`; otherwise the reader is told they are on the list while looking at a
|
|
1831
|
+
record. A crumb for a menu group that has no page of its own is plain text in the item, not a link
|
|
1832
|
+
to nowhere.
|
|
1833
|
+
|
|
1590
1834
|
## Tabs — `.fdy-tabs`
|
|
1591
1835
|
APG tabs: ←/→, Home/End, roving tabindex. Needs `freeday-tabs.js`.
|
|
1592
1836
|
`.fdy-tabs__tab` marks its active state from **`aria-selected="true"` or `aria-current="page"`**,
|
|
@@ -1713,7 +1957,7 @@ is `role="group" aria-roledescription="slide"`.
|
|
|
1713
1957
|
# Display
|
|
1714
1958
|
|
|
1715
1959
|
## Card — `.fdy-card`
|
|
1716
|
-
Parts `__body` `__title` `__desc` `__footer`. Modifiers:
|
|
1960
|
+
Parts `__head` `__body` `__title` `__desc` `__action` `__footer`. Modifiers:
|
|
1717
1961
|
|
|
1718
1962
|
- `--elevated` raises it (use sparingly; most surfaces are flat)
|
|
1719
1963
|
- `--interactive` gives cursor + hover-lift to a card that *has* a click handler. **Presentational
|
|
@@ -1730,6 +1974,26 @@ Parts `__body` `__title` `__desc` `__footer`. Modifiers:
|
|
|
1730
1974
|
- The card is `position:relative`, so a badge or ribbon you absolutely position inside it anchors
|
|
1731
1975
|
to the card. That is also what keeps hidden labels inside it from escaping. See *Containment*.
|
|
1732
1976
|
|
|
1977
|
+
**Header band: `__head`.** A mark, the title and the card's way out on one row, with a rule under
|
|
1978
|
+
it from edge to edge. The action sits beside the title so it is in the same place on every card;
|
|
1979
|
+
under the content it moved each time the content grew. The title truncates rather than wrapping
|
|
1980
|
+
under the action. The mark is an `.fdy-avatar --sm --square --tone-N`, so its tints are the ones
|
|
1981
|
+
the contrast gate already covers; pick the tone for what the card is **about** and keep it fixed.
|
|
1982
|
+
|
|
1983
|
+
```html
|
|
1984
|
+
<article class="fdy-card">
|
|
1985
|
+
<header class="fdy-card__head">
|
|
1986
|
+
<span class="fdy-avatar fdy-avatar--sm fdy-avatar--square fdy-avatar--tone-3" aria-hidden="true"><!-- icon --></span>
|
|
1987
|
+
<h3 class="fdy-card__title">Due today</h3>
|
|
1988
|
+
<div class="fdy-card__action"><a class="fdy-btn fdy-btn--text fdy-btn--sm" href="/queue">View all</a></div>
|
|
1989
|
+
</header>
|
|
1990
|
+
<div class="fdy-card__body">…</div>
|
|
1991
|
+
</article>
|
|
1992
|
+
```
|
|
1993
|
+
|
|
1994
|
+
A collapsible section whose band is a `<button>` cannot hold the action inside it (a button in a
|
|
1995
|
+
button is invalid and is read as one control): put `__action` beside the button, not in it.
|
|
1996
|
+
|
|
1733
1997
|
### One card, one primary action, one escape hatch
|
|
1734
1998
|
|
|
1735
1999
|
A card with **two** actions cannot be `--button`: interactive content nested in a `<button>` is
|
|
@@ -1790,6 +2054,8 @@ Initials or image. Sizes `--sm` / default / `--lg`; `.fdy-avatar-group` stacks t
|
|
|
1790
2054
|
group). `--tone-1`…`--tone-8` are decorative tints (WCAG AA in both themes) to distinguish
|
|
1791
2055
|
same-initial avatars. Hash the index off the **full** name, not the initials.
|
|
1792
2056
|
|
|
2057
|
+
`--square` for a mark that is not a person: a company logo, a card's icon chip.
|
|
2058
|
+
|
|
1793
2059
|
Sizes: `--xs` 1.5rem · *(base)* 2.5rem · `--sm` 2rem · `--lg` 3.5rem. Use **`--xs` inside a control**
|
|
1794
2060
|
because `.fdy-btn--sm` is itself 2rem tall, so an `--sm` avatar fills a small button edge to edge and the
|
|
1795
2061
|
ghost border cuts across the circle.
|
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.6.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`.
|
|
@@ -60,7 +60,7 @@ npm i @cahyo-dimas/freeday
|
|
|
60
60
|
```js
|
|
61
61
|
import '@cahyo-dimas/freeday/css'; // token + komponen (satu file)
|
|
62
62
|
import '@cahyo-dimas/freeday'; // semua enhancer JS (auto-init [data-fdy-*])
|
|
63
|
-
// granular bila perlu: '@cahyo-dimas/freeday/tokens' · '@cahyo-dimas/freeday/css/components' · '@cahyo-dimas/freeday/enhancers/<nama>'
|
|
63
|
+
// granular bila perlu: '@cahyo-dimas/freeday/tokens' · '@cahyo-dimas/freeday/css/components' · '@cahyo-dimas/freeday/enhancers/<nama>' · '@cahyo-dimas/freeday/media' (breakpoint @custom-media)
|
|
64
64
|
```
|
|
65
65
|
Set tema di root app: `<html data-theme="light" data-density="comfortable">`. `dist/` di-commit &
|
|
66
66
|
ter-publish → install tanpa build step; minify diserahkan ke bundler konsumen. Karena terbit di
|
|
@@ -153,9 +153,13 @@ container-nya, bukan scroll sendiri.
|
|
|
153
153
|
menyusut/melebar saat `data-density` berubah.
|
|
154
154
|
- (roadmap) `data-style` untuk varian visual lain.
|
|
155
155
|
- Skala breakpoint (`sm`/`md`/`lg`/`xl` = 600/960/1280/1920px, sama dengan utilitas
|
|
156
|
-
`src/components/breakpoints.css`)
|
|
157
|
-
'@cahyo-dimas/freeday/breakpoints'
|
|
158
|
-
|
|
156
|
+
`src/components/breakpoints.css`) tersedia untuk app-mu dalam dua bahasa: `import { breakpoints }
|
|
157
|
+
from '@cahyo-dimas/freeday/breakpoints'` untuk JS (`matchMedia`, varian utility), dan `@import
|
|
158
|
+
'@cahyo-dimas/freeday/media'` untuk CSS, yang mendeklarasikan tiap lebar sebagai `@custom-media`
|
|
159
|
+
sehingga sebuah rule ditulis `@media (--fdy-below-sm)`, bukan literal yang diketik ulang (butuh
|
|
160
|
+
PostCSS; opt-in). Objeknya juga membawa dua lebar yang **bukan** anak tangga ramp dan tak boleh
|
|
161
|
+
dibulatkan ke salah satunya: `nav` (721, saklar drawer↔sidebar milik shell) dan `filterbar` (640,
|
|
162
|
+
tempat `.fdy-filterbar` menumpuk). Lihat [`COMPONENTS.md`](COMPONENTS.md) untuk apa yang diaturnya.
|
|
159
163
|
|
|
160
164
|
## Integrasi framework (SPA)
|
|
161
165
|
> **Peta library lengkap:** [`docs/integrations.md`](docs/integrations.md). Tiap area
|