@cahyo-dimas/freeday 3.3.0 → 3.4.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 +68 -0
- package/COMPONENTS.md +49 -4
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/blazor/FdyAppShell.razor +1 -1
- package/adapters/react/components/FdyAppShell.tsx +1 -1
- package/adapters/vue/components/FdyAppShell.vue +1 -1
- package/dist/freeday.bundle.css +79 -1
- package/dist/freeday.css +79 -1
- package/docs/getting-started.md +1 -1
- package/package.json +2 -2
- package/src/components/app-shell.css +16 -1
- package/src/components/bottomnav.css +62 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,74 @@ 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.4.0] - 2026-09-07
|
|
13
|
+
|
|
14
|
+
**Tiga hal yang hanya terlihat setelah sebuah app bertemu telepon sungguhan.** `#057` (dari
|
|
15
|
+
`keamanan-cluster`) datang dari pemilik yang menguji di iPhone X, dan ketiganya punya bentuk yang
|
|
16
|
+
sama: tak satu pun dapat ditemukan di Chromium, dan tak satu pun akan pernah ditangkap uji yang
|
|
17
|
+
sudah ada — bukan karena ujinya kurang, melainkan karena mesin peramban di laptop tidak berperilaku
|
|
18
|
+
seperti Safari di HP, dan laptop tidak punya ibu jari.
|
|
19
|
+
|
|
20
|
+
Yang pertama mengunci navigasi sepenuhnya: tap di luar drawer tidak menutupnya, layar tetap gelap,
|
|
21
|
+
dan satu-satunya jalan keluar adalah memuat ulang halaman.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **`.fdy-bottomnav`** — bilah navigasi bawah untuk aplikasi di HP. Kit punya `--horizontal` untuk
|
|
25
|
+
top-nav dan drawer off-canvas untuk sidebar, dan tidak punya apa pun untuk susunan yang justru
|
|
26
|
+
dipakai hampir semua aplikasi telepon. Tampil hanya di `max-width:720px`; di desktop sidebar sudah
|
|
27
|
+
membawa tautan yang sama dan salinan kedua hanya duplikasi.
|
|
28
|
+
Sengaja **bukan** bagian markup `.fdy-app`: ia `position:fixed` sehingga bekerja dari mana pun di
|
|
29
|
+
dalam shell, dan aplikasi yang tidak memakainya tidak membayar apa pun.
|
|
30
|
+
Target minimal 44px — di bawah itu satu tap menjadi satu percobaan ulang, dan bilah ini ditekan
|
|
31
|
+
lebih sering daripada kontrol mana pun. Label tidak opsional: bilah ikon-saja menuntut setiap
|
|
32
|
+
pengguna sudah hafal arti tiap glyph. `env(safe-area-inset-bottom)` masuk ke padding, jadi label
|
|
33
|
+
tidak duduk di bawah home indicator pada HP bernotch.
|
|
34
|
+
- **`.fdy-app--has-bottomnav`** menyediakan ruang bawah untuk bilah itu. Tanpanya baris terakhir
|
|
35
|
+
setiap daftar di setiap layar berakhir di bawah bilah — dan baris yang tak bisa digulir ke sana
|
|
36
|
+
adalah baris yang tak seorang pun tahu ada.
|
|
37
|
+
- **`.fdy-app--navtoggle-end`** memindahkan tombol nav ke sisi jauh topbar. Ini ergonomi, bukan
|
|
38
|
+
selera: pada HP besar yang dipegang satu tangan, pojok kiri-ATAS adalah tempat tersulit dijangkau
|
|
39
|
+
ibu jari kanan, dan tombol itu yang paling sering ditekan di sana. Opt-in, sebab shell desktop yang
|
|
40
|
+
juga menaruh avatar di kanan punya persoalan sebaliknya.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- **`.fdy-app__backdrop` kini membawa `cursor:pointer`** (`#057`). Ini bukan untuk tetikus. Safari
|
|
44
|
+
iOS hanya mensintesis `click` dari tap pada elemen yang dianggapnya interaktif, dan bentuk kursor
|
|
45
|
+
salah satu penanda yang dipakainya. Backdrop-nya `<div>` polos dengan handler klik — tanpa button,
|
|
46
|
+
tanpa role, tanpa tabindex — sehingga tanpa aturan ini sebuah tap di atasnya sama sekali tidak
|
|
47
|
+
menghasilkan klik: drawer tetap terbuka, scrim tetap gelap, dan memuat ulang halaman adalah
|
|
48
|
+
satu-satunya jalan keluar. Tidak dapat direproduksi di Chromium, baik dengan klik tetikus maupun
|
|
49
|
+
`touchscreen.tap()` — itu sebabnya tidak satu pun uji di kit ini maupun di app yang memakainya
|
|
50
|
+
pernah menangkapnya. Dijaga `browser/bottom-nav.mjs`.
|
|
51
|
+
|
|
52
|
+
## [3.3.1] - 2026-09-07
|
|
53
|
+
|
|
54
|
+
**Tautan lewati yang tak punya tujuan, di tiga stack sekaligus.** `#056` (dari `keamanan-cluster`)
|
|
55
|
+
menunjuk satu kalimat di COMPONENTS.md — *"the markup below is for stacks without an adapter (and is
|
|
56
|
+
what the wrapper renders)"* — lalu menunjukkan bahwa untuk `.fdy-app__main` kalimat itu tidak benar.
|
|
57
|
+
Markup mentahnya membawa `id="main"`; ketiga wrapper typed tidak membawa apa pun. Jadi `.fdy-skip`
|
|
58
|
+
yang didokumentasikan dengan `href="#main"` merender tautan yang menerima fokus lalu tidak
|
|
59
|
+
memindahkan siapa pun. Ini bentuk kegagalan yang paling sulit terlihat: tak ada galat, tak ada yang
|
|
60
|
+
tampak salah, dan pemeriksaan visual tak akan pernah menangkapnya — app yang melaporkannya memilih
|
|
61
|
+
**tidak memasang tautan lewati sama sekali**, karena tak ada yang bisa dituju.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
- **`FdyAppShell` merender `<main id="main" tabindex="-1">` di Vue, React dan Blazor** (`#056` §1).
|
|
65
|
+
`id` menyamakan wrapper dengan markup mentah yang sudah didokumentasikan; `tabindex="-1"` diambil
|
|
66
|
+
dari `docs/reference-screen.html`, yang selama ini lebih benar daripada skeleton di COMPONENTS.md —
|
|
67
|
+
tanpa itu tautan fragmen menggulung halaman tanpa memindahkan fokus, sehingga Tab berikutnya
|
|
68
|
+
melanjutkan dari tempat pembaca tadi, bukan dari tempat ia dikirim. Skeleton COMPONENTS.md ikut
|
|
69
|
+
disamakan. Dijaga uji paritas baru di `browser/adapter.mjs` (Vue, React) dan
|
|
70
|
+
`test/blazor/FdyAppShellTests.cs` (Blazor).
|
|
71
|
+
|
|
72
|
+
### Docs
|
|
73
|
+
- **`.fdy-title-page` menyebut level headingnya di dalam shell** (`#056` §2). Tabel tipografi
|
|
74
|
+
menuliskan `<h1>`, "One per screen", sementara `docs/reference-screen.html` memakai
|
|
75
|
+
`<h2 class="fdy-title-page">` karena `.fdy-app__title` di topbar sudah menjadi `<h1>` layar itu.
|
|
76
|
+
Menyusun keduanya persis seperti dokumentasi menghasilkan dua `<h1>` — yang justru dilarang
|
|
77
|
+
kalimat di sebelahnya. Tabelnya kini menyebut kedua kasus; keputusan yang dipakai adalah keputusan
|
|
78
|
+
yang sudah diambil reference screen, bukan keputusan baru.
|
|
79
|
+
|
|
12
80
|
## [3.3.0] - 2026-08-28
|
|
13
81
|
|
|
14
82
|
**Tiga kemampuan yang sebenarnya sudah ada di kit ini, dan tak satu pun bisa diminta.** `#054` dan
|
package/COMPONENTS.md
CHANGED
|
@@ -146,7 +146,16 @@ off-canvas drawer and backdrop are built in.
|
|
|
146
146
|
overriding `width` on `.fdy-app__sidebar`, whose value also feeds the collapse transition.
|
|
147
147
|
- Modifiers: `--nav-open` (drawer open) · `--nav-collapsed` (collapse to zero width, ≥721px) ·
|
|
148
148
|
`--nav-overlay` (≥721px: float the nav over the content instead of displacing it) · `--static`
|
|
149
|
-
(embed the shell in a page instead of filling the viewport)
|
|
149
|
+
(embed the shell in a page instead of filling the viewport) · `--navtoggle-end` (move the nav
|
|
150
|
+
toggle to the far side of the topbar) · `--has-bottomnav` (reserve room for `.fdy-bottomnav`)
|
|
151
|
+
- **`--navtoggle-end`** is ergonomics, not taste: on a large phone held in one hand the top-LEFT
|
|
152
|
+
corner is the hardest place on the screen for a right thumb to reach, and the toggle is the
|
|
153
|
+
control pressed there most. Opt-in, because a desktop shell that also puts an avatar on the right
|
|
154
|
+
has the opposite problem.
|
|
155
|
+
- The backdrop carries `cursor:pointer`. That is **not** for the mouse: Safari on iOS only
|
|
156
|
+
synthesises a `click` from a tap on elements it treats as interactive, and the cursor is one of
|
|
157
|
+
the signals it uses. Without it, tapping outside the drawer on an iPhone does nothing at all —
|
|
158
|
+
the drawer stays open, the scrim stays dark, and reloading is the only way out.
|
|
150
159
|
|
|
151
160
|
**`--nav-overlay` is a layout choice, not a second behaviour.** With it the nav floats at *every*
|
|
152
161
|
width, so the drawer's whole code path — backdrop, Escape, close-on-follow, focus into the panel and
|
|
@@ -156,7 +165,10 @@ positioning rules differ, and only above the breakpoint. It also changes which s
|
|
|
156
165
|
On the raw path, if you add or remove the class yourself, call
|
|
157
166
|
**`FreedayAppShell.refresh(root)`** afterwards — the shell has to re-read the DOM, or `inert` and
|
|
158
167
|
`aria-expanded` keep describing the arrangement you just left.
|
|
159
|
-
- Also: `.fdy-skip`, the skip-to-content link, first child of the shell.
|
|
168
|
+
- Also: `.fdy-skip`, the skip-to-content link, first child of the shell. Its target is
|
|
169
|
+
`__main`, which carries `id="main"` **and `tabindex="-1"`** — without the latter a fragment
|
|
170
|
+
link scrolls the page without moving focus, so the next Tab resumes from where the reader
|
|
171
|
+
was, not from where they were sent. The typed wrappers render both.
|
|
160
172
|
- **Behaviour: `freeday-app-shell.js`.** Opt in with `data-fdy-app` on the root. The markup below
|
|
161
173
|
is unchanged. It owns the toggle in both modes plus everything an overlay needs that hand-rolling
|
|
162
174
|
reliably forgets: Escape, backdrop click, closing when a `.fdy-nav__item` is followed, focus moved
|
|
@@ -209,7 +221,7 @@ tall containing block. The brand goes in the **sidebar** (sized to match the top
|
|
|
209
221
|
<h1 class="fdy-app__title">Invoices</h1><!-- auto-spacer: pushes what follows right -->
|
|
210
222
|
<!-- topbar actions -->
|
|
211
223
|
</header>
|
|
212
|
-
<main class="fdy-app__main" id="main"><!-- .fdy-page goes here --></main>
|
|
224
|
+
<main class="fdy-app__main" id="main" tabindex="-1"><!-- .fdy-page goes here --></main>
|
|
213
225
|
</div>
|
|
214
226
|
|
|
215
227
|
<div class="fdy-app__backdrop"></div>
|
|
@@ -283,7 +295,7 @@ One role per level of hierarchy. Never re-use a card title for a page title.
|
|
|
283
295
|
| Class | Element | Use |
|
|
284
296
|
|---|---|---|
|
|
285
297
|
| `.fdy-eyebrow` | `<p>` | Small uppercase label above a title. Optional. |
|
|
286
|
-
| `.fdy-title-page` | `<h1>` | One per screen. |
|
|
298
|
+
| `.fdy-title-page` | `<h1>` · `<h2>` in a shell | One `<h1>` per screen. Standalone page: `<h1>`. Inside `.fdy-app`, the topbar's `.fdy-app__title` is already that `<h1>`, so the page title steps down to `<h2>` — as `docs/reference-screen.html` does. |
|
|
287
299
|
| `.fdy-title-section` | `<h2>` | A region inside the page. |
|
|
288
300
|
| `.fdy-title-card` | `<h3>` | A title inside a card or row (`.fdy-card__title` is equivalent). |
|
|
289
301
|
| `.fdy-text-muted` · `.fdy-text-subtle` | any | Secondary / tertiary text colour. |
|
|
@@ -1493,6 +1505,39 @@ Size-matched placeholders so nothing shifts when data lands: `--title` `--text`
|
|
|
1493
1505
|
|
|
1494
1506
|
# Navigation
|
|
1495
1507
|
|
|
1508
|
+
## Bottom navigation — `.fdy-bottomnav`
|
|
1509
|
+
The bar a **phone** application puts its three-to-five most-used destinations on, where a thumb
|
|
1510
|
+
reaches them. Shown only at `max-width:720px`; on a desktop the sidebar already carries the same
|
|
1511
|
+
links, and a second copy would be duplication.
|
|
1512
|
+
|
|
1513
|
+
Not part of `.fdy-app`'s markup: it is `position:fixed`, so it works from anywhere inside the
|
|
1514
|
+
shell, and an application that does not want one pays nothing. Put **`.fdy-app--has-bottomnav`** on
|
|
1515
|
+
the shell to reserve room for it — without that, the last row of every list ends underneath the bar,
|
|
1516
|
+
and a row nobody can scroll to is a row nobody knows is there.
|
|
1517
|
+
|
|
1518
|
+
- Items are `<a class="fdy-bottomnav__item">` with `__icon` / `__label`; the current one gets
|
|
1519
|
+
`aria-current="page"` — the same contract `.fdy-nav__item` uses, so "you are here" has one
|
|
1520
|
+
meaning in this kit, not two.
|
|
1521
|
+
- Targets are at least 44px tall. Under that a tap becomes a retry, and this bar is pressed more
|
|
1522
|
+
than any other control in a phone application.
|
|
1523
|
+
- Labels are **not** optional. An icon-only bar asks every user to have already learned what each
|
|
1524
|
+
glyph means. Long labels ellipsis rather than wrap, so the bar keeps one height.
|
|
1525
|
+
- Bottom padding includes `env(safe-area-inset-bottom)`, so labels do not sit under the home
|
|
1526
|
+
indicator on a notched phone.
|
|
1527
|
+
|
|
1528
|
+
```html
|
|
1529
|
+
<nav class="fdy-bottomnav" aria-label="Main">
|
|
1530
|
+
<a class="fdy-bottomnav__item" href="/dashboard" aria-current="page">
|
|
1531
|
+
<span class="fdy-bottomnav__icon"><!--svg--></span>
|
|
1532
|
+
<span class="fdy-bottomnav__label">Dashboard</span>
|
|
1533
|
+
</a>
|
|
1534
|
+
<a class="fdy-bottomnav__item" href="/schedule">
|
|
1535
|
+
<span class="fdy-bottomnav__icon"><!--svg--></span>
|
|
1536
|
+
<span class="fdy-bottomnav__label">Schedule</span>
|
|
1537
|
+
</a>
|
|
1538
|
+
</nav>
|
|
1539
|
+
```
|
|
1540
|
+
|
|
1496
1541
|
## Nav (menu) — `.fdy-nav`
|
|
1497
1542
|
Navigation links, **vertical by default** (the app shell sidebar), horizontal with `--horizontal`. Items are `<a class="fdy-nav__item">` with
|
|
1498
1543
|
`__icon` / `__label` / `__badge`; the current one gets `aria-current="page"`.
|
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.4.0)
|
|
9
9
|
|
|
10
10
|
UI KIT yang token-driven & framework-agnostic. Satu sumber kebenaran untuk warna, tipografi,
|
|
11
11
|
spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **More free days for devs. The UI kit is ready to use.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.4.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`.
|
|
@@ -186,7 +186,7 @@ export function FdyAppShell(props: FdyAppShellProps): JSX.Element {
|
|
|
186
186
|
{props.topbar}
|
|
187
187
|
</header>
|
|
188
188
|
|
|
189
|
-
<main className="fdy-app__main">{props.children}</main>
|
|
189
|
+
<main className="fdy-app__main" id="main" tabIndex={-1}>{props.children}</main>
|
|
190
190
|
</div>
|
|
191
191
|
|
|
192
192
|
<div
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -1040,6 +1040,13 @@ a { color: var(--color-primary); }
|
|
|
1040
1040
|
.fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
1041
1041
|
.fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
|
|
1042
1042
|
.fdy-app__navtoggle svg{display:block;width:1.25rem;height:1.25rem;}
|
|
1043
|
+
/* --navtoggle-end moves the toggle to the far side of the topbar. The title already
|
|
1044
|
+
* carries `margin-right:auto`, so ordering the toggle last is enough; nothing else needs
|
|
1045
|
+
* to know. On a large phone held in one hand the top-LEFT corner is the hardest place on
|
|
1046
|
+
* the screen for a right thumb to reach, and the toggle is the control most often pressed
|
|
1047
|
+
* there — so this is ergonomics, not taste. Left as an opt-in because a desktop shell that
|
|
1048
|
+
* also puts an avatar on the right has the opposite problem. */
|
|
1049
|
+
.fdy-app--navtoggle-end .fdy-app__navtoggle{order:1;}
|
|
1043
1050
|
/* Desktop collapse: animate width -> 0 (content clips via overflow:hidden; the content area
|
|
1044
1051
|
* flexes in to fill). Not display:none, so the collapse/expand slides instead of snapping. */
|
|
1045
1052
|
@media (min-width:721px){.fdy-app--nav-collapsed .fdy-app__sidebar{width:0;border-right-width:0;}}
|
|
@@ -1075,7 +1082,15 @@ a { color: var(--color-primary); }
|
|
|
1075
1082
|
* the initial `visible`, so a mode switch alone hands the page ~200ms of dead clicks. Measured: with
|
|
1076
1083
|
* only `visibility:hidden`, elementFromPoint over the nav toggle returned the backdrop.
|
|
1077
1084
|
* Mobile: the sidebar becomes an off-canvas drawer opened by the topbar toggle. */
|
|
1078
|
-
.
|
|
1085
|
+
/* `cursor:pointer` here is not decoration, and not for the mouse. Safari on iOS only
|
|
1086
|
+
* synthesises a `click` from a tap on elements it treats as interactive, and the cursor
|
|
1087
|
+
* being a pointer is one of the signals it uses. The backdrop is a plain <div> with a
|
|
1088
|
+
* click handler — no button, no role, no tabindex — so without this rule a tap on it
|
|
1089
|
+
* produces no click at all: the drawer stays open, the scrim stays dark, and the only way
|
|
1090
|
+
* out is reloading the page. Reported from a real iPhone X; not reproducible in Chromium
|
|
1091
|
+
* with either mouse click or `touchscreen.tap()`, which is why no test in this kit or in
|
|
1092
|
+
* the consuming app had ever caught it. */
|
|
1093
|
+
.fdy-app__backdrop{display:none;cursor:pointer;}
|
|
1079
1094
|
@media (max-width:720px){
|
|
1080
1095
|
.fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);transition:transform var(--dur-base) var(--ease-standard);box-shadow:var(--shadow-4);}
|
|
1081
1096
|
.fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
|
|
@@ -1200,6 +1215,69 @@ a { color: var(--color-primary); }
|
|
|
1200
1215
|
.fdy-badge-ov--bottom-right{top:auto;bottom:0;transform:translate(50%,50%);}
|
|
1201
1216
|
.fdy-badge-ov--bottom-left{top:auto;bottom:0;right:auto;left:0;transform:translate(-50%,50%);}
|
|
1202
1217
|
|
|
1218
|
+
/* Freeday. Bottom navigation
|
|
1219
|
+
*
|
|
1220
|
+
* The bar a phone application puts its three-to-five most-used destinations on. The kit
|
|
1221
|
+
* had `.fdy-nav--horizontal` for a top-nav application and the off-canvas drawer for a
|
|
1222
|
+
* sidebar one, and nothing for the arrangement almost every phone application actually
|
|
1223
|
+
* uses — which was only noticed when an app shipped to real phones and its owner reached
|
|
1224
|
+
* for a thumb-reachable bar that did not exist.
|
|
1225
|
+
*
|
|
1226
|
+
* Deliberately NOT part of `.fdy-app`'s markup: it is `position:fixed`, so it works from
|
|
1227
|
+
* anywhere inside the shell's content slot, and an application that does not want one
|
|
1228
|
+
* pays nothing. `.fdy-app--has-bottomnav` on the shell is what reserves room for it. */
|
|
1229
|
+
.fdy-bottomnav{display:none;}
|
|
1230
|
+
@media (max-width:720px){
|
|
1231
|
+
.fdy-bottomnav{
|
|
1232
|
+
display:flex;position:fixed;left:0;right:0;bottom:0;z-index:50;
|
|
1233
|
+
align-items:stretch;justify-content:space-around;gap:var(--space-1);
|
|
1234
|
+
padding:var(--space-2) var(--space-2);
|
|
1235
|
+
/* The home indicator on a notched phone sits over the bottom of the viewport, and a
|
|
1236
|
+
* bar that ignores it puts its labels underneath a physical UI element. Added to the
|
|
1237
|
+
* padding rather than substituted for it, so a phone without one is unaffected. */
|
|
1238
|
+
padding-bottom:calc(var(--space-2) + env(safe-area-inset-bottom,0px));
|
|
1239
|
+
background:var(--color-surface-raised);
|
|
1240
|
+
backdrop-filter:var(--surface-filter);
|
|
1241
|
+
-webkit-backdrop-filter:var(--surface-filter);
|
|
1242
|
+
border-top:var(--bw) solid var(--color-border);
|
|
1243
|
+
}
|
|
1244
|
+
/* Room for the bar, or the last row of every list on every screen sits under it -- and
|
|
1245
|
+
a row you cannot scroll to is a row nobody knows is there. */
|
|
1246
|
+
.fdy-app--has-bottomnav .fdy-app__main{
|
|
1247
|
+
padding-bottom:calc(var(--space-20) + env(safe-area-inset-bottom,0px));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
.fdy-bottomnav__item{
|
|
1251
|
+
display:flex;flex:1 1 0;min-width:0;flex-direction:column;align-items:center;
|
|
1252
|
+
justify-content:center;gap:2px;
|
|
1253
|
+
/* 44px is the smallest target a thumb hits reliably; anything under it turns a tap into
|
|
1254
|
+
* a retry, and this bar is pressed more than any other control in the application. */
|
|
1255
|
+
min-height:2.75rem;padding:var(--space-1);
|
|
1256
|
+
border-radius:var(--radius-md);
|
|
1257
|
+
font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-medium);
|
|
1258
|
+
color:var(--color-text-muted);text-decoration:none;text-align:center;
|
|
1259
|
+
transition:background var(--dur-fast) var(--ease-standard),
|
|
1260
|
+
color var(--dur-fast) var(--ease-standard);
|
|
1261
|
+
}
|
|
1262
|
+
.fdy-bottomnav__item:hover{color:var(--color-text);}
|
|
1263
|
+
.fdy-bottomnav__item:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
|
|
1264
|
+
/* The current destination is marked by `aria-current="page"`, the same attribute
|
|
1265
|
+
* `.fdy-nav__item` uses -- one contract for "you are here", not two. */
|
|
1266
|
+
.fdy-bottomnav__item[aria-current="page"]{
|
|
1267
|
+
color:var(--color-primary-strong);background:var(--color-primary-soft);
|
|
1268
|
+
}
|
|
1269
|
+
.fdy-bottomnav__icon{display:block;width:1.25rem;height:1.25rem;flex:none;}
|
|
1270
|
+
.fdy-bottomnav__icon svg{display:block;width:100%;height:100%;}
|
|
1271
|
+
/* Labels are never dropped in favour of icons alone: an icon-only bar asks every user to
|
|
1272
|
+
* have already learned what each glyph means, and the people using this kit's applications
|
|
1273
|
+
* on a phone are often using them for the first time. Long labels ellipsis rather than
|
|
1274
|
+
* wrap, so the bar keeps one height. */
|
|
1275
|
+
.fdy-bottomnav__label{
|
|
1276
|
+
display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
1277
|
+
line-height:1.2;
|
|
1278
|
+
}
|
|
1279
|
+
@media (prefers-reduced-motion:reduce){.fdy-bottomnav__item{transition:none;}}
|
|
1280
|
+
|
|
1203
1281
|
/* Freeday. Breadcrumb */
|
|
1204
1282
|
.fdy-breadcrumb{font-size:var(--text-sm);}
|
|
1205
1283
|
.fdy-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);list-style:none;margin:0;padding:0;}
|
package/dist/freeday.css
CHANGED
|
@@ -162,6 +162,13 @@ a { color: var(--color-primary); }
|
|
|
162
162
|
.fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
163
163
|
.fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
|
|
164
164
|
.fdy-app__navtoggle svg{display:block;width:1.25rem;height:1.25rem;}
|
|
165
|
+
/* --navtoggle-end moves the toggle to the far side of the topbar. The title already
|
|
166
|
+
* carries `margin-right:auto`, so ordering the toggle last is enough; nothing else needs
|
|
167
|
+
* to know. On a large phone held in one hand the top-LEFT corner is the hardest place on
|
|
168
|
+
* the screen for a right thumb to reach, and the toggle is the control most often pressed
|
|
169
|
+
* there — so this is ergonomics, not taste. Left as an opt-in because a desktop shell that
|
|
170
|
+
* also puts an avatar on the right has the opposite problem. */
|
|
171
|
+
.fdy-app--navtoggle-end .fdy-app__navtoggle{order:1;}
|
|
165
172
|
/* Desktop collapse: animate width -> 0 (content clips via overflow:hidden; the content area
|
|
166
173
|
* flexes in to fill). Not display:none, so the collapse/expand slides instead of snapping. */
|
|
167
174
|
@media (min-width:721px){.fdy-app--nav-collapsed .fdy-app__sidebar{width:0;border-right-width:0;}}
|
|
@@ -197,7 +204,15 @@ a { color: var(--color-primary); }
|
|
|
197
204
|
* the initial `visible`, so a mode switch alone hands the page ~200ms of dead clicks. Measured: with
|
|
198
205
|
* only `visibility:hidden`, elementFromPoint over the nav toggle returned the backdrop.
|
|
199
206
|
* Mobile: the sidebar becomes an off-canvas drawer opened by the topbar toggle. */
|
|
200
|
-
.
|
|
207
|
+
/* `cursor:pointer` here is not decoration, and not for the mouse. Safari on iOS only
|
|
208
|
+
* synthesises a `click` from a tap on elements it treats as interactive, and the cursor
|
|
209
|
+
* being a pointer is one of the signals it uses. The backdrop is a plain <div> with a
|
|
210
|
+
* click handler — no button, no role, no tabindex — so without this rule a tap on it
|
|
211
|
+
* produces no click at all: the drawer stays open, the scrim stays dark, and the only way
|
|
212
|
+
* out is reloading the page. Reported from a real iPhone X; not reproducible in Chromium
|
|
213
|
+
* with either mouse click or `touchscreen.tap()`, which is why no test in this kit or in
|
|
214
|
+
* the consuming app had ever caught it. */
|
|
215
|
+
.fdy-app__backdrop{display:none;cursor:pointer;}
|
|
201
216
|
@media (max-width:720px){
|
|
202
217
|
.fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);transition:transform var(--dur-base) var(--ease-standard);box-shadow:var(--shadow-4);}
|
|
203
218
|
.fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
|
|
@@ -322,6 +337,69 @@ a { color: var(--color-primary); }
|
|
|
322
337
|
.fdy-badge-ov--bottom-right{top:auto;bottom:0;transform:translate(50%,50%);}
|
|
323
338
|
.fdy-badge-ov--bottom-left{top:auto;bottom:0;right:auto;left:0;transform:translate(-50%,50%);}
|
|
324
339
|
|
|
340
|
+
/* Freeday. Bottom navigation
|
|
341
|
+
*
|
|
342
|
+
* The bar a phone application puts its three-to-five most-used destinations on. The kit
|
|
343
|
+
* had `.fdy-nav--horizontal` for a top-nav application and the off-canvas drawer for a
|
|
344
|
+
* sidebar one, and nothing for the arrangement almost every phone application actually
|
|
345
|
+
* uses — which was only noticed when an app shipped to real phones and its owner reached
|
|
346
|
+
* for a thumb-reachable bar that did not exist.
|
|
347
|
+
*
|
|
348
|
+
* Deliberately NOT part of `.fdy-app`'s markup: it is `position:fixed`, so it works from
|
|
349
|
+
* anywhere inside the shell's content slot, and an application that does not want one
|
|
350
|
+
* pays nothing. `.fdy-app--has-bottomnav` on the shell is what reserves room for it. */
|
|
351
|
+
.fdy-bottomnav{display:none;}
|
|
352
|
+
@media (max-width:720px){
|
|
353
|
+
.fdy-bottomnav{
|
|
354
|
+
display:flex;position:fixed;left:0;right:0;bottom:0;z-index:50;
|
|
355
|
+
align-items:stretch;justify-content:space-around;gap:var(--space-1);
|
|
356
|
+
padding:var(--space-2) var(--space-2);
|
|
357
|
+
/* The home indicator on a notched phone sits over the bottom of the viewport, and a
|
|
358
|
+
* bar that ignores it puts its labels underneath a physical UI element. Added to the
|
|
359
|
+
* padding rather than substituted for it, so a phone without one is unaffected. */
|
|
360
|
+
padding-bottom:calc(var(--space-2) + env(safe-area-inset-bottom,0px));
|
|
361
|
+
background:var(--color-surface-raised);
|
|
362
|
+
backdrop-filter:var(--surface-filter);
|
|
363
|
+
-webkit-backdrop-filter:var(--surface-filter);
|
|
364
|
+
border-top:var(--bw) solid var(--color-border);
|
|
365
|
+
}
|
|
366
|
+
/* Room for the bar, or the last row of every list on every screen sits under it -- and
|
|
367
|
+
a row you cannot scroll to is a row nobody knows is there. */
|
|
368
|
+
.fdy-app--has-bottomnav .fdy-app__main{
|
|
369
|
+
padding-bottom:calc(var(--space-20) + env(safe-area-inset-bottom,0px));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
.fdy-bottomnav__item{
|
|
373
|
+
display:flex;flex:1 1 0;min-width:0;flex-direction:column;align-items:center;
|
|
374
|
+
justify-content:center;gap:2px;
|
|
375
|
+
/* 44px is the smallest target a thumb hits reliably; anything under it turns a tap into
|
|
376
|
+
* a retry, and this bar is pressed more than any other control in the application. */
|
|
377
|
+
min-height:2.75rem;padding:var(--space-1);
|
|
378
|
+
border-radius:var(--radius-md);
|
|
379
|
+
font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-medium);
|
|
380
|
+
color:var(--color-text-muted);text-decoration:none;text-align:center;
|
|
381
|
+
transition:background var(--dur-fast) var(--ease-standard),
|
|
382
|
+
color var(--dur-fast) var(--ease-standard);
|
|
383
|
+
}
|
|
384
|
+
.fdy-bottomnav__item:hover{color:var(--color-text);}
|
|
385
|
+
.fdy-bottomnav__item:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
|
|
386
|
+
/* The current destination is marked by `aria-current="page"`, the same attribute
|
|
387
|
+
* `.fdy-nav__item` uses -- one contract for "you are here", not two. */
|
|
388
|
+
.fdy-bottomnav__item[aria-current="page"]{
|
|
389
|
+
color:var(--color-primary-strong);background:var(--color-primary-soft);
|
|
390
|
+
}
|
|
391
|
+
.fdy-bottomnav__icon{display:block;width:1.25rem;height:1.25rem;flex:none;}
|
|
392
|
+
.fdy-bottomnav__icon svg{display:block;width:100%;height:100%;}
|
|
393
|
+
/* Labels are never dropped in favour of icons alone: an icon-only bar asks every user to
|
|
394
|
+
* have already learned what each glyph means, and the people using this kit's applications
|
|
395
|
+
* on a phone are often using them for the first time. Long labels ellipsis rather than
|
|
396
|
+
* wrap, so the bar keeps one height. */
|
|
397
|
+
.fdy-bottomnav__label{
|
|
398
|
+
display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
399
|
+
line-height:1.2;
|
|
400
|
+
}
|
|
401
|
+
@media (prefers-reduced-motion:reduce){.fdy-bottomnav__item{transition:none;}}
|
|
402
|
+
|
|
325
403
|
/* Freeday. Breadcrumb */
|
|
326
404
|
.fdy-breadcrumb{font-size:var(--text-sm);}
|
|
327
405
|
.fdy-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);list-style:none;margin:0;padding:0;}
|
package/docs/getting-started.md
CHANGED
|
@@ -191,7 +191,7 @@ live docs also have a copy button per component.
|
|
|
191
191
|
```bash
|
|
192
192
|
npm i @cahyo-dimas/freeday
|
|
193
193
|
```
|
|
194
|
-
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.
|
|
194
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.4.0"` (public npm package). `dist/` is
|
|
195
195
|
committed and published → no build step; `npm ci` runs without auth.
|
|
196
196
|
|
|
197
197
|
### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node tokens/build.mjs",
|
|
94
94
|
"test": "node --test",
|
|
95
|
-
"test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/cfl-dialog-only.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/cfl-narrow.mjs browser/harness-contract.mjs",
|
|
95
|
+
"test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/cfl-dialog-only.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/bottom-nav.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/cfl-narrow.mjs browser/harness-contract.mjs",
|
|
96
96
|
"prepack": "node tokens/build.mjs",
|
|
97
97
|
"version": "node tokens/build.mjs && git add dist",
|
|
98
98
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
|
|
@@ -83,6 +83,13 @@
|
|
|
83
83
|
.fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
84
84
|
.fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
|
|
85
85
|
.fdy-app__navtoggle svg{display:block;width:1.25rem;height:1.25rem;}
|
|
86
|
+
/* --navtoggle-end moves the toggle to the far side of the topbar. The title already
|
|
87
|
+
* carries `margin-right:auto`, so ordering the toggle last is enough; nothing else needs
|
|
88
|
+
* to know. On a large phone held in one hand the top-LEFT corner is the hardest place on
|
|
89
|
+
* the screen for a right thumb to reach, and the toggle is the control most often pressed
|
|
90
|
+
* there — so this is ergonomics, not taste. Left as an opt-in because a desktop shell that
|
|
91
|
+
* also puts an avatar on the right has the opposite problem. */
|
|
92
|
+
.fdy-app--navtoggle-end .fdy-app__navtoggle{order:1;}
|
|
86
93
|
/* Desktop collapse: animate width -> 0 (content clips via overflow:hidden; the content area
|
|
87
94
|
* flexes in to fill). Not display:none, so the collapse/expand slides instead of snapping. */
|
|
88
95
|
@media (min-width:721px){.fdy-app--nav-collapsed .fdy-app__sidebar{width:0;border-right-width:0;}}
|
|
@@ -118,7 +125,15 @@
|
|
|
118
125
|
* the initial `visible`, so a mode switch alone hands the page ~200ms of dead clicks. Measured: with
|
|
119
126
|
* only `visibility:hidden`, elementFromPoint over the nav toggle returned the backdrop.
|
|
120
127
|
* Mobile: the sidebar becomes an off-canvas drawer opened by the topbar toggle. */
|
|
121
|
-
.
|
|
128
|
+
/* `cursor:pointer` here is not decoration, and not for the mouse. Safari on iOS only
|
|
129
|
+
* synthesises a `click` from a tap on elements it treats as interactive, and the cursor
|
|
130
|
+
* being a pointer is one of the signals it uses. The backdrop is a plain <div> with a
|
|
131
|
+
* click handler — no button, no role, no tabindex — so without this rule a tap on it
|
|
132
|
+
* produces no click at all: the drawer stays open, the scrim stays dark, and the only way
|
|
133
|
+
* out is reloading the page. Reported from a real iPhone X; not reproducible in Chromium
|
|
134
|
+
* with either mouse click or `touchscreen.tap()`, which is why no test in this kit or in
|
|
135
|
+
* the consuming app had ever caught it. */
|
|
136
|
+
.fdy-app__backdrop{display:none;cursor:pointer;}
|
|
122
137
|
@media (max-width:720px){
|
|
123
138
|
.fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);transition:transform var(--dur-base) var(--ease-standard);box-shadow:var(--shadow-4);}
|
|
124
139
|
.fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* Freeday. Bottom navigation
|
|
2
|
+
*
|
|
3
|
+
* The bar a phone application puts its three-to-five most-used destinations on. The kit
|
|
4
|
+
* had `.fdy-nav--horizontal` for a top-nav application and the off-canvas drawer for a
|
|
5
|
+
* sidebar one, and nothing for the arrangement almost every phone application actually
|
|
6
|
+
* uses — which was only noticed when an app shipped to real phones and its owner reached
|
|
7
|
+
* for a thumb-reachable bar that did not exist.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately NOT part of `.fdy-app`'s markup: it is `position:fixed`, so it works from
|
|
10
|
+
* anywhere inside the shell's content slot, and an application that does not want one
|
|
11
|
+
* pays nothing. `.fdy-app--has-bottomnav` on the shell is what reserves room for it. */
|
|
12
|
+
.fdy-bottomnav{display:none;}
|
|
13
|
+
@media (max-width:720px){
|
|
14
|
+
.fdy-bottomnav{
|
|
15
|
+
display:flex;position:fixed;left:0;right:0;bottom:0;z-index:50;
|
|
16
|
+
align-items:stretch;justify-content:space-around;gap:var(--space-1);
|
|
17
|
+
padding:var(--space-2) var(--space-2);
|
|
18
|
+
/* The home indicator on a notched phone sits over the bottom of the viewport, and a
|
|
19
|
+
* bar that ignores it puts its labels underneath a physical UI element. Added to the
|
|
20
|
+
* padding rather than substituted for it, so a phone without one is unaffected. */
|
|
21
|
+
padding-bottom:calc(var(--space-2) + env(safe-area-inset-bottom,0px));
|
|
22
|
+
background:var(--color-surface-raised);
|
|
23
|
+
backdrop-filter:var(--surface-filter);
|
|
24
|
+
-webkit-backdrop-filter:var(--surface-filter);
|
|
25
|
+
border-top:var(--bw) solid var(--color-border);
|
|
26
|
+
}
|
|
27
|
+
/* Room for the bar, or the last row of every list on every screen sits under it -- and
|
|
28
|
+
a row you cannot scroll to is a row nobody knows is there. */
|
|
29
|
+
.fdy-app--has-bottomnav .fdy-app__main{
|
|
30
|
+
padding-bottom:calc(var(--space-20) + env(safe-area-inset-bottom,0px));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
.fdy-bottomnav__item{
|
|
34
|
+
display:flex;flex:1 1 0;min-width:0;flex-direction:column;align-items:center;
|
|
35
|
+
justify-content:center;gap:2px;
|
|
36
|
+
/* 44px is the smallest target a thumb hits reliably; anything under it turns a tap into
|
|
37
|
+
* a retry, and this bar is pressed more than any other control in the application. */
|
|
38
|
+
min-height:2.75rem;padding:var(--space-1);
|
|
39
|
+
border-radius:var(--radius-md);
|
|
40
|
+
font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-medium);
|
|
41
|
+
color:var(--color-text-muted);text-decoration:none;text-align:center;
|
|
42
|
+
transition:background var(--dur-fast) var(--ease-standard),
|
|
43
|
+
color var(--dur-fast) var(--ease-standard);
|
|
44
|
+
}
|
|
45
|
+
.fdy-bottomnav__item:hover{color:var(--color-text);}
|
|
46
|
+
.fdy-bottomnav__item:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
|
|
47
|
+
/* The current destination is marked by `aria-current="page"`, the same attribute
|
|
48
|
+
* `.fdy-nav__item` uses -- one contract for "you are here", not two. */
|
|
49
|
+
.fdy-bottomnav__item[aria-current="page"]{
|
|
50
|
+
color:var(--color-primary-strong);background:var(--color-primary-soft);
|
|
51
|
+
}
|
|
52
|
+
.fdy-bottomnav__icon{display:block;width:1.25rem;height:1.25rem;flex:none;}
|
|
53
|
+
.fdy-bottomnav__icon svg{display:block;width:100%;height:100%;}
|
|
54
|
+
/* Labels are never dropped in favour of icons alone: an icon-only bar asks every user to
|
|
55
|
+
* have already learned what each glyph means, and the people using this kit's applications
|
|
56
|
+
* on a phone are often using them for the first time. Long labels ellipsis rather than
|
|
57
|
+
* wrap, so the bar keeps one height. */
|
|
58
|
+
.fdy-bottomnav__label{
|
|
59
|
+
display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
60
|
+
line-height:1.2;
|
|
61
|
+
}
|
|
62
|
+
@media (prefers-reduced-motion:reduce){.fdy-bottomnav__item{transition:none;}}
|