@cahyo-dimas/freeday 1.54.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 +625 -545
- package/COMPONENTS.md +141 -138
- package/README.id.md +46 -45
- package/README.md +46 -43
- package/USAGE.md +34 -34
- package/adapters/blazor/FdyAppShell.razor +2 -2
- package/adapters/blazor/FdyAppShell.razor.cs +6 -6
- 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 +1 -1
- package/adapters/core/app-shell.js +6 -6
- package/adapters/core/table-model.d.ts +2 -2
- package/adapters/core/table-model.js +8 -8
- package/adapters/react/components/FdyAppShell.tsx +5 -5
- 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 +1 -1
- package/adapters/react/useFreeday.js +3 -3
- package/adapters/vue/components/FdyAppShell.vue +5 -5
- 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 +2 -2
- package/adapters/vue/useFreeday.js +3 -3
- package/dist/asset.d.ts +3 -3
- package/dist/freeday-app-shell.js +9 -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 +162 -158
- package/dist/freeday.tokens.css +5 -5
- package/docs/agent-onboarding.md +41 -41
- package/docs/getting-started.md +35 -35
- package/docs/integrations.md +18 -18
- package/docs/reference-screen.html +14 -14
- package/package.json +4 -3
- 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,7 +115,7 @@ is a no-op.
|
|
|
114
115
|
# Shell & composition
|
|
115
116
|
|
|
116
117
|
## App shell — `.fdy-app`
|
|
117
|
-
> **Typed wrapper: `<FdyAppShell
|
|
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).
|
|
118
119
|
|
|
119
120
|
The frame every application lives in. Do not hand-roll one from flexbox: the responsive sidebar,
|
|
120
121
|
off-canvas drawer and backdrop are built in.
|
|
@@ -123,14 +124,14 @@ off-canvas drawer and backdrop are built in.
|
|
|
123
124
|
`__content` `__topbar` `__navtoggle` `__title` `__main` `__backdrop`
|
|
124
125
|
- Modifiers: `--nav-open` (mobile drawer open, ≤720px) · `--nav-collapsed` (collapse to zero width,
|
|
125
126
|
≥721px) · `--static` (embed the shell in a page instead of filling the viewport)
|
|
126
|
-
- Also: `.fdy-skip
|
|
127
|
-
- **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
|
|
128
129
|
is unchanged. It owns the toggle in both modes plus everything an overlay needs that hand-rolling
|
|
129
130
|
reliably forgets: Escape, backdrop click, closing when a `.fdy-nav__item` is followed, focus moved
|
|
130
131
|
into the panel on open and returned to `__navtoggle` on close, `inert` on `__content` while the
|
|
131
132
|
overlay is up, and a Tab trap inside the panel. `aria-expanded` on `__navtoggle` answers "is the
|
|
132
133
|
nav showing?" in both modes, so you never read the state classes yourself.
|
|
133
|
-
- **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:
|
|
134
135
|
collapsed at ≥721px (`width:0`) or off-canvas at ≤720px (`translateX(-100%)`). Both hide it from
|
|
135
136
|
the eye; neither hides it from the keyboard, so without this a nav nobody can see still swallows
|
|
136
137
|
Tab on the way to the page.
|
|
@@ -140,21 +141,21 @@ off-canvas drawer and backdrop are built in.
|
|
|
140
141
|
- **`navOpen` is one idea, in both modes.** The typed wrappers expose "is the nav visible?" and the
|
|
141
142
|
kit maps it: above the breakpoint a hidden nav is `--nav-collapsed`, below it a visible nav is
|
|
142
143
|
`--nav-open`. Leave the prop unbound (`undefined` in Vue/React, `null` in Blazor) and the shell
|
|
143
|
-
starts from the viewport
|
|
144
|
+
starts from the viewport: a column on a wide screen, hidden on a narrow one. That default is why
|
|
144
145
|
the prop is optional: a caller cannot express it as a single initial value before it knows the
|
|
145
146
|
viewport.
|
|
146
147
|
- **Binding your own state to the vanilla shell:** it emits a bubbling `fdy-app-nav` CustomEvent
|
|
147
|
-
(`detail {visible}`) on every real change
|
|
148
|
+
(`detail {visible}`) on every real change, including the ones a viewport change causes, since a
|
|
148
149
|
bound value that stayed `true` while the nav went off-canvas would be describing a panel nobody
|
|
149
150
|
can see. Drive it with `FreedayAppShell.setVisible(root, visible)` rather than the class names;
|
|
150
151
|
setting what is already set announces nothing, so a bound host cannot loop on its own echo.
|
|
151
152
|
`FreedayAppShell.isVisible(root)` reads it back.
|
|
152
153
|
- Skip the enhancer and nothing breaks: the classes still mean what they always meant and the
|
|
153
|
-
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
|
|
154
155
|
shell root itself, which is what the Blazor bridge does after its first render.
|
|
155
156
|
|
|
156
157
|
**The nesting is fixed, not free-form:** `.fdy-app` is a flex **row** of `[__sidebar | __content]`,
|
|
157
|
-
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
|
|
158
159
|
tall containing block. The brand goes in the **sidebar** (sized to match the topbar height), and
|
|
159
160
|
`__main` already carries the page padding, so don't wrap your screen in another padded box.
|
|
160
161
|
|
|
@@ -187,15 +188,15 @@ tall containing block. The brand goes in the **sidebar** (sized to match the top
|
|
|
187
188
|
Vertical section rhythm (`--space-8` between children). Assemble every screen from these; they
|
|
188
189
|
carry the doctrine in markup.
|
|
189
190
|
|
|
190
|
-
- `.fdy-page`
|
|
191
|
-
- `.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
|
|
192
193
|
left, `.fdy-page__actions` (**one** primary button) on the right
|
|
193
|
-
- `.fdy-page-section`
|
|
194
|
-
- `.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).
|
|
195
196
|
It is `align-items:center`, so it is for **bare** controls. A `.fdy-field` with a *visible* label
|
|
196
|
-
sits half a label-height low in it
|
|
197
|
+
sits half a label-height low in it, so labelled fields belong in `.fdy-filterbar`
|
|
197
198
|
(`align-items:flex-end`). In a toolbar, hide the label with `.fdy-visually-hidden`.
|
|
198
|
-
- `.fdy-stats`
|
|
199
|
+
- `.fdy-stats` is a KPI grid of `.fdy-stat` (`__label` `__value` `__meta`). Deliberately **not**
|
|
199
200
|
cards. `.fdy-stats--boxed` for one shared surface with dividers.
|
|
200
201
|
|
|
201
202
|
```html
|
|
@@ -230,7 +231,7 @@ carry the doctrine in markup.
|
|
|
230
231
|
```
|
|
231
232
|
|
|
232
233
|
## Type roles & text
|
|
233
|
-
One role per level of hierarchy
|
|
234
|
+
One role per level of hierarchy. Never re-use a card title for a page title.
|
|
234
235
|
|
|
235
236
|
| Class | Element | Use |
|
|
236
237
|
|---|---|---|
|
|
@@ -246,11 +247,11 @@ One role per level of hierarchy — never re-use a card title for a page title.
|
|
|
246
247
|
|
|
247
248
|
## Utilities
|
|
248
249
|
`.fdy-hidden` · `.fdy-visually-hidden` (screen-reader-only) · `.fdy-list-reset` (strip native
|
|
249
|
-
bullets/indent from a semantic list
|
|
250
|
+
bullets/indent from a semantic list, needed when your utility framework's preflight is off) ·
|
|
250
251
|
`.fdy-divider` (+`--vertical`) · `.fdy-kbd`.
|
|
251
252
|
|
|
252
253
|
Responsive display: `.fdy-hide-below-sm|md|lg` · `.fdy-hide-above-sm|md|lg`.
|
|
253
|
-
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
|
|
254
255
|
`import { breakpoints } from '@cahyo-dimas/freeday/breakpoints'`.
|
|
255
256
|
|
|
256
257
|
**`breakpoints.nav` (721) is separate and is the one the shell uses.** `.fdy-app` switches the
|
|
@@ -258,7 +259,7 @@ sidebar from off-canvas drawer to static column at 721px, *not* at `md`. Any `ma
|
|
|
258
259
|
utility variant that has to agree with the shell must use `nav`; using `md` leaves 721–959px broken
|
|
259
260
|
(sidebar already static while your script still treats it as an overlay).
|
|
260
261
|
|
|
261
|
-
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
|
|
262
263
|
`[data-density="compact"]` over inheriting custom properties, so one screen can be dense while the
|
|
263
264
|
rest of the app is not. `data-density="comfortable"` is a real rule too, so the reverse also works:
|
|
264
265
|
a compact root with one region opted back out (shared chrome that must match a sibling product).
|
|
@@ -268,13 +269,13 @@ a compact root with one region opted back out (shared chrome that must match a s
|
|
|
268
269
|
# Actions
|
|
269
270
|
|
|
270
271
|
## Button — `.fdy-btn`
|
|
271
|
-
**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.
|
|
272
273
|
|
|
273
274
|
- Modifiers: `--ghost` `--danger` `--text` `--icon` (square, icon-only) `--sm` `--lg`
|
|
274
275
|
- Parts: `.fdy-btn__icon` (wraps the `<svg>`) · `.fdy-btn-group` (segmented row, `role="group"` +
|
|
275
276
|
`aria-label`)
|
|
276
277
|
- **Toggle / segmented:** `aria-pressed="true"` gives a real pressed state (soft primary fill on
|
|
277
|
-
`--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`
|
|
278
279
|
is already the right attribute, and it is what turns `.fdy-btn-group` from a joined row into a
|
|
279
280
|
complete segmented control.
|
|
280
281
|
- A11y: `--icon` **requires** `aria-label`. Use `<button type="button">` unless it submits.
|
|
@@ -288,7 +289,7 @@ a compact root with one region opted back out (shared chrome that must match a s
|
|
|
288
289
|
```
|
|
289
290
|
|
|
290
291
|
**Quiet destructive:** combine `--danger` with `--ghost` or `--text`. The ground stays quiet and only
|
|
291
|
-
the ink turns red
|
|
292
|
+
the ink turns red. `.fdy-menu__item--danger` has always worked this way. Use it whenever Delete
|
|
292
293
|
shares a screen with the one primary action; a solid Delete beside Save is a second primary in all
|
|
293
294
|
but name.
|
|
294
295
|
|
|
@@ -330,7 +331,7 @@ native control, otherwise a `<div>` + explicitly associated label.
|
|
|
330
331
|
- `.fdy-label` (+`--required`) · `.fdy-input` (+`--error`) · `.fdy-textarea` · `.fdy-help` (+`--error`)
|
|
331
332
|
|
|
332
333
|
**Grouped controls are a `<fieldset>`, not a new block.** Put `.fdy-field` on the fieldset and
|
|
333
|
-
`.fdy-label` on the legend
|
|
334
|
+
`.fdy-label` on the legend. The kit already resets the UA border/padding and supplies the spacing:
|
|
334
335
|
|
|
335
336
|
```html
|
|
336
337
|
<fieldset class="fdy-field">
|
|
@@ -342,7 +343,7 @@ native control, otherwise a `<div>` + explicitly associated label.
|
|
|
342
343
|
|
|
343
344
|
**`.fdy-label--required` marks the label, not the accessibility tree.** The control already carries
|
|
344
345
|
`required`; the asterisk is painted through `::after` with CSS alt text, so a screen reader never
|
|
345
|
-
reads "star" after the label
|
|
346
|
+
reads "star" after the label, which a `<span>` you have to remember to mark `aria-hidden`
|
|
346
347
|
cannot guarantee.
|
|
347
348
|
- `[readonly]` is styled on input/textarea: full contrast, focusable, copyable.
|
|
348
349
|
|
|
@@ -360,8 +361,8 @@ cannot guarantee.
|
|
|
360
361
|
</label>
|
|
361
362
|
```
|
|
362
363
|
|
|
363
|
-
**Which `type` does `.fdy-input` cover?** Every text-like type
|
|
364
|
-
`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:
|
|
365
366
|
|
|
366
367
|
| Type | What the UA still draws | What to do |
|
|
367
368
|
|---|---|---|
|
|
@@ -386,7 +387,7 @@ Prefix/suffix addons around an input: text (`Rp`, `%`), a decorative icon
|
|
|
386
387
|
|
|
387
388
|
## Number field — `[data-fdy-number]`
|
|
388
389
|
A number input with its increment/decrement affordance back, after `.fdy-input` removed the
|
|
389
|
-
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
|
|
390
391
|
the shared border, focus ring and error promotion. Needs `freeday-number.js`.
|
|
391
392
|
|
|
392
393
|
```html
|
|
@@ -400,13 +401,13 @@ the shared border, focus ring and error promotion. Needs `freeday-number.js`.
|
|
|
400
401
|
```
|
|
401
402
|
|
|
402
403
|
- **No custom event.** Stepping fires native bubbling `input` + `change` on the input, so `v-model`,
|
|
403
|
-
`onChange` and `@bind` work with no adapter
|
|
404
|
+
`onChange` and `@bind` work with no adapter, because the input stays the source of truth.
|
|
404
405
|
- `min` / `max` / `step` live on the **input**; the buttons never do the arithmetic themselves
|
|
405
406
|
(`stepUp()`/`stepDown()` clamp for free) and go `disabled` at a bound, on a `disabled`/`readonly`
|
|
406
407
|
field, and when `step="any"` (a stepper cannot express "no defined increment").
|
|
407
408
|
- The buttons are **not tab stops** (`tabindex="-1"`): the input is already focusable and ↑/↓
|
|
408
409
|
already step it, so extra stops would cost every keyboard user and buy nothing. Keep the
|
|
409
|
-
`aria-label
|
|
410
|
+
`aria-label`, so pointer and browse-mode users still get a named control.
|
|
410
411
|
- `type="button"` is required. Inside a `<form>`, a bare `<button>` submits it.
|
|
411
412
|
|
|
412
413
|
## Checkbox · radio · switch
|
|
@@ -419,7 +420,7 @@ Native inputs, styled. `.fdy-check` · `.fdy-radio` · `.fdy-switch` on the wrap
|
|
|
419
420
|
```
|
|
420
421
|
|
|
421
422
|
## Select / combobox — `.fdy-combo`
|
|
422
|
-
> **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).
|
|
423
424
|
|
|
424
425
|
Fully styleable dropdown, APG combobox+listbox. Needs `freeday-select.js`.
|
|
425
426
|
|
|
@@ -444,12 +445,12 @@ Fully styleable dropdown, APG combobox+listbox. Needs `freeday-select.js`.
|
|
|
444
445
|
|
|
445
446
|
**The selected tick is CSS.** `.fdy-combo__check` is an empty box in the markup; the glyph is painted
|
|
446
447
|
by the stylesheet on `[aria-selected="true"]` with alt text, so it never enters the accessibility
|
|
447
|
-
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
|
|
448
449
|
is carried by `aria-selected` alone, announced once, and `getByRole('option', { name: 'August' })`
|
|
449
450
|
keeps matching after selection. Do not put a glyph in that span.
|
|
450
451
|
|
|
451
452
|
## Autocomplete — `.fdy-autocomplete`
|
|
452
|
-
> **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).
|
|
453
454
|
|
|
454
455
|
Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
455
456
|
|
|
@@ -458,7 +459,7 @@ Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
|
458
459
|
`aria-controls` → the listbox `id`, `autocomplete="off"`.
|
|
459
460
|
|
|
460
461
|
## Cascade select — `.fdy-cascade`
|
|
461
|
-
> **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).
|
|
462
463
|
|
|
463
464
|
Hierarchical drill-down. The data model is a **nested `<ul>`** inside the wrapper: an `<li>` with a
|
|
464
465
|
child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
@@ -479,7 +480,7 @@ child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
|
479
480
|
```
|
|
480
481
|
|
|
481
482
|
## Choose-from-list (CFL) — `data-fdy-cfl`
|
|
482
|
-
> **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).
|
|
483
484
|
|
|
484
485
|
A read-only field backed by master data: the button opens a searchable dialog, the picked row fills
|
|
485
486
|
the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
@@ -490,7 +491,7 @@ the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
|
490
491
|
- Inside: `.fdy-cfl__search` `__results` `__row` `__check` `__empty` `__count` `__actions`
|
|
491
492
|
- Hooks: `data-fdy-cfl-search` (the search input), `data-fdy-cfl-empty`, `data-fdy-cfl-count`,
|
|
492
493
|
`data-fdy-cfl-confirm` (multi only), `data-fdy-cfl-trigger`, `data-fdy-cfl-open`
|
|
493
|
-
- Rows are server state
|
|
494
|
+
- Rows are server state. In a real app, drive them from a controlled fetch rather than a global store.
|
|
494
495
|
- **Multi-select in the typed wrappers** (Vue · React · Blazor): `multiple` ticks rows and commits
|
|
495
496
|
them together on **Confirm** instead of committing the row that was clicked. Vue and React widen
|
|
496
497
|
their model to an array (`Row[] | null`); Blazor takes a second pair, `Values` /
|
|
@@ -537,32 +538,32 @@ the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
|
537
538
|
```
|
|
538
539
|
|
|
539
540
|
**`clearable` makes the value removable.** Without it a choose-from-list can be set but never unset,
|
|
540
|
-
which breaks every *optional* foreign key
|
|
541
|
+
which breaks every *optional* foreign key. The value type already allows null; only the component
|
|
541
542
|
could not produce it. With it, a clear button appears beside the trigger whenever a row is picked
|
|
542
543
|
and the field is editable; it emits `null` (Vue `update:modelValue`/`change`, React `onChange`,
|
|
543
544
|
Blazor `ValueChanged`) and returns focus to the trigger. It is a second `.fdy-input-group__btn`, not
|
|
544
545
|
a new class.
|
|
545
546
|
|
|
546
547
|
## Date picker — `data-fdy-datepicker`
|
|
547
|
-
> **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).
|
|
548
549
|
|
|
549
550
|
Input-styled trigger + calendar popover. Needs `freeday-datepicker.js`. **Author an empty `<div>`**
|
|
550
|
-
|
|
551
|
+
The enhancer builds everything.
|
|
551
552
|
|
|
552
553
|
- `.fdy-datepicker` (+`--error`) · `__trigger` `__value` (+`--placeholder`) `__icon` `__clear`
|
|
553
554
|
`__panel`; calendar internals `.fdy-cal__head` `__nav` `__title` `__grid` (+`--months` `--years`)
|
|
554
555
|
`__dow` `__day` `__month` `__year`
|
|
555
556
|
- Range: wrap two pickers in `.fdy-daterange` + `data-fdy-daterange` (`role="group"`), children
|
|
556
557
|
get `data-role="from"` / `"to"`; separator `.fdy-daterange__sep`. The end can't precede the start.
|
|
557
|
-
**Typed wrapper: `<FdyDateRange>`** (Vue/React/Blazor)
|
|
558
|
+
**Typed wrapper: `<FdyDateRange>`** (Vue/React/Blazor). Use it in those stacks.
|
|
558
559
|
**Getting to a distant date.** The title is a **button**, and it drills up one level each press:
|
|
559
560
|
day grid → 12-cell month grid (`.fdy-cal__grid--months` of `.fdy-cal__month`) → 12-cell year grid
|
|
560
|
-
(`.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
|
|
561
562
|
years, then pages of twelve years. Picking a year drops to that year's months, picking a month
|
|
562
563
|
drops to its days: every level above the day grid is **navigation**, so nothing is committed until a
|
|
563
564
|
day is selected. From the year grid the title steps back down to months.
|
|
564
565
|
|
|
565
|
-
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
|
|
566
567
|
pages tile and a given year always sits in the same place. From August 2026 to March 2022 that is 7
|
|
567
568
|
clicks; walking month by month is 53. To 1998 it is 8; walking is 336.
|
|
568
569
|
|
|
@@ -605,15 +606,15 @@ Click/Enter opens the file dialog; drop works too. Needs `freeday-upload.js`.
|
|
|
605
606
|
- List: `.fdy-filelist` (+`--grid`) of `.fdy-file` (+`--success`, `--error`) · `__icon` `__meta`
|
|
606
607
|
`__name` `__sub` `__progress` `__remove`
|
|
607
608
|
- Attributes: `data-max-size` (bytes), `data-filelist="#id"`, `data-fdy-upload-simulate` (demo only
|
|
608
|
-
|
|
609
|
+
the kit fakes a transfer to `done()`; never set it in an app)
|
|
609
610
|
- A11y: the dropzone is `role="button" tabindex="0"` + `aria-label`.
|
|
610
611
|
|
|
611
|
-
**Both events fire on the dropzone
|
|
612
|
+
**Both events fire on the dropzone**: `fdy-upload-add` *and* `fdy-upload-remove`. One listener, one
|
|
612
613
|
element. (The file list may be anywhere in the document; nothing is dispatched on the row, because a
|
|
613
614
|
row in a sibling list would never bubble through the zone.)
|
|
614
615
|
|
|
615
616
|
**The row is yours to drive.** `fdy-upload-add` carries `detail.row`, a small state machine over the
|
|
616
|
-
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
|
|
617
618
|
transfer started; the kit never claims one it is not performing.
|
|
618
619
|
|
|
619
620
|
| `detail.row` | State it renders |
|
|
@@ -627,15 +628,15 @@ transfer started; the kit never claims one it is not performing.
|
|
|
627
628
|
| `.ready()` | back to **rest** (e.g. after a failed attempt the user will retry) |
|
|
628
629
|
| `.el` | the row element |
|
|
629
630
|
|
|
630
|
-
**If your request outlives the transfer, drive `waiting()`.** Server-side work after the last byte
|
|
631
|
-
|
|
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
|
|
632
633
|
is read as a hang. `waiting()` is the state for it: the label says what the server is doing, and the
|
|
633
634
|
bar stops claiming a percentage it no longer has.
|
|
634
635
|
|
|
635
636
|
```js
|
|
636
637
|
zone.addEventListener('fdy-upload-add', (e) => {
|
|
637
638
|
if (e.detail.rejected) return; // the kit already rendered the reason
|
|
638
|
-
const { file, row } = e.detail; // row is at rest
|
|
639
|
+
const { file, row } = e.detail; // row is at rest; nothing is in flight yet
|
|
639
640
|
submitBtn.onclick = async () => {
|
|
640
641
|
row.uploading();
|
|
641
642
|
await send(file, (pct) => row.setProgress(pct));
|
|
@@ -648,7 +649,7 @@ zone.addEventListener('fdy-upload-remove', (e) => forget(e.detail.file)); // s
|
|
|
648
649
|
```
|
|
649
650
|
|
|
650
651
|
**Bring your own row:** omit the file list entirely (no `data-filelist`, and no `.fdy-filelist`
|
|
651
|
-
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`
|
|
652
653
|
still works, its element simply isn't attached. Note the fallback when `data-filelist` is absent is
|
|
653
654
|
`parentNode.querySelector('.fdy-filelist')`, so a bare dropzone will adopt a list that happens to
|
|
654
655
|
share its parent; give the dropzone its own container if you mean "no list".
|
|
@@ -675,7 +676,7 @@ A consistent filter row of `.fdy-field`s with a width rhythm (`--w-sm` · defaul
|
|
|
675
676
|
`--w-xl` · `--w-2xl`), one `--w-grow` field (usually search) absorbing the slack, and
|
|
676
677
|
`.fdy-filterbar__actions` pinned at the end. A control with **no label of its own** (a
|
|
677
678
|
`.fdy-check`, `.fdy-switch`, `.fdy-radio`, or a lone `.fdy-btn`) is given `--control-h` so it
|
|
678
|
-
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
|
|
679
680
|
labelled fields, which would otherwise drop a 20px checkbox below a 32px input's centre.
|
|
680
681
|
`--actions-inline` keeps the actions on the control
|
|
681
682
|
line. Zero JS. Composes with `.fdy-table-toolbar` on a shared baseline.
|
|
@@ -705,7 +706,7 @@ Linear multi-step flow: marker → check, one panel at a time, back/next. Needs
|
|
|
705
706
|
|
|
706
707
|
**`__btn` is optional.** For a **read-only indicator** put `__marker` + `__label` straight in the
|
|
707
708
|
`<li>`; for a **navigable** stepper wrap them in `<button class="fdy-step__btn">` (add `disabled`
|
|
708
|
-
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
|
|
709
710
|
over the connector line, so omitting `__btn` cannot make the connector draw through the numbers.
|
|
710
711
|
|
|
711
712
|
```html
|
|
@@ -732,23 +733,23 @@ Semantic static table. Wrap in `.fdy-table-wrap` (bordered surface) or `.fdy-tab
|
|
|
732
733
|
</div>
|
|
733
734
|
```
|
|
734
735
|
|
|
735
|
-
**Frozen axes
|
|
736
|
+
**Frozen axes, `.fdy-table--sticky`.** For a grid read against two axes at once (a rate
|
|
736
737
|
matrix, a timetable) or a wide one whose identity columns must stay visible. `.fdy-table--sticky`
|
|
737
738
|
is the enabler: it separates the borders, because a collapsed border belongs to the table rather
|
|
738
739
|
than the cell and scrolls out from under whatever you froze. Then ask for the axes you have:
|
|
739
740
|
|
|
740
|
-
- **Header row
|
|
741
|
+
- **Header row:** add `.fdy-table--sticky-head`, and put `.fdy-table-scroll--frozen` on the
|
|
741
742
|
wrapper (it scrolls both ways and is the scrollport the freeze resolves against; size it with
|
|
742
743
|
`--fdy-table-frozen-h`, default `30rem`). It is a separate modifier because a table that only
|
|
743
744
|
freezes columns scrolls with the **page**, and `top:0` against the page sticks the header under
|
|
744
|
-
the viewport edge
|
|
745
|
-
- **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
|
|
746
747
|
each frozen column its own `--fdy-freeze-left`: `0` for the first, then the summed widths of the
|
|
747
748
|
frozen columns to its left. Mark the last one `.fdy-table__freeze--edge` so the reader can see
|
|
748
749
|
where the frozen block ends. One frozen column needs no variable at all.
|
|
749
750
|
|
|
750
751
|
The offsets are the caller's job because they depend on rendered column widths, which CSS cannot
|
|
751
|
-
sum
|
|
752
|
+
sum, so measure them once after render (and on resize) and set the variable.
|
|
752
753
|
|
|
753
754
|
```html
|
|
754
755
|
<div class="fdy-table-scroll fdy-table-scroll--frozen" style="--fdy-table-frozen-h:26rem">
|
|
@@ -769,7 +770,7 @@ sum — measure them once after render (and on resize) and set the variable.
|
|
|
769
770
|
```
|
|
770
771
|
|
|
771
772
|
## Data table — `.fdy-datatable`
|
|
772
|
-
> **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.
|
|
773
774
|
|
|
774
775
|
The interactive table: global search, sort, per-column filters, row selection + bulk bar,
|
|
775
776
|
pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` + `data-fdy-table`
|
|
@@ -788,20 +789,24 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
|
|
|
788
789
|
detail), `.fdy-table__state` (in-table empty/loading row)
|
|
789
790
|
- Footer: `.fdy-table-footer` · `__info` (`data-fdy-table-info`) + optional `__size` (a label and a
|
|
790
791
|
rows-per-page control) + `<nav class="fdy-pagination" data-fdy-table-pagination>`. The typed
|
|
791
|
-
wrappers render `FdyCombo` there
|
|
792
|
+
wrappers render `FdyCombo` there, **never a native `<select>`**, whose open list is an OS menu no
|
|
792
793
|
stylesheet reaches. In the raw path the control is app-authored markup (its options *are* the
|
|
793
794
|
offer) carrying `data-fdy-table-page-size`; the enhancer only wires it, and listens for both
|
|
794
795
|
`change` and the `fdy-change` a `.fdy-combo` emits, so either kind works.
|
|
795
796
|
- Sort values: put the raw value in `data-sort-value` when the cell text is formatted.
|
|
796
|
-
- **Language
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
|
803
808
|
reads them through `textOf()`, so any of them can be replaced per element with
|
|
804
|
-
**`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.
|
|
805
810
|
(HTML lowercases attribute names, so a camelCase key like `filterText` is written
|
|
806
811
|
`data-fdy-text-filter-text`; the run-together spelling still resolves.):
|
|
807
812
|
|
|
@@ -820,25 +825,25 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
|
|
|
820
825
|
arrives overridable or not at all.
|
|
821
826
|
|
|
822
827
|
**Testing note:** a column's filter button and the dialog it opens deliberately share one
|
|
823
|
-
accessible name (`Filter <column>`)
|
|
828
|
+
accessible name (`Filter <column>`), since a dialog named after its trigger is the normal pattern. In a
|
|
824
829
|
Playwright/Testing-Library suite that means `getByLabel('Filter Name')` resolves to two elements;
|
|
825
830
|
reach for `getByRole('button', { name: 'Filter Name' })` instead.
|
|
826
831
|
|
|
827
832
|
**Rows per page.** Pass `pageSizes` (Vue/React `:page-sizes="[10, 20, 50]"`, Blazor
|
|
828
833
|
`PageSizes="…"`) and the footer grows a rows-per-page control between the range and the pager.
|
|
829
|
-
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
|
|
830
835
|
at rather than dropping them on page 1.
|
|
831
836
|
|
|
832
|
-
- **server mode
|
|
837
|
+
- **server mode:** reported through `update:page` / `onPageChange` / `PageChanged`, the same event
|
|
833
838
|
as a page click, carrying the new `size`. Read `size` to tell the two apart.
|
|
834
|
-
- **client mode
|
|
839
|
+
- **client mode:** the table applies it itself, so the control works with nothing wired, and also
|
|
835
840
|
emits `update:pageSize` / `onPageSizeChange` / `PageSizeChanged` for a caller that wants to
|
|
836
841
|
persist the choice. Changing the `pageSize` prop wins back.
|
|
837
842
|
|
|
838
|
-
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
|
|
839
844
|
ninety-row list would remove the only way back to twenty.
|
|
840
845
|
|
|
841
|
-
**Typed wrapper: `<FdyTableFooter
|
|
846
|
+
**Typed wrapper: `<FdyTableFooter>`.** The footer alone (`page` in, `update:page` /
|
|
842
847
|
`onPageChange` / `PageChanged` out), for the one shape that cannot use the table's own: a
|
|
843
848
|
**responsive** list, where a `.fdy-datatable` at `lg` and a `.fdy-list` below it are two renderings
|
|
844
849
|
of one page of rows. A footer inside the table is inside the half a phone hides, so those screens
|
|
@@ -847,17 +852,17 @@ render it once, outside both, with `pager={false}` on the table.
|
|
|
847
852
|
**Who draws the pager.** The table renders its own footer (range + pager) whenever there is more
|
|
848
853
|
than one page. Two ways to take it over:
|
|
849
854
|
|
|
850
|
-
- **client mode
|
|
851
|
-
- **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
|
|
852
857
|
your own control. In server mode this is the only way: the app already owns the page there, and
|
|
853
|
-
was still being handed a second control. The typical shape is a responsive list
|
|
854
|
-
`.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.
|
|
855
860
|
|
|
856
861
|
Do not hide the footer with CSS. `display:none` works, but it reaches into a component's internals
|
|
857
862
|
and breaks the moment the class changes.
|
|
858
863
|
|
|
859
864
|
## Pagination — `.fdy-pagination`
|
|
860
|
-
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
|
|
861
866
|
`__list` / `__link` / `__ellipsis` elements do all the styling, and the data table targets
|
|
862
867
|
`data-fdy-table-pagination`. Keep it on the wrapper anyway, for consistency with the rest of the kit.
|
|
863
868
|
|
|
@@ -872,7 +877,7 @@ Empty / error placeholder for a data area. `--danger` for failures. Parts: `__ic
|
|
|
872
877
|
replaces.
|
|
873
878
|
|
|
874
879
|
## Charts — `data-fdy-chart`
|
|
875
|
-
> **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.
|
|
876
881
|
|
|
877
882
|
Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`; call
|
|
878
883
|
`FreedayChart.update(el)` after changing data (or use `FdyChart` in Vue/React/Blazor).
|
|
@@ -881,27 +886,27 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
|
|
|
881
886
|
- Roots: `.fdy-sparkline` · `.fdy-bars` · `.fdy-donut` · `.fdy-chart-xy` (built for line/area/bar)
|
|
882
887
|
- Data: `data-values="1,2,3"` (single series) or `data-series='[{"label":"A","values":[…]}]'`
|
|
883
888
|
(multi), `data-labels="Jan,Feb"`, `data-fdy-stacked`, `data-fdy-center` (donut)
|
|
884
|
-
- 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
|
|
885
890
|
names **or** `chart-1`…`chart-8` slots to pin a category's colour. Multi-series defaults to the
|
|
886
891
|
validated categorical palette `--chart-1`…`--chart-8`.
|
|
887
892
|
- Format: `data-fdy-format="number|percent|currency"`; legend `data-fdy-legend` (`none` to drop);
|
|
888
893
|
axes `data-fdy-axes`
|
|
889
|
-
- 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
|
|
890
895
|
root height fights `aspect-ratio` instead of setting it): `.fdy-chart-xy__plot` is `width:100%`
|
|
891
896
|
+ `aspect-ratio:16/9` + `min-height:8rem`; `.fdy-bars` is a fixed `height:9rem`; `.fdy-sparkline`
|
|
892
897
|
is an inline-block `8rem × 2.25rem`.
|
|
893
898
|
- Axis type: `--fdy-chart-tick-size` (default `var(--text-xs)`) sets the y-tick and x-label size on
|
|
894
|
-
`.fdy-chart-xy`. It is a real CSS size
|
|
895
|
-
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
|
|
896
901
|
many x-labels fit. Set the token rather than `font-size` on `__tick`/`__xlabel`, or the gutter and
|
|
897
902
|
the autoskip will still be computed from the old size.
|
|
898
903
|
- A11y: every chart is `role="img"` + an **author-supplied** `aria-label`, and that label is the
|
|
899
|
-
*entire* text alternative
|
|
900
|
-
marks everything it draws `aria-hidden="true"`
|
|
901
|
-
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
|
|
902
907
|
**not** have done that: it is Children Presentational per ARIA, but browsers keep the subtree in
|
|
903
908
|
the accessibility tree anyway, so the kit hides it rather than relying on the spec. That hiding
|
|
904
|
-
needs a name to work from
|
|
909
|
+
needs a name to work from: a chart with **no** `aria-label`/`aria-labelledby` keeps its contents
|
|
905
910
|
exposed, because an image with neither a name nor any text is worse than one that leaks its
|
|
906
911
|
legend. Any `<table>`/text placed inside the element is **pre-render only** and is replaced on
|
|
907
912
|
render. Good: `aria-label="Monthly posting trend: Posted 62% → 88%, Draft 24% → 16%"`.
|
|
@@ -911,7 +916,7 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
|
|
|
911
916
|
`__center` `__hit`; `.fdy-sparkline__line` `__area` `__dot`.
|
|
912
917
|
|
|
913
918
|
## Tree view — `.fdy-tree`
|
|
914
|
-
Native `<details>` hierarchy
|
|
919
|
+
Native `<details>` hierarchy, with zero JS for expand/collapse. `.fdy-tree__branch` on `<details>`,
|
|
915
920
|
`.fdy-tree__leaf` on a leaf `<li>`, plus `__chevron` and `__icon` svgs inside `<summary>`.
|
|
916
921
|
`.fdy-tree--checkbox` + `data-fdy-tree` (needs `freeday-tree.js`) adds cascading selection:
|
|
917
922
|
`.fdy-tree__check` on each `<input class="fdy-checkbox">`; checking a branch checks its children,
|
|
@@ -919,19 +924,19 @@ a partial set makes the branch indeterminate. Checking never toggles expansion.
|
|
|
919
924
|
|
|
920
925
|
## List (flat rows) — `.fdy-list`
|
|
921
926
|
The **flat** row container: one bordered surface, hairline dividers, **no shadow**. This is what a
|
|
922
|
-
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
|
|
923
928
|
`--shadow-lift` and turns ten rows into ten floating objects.
|
|
924
929
|
|
|
925
930
|
- `.fdy-list` on a `<ul>`/`<ol>` (list-style is reset for you) or a `<div>`
|
|
926
|
-
- `.fdy-list__row`
|
|
931
|
+
- `.fdy-list__row` is one row; `--interactive` for hover feedback, `--button` when the row **is** the
|
|
927
932
|
control (render it as a real `<button>`/`<a>`; the UA box is reset without losing the list surface)
|
|
928
933
|
- Row internals: `.fdy-list__main` (truncating stack) → `.fdy-list__title` + `.fdy-list__meta`, and
|
|
929
934
|
`.fdy-list__aside` pinned right
|
|
930
|
-
- **Disabled
|
|
935
|
+
- **Disabled:** `disabled` on the `<button>` (or `aria-disabled="true"` when the row is an `<a>`/
|
|
931
936
|
`<div>`) dims the row and withdraws the hover tint and the pointer cursor, same as every other
|
|
932
937
|
control in the kit. Do not hand-roll it: an undimmed row that still lights up under the pointer
|
|
933
938
|
reads as clickable while it is refusing input.
|
|
934
|
-
- Not `.fdy-list-reset
|
|
939
|
+
- Not `.fdy-list-reset`, which only strips UA bullets/indent from a semantic list.
|
|
935
940
|
|
|
936
941
|
```html
|
|
937
942
|
<ul class="fdy-list">
|
|
@@ -982,11 +987,11 @@ Every field is optional; it returns the toast element. Classes (rendered for you
|
|
|
982
987
|
`__body` `__title` `__text` `__close`.
|
|
983
988
|
|
|
984
989
|
**Above modals, and not by z-index.** The region is a `popover` and re-enters the **top layer** as
|
|
985
|
-
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
|
|
986
991
|
a toast of yours is hidden behind something, a bigger `z-index` is not the fix; check whether that
|
|
987
992
|
something is in the top layer too. Where the Popover API is unavailable (before Chrome 114 /
|
|
988
993
|
Safari 17 / Firefox 125) the region falls back to a fixed block at `z-index: 200`, which cannot
|
|
989
|
-
clear an open modal
|
|
994
|
+
clear an open modal, the message is still announced to a screen reader.
|
|
990
995
|
|
|
991
996
|
## Tooltip — `.fdy-tooltip`
|
|
992
997
|
Hover/focus only, never the sole carrier of information. Wrap in `.fdy-tooltip-wrap`; the trigger
|
|
@@ -1006,7 +1011,7 @@ Size-matched placeholders so nothing shifts when data lands: `--title` `--text`
|
|
|
1006
1011
|
# Navigation
|
|
1007
1012
|
|
|
1008
1013
|
## Nav (menu) — `.fdy-nav`
|
|
1009
|
-
Navigation links
|
|
1014
|
+
Navigation links, **vertical by default** (the app shell sidebar), horizontal with `--horizontal`. Items are `<a class="fdy-nav__item">` with
|
|
1010
1015
|
`__icon` / `__label` / `__badge`; the current one gets `aria-current="page"`.
|
|
1011
1016
|
`--flat` drops the surface. Nested groups are native `<details>`:
|
|
1012
1017
|
|
|
@@ -1030,17 +1035,17 @@ Navigation links — **vertical by default** (the app shell sidebar), horizontal
|
|
|
1030
1035
|
</header>
|
|
1031
1036
|
```
|
|
1032
1037
|
|
|
1033
|
-
> **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
|
|
1034
1039
|
> `aria-selected` (invalid ARIA on an anchor) and never `role="tab"`. Use `.fdy-nav--horizontal`
|
|
1035
1040
|
> for an application's **primary** navigation. For **routed sub-navigation** that should look like
|
|
1036
1041
|
> tabs (`/settings/profile` · `/settings/billing`), put `.fdy-tabs__list` / `.fdy-tabs__tab` on
|
|
1037
|
-
> plain `<a>`s
|
|
1042
|
+
> plain `<a>`s, since those classes honour `aria-current="page"` too, and do **not** add the tab roles
|
|
1038
1043
|
> or `freeday-tabs.js`, which promise a keyboard contract routes do not have.
|
|
1039
1044
|
|
|
1040
1045
|
## App bar — `.fdy-appbar`
|
|
1041
1046
|
Standalone top bar (distinct from the shell's `.fdy-app__topbar`). Modifiers `--sticky`
|
|
1042
1047
|
`--elevated` `--dense` `--primary` (on-colour controls). Parts `__brand` `__spacer` `__actions`.
|
|
1043
|
-
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
|
|
1044
1049
|
for primary navigation (see Nav above).
|
|
1045
1050
|
|
|
1046
1051
|
## Breadcrumb — `.fdy-breadcrumb`
|
|
@@ -1051,7 +1056,7 @@ for primary navigation (see Nav above).
|
|
|
1051
1056
|
## Tabs — `.fdy-tabs`
|
|
1052
1057
|
APG tabs: ←/→, Home/End, roving tabindex. Needs `freeday-tabs.js`.
|
|
1053
1058
|
`.fdy-tabs__tab` marks its active state from **`aria-selected="true"` or `aria-current="page"`**,
|
|
1054
|
-
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.
|
|
1055
1060
|
|
|
1056
1061
|
```html
|
|
1057
1062
|
<div class="fdy-tabs" data-fdy-tabs>
|
|
@@ -1069,13 +1074,13 @@ so the same look serves routed sub-navigation built from plain links — see the
|
|
|
1069
1074
|
# Overlays
|
|
1070
1075
|
|
|
1071
1076
|
## Modal — `.fdy-modal`
|
|
1072
|
-
> **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).
|
|
1073
1078
|
|
|
1074
1079
|
Native `<dialog>`: focus trap, Esc and backdrop come from the browser. Sizes `--sm` `--md` `--lg`
|
|
1075
1080
|
`--wide`; `--cfl` for the choose-from-list dialog.
|
|
1076
1081
|
|
|
1077
1082
|
**It is in the TOP LAYER.** `showModal()` paints the dialog above every stacking context on the
|
|
1078
|
-
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
|
|
1079
1084
|
`popover`, or another modal) can. It also makes the rest of the document inert, which means
|
|
1080
1085
|
`elementFromPoint` outside the dialog returns the dialog: do not use a hit test to check what is
|
|
1081
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`
|
|
@@ -1083,34 +1088,34 @@ painted on top of a modal, because it will tell you the dialog is, even when it
|
|
|
1083
1088
|
`data-close` on any button that should close it.
|
|
1084
1089
|
|
|
1085
1090
|
**Stacking two overlays is supported.** Modal and drawer are both `<dialog>` opened with
|
|
1086
|
-
`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
|
|
1087
1092
|
modal opened from inside an open drawer paints **above** it, and neither needs a `z-index` for
|
|
1088
1093
|
that. Escape closes the topmost first, so cancelling returns to the still-open drawer; a second
|
|
1089
1094
|
Escape then closes the drawer. Closing them out of order is fine too: closing the drawer while the
|
|
1090
1095
|
modal is up leaves the modal open, painted and dismissible.
|
|
1091
1096
|
|
|
1092
1097
|
One caveat, and it is the browser's rather than the kit's: **the second overlay's Escape can only
|
|
1093
|
-
be refused if both were opened by a real gesture.** A `showModal()` that runs from script
|
|
1094
|
-
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
|
|
1095
1100
|
arrives **non-cancelable**, so `preventDefault()` on it is ignored and an "unsaved changes, stay
|
|
1096
1101
|
open" guard silently stops working. Routing Escape through app state instead, which is what the
|
|
1097
1102
|
typed wrappers do, is unaffected: the dialog closes and your state follows it.
|
|
1098
1103
|
|
|
1099
1104
|
*Correction to 1.51.1, which said such a dialog has its close watcher grouped with the one below so
|
|
1100
1105
|
that a single Escape closes both.* That happens only on a page that has received **no user input at
|
|
1101
|
-
all
|
|
1106
|
+
all**: a test harness, never an app. Six seconds after one click the overlays are still
|
|
1102
1107
|
independent. Open overlays with real clicks in a test, or a stacking assertion fails for a reason
|
|
1103
1108
|
that is not the kit. Both rules measured on Chromium 133 and 151, and pinned as a matrix in
|
|
1104
1109
|
`browser/overlay-stack.mjs`.
|
|
1105
1110
|
|
|
1106
1111
|
## Drawer — `.fdy-drawer`
|
|
1107
|
-
> **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).
|
|
1108
1113
|
|
|
1109
|
-
Temporary side panel on native `<dialog
|
|
1114
|
+
Temporary side panel on native `<dialog>`, left by default, `--right` to flip. Parts `__header`
|
|
1110
1115
|
`__title` `__body` `__footer` `__close`. Open it from any
|
|
1111
1116
|
`<button data-fdy-drawer="<dialog id>">`. Needs `freeday-drawer.js`.
|
|
1112
1117
|
|
|
1113
|
-
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
|
|
1114
1119
|
opening a modal over an open drawer, which is supported and described under
|
|
1115
1120
|
[Modal → *Stacking two overlays*](#modal--fdy-modal). It slides in on `transform` and never animates
|
|
1116
1121
|
opacity: wait on its position, not on `opacity`, if you need to know when it has arrived.
|
|
@@ -1132,25 +1137,25 @@ is `role="group" aria-roledescription="slide"`.
|
|
|
1132
1137
|
## Card — `.fdy-card`
|
|
1133
1138
|
Parts `__body` `__title` `__desc` `__footer`. Modifiers:
|
|
1134
1139
|
|
|
1135
|
-
- `--elevated`
|
|
1136
|
-
- `--interactive`
|
|
1137
|
-
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
|
|
1138
1143
|
know whether a handler exists, so the modifier without a control (a card that looks clickable and
|
|
1139
1144
|
swallows every click) and a control without the modifier both fail silently. Pair it with a real
|
|
1140
|
-
control
|
|
1141
|
-
- `--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
|
|
1142
1147
|
the UA button box **without** losing the card surface/border
|
|
1143
|
-
- `.fdy-card--button` never replaces keyboard semantics
|
|
1148
|
+
- `.fdy-card--button` never replaces keyboard semantics. A clickable card must be a real
|
|
1144
1149
|
`<button>` or `<a>`.
|
|
1145
|
-
- **Disabled
|
|
1150
|
+
- **Disabled:** `disabled` (or `aria-disabled="true"`) dims the card and withdraws the pointer
|
|
1146
1151
|
cursor and the `--interactive` hover-lift.
|
|
1147
1152
|
- The card is `position:relative`, so a badge or ribbon you absolutely position inside it anchors
|
|
1148
|
-
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*.
|
|
1149
1154
|
|
|
1150
1155
|
### One card, one primary action, one escape hatch
|
|
1151
1156
|
|
|
1152
1157
|
A card with **two** actions cannot be `--button`: interactive content nested in a `<button>` is
|
|
1153
|
-
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,
|
|
1154
1159
|
while it stays a real `<button>`/`<a>` with real keyboard semantics.
|
|
1155
1160
|
|
|
1156
1161
|
```html
|
|
@@ -1167,34 +1172,32 @@ while it stays a real `<button>`/`<a>` with real keyboard semantics.
|
|
|
1167
1172
|
```
|
|
1168
1173
|
|
|
1169
1174
|
- **The escape hatch needs nothing.** Every focusable element in a card that holds a stretched
|
|
1170
|
-
target is raised above the overlay automatically
|
|
1175
|
+
target is raised above the overlay automatically. Forgetting a `z-index` here fails silently, and
|
|
1171
1176
|
a silent failure is what this pattern exists to remove. The `position` that raising needs is only a
|
|
1172
|
-
**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)
|
|
1173
1178
|
keeps its own `position:absolute` and is raised anyway.
|
|
1174
1179
|
- **Name the stretched button for what the whole card does** ("Open workspace Alpha", not "Open").
|
|
1175
1180
|
Its label is the accessible name of the entire hit area.
|
|
1176
|
-
- **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
|
|
1177
1182
|
overlap, and which one that is depends on source order.
|
|
1178
1183
|
- Text under the overlay is no longer selectable; that is the cost of the pattern, not a bug.
|
|
1179
1184
|
- **Cards and list rows.** The overlay anchors to the nearest *positioned* ancestor, and both hosts
|
|
1180
1185
|
supply one: `.fdy-card` always, `.fdy-list__row` when it contains a stretched target. Everything
|
|
1181
|
-
above applies to a row unchanged
|
|
1186
|
+
above applies to a row unchanged: same markup, same automatic raise. In any *other* container,
|
|
1182
1187
|
make the element `position:relative` yourself, or the overlay resolves against whatever is
|
|
1183
1188
|
positioned further up (in a list that used to be `.fdy-list` itself, so every row's target covered
|
|
1184
|
-
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).
|
|
1185
1190
|
A row whose *whole* surface is one control is still better as `.fdy-list__row--button`.
|
|
1186
1191
|
- `.fdy-btn` nudges itself on `:hover`/`:active`, and a transformed element becomes the containing
|
|
1187
|
-
block for its own absolutely positioned descendants
|
|
1192
|
+
block for its own absolutely positioned descendants, so a hand-rolled `::after{inset:0}` overlay
|
|
1188
1193
|
**re-anchors to the button mid-gesture** and the click never lands. `--stretch` neutralises those
|
|
1189
1194
|
transforms and moves the press feedback to the card. This is why the pattern is shipped rather
|
|
1190
1195
|
than documented.
|
|
1191
1196
|
|
|
1192
1197
|
## Badge — `.fdy-badge`
|
|
1193
|
-
Inline status pill: `--success` `--warning` `--danger` `--info` `--outline`. Never colour-only
|
|
1194
|
-
the text carries the meaning.
|
|
1198
|
+
Inline status pill: `--success` `--warning` `--danger` `--info` `--outline`. Never colour-only: the text carries the meaning.
|
|
1195
1199
|
|
|
1196
|
-
`--tone-1`…`--tone-8` for a status vocabulary larger than the semantic palette. Semantics first
|
|
1197
|
-
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
|
|
1198
1201
|
the rest, so two states a workflow distinguishes never share a look. Map each status to a FIXED
|
|
1199
1202
|
tone, never to a hash of the string: a status is a closed set, and a hash reassigns colours the
|
|
1200
1203
|
moment the set grows.
|
|
@@ -1207,19 +1210,19 @@ Put the count in the wrapper's `aria-label` and mark the overlay `aria-hidden="t
|
|
|
1207
1210
|
## Avatar — `.fdy-avatar`
|
|
1208
1211
|
Initials or image. Sizes `--sm` / default / `--lg`; `.fdy-avatar-group` stacks them (label the
|
|
1209
1212
|
group). `--tone-1`…`--tone-8` are decorative tints (WCAG AA in both themes) to distinguish
|
|
1210
|
-
same-initial avatars
|
|
1213
|
+
same-initial avatars. Hash the index off the **full** name, not the initials.
|
|
1211
1214
|
|
|
1212
1215
|
Sizes: `--xs` 1.5rem · *(base)* 2.5rem · `--sm` 2rem · `--lg` 3.5rem. Use **`--xs` inside a control**
|
|
1213
|
-
|
|
1216
|
+
because `.fdy-btn--sm` is itself 2rem tall, so an `--sm` avatar fills a small button edge to edge and the
|
|
1214
1217
|
ghost border cuts across the circle.
|
|
1215
1218
|
|
|
1216
1219
|
## Chip — `.fdy-chip`
|
|
1217
1220
|
Three roles inside a `.fdy-chips` row:
|
|
1218
1221
|
|
|
1219
|
-
- **static / removable
|
|
1222
|
+
- **static / removable:** `<span class="fdy-chip">`, `--primary` for an active filter, with
|
|
1220
1223
|
`.fdy-chip__remove` (`aria-label` says what it removes)
|
|
1221
|
-
- **choice
|
|
1222
|
-
- **filter
|
|
1224
|
+
- **choice:** `<button class="fdy-chip fdy-chip--choice" aria-pressed>` toggle
|
|
1225
|
+
- **filter:** `--filter`, same but with a `.fdy-chip__check` mark
|
|
1223
1226
|
|
|
1224
1227
|
Interactive chips are managed by `freeday-chip.js`: wrap the row in `data-fdy-chips` (+
|
|
1225
1228
|
`data-single` for pick-one, `data-label` for the group name), give each `data-value`.
|
|
@@ -1233,5 +1236,5 @@ by the accompanying text, not the marker colour alone.
|
|
|
1233
1236
|
---
|
|
1234
1237
|
|
|
1235
1238
|
*Anything not listed here is not part of the public surface. If a screen needs a primitive that
|
|
1236
|
-
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
|
|
1237
1240
|
locally.*
|