@cahyo-dimas/freeday 3.7.0 → 3.8.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 CHANGED
@@ -9,6 +9,99 @@ 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.8.0] - 2026-10-23
13
+
14
+ **Empat penambahan dari catatan yang sama, dan tiga di antaranya ternyata sudah separuh ada di
15
+ kit.** App ronda yang menemukan Safari 15 di 3.7.0 menulis delapan butir; dua di antaranya cacat,
16
+ dan keduanya sudah terbit. Empat dari yang tersisa penambahan, dan keempatnya lebih murah daripada
17
+ tampaknya: satu kelas yang sudah ada sejak lama tetapi kehilangan pengaruhnya, satu jalan keluar
18
+ teks yang cuma dipasang di satu jalur, satu grid yang terpasang sejak 1.32.0 tanpa pernah bisa jadi
19
+ tujuan, dan satu varian sumbu yang papan tombolnya kurang dua literal.
20
+
21
+ Yang terakhir membuka cacat yang digambarkan catatan itu tanpa pelapornya tahu ia sedang
22
+ menggambarkan kit. Ia menulis bahwa `<input type="month">` menamai bulan menurut bahasa peramban,
23
+ bukan `<html lang>`. Kalimat itu berlaku kata per kata untuk `FdyDatepicker` Vue dan React: dokumen
24
+ sudah menjanjikan `<html lang>` sejak lama, cuma enhancer vanilla yang betul melakukannya, dan
25
+ kedua adapter menyerahkan `props.locale` yang `undefined` langsung ke `Intl`.
26
+
27
+ ### Fixed
28
+ - **Popover memposisikan panelnya dari tata letak yang ia batalkan tiga baris kemudian** (`#079`),
29
+ ada sejak 1.4.1 dan kena keenam enhancer yang memakai `attach()`: autocomplete, cascade, combo,
30
+ datepicker, menu dan timepicker. Keenam panel itu `position:absolute` dengan
31
+ `top:calc(100% + var(--space-1))` di stylesheet komponennya, dan aturan author mengalahkan gaya
32
+ UA popover, jadi panel yang baru di-`show()` sudah di top layer sambil masih memakai aturan itu.
33
+ Di top layer `100%` adalah tinggi viewport. Selama satu fungsi sinkron panelnya duduk satu
34
+ viewport di bawah halaman dan overflow dokumen tumbuh mengikutinya, terukur `scrollHeight` 1023
35
+ lawan viewport 900. Di tempat scrollbar memakan ruang tata letak, yakni setiap peramban Windows
36
+ dan Linux, dokumen mendapat scrollbar untuk sesaat, `clientWidth` menyusut selebar scrollbar itu,
37
+ halaman relayout, dan pemicunya bergeser bersamanya. `place()` membaca rect pemicu sebagai hal
38
+ pertama yang ia lakukan, jadi yang ia ukur adalah tata letak sementara itu. Pengukurannya
39
+ sekarang terjadi sesudah panel keluar dari flow.
40
+
41
+ macOS memakai scrollbar overlay yang tidak memakan ruang, jadi mesin tempat kit ini ditulis
42
+ secara struktural tidak bisa melihat cacat ini, dan 1.4.1 sampai 3.7.0 mengirimnya tanpa ada yang
43
+ tahu. Yang melihatnya runner Linux di CI, lewat uji perataan `#074` yang kebetulan mengukur
44
+ koordinat di sisi kanan layar. Reproduksinya sekarang ada di suite dan berjalan di macOS juga,
45
+ dengan menata `::-webkit-scrollbar` ke lebar tetap, plus assertion yang menolak lulus kalau
46
+ enginenya ternyata tidak memberi scrollbar ruang tata letak.
47
+
48
+ ### Added
49
+ - **`align` di popover, dibacakan dari `.fdy-menu--end`** (`#074`). Kelasnya sudah ada sejak lama
50
+ dan stylesheet selalu menghormatinya lewat `left:auto;right:0`. Jalur popover menggantinya dengan
51
+ koordinat `fixed` inline, jadi kelas itu kehilangan pengaruh persis pada saat menu masuk top
52
+ layer: menu akun di talang kanan melewati avatarnya dan berhenti 4px dari tepi layar. Nol kelas
53
+ baru. `freeday-menu.js` yang membaca kelasnya, sebab popover dipakai enam enhancer dan tidak
54
+ boleh tahu nama kelas satu pun. Jepitan viewport sekarang dua sisi: lantai `GAP` yang lama duduk
55
+ di dalam cabang jepitan kanan dan tak pernah berjalan untuk panel rata-kanan, jadi rata-kanan di
56
+ layar sempit cuma akan menukar luber kanan dengan luber kiri.
57
+ - **Enam prop teks di `FdyTableFooter`** (`#075`), di ketiga adapter. `data-fdy-text-<key>` sudah
58
+ lama membuka jalur mentah dan jalur bertipe tidak punya padanannya, jadi app yang tidak berbahasa
59
+ Inggris hanya bisa menulis ulang seluruh pita kakinya. Satu app mengirim 81 baris markup sendiri
60
+ untuk mengganti lima kata. Namanya mengikuti kunci `TEXT` enhancer di mana kuncinya ada (`info`,
61
+ `prev`, `next`), supaya satu teks tidak punya dua nama di dua jalur; tiga sisanya tanpa kembaran
62
+ sebab di jalur mentah markup itu milik app sendiri. Bawaan tetap Inggris di setiap jalur.
63
+ `FdyTable` masih merender pita kakinya sendiri tanpa meneruskan keenamnya; jalan keluarnya
64
+ `pager={false}` lalu render `<FdyTableFooter>` sendiri, dan COMPONENTS.md menuliskannya.
65
+ - **`mode="month"` di `FdyDatepicker`** (`#076`), di ketiga adapter dan jalur mentah. Grid bulannya
66
+ sudah dipasang 1.32.0, hanya tak pernah bisa jadi tujuan. Menjawab `YYYY-MM`, menerima `min` dan
67
+ `max` dalam bentuk yang sama, dan berhenti di grid itu alih-alih turun ke hari. Nama acara dan
68
+ `detail.date` tidak berubah.
69
+ - **`.fdy-tabs--vertical`, dan sumbu papan tombol dari `aria-orientation`** (`#077`). Kelasnya
70
+ memutuskan tampilan, atributnya memutuskan panah, dan satu penanda cukup: enhancer mengisi
71
+ atribut dari kelas kalau markup belum menulisnya, sementara atribut yang sudah ditulis selalu
72
+ menang. Panah kiri dan kanan tidak dipetakan di tegak, mengikuti APG satu sumbu per tablist, dan
73
+ tombol di luar sumbu jatuh tanpa `preventDefault` sehingga tetap milik halaman. Lebar kolom lewat
74
+ `--fdy-tabs-list-w`, bawaan `auto`. Tabs tidak punya wrapper bertipe di stack mana pun, jadi tidak
75
+ ada prop `orientation` yang bisa ditambahkan dan tidak ada wrapper baru yang dibuat demi punya
76
+ tempat menaruhnya.
77
+
78
+ ### Changed
79
+ - **`FdyDatepicker` Vue dan React membaca `<html lang>` kalau `locale` tidak dikirim** (`#078`).
80
+ Ini satu-satunya perubahan perilaku di rilis ini, dan ia kena mode hari juga, bukan cuma mode
81
+ bulan. Sebelumnya `undefined` sampai ke `Intl` apa adanya, yang berarti bahasa peramban: halaman
82
+ Indonesia yang dibuka di peramban Inggris menulis nama bulan Inggris, dan halaman Inggris di
83
+ peramban Indonesia menulis nama bulan Indonesia. Kontrak yang tertulis selalu `<html lang>`, dan
84
+ aturan "Inggris di semua jalur" tidak bisa ditegakkan oleh nilai yang datang dari preferensi
85
+ pembaca. App yang memang menginginkan bahasa peramban mengirim `locale` sendiri. Halaman tanpa
86
+ `lang` jatuh ke `en`, dan `lang=""` ikut ke sana sebab `Intl` melempar `RangeError` atas string
87
+ kosong.
88
+
89
+ ### Catatan dukungan peramban
90
+ Floor-nya tetap Chrome 105, Safari 15.4 dan Firefox 121. Tab tegak sengaja tidak memakai `:has()`
91
+ walau itu akan memendekkan CSS-nya, sebab `:has()` baru ada di Safari 15.4 sementara lantai yang
92
+ pernah dilaporkan dari perangkat nyata adalah iOS 15.0.
93
+
94
+ ### Yang perlu dibuang app saat upgrade
95
+ - Tambalan `translate` apa pun yang menggeser menu rata-kanan ke dalam talangnya. Menu itu sekarang
96
+ diposisikan benar, jadi tambalannya menggeser sejauh satu talang ke arah yang salah.
97
+ - Markup pita kaki tabel yang ditulis tangan cuma karena teksnya tak bisa diganti, berikut CSS
98
+ lokal yang menemaninya.
99
+
100
+ ### Belum masuk
101
+ Butir keempat catatan itu, bawaan `.fdy-badge` yang berwarna primer sehingga status netral terbaca
102
+ seperti aksi, menunggu 4.0: mengubah bawaannya mengubah tampilan setiap lencana yang sudah terkirim.
103
+ Pilihan yang tidak mematahkan apa pun, `fdy-badge--neutral` sebagai opt-in, masih terbuka lebih awal.
104
+
12
105
  ## [3.7.0] - 2026-10-23
13
106
 
14
107
  **Kit ini tidak bisa dipakai di Safari 15, dan yang menemukannya adalah sebuah HP.** Pemilik sebuah
package/COMPONENTS.md CHANGED
@@ -510,8 +510,10 @@ WAI-ARIA APG menu-button. Keyboard ↑/↓/Home/End/Esc handled by the enhancer.
510
510
 
511
511
  - Wrapper: `.fdy-menu-wrap` (or `.fdy-btn-split` for a split button, with
512
512
  `.fdy-btn-split__toggle` as the second button) + `data-fdy-menu`
513
- - List: `.fdy-menu` (`--end` aligns right), items `.fdy-menu__item` (`--danger`), separator
514
- `<hr class="fdy-menu__sep">`
513
+ - List: `.fdy-menu` (`--end` puts the panel's **right edge on the trigger's right edge**, which is
514
+ what a control parked in a page gutter needs; on a screen too narrow for that the panel slides
515
+ back inside the viewport instead of hanging off the left edge), items `.fdy-menu__item`
516
+ (`--danger`), separator `<hr class="fdy-menu__sep">`
515
517
  - A11y: trigger gets `aria-haspopup="menu"` + `aria-expanded="false"`; `<ul role="menu" hidden>`;
516
518
  each `<li role="none">` wraps a `<button role="menuitem">`.
517
519
 
@@ -526,6 +528,15 @@ WAI-ARIA APG menu-button. Keyboard ↑/↓/Home/End/Esc handled by the enhancer.
526
528
  </div>
527
529
  ```
528
530
 
531
+ `.fdy-menu--end` is honoured on both of the paths an open menu can take. The stylesheet handles the
532
+ one where the panel stays in the flow, and the enhancer handles the other: it lifts the panel into
533
+ the top layer so it escapes any ancestor overflow clip, and positions it there with coordinates of
534
+ its own, so the modifier has to be read by `freeday-popover.js` too. An app reported what happens
535
+ when it is not. An account menu beside its avatar in a 24px gutter opened 20px further right than
536
+ the avatar and stopped 4px off the screen edge, and the app's own `translate` patch for it was keyed
537
+ to a constant inside the kit. For a control the kit does not ship, the same option is
538
+ `FreedayPopover.attach(panel, trigger, { align: 'end' })`.
539
+
529
540
  ---
530
541
 
531
542
  # Forms
@@ -942,9 +953,10 @@ a new class.
942
953
 
943
954
  | Prop | Type | What it does |
944
955
  |---|---|---|
945
- | `modelValue` · `value` | `string \| null` | ISO `YYYY-MM-DD`, or `''`/`null` for empty. |
956
+ | `modelValue` · `value` | `string \| null` | ISO `YYYY-MM-DD`, or `YYYY-MM` under `mode="month"`. `''`/`null` for empty. |
946
957
  | `onChange` | `(value: string) => void` | React. Vue emits `update:modelValue` and `change`. Clearing sends `''`. |
947
- | `min?` · `max?` | `string` | ISO bounds; days outside them cannot be picked. |
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`; the raw path takes `data-mode="month"`. |
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. |
948
960
  | `locale?` | `string` | BCP-47 tag deciding month and weekday names. Defaults to the page's `<html lang>`, then `en`. |
949
961
  | `placeholder?` | `string` | |
950
962
  | `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
@@ -1013,7 +1025,26 @@ months, `Home`/`End` January/December, `PageUp`/`PageDown` a year, `Enter`/`Spac
1013
1025
  year grid likewise: arrows ±1/±3 years, `Home`/`End` the ends of the page, `PageUp`/`PageDown` a
1014
1026
  page of twelve.
1015
1027
 
1016
- - Attributes: `data-value="YYYY-MM-DD"`, `data-placeholder`, `data-label`, `data-fdy-no-icon`
1028
+ **Picking a month, not a day.** `mode="month"` (`data-mode="month"` on the raw path,
1029
+ `Mode="FdyDatepickerMode.Month"` in Blazor) makes the month grid the destination instead of a
1030
+ waypoint: the panel opens on it, a click or `Enter` commits from it, the day grid is never built,
1031
+ and the value is `YYYY-MM`. The title still steps up to the year grid and back, because that is
1032
+ navigation and not an answer.
1033
+
1034
+ This exists because `<input type="month">` names the month in the **browser's** language, which an
1035
+ Indonesian page read on an English phone cannot override. The month cells, the cell's accessible
1036
+ name and the value in the trigger all go through `Intl` with the page's `<html lang>`, the same
1037
+ 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 is unchanged: arrows move one and three months, `Home`/`End`
1039
+ jump to January and December, `PageUp`/`PageDown` step a year, and `Enter`/`Space` now answers
1040
+ rather than drilling. The cell keeps its name when it becomes selected and carries the state in
1041
+ `aria-selected`, as the day cells do.
1042
+
1043
+ Nothing changes without the prop. A picker with no `mode` is the day picker it has always been,
1044
+ down to the `YYYY-MM-DD` it reports.
1045
+
1046
+ - Attributes: `data-value="YYYY-MM-DD"`, `data-mode="month"`, `data-placeholder`, `data-label`,
1047
+ `data-fdy-no-icon`
1017
1048
 
1018
1049
  ```html
1019
1050
  <div class="fdy-field">
@@ -1021,6 +1052,11 @@ page of twelve.
1021
1052
  <div data-fdy-datepicker data-value="2026-07-21" data-label="Invoice date"></div>
1022
1053
  </div>
1023
1054
 
1055
+ <div class="fdy-field">
1056
+ <span class="fdy-label">Reporting month</span>
1057
+ <div data-fdy-datepicker data-mode="month" data-value="2026-10"></div>
1058
+ </div>
1059
+
1024
1060
  <div class="fdy-daterange" data-fdy-daterange role="group" aria-label="Reporting period">
1025
1061
  <div data-fdy-datepicker data-role="from" data-placeholder="From" data-label="Start date"></div>
1026
1062
  <span class="fdy-daterange__sep" aria-hidden="true">–</span>
@@ -1440,6 +1476,28 @@ render it once, outside both, with `pager={false}` on the table.
1440
1476
  | `page` | `FdyPageState` = `{ index, size, total }` | The page being shown. `size` drives both the range text and the rows-per-page control's value. |
1441
1477
  | `onPageChange?` | `(page: FdyPageState) => void` | React. A page click or a size change. Vue emits `update:page`; Blazor `PageChanged`. |
1442
1478
  | `pageSizes?` | `readonly number[]` | Offer a rows-per-page control. Omit it and the footer is range + pager only. Picking a size reports the new `size` together with the index that still holds the row the reader was looking at. |
1479
+ | `infoText?` | `string` | The range line, with `{from}`, `{to}` and `{total}` substituted. Default `Showing {from}–{to} of {total}`, the enhancer's `info` string. |
1480
+ | `pageSizeLabel?` | `string` | The word beside the rows-per-page control, which also names that control for a screen reader. Default `Rows`. |
1481
+ | `paginationLabel?` | `string` | Accessible name of the pager landmark. Default `Pagination`. |
1482
+ | `prevLabel?` | `string` | Accessible name of the previous-page arrow. Default `Previous page`. |
1483
+ | `nextLabel?` | `string` | Accessible name of the next-page arrow. Default `Next page`. |
1484
+ | `pageLabel?` | `string` | Accessible name of a numbered page button, with `{n}` substituted. Default `Go to page {n}`. |
1485
+
1486
+ **Saying it in another language.** The raw path has had `data-fdy-text-<key>` since 1.39.0 and this
1487
+ one had nothing, so a Vue, React or Blazor app that does not speak English could only rewrite the
1488
+ band by hand. One did: eighty one lines of its own markup, to change five words. The six string
1489
+ props above are the hatch that was missing. Where the enhancer already keys a string the prop
1490
+ carries that key's name, `infoText` for `info`, `prevLabel` for `prev`, `nextLabel` for `next`,
1491
+ and the same `{from}` `{to}` `{total}` `{n}` substitution, every occurrence replaced, so an app on
1492
+ both paths writes one wording once. The other three have no enhancer twin because on the raw path
1493
+ that markup is the app's own: the word beside the rows-per-page control, the pager landmark's name,
1494
+ and the name of a numbered page button. It is `pageSizeLabel` and not `rows`, since the enhancer's
1495
+ `rows` key is the table's `{n} rows` count, a different string.
1496
+
1497
+ Omit a prop and you get the English above, so nothing already shipped moves. `FdyTable` draws its
1498
+ own footer and does not pass these through, so a table that owns its footer still reads English; a
1499
+ screen that needs its own wording takes the footer over with `pager={false}` and renders
1500
+ `<FdyTableFooter>` beside it, which is the same route a responsive list already takes.
1443
1501
 
1444
1502
  **Who draws the pager.** The table renders its own footer (range + pager) whenever there is more
1445
1503
  than one page. Two ways to take it over:
@@ -1832,10 +1890,15 @@ record. A crumb for a menu group that has no page of its own is plain text in th
1832
1890
  to nowhere.
1833
1891
 
1834
1892
  ## Tabs — `.fdy-tabs`
1835
- APG tabs: ←/→, Home/End, roving tabindex. Needs `freeday-tabs.js`.
1893
+ APG tabs: arrows, Home/End, roving tabindex. Needs `freeday-tabs.js`.
1836
1894
  `.fdy-tabs__tab` marks its active state from **`aria-selected="true"` or `aria-current="page"`**,
1837
1895
  so the same look serves routed sub-navigation built from plain links. See the note under Nav.
1838
1896
 
1897
+ **Which arrows move the selection** comes from the tablist's `aria-orientation`: Left and Right by
1898
+ default, Up and Down when it says `vertical`. One axis per tablist, the way APG maps it. The other
1899
+ pair stays unmapped, so Left and Right over a column of tabs still reach whatever else on the page
1900
+ wants them. Home and End answer either way.
1901
+
1839
1902
  > **The strip's line is an inset `box-shadow`, not a `border-bottom`** (`#052`; through 3.1.0 it
1840
1903
  > was a border, so an app upgrading past that release changes the override with it). To
1841
1904
  > remove it — a strip inside a header that draws its own rule — override `box-shadow:none`;
@@ -1857,6 +1920,56 @@ so the same look serves routed sub-navigation built from plain links. See the no
1857
1920
  </div>
1858
1921
  ```
1859
1922
 
1923
+ ### Vertical — `.fdy-tabs--vertical`
1924
+
1925
+ The same tabs turned down the page: the list on the leading side, the panel beside it. For a
1926
+ settings screen whose sections are a list, not a strip.
1927
+
1928
+ ```html
1929
+ <div class="fdy-tabs fdy-tabs--vertical" data-fdy-tabs style="--fdy-tabs-list-w:16rem">
1930
+ <div class="fdy-tabs__list" role="tablist" aria-orientation="vertical" aria-label="Settings">
1931
+ <button class="fdy-tabs__tab" role="tab" id="s-1" aria-controls="sp-1" aria-selected="true">Identity</button>
1932
+ <button class="fdy-tabs__tab" role="tab" id="s-2" aria-controls="sp-2" aria-selected="false" tabindex="-1">Posts</button>
1933
+ </div>
1934
+ <div class="fdy-tabs__panel" role="tabpanel" id="sp-1" aria-labelledby="s-1" tabindex="0">…</div>
1935
+ <div class="fdy-tabs__panel" role="tabpanel" id="sp-2" aria-labelledby="s-2" tabindex="0" hidden>…</div>
1936
+ </div>
1937
+ ```
1938
+
1939
+ - **Two markers, and they are not the same marker.** `.fdy-tabs--vertical` decides the look,
1940
+ because the two-column box belongs to `.fdy-tabs` and a stylesheet cannot reach a parent from a
1941
+ child. `aria-orientation="vertical"` on the tablist decides the keyboard, because that is what an
1942
+ assistive technology announces and APG asks for it on a vertical tablist regardless. Write
1943
+ **either** one: `freeday-tabs.js` fills in the attribute when it finds the class without it, so
1944
+ the ARIA can never drift from the layout. An `aria-orientation` you wrote always wins, including
1945
+ when you write `horizontal` on a widget that carries the class.
1946
+ - **Selection is a soft fill.** A bar down the leading edge would be the wrong translation:
1947
+ sideways the marker is an underline beneath the tab it belongs to, and turned ninety degrees that
1948
+ line lands between two tabs and points at the wrong one. So the vertical variant spends
1949
+ `--color-primary-soft` behind `--color-primary-strong`, the same pair
1950
+ `.fdy-nav__item[aria-current="page"]` and the picker options already use for “this is where you
1951
+ are” in a vertical list. The strip's own line (`box-shadow`) is dropped with the underline.
1952
+ - **Column width:** `--fdy-tabs-list-w` on `.fdy-tabs` (default `auto`, so the column hugs the
1953
+ longest label). Set the token rather than a `width` on `.fdy-tabs__list`: the list is a flex item
1954
+ at `flex:none`, so a width arriving from outside has no effect and no error to show for it.
1955
+ - **It does not collapse on a narrow screen**, and the breakpoint is yours because the kit cannot
1956
+ know whether these tabs span the viewport or sit in a 20rem drawer. Going back to a strip takes
1957
+ three declarations, not one, since the variant turns both boxes:
1958
+
1959
+ ```css
1960
+ @media (max-width: 63.9375rem) {
1961
+ .settings-tabs.fdy-tabs--vertical { display: block; }
1962
+ .settings-tabs .fdy-tabs__list { flex-direction: row; width: auto; overflow-x: auto; }
1963
+ .settings-tabs .fdy-tabs__panel { padding: var(--space-4) 0 0; }
1964
+ }
1965
+ ```
1966
+
1967
+ The keyboard stays on Up and Down across that switch, because `aria-orientation` is an attribute
1968
+ and a media query cannot reach it. A list of sections is still a list of sections at 320px, which
1969
+ is why the axis is read from the attribute and never from the painted layout.
1970
+ - Measured in `browser/tabs-vertical.mjs`, which also holds the line that a strip with no
1971
+ `aria-orientation` behaves and paints exactly as it did before the variant existed.
1972
+
1860
1973
  ---
1861
1974
 
1862
1975
  # Overlays
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
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v3.7.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.7.0)
8
+ [![Release](https://img.shields.io/badge/release-v3.8.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.8.0)
9
9
 
10
10
  UI KIT yang token-driven & framework-agnostic. Satu sumber kebenaran untuk warna, tipografi,
11
11
  spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > **More free days for devs. The UI kit is ready to use.**
6
6
 
7
7
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v3.7.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.7.0)
8
+ [![Release](https://img.shields.io/badge/release-v3.8.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.8.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`.
@@ -4,8 +4,10 @@
4
4
  @* Seed-based wrapper over freeday-datepicker.js: the enhancer builds the input-styled
5
5
  trigger + calendar popover from this empty div and emits fdy-datepicker-change. The
6
6
  enhancer owns that DOM, so we render the seed once (ShouldRender => false) and read the
7
- picked value back through @bind-Value. Value is an ISO date string ("yyyy-MM-dd") or "". *@
7
+ picked value back through @bind-Value. Value is an ISO date string ("yyyy-MM-dd") or "",
8
+ and "yyyy-MM" when Mode is FdyDatepickerMode.Month. *@
8
9
  <div @ref="Root" data-fdy-datepicker
10
+ data-mode="@ModeAttribute"
9
11
  data-value="@Value"
10
12
  data-label="@Label"
11
13
  data-placeholder="@Placeholder"
@@ -5,11 +5,22 @@ namespace Freeday.Blazor;
5
5
 
6
6
  public partial class FdyDatepicker
7
7
  {
8
- /// <summary>Selected date as an ISO string (<c>yyyy-MM-dd</c>), or "" when unset.
8
+ /// <summary>Selected date as an ISO string (<c>yyyy-MM-dd</c>), or <c>yyyy-MM</c> when
9
+ /// <see cref="Mode"/> is <see cref="FdyDatepickerMode.Month"/>, or "" when unset.
9
10
  /// Two-way bindable via <c>@bind-Value</c>.</summary>
10
11
  [Parameter] public string? Value { get; set; }
11
12
  [Parameter] public EventCallback<string?> ValueChanged { get; set; }
12
13
 
14
+ /// <summary>What the picker answers with. <see cref="FdyDatepickerMode.Day"/> (default) is the
15
+ /// calendar as it always was. <see cref="FdyDatepickerMode.Month"/> opens on the month grid,
16
+ /// stops there, and reports <c>yyyy-MM</c>.</summary>
17
+ [Parameter] public FdyDatepickerMode Mode { get; set; } = FdyDatepickerMode.Day;
18
+
19
+ /* null rather than "day" for the default: an attribute the enhancer does not need is an
20
+ 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. */
22
+ private string? ModeAttribute => Mode == FdyDatepickerMode.Month ? "month" : null;
23
+
13
24
  /// <summary>Accessible label for the trigger.</summary>
14
25
  [Parameter] public string? Label { get; set; }
15
26
  [Parameter] public string? Placeholder { get; set; }
@@ -83,3 +94,13 @@ public partial class FdyDatepicker
83
94
  /// <summary>The detail shape of the enhancer's <c>fdy-datepicker-change</c> event.</summary>
84
95
  public sealed record ChangeDetail(string? Value, string? Date);
85
96
  }
97
+
98
+ /// <summary>What <see cref="FdyDatepicker"/> answers with.</summary>
99
+ public enum FdyDatepickerMode
100
+ {
101
+ /// <summary>A single day, reported as <c>yyyy-MM-dd</c>.</summary>
102
+ Day,
103
+
104
+ /// <summary>A whole month, reported as <c>yyyy-MM</c>.</summary>
105
+ Month,
106
+ }
@@ -9,12 +9,12 @@
9
9
  @if (Visible)
10
10
  {
11
11
  <div class="fdy-table-footer">
12
- <span class="fdy-table-footer__info">Showing @RangeFrom–@RangeTo of @Page.Total</span>
12
+ <span class="fdy-table-footer__info">@InfoLine</span>
13
13
 
14
14
  @if (Sizes.Count > 0)
15
15
  {
16
16
  <div class="fdy-table-footer__size">
17
- <span id="@SizeLabelId">Rows</span>
17
+ <span id="@SizeLabelId">@(PageSizeLabel ?? "Rows")</span>
18
18
  @* The kit's own listbox, not a native <select>: an OS menu is unthemeable, and on
19
19
  macOS it drops a dark panel into a light page. FdyCombo's own header says so. *@
20
20
  <FdyCombo TValue="string" Value="@Page.Size.ToString()" Options="SizeOptions"
@@ -24,10 +24,10 @@
24
24
 
25
25
  @if (HasPager)
26
26
  {
27
- <nav class="fdy-pagination" aria-label="Pagination">
27
+ <nav class="fdy-pagination" aria-label="@(PaginationLabel ?? "Pagination")">
28
28
  <ul class="fdy-pagination__list">
29
29
  <li>
30
- <button type="button" class="fdy-pagination__link" aria-label="Previous page"
30
+ <button type="button" class="fdy-pagination__link" aria-label="@(PrevLabel ?? "Previous page")"
31
31
  disabled="@(CurrentPage1 == 1)" @onclick="() => GoTo(CurrentPage1 - 1)">‹</button>
32
32
  </li>
33
33
  @foreach (int? p in Pages)
@@ -44,13 +44,13 @@
44
44
  else
45
45
  {
46
46
  int page = p.Value;
47
- <button type="button" class="fdy-pagination__link" aria-label="@($"Go to page {page}")"
47
+ <button type="button" class="fdy-pagination__link" aria-label="@PageAriaLabel(page)"
48
48
  @onclick="() => GoTo(page)">@(page)</button>
49
49
  }
50
50
  </li>
51
51
  }
52
52
  <li>
53
- <button type="button" class="fdy-pagination__link" aria-label="Next page"
53
+ <button type="button" class="fdy-pagination__link" aria-label="@(NextLabel ?? "Next page")"
54
54
  disabled="@(CurrentPage1 == TotalPages)" @onclick="() => GoTo(CurrentPage1 + 1)">›</button>
55
55
  </li>
56
56
  </ul>
@@ -21,6 +21,35 @@ public partial class FdyTableFooter
21
21
  /// </summary>
22
22
  [Parameter] public IReadOnlyList<int>? PageSizes { get; set; }
23
23
 
24
+ // The strings, six of them, one per thing this band says. The raw path has had a hatch since
25
+ // 1.39.0 (data-fdy-text-<key> read through textOf()) and this path had none, so an app that
26
+ // does not speak English could only rewrite the whole band by hand. A Blazor app mixes both
27
+ // paths by construction, since FreedayBlazor.initAll runs the enhancers, so these names follow
28
+ // the enhancer's TEXT keys where a key exists. Defaults stay English on every path, which
29
+ // COMPONENTS.md promises and the suite enforces.
30
+
31
+ /// <summary>
32
+ /// The range line. <c>{from}</c>, <c>{to}</c> and <c>{total}</c> are substituted, every
33
+ /// occurrence, as the enhancer's <c>textOf()</c> does. Default <c>Showing {from}–{to} of
34
+ /// {total}</c>, the enhancer's <c>info</c> key.
35
+ /// </summary>
36
+ [Parameter] public string? InfoText { get; set; }
37
+
38
+ /// <summary>The visible word beside the rows-per-page control, which also names it. Default <c>Rows</c>.</summary>
39
+ [Parameter] public string? PageSizeLabel { get; set; }
40
+
41
+ /// <summary>Accessible name of the pager landmark. Default <c>Pagination</c>.</summary>
42
+ [Parameter] public string? PaginationLabel { get; set; }
43
+
44
+ /// <summary>Accessible name of the previous-page arrow. Default <c>Previous page</c>.</summary>
45
+ [Parameter] public string? PrevLabel { get; set; }
46
+
47
+ /// <summary>Accessible name of the next-page arrow. Default <c>Next page</c>.</summary>
48
+ [Parameter] public string? NextLabel { get; set; }
49
+
50
+ /// <summary>Accessible name of a numbered page button. <c>{n}</c> substituted. Default <c>Go to page {n}</c>.</summary>
51
+ [Parameter] public string? PageLabel { get; set; }
52
+
24
53
  private IReadOnlyList<int> Sizes => PageSizes ?? Array.Empty<int>();
25
54
 
26
55
  // Just the number: the control sits beside a label reading "Rows", so "20 per page" states the
@@ -40,6 +69,13 @@ public partial class FdyTableFooter
40
69
 
41
70
  private bool HasPager => Page.Size > 0 && TotalPages > 1;
42
71
 
72
+ private string InfoLine => (InfoText ?? "Showing {from}–{to} of {total}")
73
+ .Replace("{from}", RangeFrom.ToString())
74
+ .Replace("{to}", RangeTo.ToString())
75
+ .Replace("{total}", Page.Total.ToString());
76
+
77
+ private string PageAriaLabel(int page1) => (PageLabel ?? "Go to page {n}").Replace("{n}", page1.ToString());
78
+
43
79
  // One page and no size control means there is nothing here to say, the table has always
44
80
  // withheld the whole band in that case, and this is where that decision now lives.
45
81
  private bool Visible => HasPager || Sizes.Count > 0;