@cahyo-dimas/freeday 3.8.0 → 3.9.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 +104 -0
- package/COMPONENTS.md +112 -29
- package/README.id.md +4 -4
- package/README.md +4 -3
- package/adapters/blazor/FdyDatepicker.razor.cs +15 -1
- package/adapters/blazor/FdyMonthpicker.razor +29 -0
- package/adapters/blazor/FdyMonthpicker.razor.cs +102 -0
- package/adapters/blazor/FdyTable.razor +6 -1
- package/adapters/blazor/FdyTable.razor.cs +33 -0
- package/adapters/core/month-grid.d.ts +116 -0
- package/adapters/core/month-grid.js +234 -0
- package/adapters/react/components/FdyDatepicker.tsx +78 -160
- package/adapters/react/components/FdyMonthpicker.tsx +400 -0
- package/adapters/react/components/FdyTable.tsx +38 -2
- package/adapters/react/components/FdyTableFilter.tsx +13 -1
- package/adapters/react/index.d.ts +5 -1
- package/adapters/react/index.js +1 -0
- package/adapters/react/usePopover.ts +54 -9
- package/adapters/vue/components/FdyDatepicker.vue +72 -159
- package/adapters/vue/components/FdyMonthpicker.vue +387 -0
- package/adapters/vue/components/FdyTable.vue +33 -1
- package/adapters/vue/components/FdyTableFilter.vue +13 -1
- package/adapters/vue/index.d.ts +16 -0
- package/adapters/vue/index.js +1 -0
- package/adapters/vue/usePopover.ts +53 -10
- package/dist/freeday-table.js +64 -5
- package/dist/freeday.bundle.css +19 -2
- package/dist/freeday.css +19 -2
- package/dist/freeday.js +64 -5
- package/docs/agent-onboarding.md +3 -3
- package/docs/getting-started.md +6 -6
- package/docs/integrations.md +22 -4
- package/package.json +2 -2
- package/src/components/table.css +19 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,110 @@ 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.9.0] - 2026-10-23
|
|
13
|
+
|
|
14
|
+
**Rilis ini dimulai sebagai tiga penambahan dan berakhir dengan lima cacat, dan yang menemukan
|
|
15
|
+
kelimanya adalah penambahan itu sendiri.** Pemiliknya memutuskan batas antara kit dan app yang
|
|
16
|
+
memakainya ditentukan oleh satu pertanyaan: apakah sesuatu bisa diadaptasi universal. Di bawah
|
|
17
|
+
ukuran itu beberapa hal yang sebelumnya dicatat sebagai "selera pemakai" atau diparkir menunggu
|
|
18
|
+
pemanggil kedua ternyata pekerjaan kit, dan menutupnya membuka apa yang ada di belakangnya.
|
|
19
|
+
|
|
20
|
+
Satu algoritma hidup di **empat salinan**. `#079` di 3.8.0 memperbaiki posisi panel popover di satu
|
|
21
|
+
di antaranya, dan tiga butir rilis ini adalah akibat langsungnya: salinan bertipe membawa cacat yang
|
|
22
|
+
sama untuk delapan komponen, salinan keempat di enhancer tabel memecah paritas begitu salinan
|
|
23
|
+
bertipe dibetulkan, dan memindahkan panel filter ke tempat yang benar membuka dua hal lagi. Satu di
|
|
24
|
+
antaranya sudah terkirim ke pengguna sejak lama tanpa ada yang melaporkannya: panel filter pada
|
|
25
|
+
tabel di dalam modal terbuka, tergambar, lalu menolak segala masukan.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **Cacat urutan `#079` ada di salinan bertipe juga** (`#081`), dan di sana ia kena delapan
|
|
29
|
+
komponen: `FdyDatepicker`, `FdyAutocomplete`, `FdyCascade` dan `FdyCombo`, masing-masing di Vue
|
|
30
|
+
dan React. `adapters/vue/usePopover.ts` dan padanannya di React membaca rect pemicu sebagai hal
|
|
31
|
+
pertama, sama seperti jalur mentah sebelum 3.8.0. Terukur: panel mendarat 741 lawan pemicu 756,
|
|
32
|
+
selisih tepat satu lebar scrollbar klasik. `FdyTableFilter` lolos, dan sebabnya menarik:
|
|
33
|
+
`.fdy-filter` satu-satunya panel kit yang `position:fixed`, jadi dokumen tidak pernah tumbuh dan
|
|
34
|
+
pemicunya tidak pernah bergeser.
|
|
35
|
+
- **Panel filter pada tabel di dalam modal tidak bisa diisi** (`#082`). `showModal()` membuat
|
|
36
|
+
segalanya di luar subtree dialog `inert`, dan panel jalur mentah menempel ke `document.body`,
|
|
37
|
+
yaitu di luar itu. Panelnya terbuka, tergambar, lalu menolak fokus dan klik: `focus()` milik
|
|
38
|
+
enhancer sendiri jadi no-op dan `elementFromPoint` di tengah panel mengembalikan sel tabel di
|
|
39
|
+
belakangnya. Rect tidak bisa melihat ini, jadi yang menemukannya hit test. Panelnya kini anak
|
|
40
|
+
`<th>`-nya sendiri seperti `FdyTableFilter` bertipe, dan salinan keempat logika posisi yang
|
|
41
|
+
memosisikannya ikut terhapus.
|
|
42
|
+
- **Panel filter mewarisi tipografi sel header** (`#083`). Dari sepuluh deklarasi `.fdy-table th`,
|
|
43
|
+
tujuh diwariskan, dan empat sudah ditimpa sejak terbit. Yang lolos: `white-space:nowrap`, yang
|
|
44
|
+
membuat nilai enum panjang melebar sampai `max-width` alih-alih membungkus, plus `font-size` dan
|
|
45
|
+
`text-align`. Sumber kedua dari sel yang sama: `.fdy-table__num` menambahkan
|
|
46
|
+
`font-family:var(--font-mono)`, jadi judul panel tercetak dengan huruf data.
|
|
47
|
+
- **Panel filter jalur merosot terkurung konteks penumpukan sel header** (`#084`).
|
|
48
|
+
`.fdy-table--sticky-head thead th` adalah `position:sticky;z-index:2`, yang membuat konteks
|
|
49
|
+
penumpukan, jadi `z-index:130` milik panel diurutkan di dalam angka 2 itu. Jalur popover lolos
|
|
50
|
+
sebab top layer tidak peduli konteks penumpukan, tetapi Popover API baru ada di Chrome 114,
|
|
51
|
+
Safari 17 dan Firefox 125 sementara lantai dukungan kit Chrome 105, Safari 15.4 dan Firefox 121.
|
|
52
|
+
Terukur di jalur merosot: panel tumpang tindih 224x49px dengan `.fdy-bottomnav`, dan 49px itu
|
|
53
|
+
persis tombol Reset dan Close. Panelnya kini dipasang di akar tabel dan diposisikan lewat
|
|
54
|
+
`place()`. Menaikkan angkanya tidak akan menolong, sebab angka 2 itu yang menahan header beku di
|
|
55
|
+
atas sel badan beku dan menaikkannya di atas 50 akan mencetak seluruh baris header di atas pita
|
|
56
|
+
bawah.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
- **`FdyTable` meneruskan keenam prop teks pita kakinya** (`#080`), di ketiga adapter. 3.8.0 memberi
|
|
60
|
+
`FdyTableFooter` prop itu, tetapi tabel merender pita kakinya sendiri tanpa meneruskannya, jadi
|
|
61
|
+
app non-Inggris yang memakai pita kaki bawaan tetap Inggris dan jalan keluarnya satu-satunya
|
|
62
|
+
`pager={false}` plus merender pita kakinya sendiri. Prop datar, bukan objek, sebab penjaga
|
|
63
|
+
dokumentasi membaca nama prop satu per satu dan sebuah objek akan menyembunyikan isinya dari
|
|
64
|
+
penjaga itu.
|
|
65
|
+
- **Perataan untuk popover bertipe** (`#081`). `align: 'start' | 'end'`, nama yang sama dengan jalur
|
|
66
|
+
mentah, sebagai argumen posisi keempat `usePopover`. Objek opsi ditolak sebab literal objek di
|
|
67
|
+
`useEffect` React punya identitas baru setiap render sementara primitif masuk ke deps tanpa
|
|
68
|
+
menuntut `useMemo` dari pemanggil. Yang memilih perataan komponennya sendiri, jadi `FdyTable`
|
|
69
|
+
tidak bertambah prop: `FdyTableFilter` menulis `'end'` karena ia memang selalu duduk di header
|
|
70
|
+
kolom.
|
|
71
|
+
- **`matchWidth` di jalur bertipe** (`#082`). Adapter selalu menulis `minWidth` selebar pemicu, dan
|
|
72
|
+
untuk corong filter 24px nilai inline itu mengalahkan `min-width:14rem` di stylesheet: panel
|
|
73
|
+
terukur 206px alih-alih 224. Yang lebarnya bergerak hanya `FdyTableFilter`, jadi hanya filter yang
|
|
74
|
+
mengirim `matchWidth:false`. Keempat panel lain memang harus tetap memakai bawaannya, dan
|
|
75
|
+
alasannya mekanisme yang sama dengan `#079`: ketiga listbox `min-width:100%`, dan di top layer
|
|
76
|
+
`100%` itu lebar viewport.
|
|
77
|
+
- **`loadingText` di `FdyTable` Vue dan React** (`#085`). Blazor punya `LoadingText` sejak awal,
|
|
78
|
+
kedua adapter lain menulis `Loading…` sebagai literal di templat. Sapuan yang menemukannya juga
|
|
79
|
+
mencatat dua belas string di panel filter kolom dan satu nama aksesibel di `FdyCfl` yang masih
|
|
80
|
+
tertutup di ketiga adapter sekaligus; keduanya tercatat dengan nomor baris untuk rilis berikutnya.
|
|
81
|
+
- **`FdyMonthpicker`** (`#086`), di ketiga adapter. 3.8.0 menambahkan `mode="month"` di
|
|
82
|
+
`FdyDatepicker`, dan bentuk itu membuat satu prop membawa dua format dengan prop lain yang memilih
|
|
83
|
+
format mana. Komponen tersendiri menutupnya dengan hitungan prop, bukan dengan alias tipe: tidak
|
|
84
|
+
ada `mode` yang bisa mengubah arti `modelValue`. Gridnya diangkat ke `adapters/core/month-grid.js`
|
|
85
|
+
dan dipakai balik oleh `FdyDatepicker`, jadi implementasi grid bulan di kit tetap dua, bukan tiga;
|
|
86
|
+
kedua datepicker menyusut 87 dan 82 baris. Jalur mentah tidak diubah dan `data-mode="month"` tidak
|
|
87
|
+
ditandai usang, sebab atribut string tidak punya tipe yang bisa berbohong soal isi `data-value`.
|
|
88
|
+
|
|
89
|
+
### Deprecated
|
|
90
|
+
- **`mode` di `FdyDatepicker`** (`#086`), bersama `FdyDatepickerMode.Month` dan parameter `Mode` di
|
|
91
|
+
Blazor. Tetap bekerja persis seperti di 3.8.0, dan ada uji paritas sel demi sel yang menjaga janji
|
|
92
|
+
itu. Dihapus di 4.0. Penandaannya lewat tipe dan bukan prosa, satu mekanisme per stack
|
|
93
|
+
(`@deprecated` di Vue dan React, `[Obsolete]` di Blazor), masing-masing menyebut penggantinya, dan
|
|
94
|
+
ada uji yang menuntut nama pengganti itu tetap ada di sana. Repo ini belum pernah memensiunkan apa
|
|
95
|
+
pun, jadi ini presedennya.
|
|
96
|
+
|
|
97
|
+
### Catatan: lencana netral sudah ada
|
|
98
|
+
Catatan app yang jadi sumber 3.8.0 dan 3.9.0 meminta `fdy-badge--neutral` untuk status yang tidak
|
|
99
|
+
boleh terbaca seperti aksi. Kelas itu tidak dibuat, sebab `.fdy-badge--outline` sudah menjawabnya:
|
|
100
|
+
tintanya 7.46:1 di tema terang dan 7.28:1 di gelap terhadap permukaan, jadi ia sudah lewat gerbang
|
|
101
|
+
kontras pada ambang teks untuk ketiga permukaan. Yang ditambahkan dokumentasi yang mengatakannya,
|
|
102
|
+
plus penjaga yang mengikat dokumen itu ke deklarasinya. Separuh usul yang lain, menjadikan netral
|
|
103
|
+
sebagai **bawaan** `.fdy-badge`, tetap milik 4.0 sebab ia mengubah tampilan setiap lencana yang
|
|
104
|
+
sudah terkirim.
|
|
105
|
+
|
|
106
|
+
### Catatan: dokumen yang menjelaskan varian yang tidak bekerja
|
|
107
|
+
`COMPONENTS.md` menulis bahwa `.fdy-table__num` meratakan ke kanan dan memasangnya di `<th>` pada
|
|
108
|
+
dua contoh. Kelas itu berspesifisitas `(0,1,0)` sementara `.fdy-table th` `(0,2,0)`, jadi di sel
|
|
109
|
+
header huruf data dan angka tabularnya sampai tetapi `text-align:left` dari aturan header yang
|
|
110
|
+
menang. Keduanya lahir di commit yang sama pada Juli dan kalimat dokumennya baru datang pada
|
|
111
|
+
Agustus, tidak ada uji yang pernah menegaskan perataan header lewat kelas itu, dan docs hidup kit
|
|
112
|
+
mengirim bentuk itu di delapan tempat dengan header rata kiri sejak saat itu. Jadi yang dibetulkan
|
|
113
|
+
dokumennya: perilakunya dibiarkan, sebab mengubahnya menggeser setiap header kolom angka yang sudah
|
|
114
|
+
terkirim.
|
|
115
|
+
|
|
12
116
|
## [3.8.0] - 2026-10-23
|
|
13
117
|
|
|
14
118
|
**Empat penambahan dari catatan yang sama, dan tiga di antaranya ternyata sudah separuh ada di
|
package/COMPONENTS.md
CHANGED
|
@@ -34,9 +34,9 @@ live docs.
|
|
|
34
34
|
invalid state does not belong to the control itself.
|
|
35
35
|
6. **Interactive components need their enhancer script.** Static ones (button, card, badge, table,
|
|
36
36
|
alert, breadcrumb, timeline, accordion, tree-without-cascade…) are CSS-only.
|
|
37
|
-
7. **On Vue, React or Blazor,
|
|
38
|
-
`FdyDatepicker` · `
|
|
39
|
-
`FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`, from `@cahyo-dimas/freeday/vue`, `/react`, or the
|
|
37
|
+
7. **On Vue, React or Blazor, twelve components have a typed wrapper. Use it.** `FdyCombo` ·
|
|
38
|
+
`FdyDatepicker` · `FdyMonthpicker` · `FdyDateRange` · `FdyAutocomplete` · `FdyCascade` · `FdyCfl` ·
|
|
39
|
+
`FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`, from `@cahyo-dimas/freeday/vue`, `/react`, or the
|
|
40
40
|
`Freeday.Blazor` RCL. Each is flagged at its own section below. Hand-writing their raw markup in
|
|
41
41
|
those stacks *looks* right at first, because the enhancer initialises once and the first render is
|
|
42
42
|
correct. Then
|
|
@@ -955,7 +955,7 @@ a new class.
|
|
|
955
955
|
|---|---|---|
|
|
956
956
|
| `modelValue` · `value` | `string \| null` | ISO `YYYY-MM-DD`, or `YYYY-MM` under `mode="month"`. `''`/`null` for empty. |
|
|
957
957
|
| `onChange` | `(value: string) => void` | React. Vue emits `update:modelValue` and `change`. Clearing sends `''`. |
|
|
958
|
-
| `mode?` | `'day' \| 'month'` | `day` is the calendar as it always was. `month` opens on the month grid, stops there, and answers with `YYYY-MM`. Blazor takes the enum `FdyDatepickerMode
|
|
958
|
+
| `mode?` | `'day' \| 'month'` | **Deprecated, use `<FdyMonthpicker>`.** `day` is the calendar as it always was. `month` opens on the month grid, stops there, and answers with `YYYY-MM`. Blazor takes the enum `FdyDatepickerMode`. Still works, unchanged; removed in 4.0. See below for why, and for the raw path, which keeps `data-mode="month"`. |
|
|
959
959
|
| `min?` · `max?` | `string` | ISO bounds; days outside them cannot be picked. Month mode also accepts `YYYY-MM`, and a bound inside a month leaves that month pickable. |
|
|
960
960
|
| `locale?` | `string` | BCP-47 tag deciding month and weekday names. Defaults to the page's `<html lang>`, then `en`. |
|
|
961
961
|
| `placeholder?` | `string` | |
|
|
@@ -981,6 +981,33 @@ Blazor carries the same surface bar four: `locale` (the enhancer formats through
|
|
|
981
981
|
page's `<html lang>`), `clearable` and `clearLabel` (this path builds no clear button), and
|
|
982
982
|
`ariaLabelledby` (the trigger it builds is named by `Label`).
|
|
983
983
|
|
|
984
|
+
### Props — `<FdyMonthpicker>`
|
|
985
|
+
|
|
986
|
+
> Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). It renders the same trigger
|
|
987
|
+
> and panel as the date picker, over the same classes, and shares its month and year grids.
|
|
988
|
+
|
|
989
|
+
| Prop | Type | What it does |
|
|
990
|
+
|---|---|---|
|
|
991
|
+
| `modelValue` · `value` | `string \| null` | The chosen month, ISO `YYYY-MM`. `''`/`null` for empty. One format, always: nothing on this component can change it. |
|
|
992
|
+
| `onChange` | `(value: string) => void` | React. Vue emits `update:modelValue` and `change`. Clearing sends `''`. |
|
|
993
|
+
| `min?` · `max?` | `string` | ISO `YYYY-MM` bounds (a full `YYYY-MM-DD` is read too, as its own month). A bound that falls inside a month leaves that month pickable. |
|
|
994
|
+
| `locale?` | `string` | BCP-47 tag deciding the month names. Defaults to the page's `<html lang>`, then `en`. |
|
|
995
|
+
| `placeholder?` | `string` | Shown while nothing is picked. Default `Select month`. |
|
|
996
|
+
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
997
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps focus and tab order and shows its month, but it cannot be opened, cleared or changed. |
|
|
998
|
+
| `clearable?` | `boolean` | Show a × in the trigger once a month is set, so an optional month can be unset. Sends `''`. Off by default. |
|
|
999
|
+
| `clearLabel?` | `string` | Accessible name for that ×. Default `Clear month`. |
|
|
1000
|
+
| `prevYearLabel?` · `nextYearLabel?` | `string` | The arrows beside the month grid, which step a year. Defaults `Previous year` / `Next year`. |
|
|
1001
|
+
| `chooseYearLabel?` | `string` | The title button that drills into the year grid. Default `Choose year`. |
|
|
1002
|
+
| `prevYearsLabel?` · `nextYearsLabel?` | `string` | Page arrows, shown in the year grid. Defaults `Previous years` / `Next years`. |
|
|
1003
|
+
|
|
1004
|
+
There is no `prevMonthLabel` or `nextMonthLabel` here. The arrows sit beside a month grid, so they
|
|
1005
|
+
step a year, and the day grid they would have belonged to does not exist on this component. Blazor
|
|
1006
|
+
carries the same surface bar three: `locale` (the enhancer formats through `Intl` from the page's
|
|
1007
|
+
`<html lang>`), `clearable` and `clearLabel` (that path builds no clear button) and
|
|
1008
|
+
`ariaLabelledby` (the trigger it builds is named by `Label`). It adds `BackToMonthsLabel`, the year
|
|
1009
|
+
grid's own title.
|
|
1010
|
+
|
|
984
1011
|
### Props — `<FdyDateRange>`
|
|
985
1012
|
|
|
986
1013
|
| Prop | Type | What it does |
|
|
@@ -1025,23 +1052,36 @@ months, `Home`/`End` January/December, `PageUp`/`PageDown` a year, `Enter`/`Spac
|
|
|
1025
1052
|
year grid likewise: arrows ±1/±3 years, `Home`/`End` the ends of the page, `PageUp`/`PageDown` a
|
|
1026
1053
|
page of twelve.
|
|
1027
1054
|
|
|
1028
|
-
**Picking a month, not a day.**
|
|
1029
|
-
`
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
navigation and not an answer.
|
|
1055
|
+
**Picking a month, not a day.** `<FdyMonthpicker>` answers with `YYYY-MM`. The panel opens on the
|
|
1056
|
+
12-cell month grid, a click or `Enter` commits from it, and there is no day grid to fall into. The
|
|
1057
|
+
title still steps up to the year grid and back down, because a year is navigation and never an
|
|
1058
|
+
answer. On the raw path the same thing is `data-mode="month"` on a `data-fdy-datepicker` div.
|
|
1033
1059
|
|
|
1034
1060
|
This exists because `<input type="month">` names the month in the **browser's** language, which an
|
|
1035
1061
|
Indonesian page read on an English phone cannot override. The month cells, the cell's accessible
|
|
1036
1062
|
name and the value in the trigger all go through `Intl` with the page's `<html lang>`, the same
|
|
1037
1063
|
route the day grid's month names have always taken, so `lang="id"` reads `Oktober 2026` with nothing
|
|
1038
|
-
configured. Keyboard in the month grid
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1064
|
+
configured. Keyboard in the month grid: arrows move one and three months, `Home`/`End` jump to
|
|
1065
|
+
January and December, `PageUp`/`PageDown` step a year, and `Enter`/`Space` commits. The cell keeps
|
|
1066
|
+
its name when it becomes selected and carries the state in `aria-selected`, as the day cells do.
|
|
1067
|
+
|
|
1068
|
+
**`mode="month"` on `<FdyDatepicker>` is deprecated, and still works.** It shipped in 3.8.0 and
|
|
1069
|
+
behaves exactly as it did, so nothing has to move today; it goes in 4.0. The type was the defect.
|
|
1070
|
+
That prop left `modelValue` / `value`, `min` and `max` each carrying `YYYY-MM-DD` or `YYYY-MM` while
|
|
1071
|
+
a *different* prop decided which, and a union discriminated by a sibling prop is a shape neither
|
|
1072
|
+
`defineProps<{...}>` nor an exported `FdyDatepickerProps` can hold. A separate component states it
|
|
1073
|
+
in one line per prop. A consumer's editor says so as well: `mode` is marked `@deprecated` in both
|
|
1074
|
+
adapters, and `Mode` together with `FdyDatepickerMode.Month` carries `[Obsolete]` in Blazor, each
|
|
1075
|
+
naming `FdyMonthpicker`.
|
|
1076
|
+
|
|
1077
|
+
**The raw path keeps `data-mode="month"`, and it is not deprecated.** `data-mode` is a string
|
|
1078
|
+
attribute with no type to be wrong about: a reader of the markup sees `data-mode` and `data-value`
|
|
1079
|
+
side by side and can tell what the value holds. The typed signatures were what could not say it, so
|
|
1080
|
+
the retirement is theirs. Shipping a second attribute would leave two spellings of one feature on
|
|
1081
|
+
the one path that never had the problem.
|
|
1082
|
+
|
|
1083
|
+
Nothing changes without either of them. A picker with no `mode` and no `data-mode` is the day picker
|
|
1084
|
+
it has always been, down to the `YYYY-MM-DD` it reports.
|
|
1045
1085
|
|
|
1046
1086
|
- Attributes: `data-value="YYYY-MM-DD"`, `data-mode="month"`, `data-placeholder`, `data-label`,
|
|
1047
1087
|
`data-fdy-no-icon`
|
|
@@ -1054,6 +1094,7 @@ down to the `YYYY-MM-DD` it reports.
|
|
|
1054
1094
|
|
|
1055
1095
|
<div class="fdy-field">
|
|
1056
1096
|
<span class="fdy-label">Reporting month</span>
|
|
1097
|
+
<!-- Typed stacks: <FdyMonthpicker>, see its props above. -->
|
|
1057
1098
|
<div data-fdy-datepicker data-mode="month" data-value="2026-10"></div>
|
|
1058
1099
|
</div>
|
|
1059
1100
|
|
|
@@ -1235,7 +1276,16 @@ over the connector line, so omitting `__btn` cannot make the connector draw thro
|
|
|
1235
1276
|
## Table — `.fdy-table`
|
|
1236
1277
|
Semantic static table. Wrap in `.fdy-table-wrap` (bordered surface) or `.fdy-table-scroll`
|
|
1237
1278
|
(horizontal scroll). Always a `<caption>` (use `.fdy-visually-hidden` if it shouldn't show) and
|
|
1238
|
-
`<th scope="col">`. Numeric cells get `.fdy-table__num` (right-aligned, tabular
|
|
1279
|
+
`<th scope="col">`. Numeric body cells get `.fdy-table__num` (right-aligned, tabular figures, data
|
|
1280
|
+
font).
|
|
1281
|
+
|
|
1282
|
+
**`.fdy-table__num` aligns a `<td>`, not a `<th>`.** The class is specificity `(0,1,0)` and
|
|
1283
|
+
`.fdy-table th` is `(0,2,0)`, so on a header cell the data font and the tabular figures arrive but
|
|
1284
|
+
`text-align:left` from the header rule wins: the label stays left while the numbers under it go
|
|
1285
|
+
right. Put the class on the header anyway for the font, then add `style="text-align:right"` to move
|
|
1286
|
+
the label with them. That inline value is exactly what the typed `FdyTable` writes for a column with
|
|
1287
|
+
`align: 'right'`, and it is the only thing no ancestor rule can outrank. The class is deliberately
|
|
1288
|
+
left at its own weight: raising it would shift every numeric heading already shipped.
|
|
1239
1289
|
|
|
1240
1290
|
**`--striped`** zebras the even body rows. The tint is deliberately *half* a surface step, not the
|
|
1241
1291
|
full one hover uses, so hover still reads on a striped row instead of matching it — and stripe,
|
|
@@ -1256,7 +1306,7 @@ for 4.0.
|
|
|
1256
1306
|
<div class="fdy-table-wrap">
|
|
1257
1307
|
<table class="fdy-table">
|
|
1258
1308
|
<caption>Recent invoices</caption>
|
|
1259
|
-
<thead><tr><th scope="col">Invoice</th><th scope="col" class="fdy-table__num">Amount</th></tr></thead>
|
|
1309
|
+
<thead><tr><th scope="col">Invoice</th><th scope="col" class="fdy-table__num" style="text-align:right">Amount</th></tr></thead>
|
|
1260
1310
|
<tbody>
|
|
1261
1311
|
<tr><td>INV-1042</td><td class="fdy-table__num">1,240,000</td></tr>
|
|
1262
1312
|
</tbody>
|
|
@@ -1288,7 +1338,7 @@ sum, so measure them once after render (and on resize) and set the variable.
|
|
|
1288
1338
|
<caption class="fdy-visually-hidden">Exchange rates, August 2026</caption>
|
|
1289
1339
|
<thead><tr>
|
|
1290
1340
|
<th scope="col" class="fdy-table__freeze fdy-table__freeze--edge">Date</th>
|
|
1291
|
-
<th scope="col" class="fdy-table__num">USD</th>
|
|
1341
|
+
<th scope="col" class="fdy-table__num" style="text-align:right">USD</th>
|
|
1292
1342
|
</tr></thead>
|
|
1293
1343
|
<tbody>
|
|
1294
1344
|
<tr>
|
|
@@ -1317,7 +1367,7 @@ the two ever disagree.
|
|
|
1317
1367
|
| `label` | `string` | Header label. |
|
|
1318
1368
|
| `labelHidden` | `boolean` | Render the label for assistive tech only; the header cell looks empty. |
|
|
1319
1369
|
| `sortable` | `boolean` | Show a sort toggle in the header. |
|
|
1320
|
-
| `filter` | `'text' \| 'enum' \| 'number' \| 'date'` | Offer a column filter of this type (the funnel popover). |
|
|
1370
|
+
| `filter` | `'text' \| 'enum' \| 'number' \| 'date'` | Offer a column filter of this type (the funnel popover). The panel is several times wider than the funnel, so it hangs from the funnel's RIGHT edge and the rightmost column keeps the page's gutter; near the left edge of a narrow screen it slides right instead of off it. |
|
|
1321
1371
|
| `align` | `'left' \| 'right' \| 'center'` | Cell alignment. Default `left`. |
|
|
1322
1372
|
| `mono` | `boolean` | Render cells in the monospace data font (`.fdy-mono`). |
|
|
1323
1373
|
| `sortType` | `'text' \| 'number' \| 'date'` | Override the comparator; defaults to one derived from `filter`, else `text`. |
|
|
@@ -1358,7 +1408,7 @@ provide the state prop for a concern and you own that concern instead. React nam
|
|
|
1358
1408
|
| `pageIndex?` · `onPageIndexChange?` | `number` · `(index) => void` | Client mode: own the 0-based index while the table keeps filtering, sorting and paginating — this is what lets one external pager drive both a table and a card list. |
|
|
1359
1409
|
| `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. |
|
|
1360
1410
|
| `pager?` | `boolean` | Default true. Turn it off to withhold the table's own footer when the screen renders one. |
|
|
1361
|
-
| `loading?` | `boolean` | Show the in-table loading
|
|
1411
|
+
| `loading?` · `loadingText?` | `boolean` · `string` | Show the in-table loading row, and the word in it. Default `Loading…`, the same name and the same default as Blazor's `LoadingText`, so a screen in another language translates the loading row the way it already translates the empty one instead of finding that only one of the two neighbouring states has a prop. |
|
|
1362
1412
|
| `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. |
|
|
1363
1413
|
| `ariaLabel?` | `string` | Names the table for assistive tech. |
|
|
1364
1414
|
| `rowActivatable?` · `onRowActivate?` | `boolean` · `(row) => void` | Rows become focusable and activate on click, Enter or Space. |
|
|
@@ -1371,14 +1421,16 @@ provide the state prop for a concern and you own that concern instead. React nam
|
|
|
1371
1421
|
| `bulkActions?` | `ReactNode` | React. Content in the bulk bar beside the count; Vue uses the `bulk-actions` slot (scoped: `keys`, `clear`), Blazor `BulkContent`. |
|
|
1372
1422
|
| `selectedText?` · `clearSelectionText?` | `string` | Bulk-bar copy. Defaults `{n} selected` (with `{n}` substituted) and `Clear`. |
|
|
1373
1423
|
| `selectAllLabel?` · `selectRowLabel?` · `bulkLabel?` | `string` | Accessible names for the header checkbox, each row checkbox, and the bulk-bar region. Defaults `Select all rows on this page`, `Select row`, `Bulk actions`. |
|
|
1424
|
+
| `infoText?` · `pageSizeLabel?` | `string` | The footer this table draws itself: its range line (`{from}`, `{to}`, `{total}` substituted, every occurrence) and the visible word beside its rows-per-page control. Same names, same substitution and the same English defaults as `<FdyTableFooter>` below, `Showing {from}–{to} of {total}` and `Rows`, so a screen that moves between the table's footer and its own moves a prop instead of rewriting it. |
|
|
1425
|
+
| `paginationLabel?` · `prevLabel?` · `nextLabel?` · `pageLabel?` | `string` | The accessible names inside that footer: the pager landmark, both arrows, and a numbered page button (`{n}` substituted). Defaults `Pagination`, `Previous page`, `Next page`, `Go to page {n}`. |
|
|
1374
1426
|
| `renderCell?` | `(column, row, value) => ReactNode` | React. Custom cell rendering; Vue uses the `cell` slot. |
|
|
1375
1427
|
| `toolbar?` | `ReactNode` | React. Content above the table; Vue uses the `toolbar` slot. |
|
|
1376
1428
|
| `onProcess?` | `(result: { rows, total }) => void` | React. The processed page after filter/sort/paginate, in **both** modes. Vue emits `process`, Blazor calls it `Process`. Render the same set elsewhere — a card list, a summary, a CSV export — without re-deriving the pipeline. |
|
|
1377
1429
|
|
|
1378
|
-
Blazor matches this surface (`
|
|
1379
|
-
`
|
|
1380
|
-
|
|
1381
|
-
`
|
|
1430
|
+
Blazor matches this surface (`EmptyContent` in place of `empty`, `Toolbar` and `RowDetail` as
|
|
1431
|
+
`RenderFragment`s) and is the one adapter that also exposes `FiltersChanged`. Its filter-aware
|
|
1432
|
+
empty state is `EmptyTemplate`, a `RenderFragment<FdyTableEmptyContext>` (`Filtered`,
|
|
1433
|
+
`ClearFilters`) that wins over `EmptyContent`.
|
|
1382
1434
|
|
|
1383
1435
|
**Select-all ticks the current page, not the whole result set.** A header checkbox that silently
|
|
1384
1436
|
selects rows the reader cannot see is how a bulk delete goes wrong, so it acts on the rows on
|
|
@@ -1444,6 +1496,21 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
|
|
|
1444
1496
|
labels, the timepicker's, the cascade's two defaults, the table's `Min`/`Maks` filter
|
|
1445
1497
|
placeholders and the chart's `Seri 1` all sat outside it.
|
|
1446
1498
|
|
|
1499
|
+
**Where a filter panel opens, on both paths.** `.fdy-filter` is lifted into the top layer and
|
|
1500
|
+
anchored to its funnel by `freeday-popover.js` on the raw path and by `usePopover` on the typed one,
|
|
1501
|
+
so the raw path needs that file loaded beside `freeday-table.js` (`dist/freeday.js` has both). Four
|
|
1502
|
+
things follow, and the raw path moved to meet the typed one in 3.9.0 rather than the other way
|
|
1503
|
+
round. The panel's RIGHT edge lines up with the funnel's, because a 24px icon in a column header is
|
|
1504
|
+
no guide to where a 14rem form should hang, and the rightmost column is where that used to push the
|
|
1505
|
+
panel off the page; it is clamped to stay on screen either way, so the leftmost column slides right
|
|
1506
|
+
instead of hanging off. It sits 4px below the funnel, the kit's one gap, where the raw path used to
|
|
1507
|
+
keep 4px below and 8px from the viewport edge. It flips above the funnel when there is no room
|
|
1508
|
+
below, which the raw path never did. And it keeps the `min-width:14rem` the stylesheet asks for,
|
|
1509
|
+
which the typed path used to lose to an inline floor taken from the funnel's own 24px. The panel is
|
|
1510
|
+
a child of its own `<th>` on both paths: the top layer is what escapes a card's `overflow:hidden`
|
|
1511
|
+
now, and a panel parked on `<body>` is outside any open modal `<dialog>`, which makes it inert, so a
|
|
1512
|
+
filter in a table inside a modal painted a form that refused every click.
|
|
1513
|
+
|
|
1447
1514
|
**Testing note:** a column's filter button and the dialog it opens deliberately share one
|
|
1448
1515
|
accessible name (`Filter <column>`), since a dialog named after its trigger is the normal pattern. In a
|
|
1449
1516
|
Playwright/Testing-Library suite that means `getByLabel('Filter Name')` resolves to two elements;
|
|
@@ -1494,10 +1561,14 @@ that markup is the app's own: the word beside the rows-per-page control, the pag
|
|
|
1494
1561
|
and the name of a numbered page button. It is `pageSizeLabel` and not `rows`, since the enhancer's
|
|
1495
1562
|
`rows` key is the table's `{n} rows` count, a different string.
|
|
1496
1563
|
|
|
1497
|
-
Omit a prop and you get the English above, so nothing already shipped moves. `FdyTable`
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1564
|
+
Omit a prop and you get the English above, so nothing already shipped moves. `FdyTable` takes all
|
|
1565
|
+
six under the same names and hands them to the footer it draws itself, so a table keeps its own
|
|
1566
|
+
footer and still speaks the app's language. For one version it did not, and the only way out was to
|
|
1567
|
+
switch the footer off: a hatch that opened only if you stopped using the table.
|
|
1568
|
+
|
|
1569
|
+
`pager={false}` plus your own `<FdyTableFooter>` is still the route for a **responsive** list, where
|
|
1570
|
+
one band has to serve a table at `lg` and a card list below it, and that is now the only reason to
|
|
1571
|
+
reach for it. Wording alone is not one.
|
|
1501
1572
|
|
|
1502
1573
|
**Who draws the pager.** The table renders its own footer (range + pager) whenever there is more
|
|
1503
1574
|
than one page. Two ways to take it over:
|
|
@@ -2157,6 +2228,18 @@ the rest, so two states a workflow distinguishes never share a look. Map each st
|
|
|
2157
2228
|
tone, never to a hash of the string: a status is a closed set, and a hash reassigns colours the
|
|
2158
2229
|
moment the set grows.
|
|
2159
2230
|
|
|
2231
|
+
**Neutral status takes `--outline`.** A badge with no modifier is the page's accent: primary ink
|
|
2232
|
+
on a primary tint, the same pair the primary button wears, so a status that means nothing in
|
|
2233
|
+
particular ("Not scheduled", "Holiday", "Read 0 of 3") ends up looking like something to click.
|
|
2234
|
+
`--outline` is the neutral one: no fill, a `--color-border-strong` edge, and `--color-text-muted`
|
|
2235
|
+
ink at 7.46:1 on the light surface, 7.28:1 on the dark one.
|
|
2236
|
+
|
|
2237
|
+
There is no filled neutral on purpose. The only neutral fill the token set offers is one surface
|
|
2238
|
+
step, `--color-surface-2`, and a status pill sits either on `--color-surface` or on the app ground,
|
|
2239
|
+
which is `--color-surface-2` itself, so the pill's body would land between 1.00:1 and 1.10:1
|
|
2240
|
+
against what is behind it: a badge whose shape you cannot see. The semantic fills live at 1.03:1 to
|
|
2241
|
+
1.44:1 and read clearly anyway because they carry hue, and a neutral has no hue to carry.
|
|
2242
|
+
|
|
2160
2243
|
**Notification badge is the overlay variant**, not a separate component: wrap anything in
|
|
2161
2244
|
`.fdy-badge-wrap` and add `.fdy-badge-ov` (`--primary` `--danger` `--accent` `--info` `--success`
|
|
2162
2245
|
`--warning`, `--dot` for a bare dot, `--top-left` / `--bottom-right` / `--bottom-left` to move it).
|
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.9.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`.
|
|
@@ -44,9 +44,9 @@ enhancer:
|
|
|
44
44
|
| **Blazor (net8.0)** | RCL `Freeday.Blazor` | `@bind-Value` |
|
|
45
45
|
| HTML statis · Svelte · template server-rendered | *(tanpa wrapper)* markup mentah + enhancer, di bawah | event `fdy-*` |
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
`FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` ·
|
|
49
|
-
untuk
|
|
47
|
+
Kedua belasnya: `FdyCombo` · `FdyDatepicker` · `FdyMonthpicker` · `FdyDateRange` ·
|
|
48
|
+
`FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` ·
|
|
49
|
+
`FdyAppShell`. Menulis markup mentah untuk kedua belas ini di dalam Vue/React/Blazor *kelihatan* baik-baik saja pada mulanya, karena render
|
|
50
50
|
pertamanya memang benar. Setelah itu ia rusak diam-diam: DOM yang di-render framework belakangan tak pernah ter-hydrate, dan state widget
|
|
51
51
|
berakhir di DOM alih-alih di state framework-mu. Selebihnya (button, card, badge, alert, markup
|
|
52
52
|
tabel, layout, dan komponen interaktif yang tak punya wrapper) sama persis di semua stack. Untuk
|
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.9.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`.
|
|
@@ -44,8 +44,9 @@ enhancer:
|
|
|
44
44
|
| **Blazor (net8.0)** | `Freeday.Blazor` RCL | `@bind-Value` |
|
|
45
45
|
| Static HTML · Svelte · server-rendered templates | *(no wrapper)* raw markup + enhancers, below | `fdy-*` events |
|
|
46
46
|
|
|
47
|
-
The
|
|
48
|
-
`FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`.
|
|
47
|
+
The twelve: `FdyCombo` · `FdyDatepicker` · `FdyMonthpicker` · `FdyDateRange` · `FdyAutocomplete` ·
|
|
48
|
+
`FdyCascade` · `FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`.
|
|
49
|
+
Hand-writing the raw
|
|
49
50
|
markup for these inside Vue/React/Blazor *looks* fine at first, because the first render is correct.
|
|
50
51
|
Then it breaks quietly:
|
|
51
52
|
DOM the framework renders later is never hydrated, and the widget's state ends up in the DOM instead
|
|
@@ -14,12 +14,21 @@ public partial class FdyDatepicker
|
|
|
14
14
|
/// <summary>What the picker answers with. <see cref="FdyDatepickerMode.Day"/> (default) is the
|
|
15
15
|
/// calendar as it always was. <see cref="FdyDatepickerMode.Month"/> opens on the month grid,
|
|
16
16
|
/// stops there, and reports <c>yyyy-MM</c>.</summary>
|
|
17
|
+
[Obsolete("Use FdyMonthpicker for a month. This parameter makes Value, Min and Max carry " +
|
|
18
|
+
"yyyy-MM-dd or yyyy-MM while a different parameter decides which, so the binding " +
|
|
19
|
+
"cannot state what to store in it. Still works, unchanged, and removed in 4.0.")]
|
|
17
20
|
[Parameter] public FdyDatepickerMode Mode { get; set; } = FdyDatepickerMode.Day;
|
|
18
21
|
|
|
19
22
|
/* null rather than "day" for the default: an attribute the enhancer does not need is an
|
|
20
23
|
attribute no reader of the seed has to interpret, and the enhancer's own check is
|
|
21
|
-
`=== 'month'`, so the day path is the absence of this.
|
|
24
|
+
`=== 'month'`, so the day path is the absence of this.
|
|
25
|
+
|
|
26
|
+
The pragma is the kit reading its OWN deprecated member, which is the one place that has to
|
|
27
|
+
keep doing so: the promise is that `Mode` keeps working until 4.0, and it cannot work if the
|
|
28
|
+
seed stops looking at it. Scoped to these two lines so every other use still warns. */
|
|
29
|
+
#pragma warning disable CS0618
|
|
22
30
|
private string? ModeAttribute => Mode == FdyDatepickerMode.Month ? "month" : null;
|
|
31
|
+
#pragma warning restore CS0618
|
|
23
32
|
|
|
24
33
|
/// <summary>Accessible label for the trigger.</summary>
|
|
25
34
|
[Parameter] public string? Label { get; set; }
|
|
@@ -96,11 +105,16 @@ public partial class FdyDatepicker
|
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
/// <summary>What <see cref="FdyDatepicker"/> answers with.</summary>
|
|
108
|
+
/// <remarks>The enum goes with <see cref="FdyDatepicker.Mode"/> in 4.0, so only
|
|
109
|
+
/// <see cref="Day"/> is left unmarked: it names the behaviour a picker with no
|
|
110
|
+
/// <c>Mode</c> already has, and writing it changes nothing.</remarks>
|
|
99
111
|
public enum FdyDatepickerMode
|
|
100
112
|
{
|
|
101
113
|
/// <summary>A single day, reported as <c>yyyy-MM-dd</c>.</summary>
|
|
102
114
|
Day,
|
|
103
115
|
|
|
104
116
|
/// <summary>A whole month, reported as <c>yyyy-MM</c>.</summary>
|
|
117
|
+
[Obsolete("Use FdyMonthpicker, whose Value is yyyy-MM and has no Mode to read. " +
|
|
118
|
+
"Still works, unchanged, and removed in 4.0.")]
|
|
105
119
|
Month,
|
|
106
120
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@namespace Freeday.Blazor
|
|
2
|
+
@inherits FreedayComponentBase<FdyMonthpicker>
|
|
3
|
+
|
|
4
|
+
@* Seed-based wrapper over freeday-datepicker.js in its month mode: the enhancer builds the
|
|
5
|
+
input-styled trigger + calendar popover from this empty div, opens on the month grid, and emits
|
|
6
|
+
fdy-datepicker-change. The enhancer owns that DOM, so we render the seed once
|
|
7
|
+
(ShouldRender => false) and read the picked month back through @bind-Value.
|
|
8
|
+
|
|
9
|
+
data-mode="month" is written here rather than taken as a parameter. That is the difference
|
|
10
|
+
between this component and FdyDatepicker: Value has one format, yyyy-MM, because nothing the
|
|
11
|
+
caller passes can change it. *@
|
|
12
|
+
<div @ref="Root" data-fdy-datepicker
|
|
13
|
+
data-mode="month"
|
|
14
|
+
data-value="@Value"
|
|
15
|
+
data-label="@Label"
|
|
16
|
+
data-placeholder="@Placeholder"
|
|
17
|
+
data-min="@Min"
|
|
18
|
+
data-max="@Max"
|
|
19
|
+
data-id="@Id"
|
|
20
|
+
data-describedby="@Describedby"
|
|
21
|
+
data-disabled="@(Disabled ? "true" : null)"
|
|
22
|
+
data-readonly="@(Readonly ? "true" : null)"
|
|
23
|
+
data-invalid="@(Invalid ? "true" : null)"
|
|
24
|
+
data-fdy-text-prev-year="@PrevYearLabel"
|
|
25
|
+
data-fdy-text-next-year="@NextYearLabel"
|
|
26
|
+
data-fdy-text-prev-years="@PrevYearsLabel"
|
|
27
|
+
data-fdy-text-next-years="@NextYearsLabel"
|
|
28
|
+
data-fdy-text-choose-year="@ChooseYearLabel"
|
|
29
|
+
data-fdy-text-back-to-months="@BackToMonthsLabel"></div>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
using Microsoft.AspNetCore.Components;
|
|
2
|
+
using Microsoft.JSInterop;
|
|
3
|
+
|
|
4
|
+
namespace Freeday.Blazor;
|
|
5
|
+
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// A picker that answers with a whole month (<c>yyyy-MM</c>).
|
|
8
|
+
/// </summary>
|
|
9
|
+
/// <remarks>
|
|
10
|
+
/// <para>This is the replacement for <c>FdyDatepicker</c> with <c>Mode="FdyDatepickerMode.Month"</c>,
|
|
11
|
+
/// which shipped in 3.8.0 and is deprecated. There, <c>Value</c> is one <c>string?</c> holding
|
|
12
|
+
/// <c>yyyy-MM-dd</c> or <c>yyyy-MM</c> and a different parameter decides which, so nothing about
|
|
13
|
+
/// the binding tells a caller what to store in it. Here it has one format and there is no
|
|
14
|
+
/// <c>Mode</c>. The deprecated spelling keeps working until 4.0.</para>
|
|
15
|
+
/// <para>It is a separate seed rather than a wrapper around <c>FdyDatepicker</c>: that component
|
|
16
|
+
/// renders once and then hands its DOM to the enhancer (<c>ShouldRender => !Hydrated</c>), so
|
|
17
|
+
/// nesting it would mean forwarding an <c>ElementReference</c> and an event subscription through a
|
|
18
|
+
/// component that owns neither. The logic the two share is the enhancer itself, which both reach
|
|
19
|
+
/// through <c>FreedayDatepicker.init</c>, and that is already one copy.</para>
|
|
20
|
+
/// <para>Month names come from the page's <c><html lang></c> through <c>Intl</c>, which is
|
|
21
|
+
/// what <c><input type="month"></c> cannot do: it names the month in the browser's language.
|
|
22
|
+
/// So an Indonesian page reads <c>Oktober 2026</c> with nothing configured.</para>
|
|
23
|
+
/// </remarks>
|
|
24
|
+
public partial class FdyMonthpicker
|
|
25
|
+
{
|
|
26
|
+
/// <summary>The chosen month as an ISO string (<c>yyyy-MM</c>), or "" when unset. One format,
|
|
27
|
+
/// always. Two-way bindable via <c>@bind-Value</c>.</summary>
|
|
28
|
+
[Parameter] public string? Value { get; set; }
|
|
29
|
+
[Parameter] public EventCallback<string?> ValueChanged { get; set; }
|
|
30
|
+
|
|
31
|
+
/// <summary>Accessible label for the trigger. Defaults to the enhancer's <c>Month</c>.</summary>
|
|
32
|
+
[Parameter] public string? Label { get; set; }
|
|
33
|
+
|
|
34
|
+
/// <summary>Shown while nothing is picked. Defaults to the enhancer's <c>Choose a month</c>.</summary>
|
|
35
|
+
[Parameter] public string? Placeholder { get; set; }
|
|
36
|
+
|
|
37
|
+
/// <summary>Earliest selectable month (ISO <c>yyyy-MM</c>; a full <c>yyyy-MM-dd</c> is read too,
|
|
38
|
+
/// as its own month). A bound that falls inside a month leaves that month pickable.</summary>
|
|
39
|
+
[Parameter] public string? Min { get; set; }
|
|
40
|
+
|
|
41
|
+
/// <summary>Latest selectable month.</summary>
|
|
42
|
+
[Parameter] public string? Max { get; set; }
|
|
43
|
+
|
|
44
|
+
/// <summary>Id for the trigger the enhancer builds, so a form's own label can point at it.</summary>
|
|
45
|
+
[Parameter] public string? Id { get; set; }
|
|
46
|
+
|
|
47
|
+
/// <summary>Id of the help or error text the trigger describes itself with.</summary>
|
|
48
|
+
[Parameter] public string? Describedby { get; set; }
|
|
49
|
+
|
|
50
|
+
/// <summary>Greyed and out of the tab order.</summary>
|
|
51
|
+
[Parameter] public bool Disabled { get; set; }
|
|
52
|
+
|
|
53
|
+
/// <summary>Locked/view mode: focusable and showing its month, but the calendar will not open.
|
|
54
|
+
/// Unlike <see cref="Disabled"/> it keeps tab order and is not greyed.</summary>
|
|
55
|
+
[Parameter] public bool Readonly { get; set; }
|
|
56
|
+
|
|
57
|
+
/// <summary>Marks the field invalid (<c>aria-invalid</c> + the error styling).</summary>
|
|
58
|
+
[Parameter] public bool Invalid { get; set; }
|
|
59
|
+
|
|
60
|
+
/* The calendar's navigation labels, and only the ones this picker shows. Month NAMES follow the
|
|
61
|
+
page's `lang` through Intl and need nothing here. The day grid's own arrows are absent along
|
|
62
|
+
with the day grid, so there is no PrevMonthLabel to pass: the arrows beside a month grid step
|
|
63
|
+
a YEAR. Leave one null and the enhancer's English default stands. */
|
|
64
|
+
[Parameter] public string? PrevYearLabel { get; set; }
|
|
65
|
+
[Parameter] public string? NextYearLabel { get; set; }
|
|
66
|
+
[Parameter] public string? PrevYearsLabel { get; set; }
|
|
67
|
+
[Parameter] public string? NextYearsLabel { get; set; }
|
|
68
|
+
|
|
69
|
+
/// <summary>The title button that drills into the year grid. <c>{label}</c> is the year shown.</summary>
|
|
70
|
+
[Parameter] public string? ChooseYearLabel { get; set; }
|
|
71
|
+
|
|
72
|
+
/// <summary>The year grid's title, back to months. <c>{start}</c> and <c>{end}</c> are the page range.</summary>
|
|
73
|
+
[Parameter] public string? BackToMonthsLabel { get; set; }
|
|
74
|
+
|
|
75
|
+
protected override ValueTask HydrateAsync() => JS.InvokeVoidAsync("FreedayDatepicker.init", Root);
|
|
76
|
+
|
|
77
|
+
protected override async ValueTask OnHydratedAsync()
|
|
78
|
+
=> await SubscribeAsync("fdy-datepicker-change", nameof(OnChange));
|
|
79
|
+
|
|
80
|
+
// The enhancer owns the built trigger + calendar; never let Blazor re-render the seed.
|
|
81
|
+
protected override bool ShouldRender() => !Hydrated;
|
|
82
|
+
|
|
83
|
+
/* Which is why the three states are pushed rather than re-rendered: the seed is written once,
|
|
84
|
+
so a page that disables this field later would otherwise be ignored in silence. */
|
|
85
|
+
protected override async Task OnParametersSetAsync()
|
|
86
|
+
{
|
|
87
|
+
if (!Hydrated) return;
|
|
88
|
+
await JS.InvokeVoidAsync("FreedayDatepicker.setState", Root,
|
|
89
|
+
new { disabled = Disabled, @readonly = Readonly, invalid = Invalid });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// <summary>Invoked by the bridge when the user picks (or clears) a month.</summary>
|
|
93
|
+
[JSInvokable]
|
|
94
|
+
public async Task OnChange(ChangeDetail detail)
|
|
95
|
+
{
|
|
96
|
+
Value = detail.Value;
|
|
97
|
+
await ValueChanged.InvokeAsync(detail.Value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// <summary>The detail shape of the enhancer's <c>fdy-datepicker-change</c> event.</summary>
|
|
101
|
+
public sealed record ChangeDetail(string? Value, string? Date);
|
|
102
|
+
}
|
|
@@ -146,6 +146,11 @@
|
|
|
146
146
|
|
|
147
147
|
@if (Pager)
|
|
148
148
|
{
|
|
149
|
-
|
|
149
|
+
@* The six strings go through as given, null included: every default lives in the footer, so
|
|
150
|
+
a parameter left unset here is the footer's English and not a second copy of it. *@
|
|
151
|
+
<FdyTableFooter Page="FooterPage" PageSizes="PageSizes" PageChanged="OnFooterPage"
|
|
152
|
+
InfoText="@InfoText" PageSizeLabel="@PageSizeLabel"
|
|
153
|
+
PaginationLabel="@PaginationLabel" PrevLabel="@PrevLabel"
|
|
154
|
+
NextLabel="@NextLabel" PageLabel="@PageLabel" />
|
|
150
155
|
}
|
|
151
156
|
</div>
|