@cahyo-dimas/freeday 1.18.0 → 1.20.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 +162 -0
- package/COMPONENTS.md +748 -0
- package/README.id.md +9 -1
- package/README.md +16 -2
- package/USAGE.md +34 -11
- package/adapters/blazor/FdyTable.razor.cs +66 -7
- package/adapters/blazor/TableTypes.cs +6 -0
- package/adapters/react/components/FdyTable.tsx +32 -8
- package/adapters/vue/components/FdyTable.vue +30 -6
- package/dist/freeday.bundle.css +35 -1
- package/dist/freeday.css +30 -0
- package/dist/freeday.tokens.css +5 -1
- package/docs/agent-onboarding.md +151 -0
- package/docs/getting-started.md +454 -0
- package/docs/integrations.md +321 -0
- package/docs/reference-screen.html +461 -0
- package/package.json +9 -3
- package/src/components/list.css +29 -0
- package/tokens/breakpoints.d.ts +3 -0
- package/tokens/breakpoints.mjs +8 -1
- package/src/components/.gitkeep +0 -0
- package/src/freeday-autocomplete.js +0 -135
- package/src/freeday-breakpoint.js +0 -51
- package/src/freeday-carousel.js +0 -111
- package/src/freeday-cascade.js +0 -256
- package/src/freeday-cfl.js +0 -213
- package/src/freeday-chart.js +0 -429
- package/src/freeday-chip.js +0 -83
- package/src/freeday-datepicker.js +0 -321
- package/src/freeday-datetime.js +0 -83
- package/src/freeday-drawer.js +0 -43
- package/src/freeday-form.js +0 -181
- package/src/freeday-mask.js +0 -114
- package/src/freeday-menu.js +0 -93
- package/src/freeday-popover.js +0 -69
- package/src/freeday-rating.js +0 -50
- package/src/freeday-select.js +0 -218
- package/src/freeday-slider.js +0 -34
- package/src/freeday-stepper.js +0 -90
- package/src/freeday-table.js +0 -475
- package/src/freeday-tabs.js +0 -68
- package/src/freeday-timepicker.js +0 -180
- package/src/freeday-toast.js +0 -105
- package/src/freeday-tree.js +0 -94
- package/src/freeday-upload.js +0 -206
package/COMPONENTS.md
ADDED
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
# Freeday — Component reference
|
|
2
|
+
|
|
3
|
+
The complete public class surface, in one flat file. Written to be read start-to-finish by a
|
|
4
|
+
person **or an AI coding agent** that has to produce correct Freeday markup without opening the
|
|
5
|
+
live docs.
|
|
6
|
+
|
|
7
|
+
- **Which class exists** and what its modifiers are → here.
|
|
8
|
+
- **Which class to reach for, and when** → [`USAGE.md`](USAGE.md) (the doctrine: type roles,
|
|
9
|
+
spacing rhythm, elevation, one primary per screen).
|
|
10
|
+
- **How to install / import per stack** → [`docs/getting-started.md`](docs/getting-started.md).
|
|
11
|
+
- **A whole screen assembled** → [`docs/reference-screen.html`](docs/reference-screen.html).
|
|
12
|
+
|
|
13
|
+
## Hard rules (these are not style preferences)
|
|
14
|
+
|
|
15
|
+
1. **Prefix is `fdy-`, pattern is compact BEM**: block `fdy-card`, element `fdy-card__title`,
|
|
16
|
+
modifier `fdy-card--elevated`. A modifier is **always** written alongside its block class:
|
|
17
|
+
`class="fdy-btn fdy-btn--ghost"`, never `fdy-btn--ghost` alone.
|
|
18
|
+
2. **Never invent a class.** If it is not in this file, it does not exist. Freeday has no
|
|
19
|
+
`fdy-grid`, no `fdy-flex`, no `fdy-mt-4`, no `fdy-primary`.
|
|
20
|
+
3. **Never write a raw hex or px value** in app CSS. Use the tokens — `var(--color-primary)`,
|
|
21
|
+
`var(--space-4)`, `var(--radius-md)`, `var(--shadow-1)`, `var(--dur-2)`. Spacing is a 4px scale
|
|
22
|
+
(`--space-0`…`--space-24`); a loose value breaks density switching.
|
|
23
|
+
4. **Freeday owns components + tokens, not layout.** The only layout it ships is the shell
|
|
24
|
+
(`.fdy-app`), the composition primitives below, and `.fdy-hidden`/`.fdy-visually-hidden`.
|
|
25
|
+
Grids, stacks and one-off gaps come from your own layout layer (a utility framework run
|
|
26
|
+
**utilities-only, preflight off**, with its theme defined in terms of `var(--space-N)`).
|
|
27
|
+
5. **`aria-invalid="true"` is the canonical error state** on form controls — it drives the visual
|
|
28
|
+
state *and* the screen-reader state. Pair it with `aria-describedby` → the `id` of a
|
|
29
|
+
`.fdy-help.fdy-help--error`. The `--error` class modifiers exist only for the case where the
|
|
30
|
+
invalid state does not belong to the control itself.
|
|
31
|
+
6. **Interactive components need their enhancer script.** Static ones (button, card, badge, table,
|
|
32
|
+
alert, breadcrumb, timeline, accordion, tree-without-cascade…) are CSS-only.
|
|
33
|
+
|
|
34
|
+
## Enhancers — hook, script, global, events
|
|
35
|
+
|
|
36
|
+
Every enhancer is zero-dependency, auto-initialises once on `DOMContentLoaded`, and is idempotent:
|
|
37
|
+
re-hydrate SPA-rendered DOM with `window.Freeday<X>.initAll(root)`. Events are bubbling
|
|
38
|
+
`CustomEvent`s; the payload is in `event.detail`.
|
|
39
|
+
|
|
40
|
+
| Markup hook | Script | Global | Emits |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `data-fdy-combo` | `freeday-select.js` | `FreedayCombo` | `fdy-change` |
|
|
43
|
+
| `data-fdy-autocomplete` | `freeday-autocomplete.js` | `FreedayAutocomplete` | `fdy-autocomplete-select` |
|
|
44
|
+
| `data-fdy-cascade` | `freeday-cascade.js` | `FreedayCascade` | `fdy-cascade-change` |
|
|
45
|
+
| `data-fdy-cfl` | `freeday-cfl.js` | `FreedayCfl` | `fdy-cfl-select` |
|
|
46
|
+
| `data-fdy-datepicker`, `data-fdy-daterange` | `freeday-datepicker.js` | `FreedayDatepicker` | `fdy-datepicker-change` |
|
|
47
|
+
| `data-fdy-timepicker` | `freeday-timepicker.js` | `FreedayTimepicker` | `fdy-time-select` |
|
|
48
|
+
| `data-fdy-datetimepicker` | `freeday-datetime.js` | `FreedayDatetime` | `fdy-datetime-change` |
|
|
49
|
+
| `data-fdy-table` | `freeday-table.js` | `FreedayTable` | `fdy-table-change` |
|
|
50
|
+
| `data-fdy-dropzone` | `freeday-upload.js` | `FreedayUpload` | `fdy-upload-add`, `fdy-upload-remove` |
|
|
51
|
+
| `data-fdy-validate` | `freeday-form.js` | `FreedayForm` | `fdy-form-valid`, `fdy-form-invalid` |
|
|
52
|
+
| `data-fdy-mask`, `data-fdy-password` | `freeday-mask.js` | `FreedayMask` | `fdy-mask` |
|
|
53
|
+
| `data-fdy-chips` | `freeday-chip.js` | `FreedayChip` | `fdy-chip-change`, `fdy-chip-remove` |
|
|
54
|
+
| `data-fdy-stepper` | `freeday-stepper.js` | `FreedayStepper` | `fdy-step-change`, `fdy-step-finish` |
|
|
55
|
+
| `data-fdy-carousel` | `freeday-carousel.js` | `FreedayCarousel` | `fdy-carousel-change` |
|
|
56
|
+
| `data-fdy-menu` | `freeday-menu.js` | `FreedayMenu` | — |
|
|
57
|
+
| `data-fdy-tabs` | `freeday-tabs.js` | `FreedayTabs` | — |
|
|
58
|
+
| `data-fdy-tree` | `freeday-tree.js` | `FreedayTree` | — |
|
|
59
|
+
| `data-fdy-rating` | `freeday-rating.js` | `FreedayRating` | — (native `change`) |
|
|
60
|
+
| `data-fdy-slider` | `freeday-slider.js` | `FreedaySlider` | — (native `input`) |
|
|
61
|
+
| `data-fdy-drawer="id"` | `freeday-drawer.js` | `FreedayDrawer` | — |
|
|
62
|
+
| `data-fdy-chart="type"` | `freeday-chart.js` | `FreedayChart` | — (`.update(el)` to repaint) |
|
|
63
|
+
| — (imperative) | `freeday-toast.js` | `Freeday.toast()` / `Freeday.dismiss()` | — |
|
|
64
|
+
| — (stamps `<html data-breakpoint>`) | `freeday-breakpoint.js` | `FreedayBreakpoint` | `fdy-breakpoint-change` |
|
|
65
|
+
| — (internal positioning) | `freeday-popover.js` | `FreedayPopover` | — |
|
|
66
|
+
|
|
67
|
+
**Shared conventions across the select-type widgets** (combo, cascade, datepicker, daterange,
|
|
68
|
+
timepicker, autocomplete, cfl): `data-value` seeds the value, `data-placeholder` the empty text,
|
|
69
|
+
`data-label` the accessible name, `data-fdy-disabled` / `data-fdy-readonly` / `data-fdy-invalid`
|
|
70
|
+
the states, `data-fdy-no-icon` drops the trailing icon, and a `<template data-fdy-icon>` child
|
|
71
|
+
replaces it. `readonly` is **not** `disabled`: full contrast, still focusable and copyable, opening
|
|
72
|
+
is a no-op.
|
|
73
|
+
|
|
74
|
+
**State classes** the enhancers set (read them, don't hand-author them): `is-active`,
|
|
75
|
+
`is-complete`, `is-open`, `is-selected`, `is-highlighted`, `is-filled`, `is-today`, `is-dragover`,
|
|
76
|
+
`is-leaving`, `is-error`, `is-disabled`, `is-hidden`, `is-visible`, `is-outside`.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
# Shell & composition
|
|
81
|
+
|
|
82
|
+
## App shell — `.fdy-app`
|
|
83
|
+
The frame every application lives in. Do not hand-roll one from flexbox: the responsive sidebar,
|
|
84
|
+
off-canvas drawer and backdrop are built in.
|
|
85
|
+
|
|
86
|
+
- Parts: `__sidebar` `__brand` (`__brand-mark` `__brand-text` `__brand-title` `__brand-subtitle`)
|
|
87
|
+
`__content` `__topbar` `__navtoggle` `__title` `__main` `__backdrop`
|
|
88
|
+
- Modifiers: `--nav-open` (mobile drawer open, ≤720px) · `--nav-collapsed` (collapse to zero width,
|
|
89
|
+
≥721px) · `--static` (embed the shell in a page instead of filling the viewport)
|
|
90
|
+
- Also: `.fdy-skip` — the skip-to-content link, first child of the shell.
|
|
91
|
+
- JS: none. Toggle `--nav-collapsed` (≥721px) or `--nav-open` (≤720px) on the root from
|
|
92
|
+
`__navtoggle`'s click; clear `--nav-open` when `__backdrop` is clicked.
|
|
93
|
+
|
|
94
|
+
**The nesting is fixed, not free-form:** `.fdy-app` is a flex **row** of `[__sidebar | __content]`,
|
|
95
|
+
and `__content` is the column holding `__topbar` + `__main` — it exists to give the sticky topbar a
|
|
96
|
+
tall containing block. The brand goes in the **sidebar** (sized to match the topbar height), and
|
|
97
|
+
`__main` already carries the page padding, so don't wrap your screen in another padded box.
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<div class="fdy-app">
|
|
101
|
+
<a class="fdy-skip" href="#main">Skip to content</a>
|
|
102
|
+
|
|
103
|
+
<aside class="fdy-app__sidebar">
|
|
104
|
+
<a class="fdy-app__brand" href="/">
|
|
105
|
+
<span class="fdy-app__brand-mark"><!--logo--></span>
|
|
106
|
+
<span class="fdy-app__brand-text"><span class="fdy-app__brand-title">Acme</span></span>
|
|
107
|
+
</a>
|
|
108
|
+
<nav class="fdy-nav"><!--nav items--></nav>
|
|
109
|
+
</aside>
|
|
110
|
+
|
|
111
|
+
<div class="fdy-app__content">
|
|
112
|
+
<header class="fdy-app__topbar">
|
|
113
|
+
<button class="fdy-app__navtoggle" type="button" aria-label="Toggle navigation"><!--svg--></button>
|
|
114
|
+
<h1 class="fdy-app__title">Invoices</h1><!-- auto-spacer: pushes what follows right -->
|
|
115
|
+
<!-- topbar actions -->
|
|
116
|
+
</header>
|
|
117
|
+
<main class="fdy-app__main" id="main"><!-- .fdy-page goes here --></main>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="fdy-app__backdrop"></div>
|
|
121
|
+
</div>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Page composition — `.fdy-page`
|
|
125
|
+
Vertical section rhythm (`--space-8` between children). Assemble every screen from these; they
|
|
126
|
+
carry the doctrine in markup.
|
|
127
|
+
|
|
128
|
+
- `.fdy-page` — the screen body
|
|
129
|
+
- `.fdy-page__header` — `.fdy-page__heading` (eyebrow + page title + `.fdy-page__desc`) on the
|
|
130
|
+
left, `.fdy-page__actions` (**one** primary button) on the right
|
|
131
|
+
- `.fdy-page-section` — a region: `.fdy-page-section__head` + body, `--space-4` rhythm
|
|
132
|
+
- `.fdy-toolbar` — a horizontal control row; `__spacer` (flex filler), `__search` (capped width).
|
|
133
|
+
It is `align-items:center`, so it is for **bare** controls. A `.fdy-field` with a *visible* label
|
|
134
|
+
sits half a label-height low in it — labelled fields belong in `.fdy-filterbar`
|
|
135
|
+
(`align-items:flex-end`). In a toolbar, hide the label with `.fdy-visually-hidden`.
|
|
136
|
+
- `.fdy-stats` — a KPI grid of `.fdy-stat` (`__label` `__value` `__meta`). Deliberately **not**
|
|
137
|
+
cards. `.fdy-stats--boxed` for one shared surface with dividers.
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<div class="fdy-page">
|
|
141
|
+
<header class="fdy-page__header">
|
|
142
|
+
<div class="fdy-page__heading">
|
|
143
|
+
<p class="fdy-eyebrow">Sales</p>
|
|
144
|
+
<h1 class="fdy-title-page">Invoices</h1>
|
|
145
|
+
<p class="fdy-page__desc">Every invoice issued this period.</p>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="fdy-page__actions"><button class="fdy-btn">New invoice</button></div>
|
|
148
|
+
</header>
|
|
149
|
+
|
|
150
|
+
<section class="fdy-page-section">
|
|
151
|
+
<div class="fdy-stats">
|
|
152
|
+
<div class="fdy-stat">
|
|
153
|
+
<span class="fdy-stat__label">Outstanding</span>
|
|
154
|
+
<span class="fdy-stat__value">1,240<small> M</small></span>
|
|
155
|
+
<span class="fdy-stat__meta">12 invoices</span>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</section>
|
|
159
|
+
|
|
160
|
+
<section class="fdy-page-section">
|
|
161
|
+
<div class="fdy-page-section__head">
|
|
162
|
+
<h2 class="fdy-title-section">All invoices</h2>
|
|
163
|
+
<div class="fdy-toolbar"><!-- filters, actions --></div>
|
|
164
|
+
</div>
|
|
165
|
+
<!-- component: table, cards, chart… -->
|
|
166
|
+
</section>
|
|
167
|
+
</div>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Type roles & text
|
|
171
|
+
One role per level of hierarchy — never re-use a card title for a page title.
|
|
172
|
+
|
|
173
|
+
| Class | Element | Use |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `.fdy-eyebrow` | `<p>` | Small uppercase label above a title. Optional. |
|
|
176
|
+
| `.fdy-title-page` | `<h1>` | One per screen. |
|
|
177
|
+
| `.fdy-title-section` | `<h2>` | A region inside the page. |
|
|
178
|
+
| `.fdy-title-card` | `<h3>` | A title inside a card or row (`.fdy-card__title` is equivalent). |
|
|
179
|
+
| `.fdy-text-muted` · `.fdy-text-subtle` | any | Secondary / tertiary text colour. |
|
|
180
|
+
| `.fdy-text-caption` | `<p>` | Small muted text: timestamps, help. |
|
|
181
|
+
| `.fdy-mono` | any | Tabular/monospace data (codes, amounts, ids). |
|
|
182
|
+
|
|
183
|
+
## Utilities
|
|
184
|
+
`.fdy-hidden` · `.fdy-visually-hidden` (screen-reader-only) · `.fdy-list-reset` (strip native
|
|
185
|
+
bullets/indent from a semantic list — needed when your utility framework's preflight is off) ·
|
|
186
|
+
`.fdy-divider` (+`--vertical`) · `.fdy-kbd`.
|
|
187
|
+
|
|
188
|
+
Responsive display: `.fdy-hide-below-sm|md|lg` · `.fdy-hide-above-sm|md|lg`.
|
|
189
|
+
Breakpoints (min-width): `sm` 600 · `md` 960 · `lg` 1280 · `xl` 1920 — importable as
|
|
190
|
+
`import { breakpoints } from '@cahyo-dimas/freeday/breakpoints'`.
|
|
191
|
+
|
|
192
|
+
**`breakpoints.nav` (721) is separate and is the one the shell uses.** `.fdy-app` switches the
|
|
193
|
+
sidebar from off-canvas drawer to static column at 721px, *not* at `md`. Any `matchMedia` guard or
|
|
194
|
+
utility variant that has to agree with the shell must use `nav`; using `md` leaves 721–959px broken
|
|
195
|
+
(sidebar already static while your script still treats it as an overlay).
|
|
196
|
+
|
|
197
|
+
Density: `data-density="compact"` works on `<html>` **or any wrapper** — the selector is a bare
|
|
198
|
+
`[data-density="compact"]` over inheriting custom properties, so one screen can be dense while the
|
|
199
|
+
rest of the app is not.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
# Actions
|
|
204
|
+
|
|
205
|
+
## Button — `.fdy-btn`
|
|
206
|
+
**The base class is already the primary action** — there is no `--primary`. Use it once per screen.
|
|
207
|
+
|
|
208
|
+
- Modifiers: `--ghost` `--danger` `--text` `--icon` (square, icon-only) `--sm` `--lg`
|
|
209
|
+
- Parts: `.fdy-btn__icon` (wraps the `<svg>`) · `.fdy-btn-group` (segmented row, `role="group"` +
|
|
210
|
+
`aria-label`)
|
|
211
|
+
- A11y: `--icon` **requires** `aria-label`. Use `<button type="button">` unless it submits.
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<button class="fdy-btn"><span class="fdy-btn__icon"><!--svg--></span>Save</button>
|
|
215
|
+
<button class="fdy-btn fdy-btn--ghost">Cancel</button>
|
|
216
|
+
<button class="fdy-btn fdy-btn--ghost fdy-btn--icon" type="button" aria-label="Settings">
|
|
217
|
+
<span class="fdy-btn__icon"><!--svg--></span>
|
|
218
|
+
</button>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## FAB — `.fdy-fab`
|
|
222
|
+
Floating circular action. Modifiers: `--sm` `--accent` `--danger` `--extended` (pill with a label).
|
|
223
|
+
Always `aria-label` unless `--extended` carries visible text.
|
|
224
|
+
|
|
225
|
+
## Menu & split button — `.fdy-menu`
|
|
226
|
+
WAI-ARIA APG menu-button. Keyboard ↑/↓/Home/End/Esc handled by the enhancer.
|
|
227
|
+
|
|
228
|
+
- Wrapper: `.fdy-menu-wrap` (or `.fdy-btn-split` for a split button, with
|
|
229
|
+
`.fdy-btn-split__toggle` as the second button) + `data-fdy-menu`
|
|
230
|
+
- List: `.fdy-menu` (`--end` aligns right), items `.fdy-menu__item` (`--danger`), separator
|
|
231
|
+
`<hr class="fdy-menu__sep">`
|
|
232
|
+
- A11y: trigger gets `aria-haspopup="menu"` + `aria-expanded="false"`; `<ul role="menu" hidden>`;
|
|
233
|
+
each `<li role="none">` wraps a `<button role="menuitem">`.
|
|
234
|
+
|
|
235
|
+
```html
|
|
236
|
+
<div class="fdy-menu-wrap" data-fdy-menu>
|
|
237
|
+
<button class="fdy-btn fdy-btn--ghost" aria-haspopup="menu" aria-expanded="false">Actions</button>
|
|
238
|
+
<ul class="fdy-menu" role="menu" hidden>
|
|
239
|
+
<li role="none"><button class="fdy-menu__item" role="menuitem">Edit</button></li>
|
|
240
|
+
<li role="none"><hr class="fdy-menu__sep"></li>
|
|
241
|
+
<li role="none"><button class="fdy-menu__item fdy-menu__item--danger" role="menuitem">Delete</button></li>
|
|
242
|
+
</ul>
|
|
243
|
+
</div>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
# Forms
|
|
249
|
+
|
|
250
|
+
## Field, label, input — `.fdy-field`
|
|
251
|
+
The field wrapper owns the vertical rhythm; `<label class="fdy-field">` when it wraps a single
|
|
252
|
+
native control, otherwise a `<div>` + explicitly associated label.
|
|
253
|
+
|
|
254
|
+
- `.fdy-field` (+ `--full` inside `.fdy-form-grid`; widths `--w-sm` `--w-lg` `--w-xl` `--w-2xl`
|
|
255
|
+
`--w-grow` inside `.fdy-filterbar`)
|
|
256
|
+
- `.fdy-label` · `.fdy-input` (+`--error`) · `.fdy-textarea` · `.fdy-help` (+`--error`)
|
|
257
|
+
- `[readonly]` is styled on input/textarea: full contrast, focusable, copyable.
|
|
258
|
+
|
|
259
|
+
```html
|
|
260
|
+
<label class="fdy-field">
|
|
261
|
+
<span class="fdy-label">Customer</span>
|
|
262
|
+
<input class="fdy-input" aria-describedby="cust-help">
|
|
263
|
+
<span class="fdy-help" id="cust-help">As printed on the document.</span>
|
|
264
|
+
</label>
|
|
265
|
+
|
|
266
|
+
<label class="fdy-field">
|
|
267
|
+
<span class="fdy-label">Email</span>
|
|
268
|
+
<input class="fdy-input" aria-invalid="true" aria-describedby="email-err">
|
|
269
|
+
<span class="fdy-help fdy-help--error" id="email-err">Not a valid email address.</span>
|
|
270
|
+
</label>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Input group — `.fdy-input-group`
|
|
274
|
+
Prefix/suffix addons around an input: text (`Rp`, `%`), a decorative icon
|
|
275
|
+
(`__addon--icon`), or an action button (`__btn`).
|
|
276
|
+
|
|
277
|
+
- `.fdy-input-group` · `__addon` (+`--icon`) · `__btn`
|
|
278
|
+
|
|
279
|
+
```html
|
|
280
|
+
<div class="fdy-input-group">
|
|
281
|
+
<span class="fdy-input-group__addon">Rp</span>
|
|
282
|
+
<input class="fdy-input" inputmode="numeric" aria-label="Amount">
|
|
283
|
+
</div>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Checkbox · radio · switch
|
|
287
|
+
Native inputs, styled. `.fdy-check` · `.fdy-radio` · `.fdy-switch` on the wrapping `<label>`;
|
|
288
|
+
`.fdy-checkbox` on a bare `<input type="checkbox">` used inside tables/trees.
|
|
289
|
+
|
|
290
|
+
```html
|
|
291
|
+
<label class="fdy-check"><input type="checkbox" checked> Email me a copy</label>
|
|
292
|
+
<label class="fdy-switch"><input type="checkbox"> Enable notifications</label>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Select / combobox — `.fdy-combo`
|
|
296
|
+
Fully styleable dropdown, APG combobox+listbox. Needs `freeday-select.js`.
|
|
297
|
+
|
|
298
|
+
- `.fdy-combo` (+`--error`, `--no-icon`) · `__button` `__value` (+`--placeholder`) `__listbox`
|
|
299
|
+
`__option` `__check` `__icon` (custom trailing icon, with `--no-icon`)
|
|
300
|
+
|
|
301
|
+
```html
|
|
302
|
+
<div class="fdy-field">
|
|
303
|
+
<span class="fdy-label" id="st-l">Status</span>
|
|
304
|
+
<div class="fdy-combo" data-fdy-combo data-value="pending">
|
|
305
|
+
<button type="button" class="fdy-combo__button" role="combobox" aria-haspopup="listbox"
|
|
306
|
+
aria-expanded="false" aria-labelledby="st-l st-v">
|
|
307
|
+
<span class="fdy-combo__value" id="st-v">Pending</span>
|
|
308
|
+
</button>
|
|
309
|
+
<ul class="fdy-combo__listbox" role="listbox" aria-labelledby="st-l" hidden>
|
|
310
|
+
<li class="fdy-combo__option" role="option" data-value="paid" aria-selected="false"><span class="fdy-combo__check"></span>Paid</li>
|
|
311
|
+
<li class="fdy-combo__option" role="option" data-value="pending" aria-selected="true"><span class="fdy-combo__check">✓</span>Pending</li>
|
|
312
|
+
</ul>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Autocomplete — `.fdy-autocomplete`
|
|
318
|
+
Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
319
|
+
|
|
320
|
+
- `.fdy-autocomplete` · `__listbox` `__option` `__empty`
|
|
321
|
+
- The `<input>` carries `role="combobox"`, `aria-expanded`, `aria-autocomplete="list"`,
|
|
322
|
+
`aria-controls` → the listbox `id`, `autocomplete="off"`.
|
|
323
|
+
|
|
324
|
+
## Cascade select — `.fdy-cascade`
|
|
325
|
+
Hierarchical drill-down. The data model is a **nested `<ul>`** inside the wrapper: an `<li>` with a
|
|
326
|
+
child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
327
|
+
|
|
328
|
+
- `.fdy-cascade` (+`--error`) · `__trigger` `__value` (+`--placeholder`) `__panel` `__head`
|
|
329
|
+
`__back` `__crumb` `__list` `__opt` `__opt-label` `__opt-arrow`
|
|
330
|
+
- Author only the wrapper + nested list; the enhancer builds the trigger and panel.
|
|
331
|
+
|
|
332
|
+
```html
|
|
333
|
+
<div data-fdy-cascade data-label="Category" data-placeholder="Pick a category">
|
|
334
|
+
<ul>
|
|
335
|
+
<li data-value="electronics">Electronics
|
|
336
|
+
<ul><li data-value="phones">Phones</li><li data-value="laptops">Laptops</li></ul>
|
|
337
|
+
</li>
|
|
338
|
+
<li data-value="other">Other</li>
|
|
339
|
+
</ul>
|
|
340
|
+
</div>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Choose-from-list (CFL) — `data-fdy-cfl`
|
|
344
|
+
A read-only field backed by master data: the button opens a searchable dialog, the picked row fills
|
|
345
|
+
the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
346
|
+
|
|
347
|
+
- Field: `.fdy-input-group` + `data-fdy-cfl="<dialog id>"`, `data-fdy-cfl-display="code"`,
|
|
348
|
+
`data-fdy-cfl-summary="{n} selected"` (multi)
|
|
349
|
+
- Dialog: `<dialog class="fdy-modal fdy-modal--cfl">` + `data-fdy-cfl-multiple` for multi-select
|
|
350
|
+
- Inside: `.fdy-cfl__search` `__results` `__row` `__check` `__empty` `__count` `__actions`
|
|
351
|
+
- Hooks: `data-fdy-cfl-search` (the search input), `data-fdy-cfl-empty`, `data-fdy-cfl-count`,
|
|
352
|
+
`data-fdy-cfl-confirm` (multi only), `data-fdy-cfl-trigger`, `data-fdy-cfl-open`
|
|
353
|
+
- Rows are server state — in a real app drive them from a controlled fetch, not a global store.
|
|
354
|
+
|
|
355
|
+
```html
|
|
356
|
+
<div class="fdy-field">
|
|
357
|
+
<span class="fdy-label" id="c-l">Customer code</span>
|
|
358
|
+
<div class="fdy-input-group" data-fdy-cfl="dlg-cust" data-fdy-cfl-display="code">
|
|
359
|
+
<input class="fdy-input" aria-labelledby="c-l" placeholder="Pick a customer…" readonly>
|
|
360
|
+
<button class="fdy-input-group__btn" type="button" aria-haspopup="dialog" aria-label="Pick a customer"><!--svg--></button>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<dialog class="fdy-modal fdy-modal--cfl" id="dlg-cust" aria-labelledby="dlg-cust-t">
|
|
365
|
+
<div class="fdy-modal__header">
|
|
366
|
+
<h3 class="fdy-modal__title" id="dlg-cust-t">Pick a customer</h3>
|
|
367
|
+
<button class="fdy-modal__close" aria-label="Close" data-close>×</button>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="fdy-modal__body">
|
|
370
|
+
<div class="fdy-cfl__search">
|
|
371
|
+
<div class="fdy-input-group">
|
|
372
|
+
<span class="fdy-input-group__addon fdy-input-group__addon--icon"><!--svg--></span>
|
|
373
|
+
<input class="fdy-input" type="search" aria-label="Search customers" data-fdy-cfl-search>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="fdy-cfl__results">
|
|
377
|
+
<table class="fdy-table">
|
|
378
|
+
<thead><tr><th scope="col">Code</th><th scope="col">Name</th></tr></thead>
|
|
379
|
+
<tbody>
|
|
380
|
+
<tr class="fdy-cfl__row" data-code="C-1001" data-name="Acme Ltd"><td>C-1001</td><td>Acme Ltd</td></tr>
|
|
381
|
+
</tbody>
|
|
382
|
+
</table>
|
|
383
|
+
<p class="fdy-cfl__empty" data-fdy-cfl-empty hidden>No match.</p>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<div class="fdy-modal__footer">
|
|
387
|
+
<span class="fdy-cfl__count">Click a row to pick</span>
|
|
388
|
+
<div class="fdy-cfl__actions"><button class="fdy-btn fdy-btn--ghost" type="button" data-close>Close</button></div>
|
|
389
|
+
</div>
|
|
390
|
+
</dialog>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Date picker — `data-fdy-datepicker`
|
|
394
|
+
Input-styled trigger + calendar popover. Needs `freeday-datepicker.js`. **Author an empty `<div>`**
|
|
395
|
+
— the enhancer builds everything.
|
|
396
|
+
|
|
397
|
+
- `.fdy-datepicker` (+`--error`) · `__trigger` `__value` (+`--placeholder`) `__icon` `__clear`
|
|
398
|
+
`__panel`; calendar internals `.fdy-cal__head` `__nav` `__title` `__grid` `__dow` `__day`
|
|
399
|
+
- Range: wrap two pickers in `.fdy-daterange` + `data-fdy-daterange` (`role="group"`), children
|
|
400
|
+
get `data-role="from"` / `"to"`; separator `.fdy-daterange__sep`. The end can't precede the start.
|
|
401
|
+
- Attributes: `data-value="YYYY-MM-DD"`, `data-placeholder`, `data-label`, `data-fdy-no-icon`
|
|
402
|
+
|
|
403
|
+
```html
|
|
404
|
+
<div class="fdy-field">
|
|
405
|
+
<span class="fdy-label">Invoice date</span>
|
|
406
|
+
<div data-fdy-datepicker data-value="2026-07-21" data-label="Invoice date"></div>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<div class="fdy-daterange" data-fdy-daterange role="group" aria-label="Reporting period">
|
|
410
|
+
<div data-fdy-datepicker data-role="from" data-placeholder="From" data-label="Start date"></div>
|
|
411
|
+
<span class="fdy-daterange__sep" aria-hidden="true">–</span>
|
|
412
|
+
<div data-fdy-datepicker data-role="to" data-placeholder="To" data-label="End date"></div>
|
|
413
|
+
</div>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Time picker — `data-fdy-timepicker`
|
|
417
|
+
24-hour `HH:MM` listbox. `data-step` (minutes, default 30), `data-min`, `data-max`. Needs
|
|
418
|
+
`freeday-timepicker.js`. Classes: `.fdy-timepicker` (+`--error`) · `__trigger` `__value`
|
|
419
|
+
(+`--placeholder`) `__icon` `__panel` `__opt`.
|
|
420
|
+
|
|
421
|
+
## Datetime picker — `data-fdy-datetimepicker`
|
|
422
|
+
A **composition** of the two above, not a monolith: nest one datepicker and one timepicker inside
|
|
423
|
+
the wrapper. `freeday-datetime.js` joins them and emits one `fdy-datetime-change` with
|
|
424
|
+
`YYYY-MM-DDTHH:MM`. Class `.fdy-datetimepicker`.
|
|
425
|
+
|
|
426
|
+
## File upload — `.fdy-dropzone`
|
|
427
|
+
Click/Enter opens the file dialog; drop works too. Needs `freeday-upload.js`.
|
|
428
|
+
|
|
429
|
+
- `.fdy-dropzone` (+`--row`) · `__icon` `__text` `__title` `__hint`; contains a hidden
|
|
430
|
+
`<input type="file">`
|
|
431
|
+
- List: `.fdy-filelist` (+`--grid`) of `.fdy-file` (+`--success`, `--error`) · `__icon` `__meta`
|
|
432
|
+
`__name` `__sub` `__progress` `__remove`
|
|
433
|
+
- Attributes: `data-max-size` (bytes), `data-filelist="#id"`
|
|
434
|
+
- A11y: the dropzone is `role="button" tabindex="0"` + `aria-label`.
|
|
435
|
+
- Wire `fdy-upload-add` to your real upload; render explicit progress/success/error state.
|
|
436
|
+
|
|
437
|
+
## Form validation — `data-fdy-validate`
|
|
438
|
+
Native Constraint Validation wired to accessible inline errors: `aria-invalid` +
|
|
439
|
+
`aria-describedby`, focus to the first invalid field on submit, live re-validation on
|
|
440
|
+
blur/input. Needs `freeday-form.js`.
|
|
441
|
+
|
|
442
|
+
- Per-control messages: `data-fdy-msg-required`, `-type`, `-minlength`, `-min`, `-mismatch`, …
|
|
443
|
+
- Cross-field: `data-fdy-match="#otherFieldId"`
|
|
444
|
+
- Emits `fdy-form-valid` / `fdy-form-invalid`.
|
|
445
|
+
|
|
446
|
+
## Password & mask
|
|
447
|
+
`data-fdy-password` on `<input type="password">` adds a reveal toggle (reusing the input-group
|
|
448
|
+
button chrome). `data-fdy-mask="####-####"` formats while typing: `#` digit, `A` letter, `*`
|
|
449
|
+
alphanumeric, anything else is a literal. Needs `freeday-mask.js`.
|
|
450
|
+
|
|
451
|
+
## Form grid — `.fdy-form-grid`
|
|
452
|
+
Responsive two-column document header. Children are `.fdy-field`; `.fdy-field--full` spans both.
|
|
453
|
+
|
|
454
|
+
## Filter bar — `.fdy-filterbar`
|
|
455
|
+
A consistent filter row of `.fdy-field`s with a width rhythm (`--w-sm` · default · `--w-lg` ·
|
|
456
|
+
`--w-xl` · `--w-2xl`), one `--w-grow` field (usually search) absorbing the slack, and
|
|
457
|
+
`.fdy-filterbar__actions` pinned at the end. `--actions-inline` keeps the actions on the control
|
|
458
|
+
line. Zero JS. Composes with `.fdy-table-toolbar` on a shared baseline.
|
|
459
|
+
|
|
460
|
+
## Slider — `.fdy-slider`
|
|
461
|
+
Native `<input type="range">` themed via `accent-color`; `freeday-slider.js` only mirrors the value
|
|
462
|
+
into an `<output>`. Modifiers `--accent` `--danger`. Wrapper `.fdy-slider-field` + `__head`,
|
|
463
|
+
value `.fdy-slider__value` (`data-fdy-prefix` / `data-fdy-suffix`), scale `.fdy-slider__scale`.
|
|
464
|
+
|
|
465
|
+
## Rating — `.fdy-rating`
|
|
466
|
+
A native radio group (arrow keys, form-associated). Filled vs empty differ by **fill, not colour
|
|
467
|
+
alone**. Modifiers `--sm` `--readonly` `--disabled` `--error`; star `.fdy-rating__star`. Read-only
|
|
468
|
+
variant is `role="img"` + `aria-label`; each interactive star needs a `.fdy-visually-hidden` label.
|
|
469
|
+
|
|
470
|
+
## Stepper / wizard — `.fdy-stepper`
|
|
471
|
+
Linear multi-step flow: marker → check, one panel at a time, back/next. Needs
|
|
472
|
+
`freeday-stepper.js`.
|
|
473
|
+
|
|
474
|
+
- `.fdy-stepper` (`<ol>`) of `.fdy-step` (`is-active` / `is-complete` set by JS) ·
|
|
475
|
+
`.fdy-step__btn` `__marker` `__label`
|
|
476
|
+
- `.fdy-step-panels` wrapping one `.fdy-step-panel` per step (`hidden` on the inactive ones)
|
|
477
|
+
- `.fdy-step-nav` with `data-fdy-step-prev` / `data-fdy-step-next` buttons
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
# Data
|
|
482
|
+
|
|
483
|
+
## Table — `.fdy-table`
|
|
484
|
+
Semantic static table. Wrap in `.fdy-table-wrap` (bordered surface) or `.fdy-table-scroll`
|
|
485
|
+
(horizontal scroll). Always a `<caption>` (use `.fdy-visually-hidden` if it shouldn't show) and
|
|
486
|
+
`<th scope="col">`. Numeric cells get `.fdy-table__num` (right-aligned, tabular).
|
|
487
|
+
|
|
488
|
+
```html
|
|
489
|
+
<div class="fdy-table-wrap">
|
|
490
|
+
<table class="fdy-table">
|
|
491
|
+
<caption>Recent invoices</caption>
|
|
492
|
+
<thead><tr><th scope="col">Invoice</th><th scope="col" class="fdy-table__num">Amount</th></tr></thead>
|
|
493
|
+
<tbody>
|
|
494
|
+
<tr><td>INV-1042</td><td class="fdy-table__num">1,240,000</td></tr>
|
|
495
|
+
</tbody>
|
|
496
|
+
</table>
|
|
497
|
+
</div>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Data table — `.fdy-datatable`
|
|
501
|
+
The interactive table: global search, sort, per-column filters, row selection + bulk bar,
|
|
502
|
+
pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` + `data-fdy-table`
|
|
503
|
+
(`data-page-size="N"`).
|
|
504
|
+
|
|
505
|
+
- Toolbar: `.fdy-table-toolbar` · `__search` `__spacer` `__count` (hooks `data-fdy-table-search`,
|
|
506
|
+
`data-fdy-table-count`)
|
|
507
|
+
- Bulk bar: `.fdy-table-bulkbar` · `__count` `__spacer` `__actions` (hooks `data-fdy-table-bulk`,
|
|
508
|
+
`data-fdy-table-bulk-count`, `data-fdy-table-bulk-clear`)
|
|
509
|
+
- Header cells: `<button class="fdy-table__sortbtn" data-fdy-sort>` (`data-fdy-sort="number"` for
|
|
510
|
+
numeric); `data-fdy-filter="text|enum|number"` on the `<th>` adds the funnel
|
|
511
|
+
(`.fdy-table__filterbtn` / `__filterwrap` → panel `.fdy-filter` · `__title` `__list` `__check`
|
|
512
|
+
`__range` `__foot`)
|
|
513
|
+
- Selection: `.fdy-table__selcol` cells with `data-fdy-select-all` / `data-fdy-row-select`
|
|
514
|
+
- Rows: `.fdy-table__row--activatable` (clickable rows), `.fdy-table__detailrow` (expandable
|
|
515
|
+
detail), `.fdy-table__state` (in-table empty/loading row)
|
|
516
|
+
- Footer: `.fdy-table-footer` · `__info` (`data-fdy-table-info`) + `<nav class="fdy-pagination"
|
|
517
|
+
data-fdy-table-pagination>`
|
|
518
|
+
- Sort values: put the raw value in `data-sort-value` when the cell text is formatted.
|
|
519
|
+
- **Language caveat:** the vanilla enhancer writes the footer and bulk-count strings in Indonesian
|
|
520
|
+
(`Menampilkan 1–5 dari 7`, `N dipilih`) and has no override hook. The Vue/React/Blazor `FdyTable`
|
|
521
|
+
components are English. For an English static page, render those two nodes yourself from the
|
|
522
|
+
`fdy-table-change` event instead of using `data-fdy-table-info` / `data-fdy-table-bulk-count`.
|
|
523
|
+
|
|
524
|
+
## Pagination — `.fdy-pagination`
|
|
525
|
+
The block class on the `<nav>` is a **structural hook only** — it carries no rule of its own; the
|
|
526
|
+
`__list` / `__link` / `__ellipsis` elements do all the styling, and the data table targets
|
|
527
|
+
`data-fdy-table-pagination`. Keep it on the wrapper anyway, for consistency with the rest of the kit.
|
|
528
|
+
|
|
529
|
+
`<nav class="fdy-pagination" aria-label="Pagination">` → `<ul class="fdy-pagination__list">`;
|
|
530
|
+
each item is a `.fdy-pagination__link` (`<button>` when navigable, `<span aria-current="page">`
|
|
531
|
+
for the current page, `<span aria-disabled="true">` for a dead arrow) and
|
|
532
|
+
`.fdy-pagination__ellipsis` for the gap.
|
|
533
|
+
|
|
534
|
+
## States — `.fdy-state`
|
|
535
|
+
Empty / error placeholder for a data area. `--danger` for failures. Parts: `__icon`
|
|
536
|
+
(`aria-hidden="true"`) `__title` `__text` `__actions`. Put it inside the card or table area it
|
|
537
|
+
replaces.
|
|
538
|
+
|
|
539
|
+
## Charts — `data-fdy-chart`
|
|
540
|
+
Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`; call
|
|
541
|
+
`FreedayChart.update(el)` after changing data (or use `FdyChart` in Vue/React/Blazor).
|
|
542
|
+
|
|
543
|
+
- Types: `sparkline` `bar` `line` `area` `donut`
|
|
544
|
+
- Roots: `.fdy-sparkline` · `.fdy-bars` · `.fdy-donut` · `.fdy-chart-xy` (built for line/area/bar)
|
|
545
|
+
- Data: `data-values="1,2,3"` (single series) or `data-series='[{"label":"A","values":[…]}]'`
|
|
546
|
+
(multi), `data-labels="Jan,Feb"`, `data-fdy-stacked`, `data-fdy-center` (donut)
|
|
547
|
+
- Colour: `data-fdy-color="primary"` or `data-fdy-colors="success,warning,danger"` — semantic token
|
|
548
|
+
names **or** `chart-1`…`chart-8` slots to pin a category's colour. Multi-series defaults to the
|
|
549
|
+
validated categorical palette `--chart-1`…`--chart-8`.
|
|
550
|
+
- Format: `data-fdy-format="number|percent|currency"`; legend `data-fdy-legend` (`none` to drop);
|
|
551
|
+
axes `data-fdy-axes`
|
|
552
|
+
- A11y: every chart is `role="img"` + `aria-label`, with a `<table>` fallback inside the element.
|
|
553
|
+
- Internal parts (set by JS, useful for overrides): `.fdy-chart__legend` (+`--row`) `__swatch`
|
|
554
|
+
`__tip`; `.fdy-bars__col` `__bar` `__track` `__label` `__val`; `.fdy-chart-xy__plot` `__grid`
|
|
555
|
+
`__tick` `__xlabel` `__line` `__area` `__bar` `__dot` `__band`; `.fdy-donut__ring` `__seg`
|
|
556
|
+
`__center` `__hit`; `.fdy-sparkline__line` `__area` `__dot`.
|
|
557
|
+
|
|
558
|
+
## Tree view — `.fdy-tree`
|
|
559
|
+
Native `<details>` hierarchy — zero JS for expand/collapse. `.fdy-tree__branch` on `<details>`,
|
|
560
|
+
`.fdy-tree__leaf` on a leaf `<li>`, plus `__chevron` and `__icon` svgs inside `<summary>`.
|
|
561
|
+
`.fdy-tree--checkbox` + `data-fdy-tree` (needs `freeday-tree.js`) adds cascading selection:
|
|
562
|
+
`.fdy-tree__check` on each `<input class="fdy-checkbox">`; checking a branch checks its children,
|
|
563
|
+
a partial set makes the branch indeterminate. Checking never toggles expansion.
|
|
564
|
+
|
|
565
|
+
## List (flat rows) — `.fdy-list`
|
|
566
|
+
The **flat** row container: one bordered surface, hairline dividers, **no shadow**. This is what a
|
|
567
|
+
responsive `.fdy-datatable` should become below `md` — not a stack of `.fdy-card`s, which carries
|
|
568
|
+
`--shadow-lift` and turns ten rows into ten floating objects.
|
|
569
|
+
|
|
570
|
+
- `.fdy-list` on a `<ul>`/`<ol>` (list-style is reset for you) or a `<div>`
|
|
571
|
+
- `.fdy-list__row` — one row; `--interactive` for hover feedback, `--button` when the row **is** the
|
|
572
|
+
control (render it as a real `<button>`/`<a>`; the UA box is reset without losing the list surface)
|
|
573
|
+
- Row internals: `.fdy-list__main` (truncating stack) → `.fdy-list__title` + `.fdy-list__meta`, and
|
|
574
|
+
`.fdy-list__aside` pinned right
|
|
575
|
+
- Not `.fdy-list-reset` — that utility only strips UA bullets/indent from a semantic list.
|
|
576
|
+
|
|
577
|
+
```html
|
|
578
|
+
<ul class="fdy-list">
|
|
579
|
+
<li>
|
|
580
|
+
<button type="button" class="fdy-list__row fdy-list__row--button">
|
|
581
|
+
<span class="fdy-list__main">
|
|
582
|
+
<span class="fdy-list__title">Northwind Trading</span>
|
|
583
|
+
<span class="fdy-list__meta">INV-2042 · due 12 Jul <span class="fdy-badge fdy-badge--warning">Overdue</span></span>
|
|
584
|
+
</span>
|
|
585
|
+
<span class="fdy-list__aside fdy-mono">290,000</span>
|
|
586
|
+
</button>
|
|
587
|
+
</li>
|
|
588
|
+
</ul>
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Description list — `.fdy-dl`
|
|
592
|
+
Key–value detail view: `<dl class="fdy-dl">` with `<dt>`/`<dd>` pairs. `--rows` for a row-per-pair
|
|
593
|
+
layout.
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
# Feedback
|
|
598
|
+
|
|
599
|
+
## Alert — `.fdy-alert`
|
|
600
|
+
Inline message. Modifiers `--info` `--success` `--warning` `--danger`. Parts `__icon` `__body`
|
|
601
|
+
`__title` `__text` `__close`. The danger variant takes `role="alert"`.
|
|
602
|
+
|
|
603
|
+
```html
|
|
604
|
+
<div class="fdy-alert fdy-alert--danger" role="alert">
|
|
605
|
+
<div class="fdy-alert__body">
|
|
606
|
+
<span class="fdy-alert__title">Failed</span>
|
|
607
|
+
<span class="fdy-alert__text">Connection to the server was lost.</span>
|
|
608
|
+
</div>
|
|
609
|
+
<button class="fdy-alert__close" type="button" aria-label="Dismiss">×</button>
|
|
610
|
+
</div>
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Toast — `Freeday.toast()`
|
|
614
|
+
Transient notification in a live region. Imperative only:
|
|
615
|
+
|
|
616
|
+
```js
|
|
617
|
+
const node = Freeday.toast({ variant: 'success', title: 'Saved', message: 'INV-1042 saved.' })
|
|
618
|
+
Freeday.toast({ variant: 'danger', title: 'Failed', message: '…', key: 'net-fail' }) // same key replaces
|
|
619
|
+
Freeday.dismiss('net-fail') // or Freeday.dismiss(node)
|
|
620
|
+
```
|
|
621
|
+
Every field is optional; it returns the toast element. Classes (rendered for you):
|
|
622
|
+
`.fdy-toast-region`, `.fdy-toast` (+`--info` `--success` `--warning` `--danger`) · `__accent`
|
|
623
|
+
`__body` `__title` `__text` `__close`.
|
|
624
|
+
|
|
625
|
+
## Tooltip — `.fdy-tooltip`
|
|
626
|
+
Hover/focus only, never the sole carrier of information. Wrap in `.fdy-tooltip-wrap`; the trigger
|
|
627
|
+
gets `aria-describedby` → the `.fdy-tooltip[role="tooltip"]` `id`.
|
|
628
|
+
|
|
629
|
+
## Progress & spinner
|
|
630
|
+
`.fdy-progress` + `.fdy-progress__bar` (set `style="width:N%"`); `--indeterminate` for unknown
|
|
631
|
+
duration. `role="progressbar"` with `aria-valuenow/min/max` (omit `valuenow` when indeterminate).
|
|
632
|
+
`.fdy-spinner` (+`--sm` `--lg`) with `role="status"` + `aria-label`.
|
|
633
|
+
|
|
634
|
+
## Skeleton — `.fdy-skeleton`
|
|
635
|
+
Size-matched placeholders so nothing shifts when data lands: `--title` `--text` `--circle`
|
|
636
|
+
`--avatar` (+`--avatar-sm` `--avatar-lg`, exactly `.fdy-avatar`'s box).
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
# Navigation
|
|
641
|
+
|
|
642
|
+
## Nav (sidebar menu) — `.fdy-nav`
|
|
643
|
+
Vertical navigation, used by the app shell. Items are `<a class="fdy-nav__item">` with
|
|
644
|
+
`__icon` / `__label` / `__badge`; the current one gets `aria-current="page"`.
|
|
645
|
+
`--flat` drops the surface. Nested groups are native `<details>`:
|
|
646
|
+
|
|
647
|
+
- `.fdy-nav__tree` + `<summary class="fdy-nav__item">` + `.fdy-nav__caret` → children in
|
|
648
|
+
`.fdy-nav__sub`
|
|
649
|
+
- `.fdy-nav__group` + `<summary class="fdy-nav__grouplabel">` → a collapsible section
|
|
650
|
+
|
|
651
|
+
## App bar — `.fdy-appbar`
|
|
652
|
+
Standalone top bar (distinct from the shell's `.fdy-app__topbar`). Modifiers `--sticky`
|
|
653
|
+
`--elevated` `--dense` `--primary` (on-colour controls). Parts `__brand` `__spacer` `__actions`.
|
|
654
|
+
|
|
655
|
+
## Breadcrumb — `.fdy-breadcrumb`
|
|
656
|
+
`<nav class="fdy-breadcrumb" aria-label="Breadcrumb">` → `<ol class="fdy-breadcrumb__list">` with
|
|
657
|
+
`.fdy-breadcrumb__item` (last one `aria-current="page"`, no link), `.fdy-breadcrumb__link`, and
|
|
658
|
+
`.fdy-breadcrumb__sep` marked `aria-hidden="true"`.
|
|
659
|
+
|
|
660
|
+
## Tabs — `.fdy-tabs`
|
|
661
|
+
APG tabs: ←/→, Home/End, roving tabindex. Needs `freeday-tabs.js`.
|
|
662
|
+
|
|
663
|
+
```html
|
|
664
|
+
<div class="fdy-tabs" data-fdy-tabs>
|
|
665
|
+
<div class="fdy-tabs__list" role="tablist" aria-label="Invoice detail">
|
|
666
|
+
<button class="fdy-tabs__tab" role="tab" id="t-1" aria-controls="p-1" aria-selected="true">Summary</button>
|
|
667
|
+
<button class="fdy-tabs__tab" role="tab" id="t-2" aria-controls="p-2" aria-selected="false" tabindex="-1">Items</button>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="fdy-tabs__panel" role="tabpanel" id="p-1" aria-labelledby="t-1" tabindex="0">…</div>
|
|
670
|
+
<div class="fdy-tabs__panel" role="tabpanel" id="p-2" aria-labelledby="t-2" tabindex="0" hidden>…</div>
|
|
671
|
+
</div>
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
# Overlays
|
|
677
|
+
|
|
678
|
+
## Modal — `.fdy-modal`
|
|
679
|
+
Native `<dialog>`: focus trap, Esc and backdrop come from the browser. Sizes `--sm` `--md` `--lg`
|
|
680
|
+
`--wide`; `--cfl` for the choose-from-list dialog. Parts `__header` `__title` `__body` `__footer`
|
|
681
|
+
`__close`. The body scrolls; the footer never clips. `aria-labelledby` → the title's `id`;
|
|
682
|
+
`data-close` on any button that should close it.
|
|
683
|
+
|
|
684
|
+
## Drawer — `.fdy-drawer`
|
|
685
|
+
Temporary side panel on native `<dialog>` — left by default, `--right` to flip. Parts `__header`
|
|
686
|
+
`__title` `__body` `__footer` `__close`. Open it from any
|
|
687
|
+
`<button data-fdy-drawer="<dialog id>">`. Needs `freeday-drawer.js`.
|
|
688
|
+
|
|
689
|
+
## Accordion — `.fdy-accordion`
|
|
690
|
+
Native `<details>`, zero JS: `.fdy-accordion__item` on each `<details>`, content in
|
|
691
|
+
`.fdy-accordion__panel`, the trigger is the `<summary>`.
|
|
692
|
+
|
|
693
|
+
## Carousel — `.fdy-carousel`
|
|
694
|
+
Scroll-snap slides + arrows + dots; optional `data-fdy-autoplay` (pauses on hover/focus). Needs
|
|
695
|
+
`freeday-carousel.js`. Parts `__viewport` `__slide` `__arrow` (+`--prev` `--next`) `__dot` `__dots`.
|
|
696
|
+
Root is `role="region" aria-roledescription="carousel"` + `aria-label` + `tabindex="0"`; each slide
|
|
697
|
+
is `role="group" aria-roledescription="slide"`.
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
# Display
|
|
702
|
+
|
|
703
|
+
## Card — `.fdy-card`
|
|
704
|
+
Parts `__body` `__title` `__desc` `__footer`. Modifiers:
|
|
705
|
+
|
|
706
|
+
- `--elevated` — raise it (use sparingly; most surfaces are flat)
|
|
707
|
+
- `--interactive` — cursor + hover-lift for a card that *has* a click handler
|
|
708
|
+
- `--button` — for a card that **is** the control: render it as `<button>` and add this to reset
|
|
709
|
+
the UA button box **without** losing the card surface/border
|
|
710
|
+
- `.fdy-card--button` never replaces keyboard semantics — a clickable card must be a real
|
|
711
|
+
`<button>` or `<a>`.
|
|
712
|
+
|
|
713
|
+
## Badge — `.fdy-badge`
|
|
714
|
+
Inline status pill: `--success` `--warning` `--danger` `--info` `--outline`. Never colour-only —
|
|
715
|
+
the text carries the meaning.
|
|
716
|
+
|
|
717
|
+
**Notification badge is the overlay variant**, not a separate component: wrap anything in
|
|
718
|
+
`.fdy-badge-wrap` and add `.fdy-badge-ov` (`--primary` `--danger` `--accent` `--info` `--success`
|
|
719
|
+
`--warning`, `--dot` for a bare dot, `--top-left` / `--bottom-right` / `--bottom-left` to move it).
|
|
720
|
+
Put the count in the wrapper's `aria-label` and mark the overlay `aria-hidden="true"`.
|
|
721
|
+
|
|
722
|
+
## Avatar — `.fdy-avatar`
|
|
723
|
+
Initials or image. Sizes `--sm` / default / `--lg`; `.fdy-avatar-group` stacks them (label the
|
|
724
|
+
group). `--tone-1`…`--tone-8` are decorative tints (WCAG AA in both themes) to distinguish
|
|
725
|
+
same-initial avatars — hash the index off the **full** name, not the initials.
|
|
726
|
+
|
|
727
|
+
## Chip — `.fdy-chip`
|
|
728
|
+
Three roles inside a `.fdy-chips` row:
|
|
729
|
+
|
|
730
|
+
- **static / removable** — `<span class="fdy-chip">`, `--primary` for an active filter, with
|
|
731
|
+
`.fdy-chip__remove` (`aria-label` says what it removes)
|
|
732
|
+
- **choice** — `<button class="fdy-chip fdy-chip--choice" aria-pressed>` toggle
|
|
733
|
+
- **filter** — `--filter`, same but with a `.fdy-chip__check` mark
|
|
734
|
+
|
|
735
|
+
Interactive chips are managed by `freeday-chip.js`: wrap the row in `data-fdy-chips` (+
|
|
736
|
+
`data-single` for pick-one, `data-label` for the group name), give each `data-value`.
|
|
737
|
+
`--tone-1`…`--tone-8` for non-semantic category colours.
|
|
738
|
+
|
|
739
|
+
## Timeline — `.fdy-timeline`
|
|
740
|
+
Vertical event feed, pure CSS. `<ol class="fdy-timeline">` of `.fdy-timeline__item` (`--muted`
|
|
741
|
+
`--success` `--warning` `--danger`) with `__marker` `__time` `__title` `__text`. Status is carried
|
|
742
|
+
by the accompanying text, not the marker colour alone.
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
*Anything not listed here is not part of the public surface. If a screen needs a primitive that
|
|
747
|
+
isn't in this file, it probably belongs in the kit — open an issue instead of re-inventing it
|
|
748
|
+
locally.*
|