@cahyo-dimas/freeday 1.53.0 → 2.0.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 +657 -535
- package/COMPONENTS.md +153 -135
- package/README.id.md +46 -45
- package/README.md +46 -43
- package/USAGE.md +39 -35
- package/adapters/blazor/FdyAppShell.razor +50 -0
- package/adapters/blazor/FdyAppShell.razor.cs +114 -0
- package/adapters/blazor/FdyCfl.razor.cs +1 -1
- package/adapters/blazor/FdyChart.razor +1 -1
- package/adapters/blazor/FdyChart.razor.cs +3 -3
- package/adapters/blazor/FdyChartSeries.cs +1 -1
- package/adapters/blazor/FdyCombo.razor.cs +2 -2
- package/adapters/blazor/FdyModal.razor +1 -1
- package/adapters/blazor/FdyTable.razor.cs +7 -7
- package/adapters/blazor/FdyTableFilter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
- package/adapters/blazor/Freeday.Blazor.csproj +1 -1
- package/adapters/blazor/FreedayComponentBase.cs +1 -1
- package/adapters/blazor/TableModel.cs +3 -3
- package/adapters/blazor/TableTypes.cs +3 -3
- package/adapters/blazor/freeday-blazor.js +5 -5
- package/adapters/core/app-shell.d.ts +30 -0
- package/adapters/core/app-shell.js +134 -0
- package/adapters/core/table-model.d.ts +2 -2
- package/adapters/core/table-model.js +8 -8
- package/adapters/react/components/FdyAppShell.tsx +159 -0
- package/adapters/react/components/FdyAutocomplete.tsx +1 -1
- package/adapters/react/components/FdyCascade.tsx +1 -1
- package/adapters/react/components/FdyCfl.tsx +11 -11
- package/adapters/react/components/FdyDateRange.tsx +2 -2
- package/adapters/react/components/FdyDatepicker.tsx +5 -5
- package/adapters/react/components/FdyDrawer.tsx +1 -1
- package/adapters/react/components/FdyModal.tsx +2 -2
- package/adapters/react/components/FdyTable.tsx +7 -7
- package/adapters/react/components/FdyTableFilter.tsx +1 -1
- package/adapters/react/components/FdyTableFooter.tsx +5 -5
- package/adapters/react/index.d.ts +2 -1
- package/adapters/react/index.js +1 -0
- package/adapters/react/useFreeday.js +3 -3
- package/adapters/vue/components/FdyAppShell.vue +160 -0
- package/adapters/vue/components/FdyAutocomplete.vue +1 -1
- package/adapters/vue/components/FdyCascade.vue +1 -1
- package/adapters/vue/components/FdyCfl.vue +8 -8
- package/adapters/vue/components/FdyChart.vue +2 -2
- package/adapters/vue/components/FdyCombo.vue +1 -1
- package/adapters/vue/components/FdyDateRange.vue +2 -2
- package/adapters/vue/components/FdyDatepicker.vue +5 -5
- package/adapters/vue/components/FdyDrawer.vue +3 -3
- package/adapters/vue/components/FdyModal.vue +3 -3
- package/adapters/vue/components/FdyTable.vue +7 -7
- package/adapters/vue/components/FdyTableFilter.vue +1 -1
- package/adapters/vue/components/FdyTableFooter.vue +5 -5
- package/adapters/vue/index.d.ts +3 -2
- package/adapters/vue/index.js +1 -0
- package/adapters/vue/useFreeday.js +3 -3
- package/dist/asset.d.ts +3 -3
- package/dist/freeday-app-shell.js +49 -9
- package/dist/freeday-autocomplete.js +2 -2
- package/dist/freeday-breakpoint.js +1 -1
- package/dist/freeday-carousel.js +5 -5
- package/dist/freeday-cascade.js +7 -7
- package/dist/freeday-cfl.js +9 -9
- package/dist/freeday-chart.js +11 -11
- package/dist/freeday-chip.js +3 -3
- package/dist/freeday-datepicker.js +11 -7
- package/dist/freeday-datetime.js +5 -5
- package/dist/freeday-drawer.js +1 -1
- package/dist/freeday-form.js +14 -14
- package/dist/freeday-mask.js +6 -6
- package/dist/freeday-menu.js +1 -1
- package/dist/freeday-number.js +3 -3
- package/dist/freeday-popover.js +3 -3
- package/dist/freeday-rating.js +2 -2
- package/dist/freeday-select.js +5 -5
- package/dist/freeday-slider.js +2 -2
- package/dist/freeday-stepper.js +7 -7
- package/dist/freeday-table.js +15 -15
- package/dist/freeday-tabs.js +1 -1
- package/dist/freeday-timepicker.js +2 -2
- package/dist/freeday-toast.js +8 -8
- package/dist/freeday-tree.js +3 -3
- package/dist/freeday-upload.js +25 -25
- package/dist/freeday.bundle.css +176 -176
- package/dist/freeday.css +170 -170
- package/dist/freeday.d.ts +2 -2
- package/dist/freeday.js +202 -158
- package/dist/freeday.tokens.css +5 -5
- package/docs/agent-onboarding.md +42 -42
- package/docs/getting-started.md +35 -35
- package/docs/integrations.md +18 -18
- package/docs/reference-screen.html +14 -14
- package/package.json +5 -4
- package/src/base.css +6 -6
- package/src/components/accordion.css +4 -4
- package/src/components/alert.css +1 -1
- package/src/components/app-shell.css +10 -10
- package/src/components/appbar.css +3 -3
- package/src/components/autocomplete.css +1 -1
- package/src/components/avatar.css +3 -3
- package/src/components/badge.css +5 -5
- package/src/components/breadcrumb.css +1 -1
- package/src/components/breakpoints.css +4 -4
- package/src/components/button.css +13 -13
- package/src/components/card.css +8 -8
- package/src/components/carousel.css +2 -2
- package/src/components/cascade.css +2 -2
- package/src/components/cfl.css +4 -4
- package/src/components/chart.css +3 -3
- package/src/components/chip.css +2 -2
- package/src/components/combo.css +3 -3
- package/src/components/composition.css +7 -7
- package/src/components/datepicker.css +6 -6
- package/src/components/datetimepicker.css +1 -1
- package/src/components/description-list.css +1 -1
- package/src/components/divider.css +1 -1
- package/src/components/drawer.css +1 -1
- package/src/components/file-upload.css +5 -5
- package/src/components/filterbar.css +7 -7
- package/src/components/form-grid.css +2 -2
- package/src/components/input-group.css +6 -6
- package/src/components/input.css +6 -6
- package/src/components/kbd.css +1 -1
- package/src/components/list.css +6 -6
- package/src/components/menu.css +3 -3
- package/src/components/modal.css +1 -1
- package/src/components/pagination.css +1 -1
- package/src/components/progress.css +1 -1
- package/src/components/rating.css +2 -2
- package/src/components/selection.css +4 -4
- package/src/components/skeleton.css +1 -1
- package/src/components/slider.css +1 -1
- package/src/components/spinner.css +1 -1
- package/src/components/states.css +1 -1
- package/src/components/stepper.css +2 -2
- package/src/components/table.css +15 -15
- package/src/components/tabs.css +3 -3
- package/src/components/timeline.css +1 -1
- package/src/components/timepicker.css +1 -1
- package/src/components/toast.css +3 -3
- package/src/components/tooltip.css +1 -1
- package/src/components/tree.css +2 -2
- package/tokens/breakpoints.d.ts +2 -2
- package/tokens/breakpoints.mjs +3 -3
- package/tokens/tokens.json +1 -1
package/COMPONENTS.md
CHANGED
|
@@ -15,30 +15,31 @@ live docs.
|
|
|
15
15
|
1. **Prefix is `fdy-`, pattern is compact BEM**: block `fdy-card`, element `fdy-card__title`,
|
|
16
16
|
modifier `fdy-card--elevated`. A modifier is **always** written alongside its block class:
|
|
17
17
|
`class="fdy-btn fdy-btn--ghost"`, never `fdy-btn--ghost` alone.
|
|
18
|
-
**One documented exception:** `.fdy-input-group__addon--icon` is **standalone
|
|
18
|
+
**One documented exception:** `.fdy-input-group__addon--icon` is **standalone**. It is a
|
|
19
19
|
borderless leading glyph, and adding the base `.fdy-input-group__addon` would give it the grey
|
|
20
20
|
fill and divider of a `Rp` / `%` prefix, which is not what a search icon is. Written
|
|
21
21
|
`class="fdy-input-group__addon--icon"`, alone. It is the only one; everywhere else the rule holds.
|
|
22
22
|
2. **Never invent a class.** If it is not in this file, it does not exist. Freeday has no
|
|
23
23
|
`fdy-grid`, no `fdy-flex`, no `fdy-mt-4`, no `fdy-primary`.
|
|
24
|
-
3. **Never write a raw hex or px value** in app CSS. Use the tokens
|
|
24
|
+
3. **Never write a raw hex or px value** in app CSS. Use the tokens: `var(--color-primary)`,
|
|
25
25
|
`var(--space-4)`, `var(--radius-md)`, `var(--shadow-1)`, `var(--dur-2)`. Spacing is a 4px scale
|
|
26
26
|
(`--space-0`…`--space-24`); a loose value breaks density switching.
|
|
27
27
|
4. **Freeday owns components + tokens, not layout.** The only layout it ships is the shell
|
|
28
28
|
(`.fdy-app`), the composition primitives below, and `.fdy-hidden`/`.fdy-visually-hidden`.
|
|
29
29
|
Grids, stacks and one-off gaps come from your own layout layer (a utility framework run
|
|
30
30
|
**utilities-only, preflight off**, with its theme defined in terms of `var(--space-N)`).
|
|
31
|
-
5. **`aria-invalid="true"` is the canonical error state** on form controls
|
|
31
|
+
5. **`aria-invalid="true"` is the canonical error state** on form controls. It drives the visual
|
|
32
32
|
state *and* the screen-reader state. Pair it with `aria-describedby` → the `id` of a
|
|
33
33
|
`.fdy-help.fdy-help--error`. The `--error` class modifiers exist only for the case where the
|
|
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,
|
|
37
|
+
7. **On Vue, React or Blazor, eleven components have a typed wrapper. Use it.** `FdyCombo` ·
|
|
38
38
|
`FdyDatepicker` · `FdyDateRange` · `FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart` ·
|
|
39
39
|
`FdyTable` · `FdyModal` · `FdyDrawer`, 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
|
-
those stacks *looks* right
|
|
41
|
+
those stacks *looks* right at first, because the enhancer initialises once and the first render is
|
|
42
|
+
correct. Then
|
|
42
43
|
breaks quietly: later-rendered DOM is never hydrated and the widget's state sits in the DOM
|
|
43
44
|
instead of your framework's. Everything else is the same markup everywhere; hydrate the other
|
|
44
45
|
interactive components with `useFreeday` (Vue/React) or `FreedayBlazor.initAll` (Blazor).
|
|
@@ -47,11 +48,11 @@ live docs.
|
|
|
47
48
|
|
|
48
49
|
`.fdy-visually-hidden` is `position:absolute`, and `clip` hides **painting**, not **layout**. An
|
|
49
50
|
absolutely positioned box resolves against its nearest *positioned* ancestor, and `overflow` clips
|
|
50
|
-
only what is contained that way
|
|
51
|
+
only what is contained that way, so in an unpositioned scroller, a hidden label parks at its static
|
|
51
52
|
position (possibly thousands of px to the right) and drags the **whole document** sideways. It is
|
|
52
53
|
invisible in the DOM and immune to `overflow-x: hidden` on every wrapper.
|
|
53
54
|
|
|
54
|
-
Every kit container that clips or scrolls therefore declares `position: relative
|
|
55
|
+
Every kit container that clips or scrolls therefore declares `position: relative`:
|
|
55
56
|
`.fdy-table-scroll`, `.fdy-table-wrap`, `.fdy-list`, `.fdy-card`, `.fdy-tabs__list`,
|
|
56
57
|
`.fdy-carousel__viewport`, `.fdy-accordion` (the rest are already inside a positioned ancestor).
|
|
57
58
|
Two consequences for you:
|
|
@@ -66,7 +67,7 @@ Two consequences for you:
|
|
|
66
67
|
|
|
67
68
|
Every enhancer is zero-dependency, auto-initialises once on `DOMContentLoaded`, and is idempotent:
|
|
68
69
|
re-hydrate SPA-rendered DOM with `window.Freeday<X>.initAll(root)`. **`root` may be the widget
|
|
69
|
-
itself or an ancestor of it
|
|
70
|
+
itself or an ancestor of it**. Both work, so a framework ref placed directly on the enhanced
|
|
70
71
|
element is fine. Events are bubbling
|
|
71
72
|
`CustomEvent`s; the payload is in `event.detail`.
|
|
72
73
|
|
|
@@ -114,6 +115,8 @@ is a no-op.
|
|
|
114
115
|
# Shell & composition
|
|
115
116
|
|
|
116
117
|
## App shell — `.fdy-app`
|
|
118
|
+
> **Typed wrapper: `<FdyAppShell>`**. Vue (`v-model:navOpen`) · React (`navOpen`/`onNavOpenChange`) · Blazor (`@bind-NavOpen`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
119
|
+
|
|
117
120
|
The frame every application lives in. Do not hand-roll one from flexbox: the responsive sidebar,
|
|
118
121
|
off-canvas drawer and backdrop are built in.
|
|
119
122
|
|
|
@@ -121,25 +124,38 @@ off-canvas drawer and backdrop are built in.
|
|
|
121
124
|
`__content` `__topbar` `__navtoggle` `__title` `__main` `__backdrop`
|
|
122
125
|
- Modifiers: `--nav-open` (mobile drawer open, ≤720px) · `--nav-collapsed` (collapse to zero width,
|
|
123
126
|
≥721px) · `--static` (embed the shell in a page instead of filling the viewport)
|
|
124
|
-
- Also: `.fdy-skip
|
|
125
|
-
- **Behaviour: `freeday-app-shell.js`.** Opt in with `data-fdy-app` on the root
|
|
127
|
+
- Also: `.fdy-skip`, the skip-to-content link, first child of the shell.
|
|
128
|
+
- **Behaviour: `freeday-app-shell.js`.** Opt in with `data-fdy-app` on the root. The markup below
|
|
126
129
|
is unchanged. It owns the toggle in both modes plus everything an overlay needs that hand-rolling
|
|
127
130
|
reliably forgets: Escape, backdrop click, closing when a `.fdy-nav__item` is followed, focus moved
|
|
128
131
|
into the panel on open and returned to `__navtoggle` on close, `inert` on `__content` while the
|
|
129
132
|
overlay is up, and a Tab trap inside the panel. `aria-expanded` on `__navtoggle` answers "is the
|
|
130
133
|
nav showing?" in both modes, so you never read the state classes yourself.
|
|
131
|
-
- **A hidden nav is not a tabbable nav.** `__sidebar` is `inert` whenever the nav is not visible
|
|
134
|
+
- **A hidden nav is not a tabbable nav.** `__sidebar` is `inert` whenever the nav is not visible:
|
|
132
135
|
collapsed at ≥721px (`width:0`) or off-canvas at ≤720px (`translateX(-100%)`). Both hide it from
|
|
133
136
|
the eye; neither hides it from the keyboard, so without this a nav nobody can see still swallows
|
|
134
137
|
Tab on the way to the page.
|
|
135
138
|
- **Crossing the breakpoint is part of the contract**: open the overlay, then widen the window, and
|
|
136
139
|
`--nav-open` is dropped and `inert` removed. Leaving them set is how a page becomes permanently
|
|
137
140
|
unreachable.
|
|
141
|
+
- **`navOpen` is one idea, in both modes.** The typed wrappers expose "is the nav visible?" and the
|
|
142
|
+
kit maps it: above the breakpoint a hidden nav is `--nav-collapsed`, below it a visible nav is
|
|
143
|
+
`--nav-open`. Leave the prop unbound (`undefined` in Vue/React, `null` in Blazor) and the shell
|
|
144
|
+
starts from the viewport: a column on a wide screen, hidden on a narrow one. That default is why
|
|
145
|
+
the prop is optional: a caller cannot express it as a single initial value before it knows the
|
|
146
|
+
viewport.
|
|
147
|
+
- **Binding your own state to the vanilla shell:** it emits a bubbling `fdy-app-nav` CustomEvent
|
|
148
|
+
(`detail {visible}`) on every real change, including the ones a viewport change causes, since a
|
|
149
|
+
bound value that stayed `true` while the nav went off-canvas would be describing a panel nobody
|
|
150
|
+
can see. Drive it with `FreedayAppShell.setVisible(root, visible)` rather than the class names;
|
|
151
|
+
setting what is already set announces nothing, so a bound host cannot loop on its own echo.
|
|
152
|
+
`FreedayAppShell.isVisible(root)` reads it back.
|
|
138
153
|
- Skip the enhancer and nothing breaks: the classes still mean what they always meant and the
|
|
139
|
-
behaviour is yours to write. `FreedayAppShell.init(root)` for markup mounted later.
|
|
154
|
+
behaviour is yours to write. `FreedayAppShell.init(root)` for markup mounted later. Hand it the
|
|
155
|
+
shell root itself, which is what the Blazor bridge does after its first render.
|
|
140
156
|
|
|
141
157
|
**The nesting is fixed, not free-form:** `.fdy-app` is a flex **row** of `[__sidebar | __content]`,
|
|
142
|
-
and `__content` is the column holding `__topbar` + `__main
|
|
158
|
+
and `__content` is the column holding `__topbar` + `__main`, which exists to give the sticky topbar a
|
|
143
159
|
tall containing block. The brand goes in the **sidebar** (sized to match the topbar height), and
|
|
144
160
|
`__main` already carries the page padding, so don't wrap your screen in another padded box.
|
|
145
161
|
|
|
@@ -172,15 +188,15 @@ tall containing block. The brand goes in the **sidebar** (sized to match the top
|
|
|
172
188
|
Vertical section rhythm (`--space-8` between children). Assemble every screen from these; they
|
|
173
189
|
carry the doctrine in markup.
|
|
174
190
|
|
|
175
|
-
- `.fdy-page`
|
|
176
|
-
- `.fdy-page__header`
|
|
191
|
+
- `.fdy-page` is the screen body
|
|
192
|
+
- `.fdy-page__header` holds `.fdy-page__heading` (eyebrow + page title + `.fdy-page__desc`) on the
|
|
177
193
|
left, `.fdy-page__actions` (**one** primary button) on the right
|
|
178
|
-
- `.fdy-page-section`
|
|
179
|
-
- `.fdy-toolbar`
|
|
194
|
+
- `.fdy-page-section` is a region: `.fdy-page-section__head` + body, `--space-4` rhythm
|
|
195
|
+
- `.fdy-toolbar` is a horizontal control row; `__spacer` (flex filler), `__search` (capped width).
|
|
180
196
|
It is `align-items:center`, so it is for **bare** controls. A `.fdy-field` with a *visible* label
|
|
181
|
-
sits half a label-height low in it
|
|
197
|
+
sits half a label-height low in it, so labelled fields belong in `.fdy-filterbar`
|
|
182
198
|
(`align-items:flex-end`). In a toolbar, hide the label with `.fdy-visually-hidden`.
|
|
183
|
-
- `.fdy-stats`
|
|
199
|
+
- `.fdy-stats` is a KPI grid of `.fdy-stat` (`__label` `__value` `__meta`). Deliberately **not**
|
|
184
200
|
cards. `.fdy-stats--boxed` for one shared surface with dividers.
|
|
185
201
|
|
|
186
202
|
```html
|
|
@@ -215,7 +231,7 @@ carry the doctrine in markup.
|
|
|
215
231
|
```
|
|
216
232
|
|
|
217
233
|
## Type roles & text
|
|
218
|
-
One role per level of hierarchy
|
|
234
|
+
One role per level of hierarchy. Never re-use a card title for a page title.
|
|
219
235
|
|
|
220
236
|
| Class | Element | Use |
|
|
221
237
|
|---|---|---|
|
|
@@ -231,11 +247,11 @@ One role per level of hierarchy — never re-use a card title for a page title.
|
|
|
231
247
|
|
|
232
248
|
## Utilities
|
|
233
249
|
`.fdy-hidden` · `.fdy-visually-hidden` (screen-reader-only) · `.fdy-list-reset` (strip native
|
|
234
|
-
bullets/indent from a semantic list
|
|
250
|
+
bullets/indent from a semantic list, needed when your utility framework's preflight is off) ·
|
|
235
251
|
`.fdy-divider` (+`--vertical`) · `.fdy-kbd`.
|
|
236
252
|
|
|
237
253
|
Responsive display: `.fdy-hide-below-sm|md|lg` · `.fdy-hide-above-sm|md|lg`.
|
|
238
|
-
Breakpoints (min-width): `sm` 600 · `md` 960 · `lg` 1280 · `xl` 1920
|
|
254
|
+
Breakpoints (min-width): `sm` 600 · `md` 960 · `lg` 1280 · `xl` 1920, importable as
|
|
239
255
|
`import { breakpoints } from '@cahyo-dimas/freeday/breakpoints'`.
|
|
240
256
|
|
|
241
257
|
**`breakpoints.nav` (721) is separate and is the one the shell uses.** `.fdy-app` switches the
|
|
@@ -243,7 +259,7 @@ sidebar from off-canvas drawer to static column at 721px, *not* at `md`. Any `ma
|
|
|
243
259
|
utility variant that has to agree with the shell must use `nav`; using `md` leaves 721–959px broken
|
|
244
260
|
(sidebar already static while your script still treats it as an overlay).
|
|
245
261
|
|
|
246
|
-
Density: `data-density="compact"` works on `<html>` **or any wrapper
|
|
262
|
+
Density: `data-density="compact"` works on `<html>` **or any wrapper**, because the selector is a bare
|
|
247
263
|
`[data-density="compact"]` over inheriting custom properties, so one screen can be dense while the
|
|
248
264
|
rest of the app is not. `data-density="comfortable"` is a real rule too, so the reverse also works:
|
|
249
265
|
a compact root with one region opted back out (shared chrome that must match a sibling product).
|
|
@@ -253,13 +269,13 @@ a compact root with one region opted back out (shared chrome that must match a s
|
|
|
253
269
|
# Actions
|
|
254
270
|
|
|
255
271
|
## Button — `.fdy-btn`
|
|
256
|
-
**The base class is already the primary action
|
|
272
|
+
**The base class is already the primary action.** There is no `--primary`. Use it once per screen.
|
|
257
273
|
|
|
258
274
|
- Modifiers: `--ghost` `--danger` `--text` `--icon` (square, icon-only) `--sm` `--lg`
|
|
259
275
|
- Parts: `.fdy-btn__icon` (wraps the `<svg>`) · `.fdy-btn-group` (segmented row, `role="group"` +
|
|
260
276
|
`aria-label`)
|
|
261
277
|
- **Toggle / segmented:** `aria-pressed="true"` gives a real pressed state (soft primary fill on
|
|
262
|
-
`--ghost`/`--text`, an inset-sunk gradient on the solid button). No extra class
|
|
278
|
+
`--ghost`/`--text`, an inset-sunk gradient on the solid button). No extra class: `aria-pressed`
|
|
263
279
|
is already the right attribute, and it is what turns `.fdy-btn-group` from a joined row into a
|
|
264
280
|
complete segmented control.
|
|
265
281
|
- A11y: `--icon` **requires** `aria-label`. Use `<button type="button">` unless it submits.
|
|
@@ -273,7 +289,7 @@ a compact root with one region opted back out (shared chrome that must match a s
|
|
|
273
289
|
```
|
|
274
290
|
|
|
275
291
|
**Quiet destructive:** combine `--danger` with `--ghost` or `--text`. The ground stays quiet and only
|
|
276
|
-
the ink turns red
|
|
292
|
+
the ink turns red. `.fdy-menu__item--danger` has always worked this way. Use it whenever Delete
|
|
277
293
|
shares a screen with the one primary action; a solid Delete beside Save is a second primary in all
|
|
278
294
|
but name.
|
|
279
295
|
|
|
@@ -315,7 +331,7 @@ native control, otherwise a `<div>` + explicitly associated label.
|
|
|
315
331
|
- `.fdy-label` (+`--required`) · `.fdy-input` (+`--error`) · `.fdy-textarea` · `.fdy-help` (+`--error`)
|
|
316
332
|
|
|
317
333
|
**Grouped controls are a `<fieldset>`, not a new block.** Put `.fdy-field` on the fieldset and
|
|
318
|
-
`.fdy-label` on the legend
|
|
334
|
+
`.fdy-label` on the legend. The kit already resets the UA border/padding and supplies the spacing:
|
|
319
335
|
|
|
320
336
|
```html
|
|
321
337
|
<fieldset class="fdy-field">
|
|
@@ -327,7 +343,7 @@ native control, otherwise a `<div>` + explicitly associated label.
|
|
|
327
343
|
|
|
328
344
|
**`.fdy-label--required` marks the label, not the accessibility tree.** The control already carries
|
|
329
345
|
`required`; the asterisk is painted through `::after` with CSS alt text, so a screen reader never
|
|
330
|
-
reads "star" after the label
|
|
346
|
+
reads "star" after the label, which a `<span>` you have to remember to mark `aria-hidden`
|
|
331
347
|
cannot guarantee.
|
|
332
348
|
- `[readonly]` is styled on input/textarea: full contrast, focusable, copyable.
|
|
333
349
|
|
|
@@ -345,8 +361,8 @@ cannot guarantee.
|
|
|
345
361
|
</label>
|
|
346
362
|
```
|
|
347
363
|
|
|
348
|
-
**Which `type` does `.fdy-input` cover?** Every text-like type
|
|
349
|
-
`url` `search` `number`
|
|
364
|
+
**Which `type` does `.fdy-input` cover?** Every text-like type (`text` `email` `password` `tel`
|
|
365
|
+
`url` `search` `number`) themes identically. Two carry a native widget the kit does *not* override:
|
|
350
366
|
|
|
351
367
|
| Type | What the UA still draws | What to do |
|
|
352
368
|
|---|---|---|
|
|
@@ -371,7 +387,7 @@ Prefix/suffix addons around an input: text (`Rp`, `%`), a decorative icon
|
|
|
371
387
|
|
|
372
388
|
## Number field — `[data-fdy-number]`
|
|
373
389
|
A number input with its increment/decrement affordance back, after `.fdy-input` removed the
|
|
374
|
-
browser's own. **Not a new block
|
|
390
|
+
browser's own. **Not a new block.** It is an `.fdy-input-group` with two `__btn`s, so it inherits
|
|
375
391
|
the shared border, focus ring and error promotion. Needs `freeday-number.js`.
|
|
376
392
|
|
|
377
393
|
```html
|
|
@@ -385,13 +401,13 @@ the shared border, focus ring and error promotion. Needs `freeday-number.js`.
|
|
|
385
401
|
```
|
|
386
402
|
|
|
387
403
|
- **No custom event.** Stepping fires native bubbling `input` + `change` on the input, so `v-model`,
|
|
388
|
-
`onChange` and `@bind` work with no adapter
|
|
404
|
+
`onChange` and `@bind` work with no adapter, because the input stays the source of truth.
|
|
389
405
|
- `min` / `max` / `step` live on the **input**; the buttons never do the arithmetic themselves
|
|
390
406
|
(`stepUp()`/`stepDown()` clamp for free) and go `disabled` at a bound, on a `disabled`/`readonly`
|
|
391
407
|
field, and when `step="any"` (a stepper cannot express "no defined increment").
|
|
392
408
|
- The buttons are **not tab stops** (`tabindex="-1"`): the input is already focusable and ↑/↓
|
|
393
409
|
already step it, so extra stops would cost every keyboard user and buy nothing. Keep the
|
|
394
|
-
`aria-label
|
|
410
|
+
`aria-label`, so pointer and browse-mode users still get a named control.
|
|
395
411
|
- `type="button"` is required. Inside a `<form>`, a bare `<button>` submits it.
|
|
396
412
|
|
|
397
413
|
## Checkbox · radio · switch
|
|
@@ -404,7 +420,7 @@ Native inputs, styled. `.fdy-check` · `.fdy-radio` · `.fdy-switch` on the wrap
|
|
|
404
420
|
```
|
|
405
421
|
|
|
406
422
|
## Select / combobox — `.fdy-combo`
|
|
407
|
-
> **Typed wrapper: `<FdyCombo
|
|
423
|
+
> **Typed wrapper: `<FdyCombo>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
408
424
|
|
|
409
425
|
Fully styleable dropdown, APG combobox+listbox. Needs `freeday-select.js`.
|
|
410
426
|
|
|
@@ -429,12 +445,12 @@ Fully styleable dropdown, APG combobox+listbox. Needs `freeday-select.js`.
|
|
|
429
445
|
|
|
430
446
|
**The selected tick is CSS.** `.fdy-combo__check` is an empty box in the markup; the glyph is painted
|
|
431
447
|
by the stylesheet on `[aria-selected="true"]` with alt text, so it never enters the accessibility
|
|
432
|
-
tree. That keeps an option's accessible name **identical whether or not it is selected
|
|
448
|
+
tree. That keeps an option's accessible name **identical whether or not it is selected**. The state
|
|
433
449
|
is carried by `aria-selected` alone, announced once, and `getByRole('option', { name: 'August' })`
|
|
434
450
|
keeps matching after selection. Do not put a glyph in that span.
|
|
435
451
|
|
|
436
452
|
## Autocomplete — `.fdy-autocomplete`
|
|
437
|
-
> **Typed wrapper: `<FdyAutocomplete
|
|
453
|
+
> **Typed wrapper: `<FdyAutocomplete>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
438
454
|
|
|
439
455
|
Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
440
456
|
|
|
@@ -443,7 +459,7 @@ Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
|
443
459
|
`aria-controls` → the listbox `id`, `autocomplete="off"`.
|
|
444
460
|
|
|
445
461
|
## Cascade select — `.fdy-cascade`
|
|
446
|
-
> **Typed wrapper: `<FdyCascade
|
|
462
|
+
> **Typed wrapper: `<FdyCascade>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
447
463
|
|
|
448
464
|
Hierarchical drill-down. The data model is a **nested `<ul>`** inside the wrapper: an `<li>` with a
|
|
449
465
|
child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
@@ -464,7 +480,7 @@ child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
|
464
480
|
```
|
|
465
481
|
|
|
466
482
|
## Choose-from-list (CFL) — `data-fdy-cfl`
|
|
467
|
-
> **Typed wrapper: `<FdyCfl
|
|
483
|
+
> **Typed wrapper: `<FdyCfl>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
468
484
|
|
|
469
485
|
A read-only field backed by master data: the button opens a searchable dialog, the picked row fills
|
|
470
486
|
the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
@@ -475,7 +491,7 @@ the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
|
475
491
|
- Inside: `.fdy-cfl__search` `__results` `__row` `__check` `__empty` `__count` `__actions`
|
|
476
492
|
- Hooks: `data-fdy-cfl-search` (the search input), `data-fdy-cfl-empty`, `data-fdy-cfl-count`,
|
|
477
493
|
`data-fdy-cfl-confirm` (multi only), `data-fdy-cfl-trigger`, `data-fdy-cfl-open`
|
|
478
|
-
- Rows are server state
|
|
494
|
+
- Rows are server state. In a real app, drive them from a controlled fetch rather than a global store.
|
|
479
495
|
- **Multi-select in the typed wrappers** (Vue · React · Blazor): `multiple` ticks rows and commits
|
|
480
496
|
them together on **Confirm** instead of committing the row that was clicked. Vue and React widen
|
|
481
497
|
their model to an array (`Row[] | null`); Blazor takes a second pair, `Values` /
|
|
@@ -522,32 +538,32 @@ the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
|
522
538
|
```
|
|
523
539
|
|
|
524
540
|
**`clearable` makes the value removable.** Without it a choose-from-list can be set but never unset,
|
|
525
|
-
which breaks every *optional* foreign key
|
|
541
|
+
which breaks every *optional* foreign key. The value type already allows null; only the component
|
|
526
542
|
could not produce it. With it, a clear button appears beside the trigger whenever a row is picked
|
|
527
543
|
and the field is editable; it emits `null` (Vue `update:modelValue`/`change`, React `onChange`,
|
|
528
544
|
Blazor `ValueChanged`) and returns focus to the trigger. It is a second `.fdy-input-group__btn`, not
|
|
529
545
|
a new class.
|
|
530
546
|
|
|
531
547
|
## Date picker — `data-fdy-datepicker`
|
|
532
|
-
> **Typed wrapper: `<FdyDatepicker
|
|
548
|
+
> **Typed wrapper: `<FdyDatepicker>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
533
549
|
|
|
534
550
|
Input-styled trigger + calendar popover. Needs `freeday-datepicker.js`. **Author an empty `<div>`**
|
|
535
|
-
|
|
551
|
+
The enhancer builds everything.
|
|
536
552
|
|
|
537
553
|
- `.fdy-datepicker` (+`--error`) · `__trigger` `__value` (+`--placeholder`) `__icon` `__clear`
|
|
538
554
|
`__panel`; calendar internals `.fdy-cal__head` `__nav` `__title` `__grid` (+`--months` `--years`)
|
|
539
555
|
`__dow` `__day` `__month` `__year`
|
|
540
556
|
- Range: wrap two pickers in `.fdy-daterange` + `data-fdy-daterange` (`role="group"`), children
|
|
541
557
|
get `data-role="from"` / `"to"`; separator `.fdy-daterange__sep`. The end can't precede the start.
|
|
542
|
-
**Typed wrapper: `<FdyDateRange>`** (Vue/React/Blazor)
|
|
558
|
+
**Typed wrapper: `<FdyDateRange>`** (Vue/React/Blazor). Use it in those stacks.
|
|
543
559
|
**Getting to a distant date.** The title is a **button**, and it drills up one level each press:
|
|
544
560
|
day grid → 12-cell month grid (`.fdy-cal__grid--months` of `.fdy-cal__month`) → 12-cell year grid
|
|
545
|
-
(`.fdy-cal__grid--years` of `.fdy-cal__year`). The arrows follow the level shown
|
|
561
|
+
(`.fdy-cal__grid--years` of `.fdy-cal__year`). The arrows follow the level shown: months, then
|
|
546
562
|
years, then pages of twelve years. Picking a year drops to that year's months, picking a month
|
|
547
563
|
drops to its days: every level above the day grid is **navigation**, so nothing is committed until a
|
|
548
564
|
day is selected. From the year grid the title steps back down to months.
|
|
549
565
|
|
|
550
|
-
Year pages are **aligned**, not centred on the year in view
|
|
566
|
+
Year pages are **aligned**, not centred on the year in view (2016–2027, then 2028–2039), so the
|
|
551
567
|
pages tile and a given year always sits in the same place. From August 2026 to March 2022 that is 7
|
|
552
568
|
clicks; walking month by month is 53. To 1998 it is 8; walking is 336.
|
|
553
569
|
|
|
@@ -590,15 +606,15 @@ Click/Enter opens the file dialog; drop works too. Needs `freeday-upload.js`.
|
|
|
590
606
|
- List: `.fdy-filelist` (+`--grid`) of `.fdy-file` (+`--success`, `--error`) · `__icon` `__meta`
|
|
591
607
|
`__name` `__sub` `__progress` `__remove`
|
|
592
608
|
- Attributes: `data-max-size` (bytes), `data-filelist="#id"`, `data-fdy-upload-simulate` (demo only
|
|
593
|
-
|
|
609
|
+
the kit fakes a transfer to `done()`; never set it in an app)
|
|
594
610
|
- A11y: the dropzone is `role="button" tabindex="0"` + `aria-label`.
|
|
595
611
|
|
|
596
|
-
**Both events fire on the dropzone
|
|
612
|
+
**Both events fire on the dropzone**: `fdy-upload-add` *and* `fdy-upload-remove`. One listener, one
|
|
597
613
|
element. (The file list may be anywhere in the document; nothing is dispatched on the row, because a
|
|
598
614
|
row in a sibling list would never bubble through the zone.)
|
|
599
615
|
|
|
600
616
|
**The row is yours to drive.** `fdy-upload-add` carries `detail.row`, a small state machine over the
|
|
601
|
-
rendered `.fdy-file`. A dropped file **rests
|
|
617
|
+
rendered `.fdy-file`. A dropped file **rests**, showing its size and nothing else, until you say a
|
|
602
618
|
transfer started; the kit never claims one it is not performing.
|
|
603
619
|
|
|
604
620
|
| `detail.row` | State it renders |
|
|
@@ -612,15 +628,15 @@ transfer started; the kit never claims one it is not performing.
|
|
|
612
628
|
| `.ready()` | back to **rest** (e.g. after a failed attempt the user will retry) |
|
|
613
629
|
| `.el` | the row element |
|
|
614
630
|
|
|
615
|
-
**If your request outlives the transfer, drive `waiting()`.** Server-side work after the last byte
|
|
616
|
-
|
|
631
|
+
**If your request outlives the transfer, drive `waiting()`.** Server-side work after the last byte (OCR,
|
|
632
|
+
extraction, virus scanning, transcoding) is not uploading, and `setProgress(100)` left standing
|
|
617
633
|
is read as a hang. `waiting()` is the state for it: the label says what the server is doing, and the
|
|
618
634
|
bar stops claiming a percentage it no longer has.
|
|
619
635
|
|
|
620
636
|
```js
|
|
621
637
|
zone.addEventListener('fdy-upload-add', (e) => {
|
|
622
638
|
if (e.detail.rejected) return; // the kit already rendered the reason
|
|
623
|
-
const { file, row } = e.detail; // row is at rest
|
|
639
|
+
const { file, row } = e.detail; // row is at rest; nothing is in flight yet
|
|
624
640
|
submitBtn.onclick = async () => {
|
|
625
641
|
row.uploading();
|
|
626
642
|
await send(file, (pct) => row.setProgress(pct));
|
|
@@ -633,7 +649,7 @@ zone.addEventListener('fdy-upload-remove', (e) => forget(e.detail.file)); // s
|
|
|
633
649
|
```
|
|
634
650
|
|
|
635
651
|
**Bring your own row:** omit the file list entirely (no `data-filelist`, and no `.fdy-filelist`
|
|
636
|
-
sibling) and the enhancer renders nothing while still dispatching `fdy-upload-add
|
|
652
|
+
sibling) and the enhancer renders nothing while still dispatching `fdy-upload-add`, where `detail.row`
|
|
637
653
|
still works, its element simply isn't attached. Note the fallback when `data-filelist` is absent is
|
|
638
654
|
`parentNode.querySelector('.fdy-filelist')`, so a bare dropzone will adopt a list that happens to
|
|
639
655
|
share its parent; give the dropzone its own container if you mean "no list".
|
|
@@ -660,7 +676,7 @@ A consistent filter row of `.fdy-field`s with a width rhythm (`--w-sm` · defaul
|
|
|
660
676
|
`--w-xl` · `--w-2xl`), one `--w-grow` field (usually search) absorbing the slack, and
|
|
661
677
|
`.fdy-filterbar__actions` pinned at the end. A control with **no label of its own** (a
|
|
662
678
|
`.fdy-check`, `.fdy-switch`, `.fdy-radio`, or a lone `.fdy-btn`) is given `--control-h` so it
|
|
663
|
-
centres on the input line instead of hanging below it
|
|
679
|
+
centres on the input line instead of hanging below it. The bar aligns `flex-end` to line up
|
|
664
680
|
labelled fields, which would otherwise drop a 20px checkbox below a 32px input's centre.
|
|
665
681
|
`--actions-inline` keeps the actions on the control
|
|
666
682
|
line. Zero JS. Composes with `.fdy-table-toolbar` on a shared baseline.
|
|
@@ -690,7 +706,7 @@ Linear multi-step flow: marker → check, one panel at a time, back/next. Needs
|
|
|
690
706
|
|
|
691
707
|
**`__btn` is optional.** For a **read-only indicator** put `__marker` + `__label` straight in the
|
|
692
708
|
`<li>`; for a **navigable** stepper wrap them in `<button class="fdy-step__btn">` (add `disabled`
|
|
693
|
-
for a step that is not reachable yet). Both render identically
|
|
709
|
+
for a step that is not reachable yet). Both render identically; the marker carries its own lift
|
|
694
710
|
over the connector line, so omitting `__btn` cannot make the connector draw through the numbers.
|
|
695
711
|
|
|
696
712
|
```html
|
|
@@ -717,23 +733,23 @@ Semantic static table. Wrap in `.fdy-table-wrap` (bordered surface) or `.fdy-tab
|
|
|
717
733
|
</div>
|
|
718
734
|
```
|
|
719
735
|
|
|
720
|
-
**Frozen axes
|
|
736
|
+
**Frozen axes, `.fdy-table--sticky`.** For a grid read against two axes at once (a rate
|
|
721
737
|
matrix, a timetable) or a wide one whose identity columns must stay visible. `.fdy-table--sticky`
|
|
722
738
|
is the enabler: it separates the borders, because a collapsed border belongs to the table rather
|
|
723
739
|
than the cell and scrolls out from under whatever you froze. Then ask for the axes you have:
|
|
724
740
|
|
|
725
|
-
- **Header row
|
|
741
|
+
- **Header row:** add `.fdy-table--sticky-head`, and put `.fdy-table-scroll--frozen` on the
|
|
726
742
|
wrapper (it scrolls both ways and is the scrollport the freeze resolves against; size it with
|
|
727
743
|
`--fdy-table-frozen-h`, default `30rem`). It is a separate modifier because a table that only
|
|
728
744
|
freezes columns scrolls with the **page**, and `top:0` against the page sticks the header under
|
|
729
|
-
the viewport edge
|
|
730
|
-
- **Columns
|
|
745
|
+
the viewport edge, over the app's own top bar.
|
|
746
|
+
- **Columns:** put `.fdy-table__freeze` on **every cell of the column, header included**, and give
|
|
731
747
|
each frozen column its own `--fdy-freeze-left`: `0` for the first, then the summed widths of the
|
|
732
748
|
frozen columns to its left. Mark the last one `.fdy-table__freeze--edge` so the reader can see
|
|
733
749
|
where the frozen block ends. One frozen column needs no variable at all.
|
|
734
750
|
|
|
735
751
|
The offsets are the caller's job because they depend on rendered column widths, which CSS cannot
|
|
736
|
-
sum
|
|
752
|
+
sum, so measure them once after render (and on resize) and set the variable.
|
|
737
753
|
|
|
738
754
|
```html
|
|
739
755
|
<div class="fdy-table-scroll fdy-table-scroll--frozen" style="--fdy-table-frozen-h:26rem">
|
|
@@ -754,7 +770,7 @@ sum — measure them once after render (and on resize) and set the variable.
|
|
|
754
770
|
```
|
|
755
771
|
|
|
756
772
|
## Data table — `.fdy-datatable`
|
|
757
|
-
> **Typed wrapper: `<FdyTable
|
|
773
|
+
> **Typed wrapper: `<FdyTable>`**. controlled: `columns` + `rows`, with sort/filter/page events (`update:pageIndex` · `onPageIndexChange` · `PageIndexChanged`) and `process` for driving a card list off the same processed set. The markup below is the raw enhancer path.
|
|
758
774
|
|
|
759
775
|
The interactive table: global search, sort, per-column filters, row selection + bulk bar,
|
|
760
776
|
pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` + `data-fdy-table`
|
|
@@ -773,20 +789,24 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
|
|
|
773
789
|
detail), `.fdy-table__state` (in-table empty/loading row)
|
|
774
790
|
- Footer: `.fdy-table-footer` · `__info` (`data-fdy-table-info`) + optional `__size` (a label and a
|
|
775
791
|
rows-per-page control) + `<nav class="fdy-pagination" data-fdy-table-pagination>`. The typed
|
|
776
|
-
wrappers render `FdyCombo` there
|
|
792
|
+
wrappers render `FdyCombo` there, **never a native `<select>`**, whose open list is an OS menu no
|
|
777
793
|
stylesheet reaches. In the raw path the control is app-authored markup (its options *are* the
|
|
778
794
|
offer) carrying `data-fdy-table-page-size`; the enhancer only wires it, and listens for both
|
|
779
795
|
`change` and the `fdy-change` a `.fdy-combo` emits, so either kind works.
|
|
780
796
|
- Sort values: put the raw value in `data-sort-value` when the cell text is formatted.
|
|
781
|
-
- **Language
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
797
|
+
- **Language: English throughout, on every path.** The vanilla enhancers wrote Indonesian until
|
|
798
|
+
2.0.0 while the typed wrappers wrote English, which meant an app mixing the two paths read as
|
|
799
|
+
two products. A Blazor app mixes them by construction, since `FreedayBlazor.initAll` runs the
|
|
800
|
+
enhancers. The defaults are now English everywhere, and the suite enforces it rather than
|
|
801
|
+
stating it.
|
|
802
|
+
**Dates are the exception, and deliberately so:** the datepicker formats through `Intl` using the
|
|
803
|
+
page's `<html lang>`, falling back to `en`. An Indonesian app writes `lang="id"` and gets
|
|
804
|
+
Indonesian month and weekday names with no configuration at all, which is a better hatch than anything
|
|
805
|
+
the kit could invent, because it is the platform's.
|
|
806
|
+
- **Overriding an enhancer's strings.** This is also the migration path for an Indonesian app
|
|
807
|
+
upgrading past 2.0.0. Each enhancer keeps its strings in one `TEXT` table and
|
|
788
808
|
reads them through `textOf()`, so any of them can be replaced per element with
|
|
789
|
-
**`data-fdy-text-<key>`**, the key kebab-cased
|
|
809
|
+
**`data-fdy-text-<key>`**, the key kebab-cased. No forking, and no rendering the nodes yourself.
|
|
790
810
|
(HTML lowercases attribute names, so a camelCase key like `filterText` is written
|
|
791
811
|
`data-fdy-text-filter-text`; the run-together spelling still resolves.):
|
|
792
812
|
|
|
@@ -805,25 +825,25 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
|
|
|
805
825
|
arrives overridable or not at all.
|
|
806
826
|
|
|
807
827
|
**Testing note:** a column's filter button and the dialog it opens deliberately share one
|
|
808
|
-
accessible name (`Filter <column>`)
|
|
828
|
+
accessible name (`Filter <column>`), since a dialog named after its trigger is the normal pattern. In a
|
|
809
829
|
Playwright/Testing-Library suite that means `getByLabel('Filter Name')` resolves to two elements;
|
|
810
830
|
reach for `getByRole('button', { name: 'Filter Name' })` instead.
|
|
811
831
|
|
|
812
832
|
**Rows per page.** Pass `pageSizes` (Vue/React `:page-sizes="[10, 20, 50]"`, Blazor
|
|
813
833
|
`PageSizes="…"`) and the footer grows a rows-per-page control between the range and the pager.
|
|
814
|
-
Omit it for none
|
|
834
|
+
Omit it for none, which is the unchanged default. Picking a size keeps the reader on the row they were looking
|
|
815
835
|
at rather than dropping them on page 1.
|
|
816
836
|
|
|
817
|
-
- **server mode
|
|
837
|
+
- **server mode:** reported through `update:page` / `onPageChange` / `PageChanged`, the same event
|
|
818
838
|
as a page click, carrying the new `size`. Read `size` to tell the two apart.
|
|
819
|
-
- **client mode
|
|
839
|
+
- **client mode:** the table applies it itself, so the control works with nothing wired, and also
|
|
820
840
|
emits `update:pageSize` / `onPageSizeChange` / `PageSizeChanged` for a caller that wants to
|
|
821
841
|
persist the choice. Changing the `pageSize` prop wins back.
|
|
822
842
|
|
|
823
|
-
A footer with a size control stays visible on a single page
|
|
843
|
+
A footer with a size control stays visible on a single page. Otherwise picking "100" on a
|
|
824
844
|
ninety-row list would remove the only way back to twenty.
|
|
825
845
|
|
|
826
|
-
**Typed wrapper: `<FdyTableFooter
|
|
846
|
+
**Typed wrapper: `<FdyTableFooter>`.** The footer alone (`page` in, `update:page` /
|
|
827
847
|
`onPageChange` / `PageChanged` out), for the one shape that cannot use the table's own: a
|
|
828
848
|
**responsive** list, where a `.fdy-datatable` at `lg` and a `.fdy-list` below it are two renderings
|
|
829
849
|
of one page of rows. A footer inside the table is inside the half a phone hides, so those screens
|
|
@@ -832,17 +852,17 @@ render it once, outside both, with `pager={false}` on the table.
|
|
|
832
852
|
**Who draws the pager.** The table renders its own footer (range + pager) whenever there is more
|
|
833
853
|
than one page. Two ways to take it over:
|
|
834
854
|
|
|
835
|
-
- **client mode
|
|
836
|
-
- **either mode
|
|
855
|
+
- **client mode:** pass `pageIndex` (with `pageSize`, without `page`) and drive it yourself;
|
|
856
|
+
- **either mode:** pass `pager={false}` (Vue `:pager="false"`, Blazor `Pager="false"`) and render
|
|
837
857
|
your own control. In server mode this is the only way: the app already owns the page there, and
|
|
838
|
-
was still being handed a second control. The typical shape is a responsive list
|
|
839
|
-
`.fdy-list` below it
|
|
858
|
+
was still being handed a second control. The typical shape is a responsive list (a table at `lg`,
|
|
859
|
+
`.fdy-list` below it) where one pager has to serve both views so they cannot disagree.
|
|
840
860
|
|
|
841
861
|
Do not hide the footer with CSS. `display:none` works, but it reaches into a component's internals
|
|
842
862
|
and breaks the moment the class changes.
|
|
843
863
|
|
|
844
864
|
## Pagination — `.fdy-pagination`
|
|
845
|
-
The block class on the `<nav>` is a **structural hook only
|
|
865
|
+
The block class on the `<nav>` is a **structural hook only**. It carries no rule of its own; the
|
|
846
866
|
`__list` / `__link` / `__ellipsis` elements do all the styling, and the data table targets
|
|
847
867
|
`data-fdy-table-pagination`. Keep it on the wrapper anyway, for consistency with the rest of the kit.
|
|
848
868
|
|
|
@@ -857,7 +877,7 @@ Empty / error placeholder for a data area. `--danger` for failures. Parts: `__ic
|
|
|
857
877
|
replaces.
|
|
858
878
|
|
|
859
879
|
## Charts — `data-fdy-chart`
|
|
860
|
-
> **Typed wrapper: `<FdyChart
|
|
880
|
+
> **Typed wrapper: `<FdyChart>`**. data props in all three (`type` + `values` / `series`); it repaints on data change, so `FreedayChart.update(el)` is only for the raw path below.
|
|
861
881
|
|
|
862
882
|
Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`; call
|
|
863
883
|
`FreedayChart.update(el)` after changing data (or use `FdyChart` in Vue/React/Blazor).
|
|
@@ -866,27 +886,27 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
|
|
|
866
886
|
- Roots: `.fdy-sparkline` · `.fdy-bars` · `.fdy-donut` · `.fdy-chart-xy` (built for line/area/bar)
|
|
867
887
|
- Data: `data-values="1,2,3"` (single series) or `data-series='[{"label":"A","values":[…]}]'`
|
|
868
888
|
(multi), `data-labels="Jan,Feb"`, `data-fdy-stacked`, `data-fdy-center` (donut)
|
|
869
|
-
- Colour: `data-fdy-color="primary"` or `data-fdy-colors="success,warning,danger"`
|
|
889
|
+
- Colour: `data-fdy-color="primary"` or `data-fdy-colors="success,warning,danger"` for semantic token
|
|
870
890
|
names **or** `chart-1`…`chart-8` slots to pin a category's colour. Multi-series defaults to the
|
|
871
891
|
validated categorical palette `--chart-1`…`--chart-8`.
|
|
872
892
|
- Format: `data-fdy-format="number|percent|currency"`; legend `data-fdy-legend` (`none` to drop);
|
|
873
893
|
axes `data-fdy-axes`
|
|
874
|
-
- Sizing is already set by the kit
|
|
894
|
+
- Sizing is already set by the kit. Override **these**, never a `height` on the chart root (a
|
|
875
895
|
root height fights `aspect-ratio` instead of setting it): `.fdy-chart-xy__plot` is `width:100%`
|
|
876
896
|
+ `aspect-ratio:16/9` + `min-height:8rem`; `.fdy-bars` is a fixed `height:9rem`; `.fdy-sparkline`
|
|
877
897
|
is an inline-block `8rem × 2.25rem`.
|
|
878
898
|
- Axis type: `--fdy-chart-tick-size` (default `var(--text-xs)`) sets the y-tick and x-label size on
|
|
879
|
-
`.fdy-chart-xy`. It is a real CSS size
|
|
880
|
-
one user unit is one pixel
|
|
899
|
+
`.fdy-chart-xy`. It is a real CSS size: a cartesian chart's `viewBox` is measured to its plot, so
|
|
900
|
+
one user unit is one pixel, and the renderer reads it back to size the y-gutter and to decide how
|
|
881
901
|
many x-labels fit. Set the token rather than `font-size` on `__tick`/`__xlabel`, or the gutter and
|
|
882
902
|
the autoskip will still be computed from the old size.
|
|
883
903
|
- A11y: every chart is `role="img"` + an **author-supplied** `aria-label`, and that label is the
|
|
884
|
-
*entire* text alternative
|
|
885
|
-
marks everything it draws `aria-hidden="true"`
|
|
886
|
-
SVG
|
|
904
|
+
*entire* text alternative, so write one that carries the numbers rather than just the shape. The renderer
|
|
905
|
+
marks everything it draws `aria-hidden="true"` (legend, bar values and labels, donut centre, the
|
|
906
|
+
SVG), so nothing inside the chart competes with your label or repeats it. `role="img"` alone would
|
|
887
907
|
**not** have done that: it is Children Presentational per ARIA, but browsers keep the subtree in
|
|
888
908
|
the accessibility tree anyway, so the kit hides it rather than relying on the spec. That hiding
|
|
889
|
-
needs a name to work from
|
|
909
|
+
needs a name to work from: a chart with **no** `aria-label`/`aria-labelledby` keeps its contents
|
|
890
910
|
exposed, because an image with neither a name nor any text is worse than one that leaks its
|
|
891
911
|
legend. Any `<table>`/text placed inside the element is **pre-render only** and is replaced on
|
|
892
912
|
render. Good: `aria-label="Monthly posting trend: Posted 62% → 88%, Draft 24% → 16%"`.
|
|
@@ -896,7 +916,7 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
|
|
|
896
916
|
`__center` `__hit`; `.fdy-sparkline__line` `__area` `__dot`.
|
|
897
917
|
|
|
898
918
|
## Tree view — `.fdy-tree`
|
|
899
|
-
Native `<details>` hierarchy
|
|
919
|
+
Native `<details>` hierarchy, with zero JS for expand/collapse. `.fdy-tree__branch` on `<details>`,
|
|
900
920
|
`.fdy-tree__leaf` on a leaf `<li>`, plus `__chevron` and `__icon` svgs inside `<summary>`.
|
|
901
921
|
`.fdy-tree--checkbox` + `data-fdy-tree` (needs `freeday-tree.js`) adds cascading selection:
|
|
902
922
|
`.fdy-tree__check` on each `<input class="fdy-checkbox">`; checking a branch checks its children,
|
|
@@ -904,19 +924,19 @@ a partial set makes the branch indeterminate. Checking never toggles expansion.
|
|
|
904
924
|
|
|
905
925
|
## List (flat rows) — `.fdy-list`
|
|
906
926
|
The **flat** row container: one bordered surface, hairline dividers, **no shadow**. This is what a
|
|
907
|
-
responsive `.fdy-datatable` should become below `md
|
|
927
|
+
responsive `.fdy-datatable` should become below `md`, rather than a stack of `.fdy-card`s, which carries
|
|
908
928
|
`--shadow-lift` and turns ten rows into ten floating objects.
|
|
909
929
|
|
|
910
930
|
- `.fdy-list` on a `<ul>`/`<ol>` (list-style is reset for you) or a `<div>`
|
|
911
|
-
- `.fdy-list__row`
|
|
931
|
+
- `.fdy-list__row` is one row; `--interactive` for hover feedback, `--button` when the row **is** the
|
|
912
932
|
control (render it as a real `<button>`/`<a>`; the UA box is reset without losing the list surface)
|
|
913
933
|
- Row internals: `.fdy-list__main` (truncating stack) → `.fdy-list__title` + `.fdy-list__meta`, and
|
|
914
934
|
`.fdy-list__aside` pinned right
|
|
915
|
-
- **Disabled
|
|
935
|
+
- **Disabled:** `disabled` on the `<button>` (or `aria-disabled="true"` when the row is an `<a>`/
|
|
916
936
|
`<div>`) dims the row and withdraws the hover tint and the pointer cursor, same as every other
|
|
917
937
|
control in the kit. Do not hand-roll it: an undimmed row that still lights up under the pointer
|
|
918
938
|
reads as clickable while it is refusing input.
|
|
919
|
-
- Not `.fdy-list-reset
|
|
939
|
+
- Not `.fdy-list-reset`, which only strips UA bullets/indent from a semantic list.
|
|
920
940
|
|
|
921
941
|
```html
|
|
922
942
|
<ul class="fdy-list">
|
|
@@ -967,11 +987,11 @@ Every field is optional; it returns the toast element. Classes (rendered for you
|
|
|
967
987
|
`__body` `__title` `__text` `__close`.
|
|
968
988
|
|
|
969
989
|
**Above modals, and not by z-index.** The region is a `popover` and re-enters the **top layer** as
|
|
970
|
-
each toast lands, because `.fdy-modal` is a native `<dialog
|
|
990
|
+
each toast lands, because `.fdy-modal` is a native `<dialog>`. See the note under Modal below. If
|
|
971
991
|
a toast of yours is hidden behind something, a bigger `z-index` is not the fix; check whether that
|
|
972
992
|
something is in the top layer too. Where the Popover API is unavailable (before Chrome 114 /
|
|
973
993
|
Safari 17 / Firefox 125) the region falls back to a fixed block at `z-index: 200`, which cannot
|
|
974
|
-
clear an open modal
|
|
994
|
+
clear an open modal, the message is still announced to a screen reader.
|
|
975
995
|
|
|
976
996
|
## Tooltip — `.fdy-tooltip`
|
|
977
997
|
Hover/focus only, never the sole carrier of information. Wrap in `.fdy-tooltip-wrap`; the trigger
|
|
@@ -991,7 +1011,7 @@ Size-matched placeholders so nothing shifts when data lands: `--title` `--text`
|
|
|
991
1011
|
# Navigation
|
|
992
1012
|
|
|
993
1013
|
## Nav (menu) — `.fdy-nav`
|
|
994
|
-
Navigation links
|
|
1014
|
+
Navigation links, **vertical by default** (the app shell sidebar), horizontal with `--horizontal`. Items are `<a class="fdy-nav__item">` with
|
|
995
1015
|
`__icon` / `__label` / `__badge`; the current one gets `aria-current="page"`.
|
|
996
1016
|
`--flat` drops the surface. Nested groups are native `<details>`:
|
|
997
1017
|
|
|
@@ -1015,17 +1035,17 @@ Navigation links — **vertical by default** (the app shell sidebar), horizontal
|
|
|
1015
1035
|
</header>
|
|
1016
1036
|
```
|
|
1017
1037
|
|
|
1018
|
-
> **Which one for navigation?** A nav link is a link marked `aria-current="page"
|
|
1038
|
+
> **Which one for navigation?** A nav link is a link marked `aria-current="page"`, never
|
|
1019
1039
|
> `aria-selected` (invalid ARIA on an anchor) and never `role="tab"`. Use `.fdy-nav--horizontal`
|
|
1020
1040
|
> for an application's **primary** navigation. For **routed sub-navigation** that should look like
|
|
1021
1041
|
> tabs (`/settings/profile` · `/settings/billing`), put `.fdy-tabs__list` / `.fdy-tabs__tab` on
|
|
1022
|
-
> plain `<a>`s
|
|
1042
|
+
> plain `<a>`s, since those classes honour `aria-current="page"` too, and do **not** add the tab roles
|
|
1023
1043
|
> or `freeday-tabs.js`, which promise a keyboard contract routes do not have.
|
|
1024
1044
|
|
|
1025
1045
|
## App bar — `.fdy-appbar`
|
|
1026
1046
|
Standalone top bar (distinct from the shell's `.fdy-app__topbar`). Modifiers `--sticky`
|
|
1027
1047
|
`--elevated` `--dense` `--primary` (on-colour controls). Parts `__brand` `__spacer` `__actions`.
|
|
1028
|
-
It ships **no link class of its own
|
|
1048
|
+
It ships **no link class of its own**. Put `<nav class="fdy-nav fdy-nav--horizontal">` in it
|
|
1029
1049
|
for primary navigation (see Nav above).
|
|
1030
1050
|
|
|
1031
1051
|
## Breadcrumb — `.fdy-breadcrumb`
|
|
@@ -1036,7 +1056,7 @@ for primary navigation (see Nav above).
|
|
|
1036
1056
|
## Tabs — `.fdy-tabs`
|
|
1037
1057
|
APG tabs: ←/→, Home/End, roving tabindex. Needs `freeday-tabs.js`.
|
|
1038
1058
|
`.fdy-tabs__tab` marks its active state from **`aria-selected="true"` or `aria-current="page"`**,
|
|
1039
|
-
so the same look serves routed sub-navigation built from plain links
|
|
1059
|
+
so the same look serves routed sub-navigation built from plain links. See the note under Nav.
|
|
1040
1060
|
|
|
1041
1061
|
```html
|
|
1042
1062
|
<div class="fdy-tabs" data-fdy-tabs>
|
|
@@ -1054,13 +1074,13 @@ so the same look serves routed sub-navigation built from plain links — see the
|
|
|
1054
1074
|
# Overlays
|
|
1055
1075
|
|
|
1056
1076
|
## Modal — `.fdy-modal`
|
|
1057
|
-
> **Typed wrapper: `<FdyModal
|
|
1077
|
+
> **Typed wrapper: `<FdyModal>`**. Vue (`:open` + `@close`) · React (`open` + `onClose`) · Blazor (`@bind-Open`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
1058
1078
|
|
|
1059
1079
|
Native `<dialog>`: focus trap, Esc and backdrop come from the browser. Sizes `--sm` `--md` `--lg`
|
|
1060
1080
|
`--wide`; `--cfl` for the choose-from-list dialog.
|
|
1061
1081
|
|
|
1062
1082
|
**It is in the TOP LAYER.** `showModal()` paints the dialog above every stacking context on the
|
|
1063
|
-
page, so no `z-index` anywhere can put anything over it
|
|
1083
|
+
page, so no `z-index` anywhere can put anything over it. Only another top-layer element (a
|
|
1064
1084
|
`popover`, or another modal) can. It also makes the rest of the document inert, which means
|
|
1065
1085
|
`elementFromPoint` outside the dialog returns the dialog: do not use a hit test to check what is
|
|
1066
1086
|
painted on top of a modal, because it will tell you the dialog is, even when it visibly is not. Parts `__header` `__title` `__body` `__footer`
|
|
@@ -1068,34 +1088,34 @@ painted on top of a modal, because it will tell you the dialog is, even when it
|
|
|
1068
1088
|
`data-close` on any button that should close it.
|
|
1069
1089
|
|
|
1070
1090
|
**Stacking two overlays is supported.** Modal and drawer are both `<dialog>` opened with
|
|
1071
|
-
`showModal()`, so both enter the top layer and paint in the order they were opened
|
|
1091
|
+
`showModal()`, so both enter the top layer and paint in the order they were opened. A confirm
|
|
1072
1092
|
modal opened from inside an open drawer paints **above** it, and neither needs a `z-index` for
|
|
1073
1093
|
that. Escape closes the topmost first, so cancelling returns to the still-open drawer; a second
|
|
1074
1094
|
Escape then closes the drawer. Closing them out of order is fine too: closing the drawer while the
|
|
1075
1095
|
modal is up leaves the modal open, painted and dismissible.
|
|
1076
1096
|
|
|
1077
1097
|
One caveat, and it is the browser's rather than the kit's: **the second overlay's Escape can only
|
|
1078
|
-
be refused if both were opened by a real gesture.** A `showModal()` that runs from script
|
|
1079
|
-
or after an `await` that outlived the click
|
|
1098
|
+
be refused if both were opened by a real gesture.** A `showModal()` that runs from script (a timer,
|
|
1099
|
+
or after an `await` that outlived the click) still closes only the topmost, but its `cancel` event
|
|
1080
1100
|
arrives **non-cancelable**, so `preventDefault()` on it is ignored and an "unsaved changes, stay
|
|
1081
1101
|
open" guard silently stops working. Routing Escape through app state instead, which is what the
|
|
1082
1102
|
typed wrappers do, is unaffected: the dialog closes and your state follows it.
|
|
1083
1103
|
|
|
1084
1104
|
*Correction to 1.51.1, which said such a dialog has its close watcher grouped with the one below so
|
|
1085
1105
|
that a single Escape closes both.* That happens only on a page that has received **no user input at
|
|
1086
|
-
all
|
|
1106
|
+
all**: a test harness, never an app. Six seconds after one click the overlays are still
|
|
1087
1107
|
independent. Open overlays with real clicks in a test, or a stacking assertion fails for a reason
|
|
1088
1108
|
that is not the kit. Both rules measured on Chromium 133 and 151, and pinned as a matrix in
|
|
1089
1109
|
`browser/overlay-stack.mjs`.
|
|
1090
1110
|
|
|
1091
1111
|
## Drawer — `.fdy-drawer`
|
|
1092
|
-
> **Typed wrapper: `<FdyDrawer
|
|
1112
|
+
> **Typed wrapper: `<FdyDrawer>`**. Vue (`:open` + `@close`) · React (`open` + `onClose`) · Blazor (`@bind-Open`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
1093
1113
|
|
|
1094
|
-
Temporary side panel on native `<dialog
|
|
1114
|
+
Temporary side panel on native `<dialog>`, left by default, `--right` to flip. Parts `__header`
|
|
1095
1115
|
`__title` `__body` `__footer` `__close`. Open it from any
|
|
1096
1116
|
`<button data-fdy-drawer="<dialog id>">`. Needs `freeday-drawer.js`.
|
|
1097
1117
|
|
|
1098
|
-
Opened with `showModal()`, so it is in the **top layer** on the same terms as the modal
|
|
1118
|
+
Opened with `showModal()`, so it is in the **top layer** on the same terms as the modal, including
|
|
1099
1119
|
opening a modal over an open drawer, which is supported and described under
|
|
1100
1120
|
[Modal → *Stacking two overlays*](#modal--fdy-modal). It slides in on `transform` and never animates
|
|
1101
1121
|
opacity: wait on its position, not on `opacity`, if you need to know when it has arrived.
|
|
@@ -1117,25 +1137,25 @@ is `role="group" aria-roledescription="slide"`.
|
|
|
1117
1137
|
## Card — `.fdy-card`
|
|
1118
1138
|
Parts `__body` `__title` `__desc` `__footer`. Modifiers:
|
|
1119
1139
|
|
|
1120
|
-
- `--elevated`
|
|
1121
|
-
- `--interactive`
|
|
1122
|
-
only:** it is the one affordance in the kit whose correctness lives outside it
|
|
1140
|
+
- `--elevated` raises it (use sparingly; most surfaces are flat)
|
|
1141
|
+
- `--interactive` gives cursor + hover-lift to a card that *has* a click handler. **Presentational
|
|
1142
|
+
only:** it is the one affordance in the kit whose correctness lives outside it, because the CSS cannot
|
|
1123
1143
|
know whether a handler exists, so the modifier without a control (a card that looks clickable and
|
|
1124
1144
|
swallows every click) and a control without the modifier both fail silently. Pair it with a real
|
|
1125
|
-
control
|
|
1126
|
-
- `--button`
|
|
1145
|
+
control, normally the stretched target below.
|
|
1146
|
+
- `--button` is for a card that **is** the control: render it as `<button>` and add this to reset
|
|
1127
1147
|
the UA button box **without** losing the card surface/border
|
|
1128
|
-
- `.fdy-card--button` never replaces keyboard semantics
|
|
1148
|
+
- `.fdy-card--button` never replaces keyboard semantics. A clickable card must be a real
|
|
1129
1149
|
`<button>` or `<a>`.
|
|
1130
|
-
- **Disabled
|
|
1150
|
+
- **Disabled:** `disabled` (or `aria-disabled="true"`) dims the card and withdraws the pointer
|
|
1131
1151
|
cursor and the `--interactive` hover-lift.
|
|
1132
1152
|
- The card is `position:relative`, so a badge or ribbon you absolutely position inside it anchors
|
|
1133
|
-
to the card. That is also what keeps hidden labels inside it from escaping
|
|
1153
|
+
to the card. That is also what keeps hidden labels inside it from escaping. See *Containment*.
|
|
1134
1154
|
|
|
1135
1155
|
### One card, one primary action, one escape hatch
|
|
1136
1156
|
|
|
1137
1157
|
A card with **two** actions cannot be `--button`: interactive content nested in a `<button>` is
|
|
1138
|
-
invalid HTML. Give the primary control `.fdy-btn--stretch
|
|
1158
|
+
invalid HTML. Give the primary control `.fdy-btn--stretch`: its hit area covers the whole card,
|
|
1139
1159
|
while it stays a real `<button>`/`<a>` with real keyboard semantics.
|
|
1140
1160
|
|
|
1141
1161
|
```html
|
|
@@ -1152,34 +1172,32 @@ while it stays a real `<button>`/`<a>` with real keyboard semantics.
|
|
|
1152
1172
|
```
|
|
1153
1173
|
|
|
1154
1174
|
- **The escape hatch needs nothing.** Every focusable element in a card that holds a stretched
|
|
1155
|
-
target is raised above the overlay automatically
|
|
1175
|
+
target is raised above the overlay automatically. Forgetting a `z-index` here fails silently, and
|
|
1156
1176
|
a silent failure is what this pattern exists to remove. The `position` that raising needs is only a
|
|
1157
|
-
**default** (zero-specificity), so a control you pin yourself
|
|
1177
|
+
**default** (zero-specificity), so a control you pin yourself (a corner dismiss, a favourite star)
|
|
1158
1178
|
keeps its own `position:absolute` and is raised anyway.
|
|
1159
1179
|
- **Name the stretched button for what the whole card does** ("Open workspace Alpha", not "Open").
|
|
1160
1180
|
Its label is the accessible name of the entire hit area.
|
|
1161
|
-
- **Do not add `.fdy-btn--stretch` to more than one control per card
|
|
1181
|
+
- **Do not add `.fdy-btn--stretch` to more than one control per card.** The last one wins the
|
|
1162
1182
|
overlap, and which one that is depends on source order.
|
|
1163
1183
|
- Text under the overlay is no longer selectable; that is the cost of the pattern, not a bug.
|
|
1164
1184
|
- **Cards and list rows.** The overlay anchors to the nearest *positioned* ancestor, and both hosts
|
|
1165
1185
|
supply one: `.fdy-card` always, `.fdy-list__row` when it contains a stretched target. Everything
|
|
1166
|
-
above applies to a row unchanged
|
|
1186
|
+
above applies to a row unchanged: same markup, same automatic raise. In any *other* container,
|
|
1167
1187
|
make the element `position:relative` yourself, or the overlay resolves against whatever is
|
|
1168
1188
|
positioned further up (in a list that used to be `.fdy-list` itself, so every row's target covered
|
|
1169
|
-
the whole list and the last one in the DOM won
|
|
1189
|
+
the whole list and the last one in the DOM won, so a click on row one opened row two).
|
|
1170
1190
|
A row whose *whole* surface is one control is still better as `.fdy-list__row--button`.
|
|
1171
1191
|
- `.fdy-btn` nudges itself on `:hover`/`:active`, and a transformed element becomes the containing
|
|
1172
|
-
block for its own absolutely positioned descendants
|
|
1192
|
+
block for its own absolutely positioned descendants, so a hand-rolled `::after{inset:0}` overlay
|
|
1173
1193
|
**re-anchors to the button mid-gesture** and the click never lands. `--stretch` neutralises those
|
|
1174
1194
|
transforms and moves the press feedback to the card. This is why the pattern is shipped rather
|
|
1175
1195
|
than documented.
|
|
1176
1196
|
|
|
1177
1197
|
## Badge — `.fdy-badge`
|
|
1178
|
-
Inline status pill: `--success` `--warning` `--danger` `--info` `--outline`. Never colour-only
|
|
1179
|
-
the text carries the meaning.
|
|
1198
|
+
Inline status pill: `--success` `--warning` `--danger` `--info` `--outline`. Never colour-only: the text carries the meaning.
|
|
1180
1199
|
|
|
1181
|
-
`--tone-1`…`--tone-8` for a status vocabulary larger than the semantic palette. Semantics first
|
|
1182
|
-
a state that IS good, bad or waiting takes `--success` / `--danger` / `--warning`; these are for
|
|
1200
|
+
`--tone-1`…`--tone-8` for a status vocabulary larger than the semantic palette. Semantics first: a state that IS good, bad or waiting takes `--success` / `--danger` / `--warning`; these are for
|
|
1183
1201
|
the rest, so two states a workflow distinguishes never share a look. Map each status to a FIXED
|
|
1184
1202
|
tone, never to a hash of the string: a status is a closed set, and a hash reassigns colours the
|
|
1185
1203
|
moment the set grows.
|
|
@@ -1192,19 +1210,19 @@ Put the count in the wrapper's `aria-label` and mark the overlay `aria-hidden="t
|
|
|
1192
1210
|
## Avatar — `.fdy-avatar`
|
|
1193
1211
|
Initials or image. Sizes `--sm` / default / `--lg`; `.fdy-avatar-group` stacks them (label the
|
|
1194
1212
|
group). `--tone-1`…`--tone-8` are decorative tints (WCAG AA in both themes) to distinguish
|
|
1195
|
-
same-initial avatars
|
|
1213
|
+
same-initial avatars. Hash the index off the **full** name, not the initials.
|
|
1196
1214
|
|
|
1197
1215
|
Sizes: `--xs` 1.5rem · *(base)* 2.5rem · `--sm` 2rem · `--lg` 3.5rem. Use **`--xs` inside a control**
|
|
1198
|
-
|
|
1216
|
+
because `.fdy-btn--sm` is itself 2rem tall, so an `--sm` avatar fills a small button edge to edge and the
|
|
1199
1217
|
ghost border cuts across the circle.
|
|
1200
1218
|
|
|
1201
1219
|
## Chip — `.fdy-chip`
|
|
1202
1220
|
Three roles inside a `.fdy-chips` row:
|
|
1203
1221
|
|
|
1204
|
-
- **static / removable
|
|
1222
|
+
- **static / removable:** `<span class="fdy-chip">`, `--primary` for an active filter, with
|
|
1205
1223
|
`.fdy-chip__remove` (`aria-label` says what it removes)
|
|
1206
|
-
- **choice
|
|
1207
|
-
- **filter
|
|
1224
|
+
- **choice:** `<button class="fdy-chip fdy-chip--choice" aria-pressed>` toggle
|
|
1225
|
+
- **filter:** `--filter`, same but with a `.fdy-chip__check` mark
|
|
1208
1226
|
|
|
1209
1227
|
Interactive chips are managed by `freeday-chip.js`: wrap the row in `data-fdy-chips` (+
|
|
1210
1228
|
`data-single` for pick-one, `data-label` for the group name), give each `data-value`.
|
|
@@ -1218,5 +1236,5 @@ by the accompanying text, not the marker colour alone.
|
|
|
1218
1236
|
---
|
|
1219
1237
|
|
|
1220
1238
|
*Anything not listed here is not part of the public surface. If a screen needs a primitive that
|
|
1221
|
-
isn't in this file, it probably belongs in the kit
|
|
1239
|
+
isn't in this file, it probably belongs in the kit. Open an issue instead of re-inventing it
|
|
1222
1240
|
locally.*
|