@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/COMPONENTS.md +748 -0
  3. package/README.id.md +9 -1
  4. package/README.md +16 -2
  5. package/USAGE.md +34 -11
  6. package/adapters/blazor/FdyTable.razor.cs +66 -7
  7. package/adapters/blazor/TableTypes.cs +6 -0
  8. package/adapters/react/components/FdyTable.tsx +32 -8
  9. package/adapters/vue/components/FdyTable.vue +30 -6
  10. package/dist/freeday.bundle.css +35 -1
  11. package/dist/freeday.css +30 -0
  12. package/dist/freeday.tokens.css +5 -1
  13. package/docs/agent-onboarding.md +151 -0
  14. package/docs/getting-started.md +454 -0
  15. package/docs/integrations.md +321 -0
  16. package/docs/reference-screen.html +461 -0
  17. package/package.json +9 -3
  18. package/src/components/list.css +29 -0
  19. package/tokens/breakpoints.d.ts +3 -0
  20. package/tokens/breakpoints.mjs +8 -1
  21. package/src/components/.gitkeep +0 -0
  22. package/src/freeday-autocomplete.js +0 -135
  23. package/src/freeday-breakpoint.js +0 -51
  24. package/src/freeday-carousel.js +0 -111
  25. package/src/freeday-cascade.js +0 -256
  26. package/src/freeday-cfl.js +0 -213
  27. package/src/freeday-chart.js +0 -429
  28. package/src/freeday-chip.js +0 -83
  29. package/src/freeday-datepicker.js +0 -321
  30. package/src/freeday-datetime.js +0 -83
  31. package/src/freeday-drawer.js +0 -43
  32. package/src/freeday-form.js +0 -181
  33. package/src/freeday-mask.js +0 -114
  34. package/src/freeday-menu.js +0 -93
  35. package/src/freeday-popover.js +0 -69
  36. package/src/freeday-rating.js +0 -50
  37. package/src/freeday-select.js +0 -218
  38. package/src/freeday-slider.js +0 -34
  39. package/src/freeday-stepper.js +0 -90
  40. package/src/freeday-table.js +0 -475
  41. package/src/freeday-tabs.js +0 -68
  42. package/src/freeday-timepicker.js +0 -180
  43. package/src/freeday-toast.js +0 -105
  44. package/src/freeday-tree.js +0 -94
  45. package/src/freeday-upload.js +0 -206
@@ -0,0 +1,151 @@
1
+ # Freeday — AI agent onboarding
2
+
3
+ For **a coding agent working in a project that consumes Freeday** (Claude Code, Codex, Cursor,
4
+ Copilot…). No model has Freeday in its training data, so an agent that is merely told "use Freeday"
5
+ will invent class names or silently fall back to Bootstrap/Tailwind conventions. This file is the
6
+ fix: paste the block below into the consuming project's agent instruction file, once.
7
+
8
+ > Working on **the kit itself**, not a consuming app? That's [`../CLAUDE.md`](../CLAUDE.md) — this
9
+ > file is about *using* the published package.
10
+
11
+ ---
12
+
13
+ ## 1. Paste this into your project's agent instructions
14
+
15
+ Into `CLAUDE.md` (Claude Code) / `AGENTS.md` (Codex, others) / `.github/copilot-instructions.md` at
16
+ the **root of the consuming project**:
17
+
18
+ ```markdown
19
+ ## UI: Freeday design system (@cahyo-dimas/freeday)
20
+
21
+ All UI in this project is built from Freeday. It is a **token-driven CSS kit + zero-dependency JS
22
+ enhancers**, not a component framework — components are plain markup with `fdy-*` classes.
23
+
24
+ **Before writing or editing any markup/CSS, read these (they ship inside the package):**
25
+ - `node_modules/@cahyo-dimas/freeday/COMPONENTS.md` — every class that exists, with minimal markup
26
+ skeletons, enhancer hooks and the a11y contract per component. **The class list is closed:
27
+ if a class is not in that file, it does not exist — do not invent one.**
28
+ - `node_modules/@cahyo-dimas/freeday/USAGE.md` — the doctrine: which token/role/shadow to use when.
29
+ - `node_modules/@cahyo-dimas/freeday/docs/reference-screen.html` — one complete screen, assembled
30
+ the intended way. Copy this structure for a new screen.
31
+
32
+ **Non-negotiables:**
33
+ 1. No raw hex or px in app CSS. Use tokens: `var(--color-primary)`, `var(--space-4)` (4px scale),
34
+ `var(--radius-md)`, `var(--shadow-1)`, `var(--dur-2)`.
35
+ 2. Components only touch semantic tokens (`--color-*`) — never the primitive ramp (`--azure-600`).
36
+ 3. `.fdy-btn` is already the primary action (there is no `--primary`). One per screen; everything
37
+ else is `--ghost` or `--text`.
38
+ 4. Three title roles only: `.fdy-title-page` (one `<h1>`) / `.fdy-title-section` / `.fdy-title-card`.
39
+ Never reuse a card title as a page title.
40
+ 5. Assemble from the frame down: `.fdy-app` → `.fdy-page` → `.fdy-page-section` → components.
41
+ 6. Form errors: `aria-invalid="true"` + `aria-describedby` → a `.fdy-help.fdy-help--error`.
42
+ Icon-only buttons need `aria-label`. Status is never colour-only.
43
+ 7. Interactive components need their enhancer script loaded (see the table in COMPONENTS.md);
44
+ in an SPA, re-hydrate dynamic DOM (`useFreeday` in Vue/React does this).
45
+ 8. Freeday owns components + tokens, **not layout**. Grids/stacks/one-off gaps come from our own
46
+ layout layer — build its theme on `var(--space-N)` so both systems stay in step.
47
+ ```
48
+
49
+ Adjust the paths if the package lives somewhere else (a workspace, a vendored copy, `wwwroot/` for
50
+ Blazor). Then verify the agent can actually read those files — an agent that can't open
51
+ `node_modules` will keep guessing.
52
+
53
+ ## 2. What ships in the package
54
+
55
+ | File | What it answers |
56
+ |---|---|
57
+ | `COMPONENTS.md` | The complete class surface — what exists, its modifiers, minimal markup, a11y. |
58
+ | `USAGE.md` | The doctrine — which token/role/shadow/emphasis to use when. |
59
+ | `docs/getting-started.md` | Install + import + theme, per stack (Static HTML · Vue · React · Blazor). |
60
+ | `docs/integrations.md` | How to bridge third-party libraries (validation, charts, dates, i18n…). |
61
+ | `docs/reference-screen.html` | A full screen assembled from the shell down. Open it in a browser. |
62
+ | `docs/agent-onboarding.md` | This file. |
63
+ | `dist/` | Built CSS + enhancers. **`freeday.bundle.css` = tokens + components** (what `@cahyo-dimas/freeday/css` resolves to); `freeday.css` is components **only**, `freeday.tokens.css` tokens only — linking `freeday.css` alone leaves every `var(--…)` unresolved. Plus `freeday-*.js` and the `.d.ts` files. |
64
+ | `src/components/*.css` | The authoritative source for every class, when a doc is ambiguous. |
65
+ | `tokens/tokens.json` | Every token in W3C DTCG format — machine-readable. |
66
+ | `adapters/vue` · `adapters/react` · `adapters/blazor` | Typed wrappers, 10 components each. |
67
+
68
+ The live docs (with an interactive playground) are at
69
+ <https://cahyo-dimas.github.io/freeday-ui-kit/>, and the repo — including three complete example
70
+ apps under `examples/` (Vue, React, Blazor) that are **not** in the npm tarball — is at
71
+ <https://github.com/cahyo-dimas/freeday-ui-kit>.
72
+
73
+ ## 3. Starting a new screen
74
+
75
+ The order matters; skipping to components is what produces flat, identical-card screens.
76
+
77
+ 0. **Pick the screen shape first.** Which archetype is this — dashboard, master-detail, kanban,
78
+ wizard, POS…? The repo's
79
+ [`reference/README.md`](https://github.com/cahyo-dimas/freeday-ui-kit/blob/main/reference/README.md)
80
+ maps 15 archetypes to the exact primitives that compose each one, and says plainly which shapes
81
+ the kit has **no** component for (kanban columns, calendar month grid, chat bubbles, canvas) so
82
+ you build the frame instead of inventing a class. Not in the npm package — read it on GitHub.
83
+ 1. **Shell** — is `.fdy-app` already in place (usually once, in the app layout)? If not, copy it
84
+ from `docs/getting-started.md` §The app shell.
85
+ 2. **Theme** — `data-theme="light|dark"` + `data-density="comfortable|compact"` on `<html>`, set
86
+ once at the root. Use `compact` for table-heavy back-office screens.
87
+ 3. **Fonts** — the package ships **no** `@font-face`. Load Sora / IBM Plex Sans / JetBrains Mono
88
+ yourself, or override `--font-display`/`--font-body`/`--font-mono`. Skipping this reads as
89
+ "unfinished design", not "missing dependency".
90
+ 4. **Page frame** — `.fdy-page` + `.fdy-page__header` (eyebrow + `.fdy-title-page` + desc on the
91
+ left, **one** primary action on the right).
92
+ 5. **Sections** — one `.fdy-page-section` per region, each with a `.fdy-title-section`.
93
+ 6. **Components** — from `COMPONENTS.md`, inside the sections.
94
+ 7. **Verify** — the checklist in §5.
95
+
96
+ ## 4. Migrating an existing UI to Freeday
97
+
98
+ Migration is a **class-and-structure swap**, not a rewrite. Keep the app's DOM semantics; replace
99
+ the styling layer. Rough equivalents — always confirm the target class in `COMPONENTS.md`, and note
100
+ that Freeday deliberately has **no** layout/spacing utilities, so grid/flex/margin classes stay with
101
+ your own layout layer:
102
+
103
+ | Coming from | Freeday |
104
+ |---|---|
105
+ | `btn btn-primary` / `MudButton Variant=Filled` | `fdy-btn` |
106
+ | `btn btn-secondary` / `btn-outline-*` | `fdy-btn fdy-btn--ghost` |
107
+ | `btn btn-danger` | `fdy-btn fdy-btn--danger` |
108
+ | `btn btn-link` | `fdy-btn fdy-btn--text` |
109
+ | `btn-sm` / `btn-lg` | `fdy-btn--sm` / `fdy-btn--lg` |
110
+ | `form-control` / `MudTextField` | `fdy-field` + `fdy-label` + `fdy-input` |
111
+ | `form-select` / `<select>` / `MudSelect` | `fdy-combo` + `data-fdy-combo` (+ `freeday-select.js`) |
112
+ | `invalid-feedback` / `is-invalid` | `aria-invalid="true"` + `fdy-help fdy-help--error` |
113
+ | `input-group` / `input-group-text` | `fdy-input-group` + `__addon` / `__btn` |
114
+ | `form-check` / `form-switch` | `fdy-check` / `fdy-radio` / `fdy-switch` |
115
+ | `card` / `card-body` / `card-title` | `fdy-card` / `__body` / `__title` |
116
+ | `table table-striped` / `MudTable` | `fdy-table` in `fdy-table-wrap`; interactive → `fdy-datatable` |
117
+ | `badge bg-success` / `MudChip` (status) | `fdy-badge fdy-badge--success` |
118
+ | `alert alert-danger` | `fdy-alert fdy-alert--danger` + `role="alert"` |
119
+ | `modal` / `MudDialog` | `<dialog class="fdy-modal">` (native — drop the JS backdrop plumbing) |
120
+ | `offcanvas` / `MudDrawer` | `<dialog class="fdy-drawer">` + `data-fdy-drawer` |
121
+ | `nav nav-tabs` | `fdy-tabs` + `data-fdy-tabs` |
122
+ | `breadcrumb` / `pagination` | `fdy-breadcrumb` / `fdy-pagination` (same `<nav><ol>` structure) |
123
+ | `spinner-border` / `progress` | `fdy-spinner` / `fdy-progress` + `__bar` |
124
+ | `toast` container + JS | `Freeday.toast({…})` — imperative, no markup to author |
125
+ | `text-muted` | `fdy-text-muted` |
126
+ | `d-none` / `visually-hidden` | `fdy-hidden` / `fdy-visually-hidden` |
127
+ | `container` / `row` / `col-*` / `mb-3` / `gap-2` | **stays yours** — Freeday ships no layout utilities |
128
+
129
+ Order of work that avoids a half-migrated mess:
130
+
131
+ 1. Load Freeday's CSS and **turn off the old framework's reset/preflight** — `base.css` is the
132
+ reset now. Two resets fighting is the usual source of "everything looks slightly off".
133
+ 2. Shell + theme attributes first, so tokens resolve everywhere.
134
+ 3. Then screen by screen: page frame → sections → controls. Convert a whole screen at a time;
135
+ half-converted screens can't be reviewed visually.
136
+ 4. Delete the old framework's CSS only when no screen references it, then grep for leftover class
137
+ prefixes.
138
+ 5. Replace hand-rolled modal/drawer/dropdown JS with the native-`<dialog>` components and the
139
+ enhancers — that is usually where the most code disappears.
140
+
141
+ ## 5. Verification checklist (before claiming a screen is done)
142
+
143
+ - Every `fdy-*` class used appears in `COMPONENTS.md`. Grep the diff for `fdy-` and check.
144
+ - No raw hex/rgb/px in the diff's CSS. Grep for `#` and `px`.
145
+ - Exactly one `.fdy-btn` without a variant modifier on the screen; one `.fdy-title-page`.
146
+ - Toggle `data-theme="dark"` on `<html>` — nothing becomes unreadable, no hard-coded white/black.
147
+ - Toggle `data-density="compact"` — the layout still holds.
148
+ - Keyboard: Tab reaches every control, focus is always visible, Esc closes overlays.
149
+ - Form errors carry `aria-invalid` + a linked message; icon-only buttons have `aria-label`.
150
+ - The interactive components on the screen have their enhancer loaded, and SPA-rendered DOM is
151
+ re-hydrated.
@@ -0,0 +1,454 @@
1
+ # Freeday — Getting Started (per stack)
2
+
3
+ A step-by-step guide to adopting Freeday in **your new project**. Pick your stack:
4
+
5
+ **[Static HTML](#static-html-no-build)** · **[Vue 3 (Vite)](#vue-3-vite)** · **[React (Vite)](#react-vite)** · **[Blazor (WASM)](#blazor-wasm)**
6
+
7
+ > **Component reference** (each component's exact markup + ARIA): live docs →
8
+ > <https://cahyo-dimas.github.io/freeday-ui-kit/> (open a component section, copy its markup).
9
+ > **Ecosystem library map & how to bridge:** [`integrations.md`](integrations.md).
10
+
11
+ ---
12
+
13
+ ## Core concepts (read once, applies to every stack)
14
+
15
+ Freeday = **CSS** (semantic tokens + `fdy-*` classes) + **zero-dependency JS enhancers** (optional).
16
+
17
+ 1. **Static vs interactive.** Static components (button, card, badge, plain input, layout) need
18
+ only the **`fdy-*` classes** — no JS. Interactive components (select/combo, cascade, date/time
19
+ picker, table, dropzone, form validation, input mask, chip) need the **JS enhancers**.
20
+ 2. **The enhancer is the source of truth.** You don't re-implement components; the enhancer owns
21
+ the widget's DOM. You **listen for `fdy-*` events** (all bubbling `CustomEvent`s, data in
22
+ `event.detail`) → store them in your framework state. Event/API contract table:
23
+ [`integrations.md` §Event & API contract](integrations.md).
24
+ 3. **Hydrate dynamic DOM.** Enhancers auto-init once on `DOMContentLoaded`. DOM an SPA renders
25
+ **after** that must be re-hydrated: `window.Freeday<X>.initAll(el)` (idempotent, safe to repeat).
26
+ Each framework's adapter wraps this — you don't call it manually.
27
+ 4. **Theme via `data-*` on `<html>`.** `data-theme="light|dark"` (all semantic tokens switch) +
28
+ `data-density="comfortable|compact"` (control height, for data-dense screens). Change at runtime:
29
+ `document.documentElement.dataset.theme = 'dark'`.
30
+ 5. **3-tier token rule.** Components only touch **Tier 2/3** (`var(--color-primary)`,
31
+ `var(--space-4)`, `var(--radius-md)`…). **Never** write raw hex/px.
32
+ 6. **Scope: components + tokens, deliberately *not* layout.** Freeday ships components and tokens;
33
+ the only layout helpers are `.fdy-hidden` / `.fdy-visually-hidden`. Stacks, grids, gaps and sizing
34
+ come from **your** layout layer — pair Freeday with a utility framework (Tailwind, UnoCSS…) run
35
+ **utilities-only, preflight OFF** (Freeday's `base.css` is your reset). Two consequences worth
36
+ knowing up front:
37
+ - **base.css is a *light* reset** — it does not strip `ul`/`ol`/`p` margins. With preflight off, a
38
+ semantic `<ul>` keeps native bullets + a 40px indent; add **`.fdy-list-reset`** (or use a Freeday
39
+ list component) on such lists.
40
+ - **The spacing scale is public.** `--space-0`…`--space-24`, `--radius-*`, `--dur-*` etc. are real
41
+ custom properties in `dist/freeday.tokens.css` — **define your utility theme in terms of them**
42
+ (`spacing: { 4: 'var(--space-4)' }`) so both systems stay in step. `data-density="compact"` steps
43
+ `--control-h` **and** the mid-range spacing scale (`--space-3`…`--space-6`), so Freeday components
44
+ densify — and if your utility theme is built on `var(--space-N)`, density reaches your utilities too.
45
+ 7. **Load the fonts — the package does not.** The type tokens *name* **Sora** (display), **IBM Plex
46
+ Sans** (body) and **JetBrains Mono** (data), but Freeday bundles no `@font-face` and no font files.
47
+ Load them yourself, or the kit renders in the system fallback — which reads as "unfinished design",
48
+ not "missing dependency". One line with [Fontsource](https://fontsource.org):
49
+ ```css
50
+ @import '@fontsource/sora/600.css'; @import '@fontsource/sora/700.css';
51
+ @import '@fontsource-variable/ibm-plex-sans'; @import '@fontsource/jetbrains-mono/500.css';
52
+ ```
53
+ (Or a `<link>` to your own self-hosted copies, or override `--font-display`/`--font-body`/`--font-mono`
54
+ to faces you already ship. If you keep a system-sans fallback, consider softening
55
+ `--tracking-tighter` on headings — it's tuned for Sora's proportions.)
56
+ 8. **Start from the shell, then compose.** Every application goes inside **`.fdy-app`** (see below);
57
+ inside it, assemble screens from the composition primitives — `.fdy-page`, `.fdy-page__header`,
58
+ `.fdy-page-section`, `.fdy-toolbar`, `.fdy-stats`/`.fdy-stat` — and the type roles (`.fdy-title-page`
59
+ / `-section` / `-card`), not by re-using `.fdy-card__title` for everything. **Which token/role/shadow
60
+ to use when lives in [`USAGE.md`](../USAGE.md)** — read it once; it's what makes screens cohere.
61
+
62
+ ---
63
+
64
+ ## The app shell (start here)
65
+
66
+ Every Freeday application goes inside **`.fdy-app`** — the frame that holds a top bar, a sidebar, and
67
+ the scrolling content. Don't hand-roll one from flexbox; the responsive sidebar + backdrop are built in.
68
+ The nav toggle is one line: toggle `.fdy-app--nav-open` (mobile drawer) / `.fdy-app--nav-collapsed`
69
+ (desktop) on the `.fdy-app` element from the `__navtoggle` button's click.
70
+
71
+ The nesting is not free-form — `.fdy-app` is a flex **row** of `[sidebar | content]`, and `__content`
72
+ is the column that holds the topbar and the main area (it gives the sticky topbar a tall containing
73
+ block to travel in). The brand belongs in the **sidebar**, sized to match the topbar's height:
74
+
75
+ ```html
76
+ <div class="fdy-app">
77
+ <a class="fdy-skip" href="#main">Skip to content</a>
78
+
79
+ <aside class="fdy-app__sidebar">
80
+ <a class="fdy-app__brand" href="/">
81
+ <span class="fdy-app__brand-mark"><!-- logo --></span>
82
+ <span class="fdy-app__brand-text">
83
+ <span class="fdy-app__brand-title">Acme</span>
84
+ <span class="fdy-app__brand-subtitle">Finance</span><!-- optional -->
85
+ </span>
86
+ </a>
87
+ <nav class="fdy-nav"><!-- .fdy-nav__item … --></nav>
88
+ </aside>
89
+
90
+ <div class="fdy-app__content">
91
+ <header class="fdy-app__topbar">
92
+ <button class="fdy-app__navtoggle" aria-label="Toggle navigation"><!-- hamburger svg --></button>
93
+ <h1 class="fdy-app__title">Invoices</h1><!-- auto-spacer: what follows goes right -->
94
+ <!-- topbar actions … -->
95
+ </header>
96
+ <main class="fdy-app__main" id="main">
97
+ <!-- YOUR SCREEN: a .fdy-page … (see USAGE.md) -->
98
+ </main>
99
+ </div>
100
+
101
+ <div class="fdy-app__backdrop"></div>
102
+ </div>
103
+ ```
104
+
105
+ `.fdy-app__main` already carries the page padding (`--space-8`, `--space-5` on mobile) — don't wrap
106
+ your screen in another padded box. The toggle's two states split at **720px**: above it,
107
+ `.fdy-app--nav-collapsed` collapses the sidebar to zero width; at or below it,
108
+ `.fdy-app--nav-open` slides the sidebar in as an off-canvas drawer over the backdrop. A complete,
109
+ working version of all of this — including the toggle script — is
110
+ [`reference-screen.html`](reference-screen.html).
111
+
112
+ Then compose the screen inside `__main` with `.fdy-page` / `.fdy-page__header` / `.fdy-page-section`
113
+ / `.fdy-stats` and the type roles. The live **App shell** + **Sidebar menu** demos in
114
+ [`docs/index.html`](index.html) are copy-pasteable; **[`USAGE.md`](../USAGE.md)** says which role and
115
+ token to use where.
116
+
117
+ ---
118
+
119
+ ## Static HTML (no build)
120
+
121
+ Good for plain `.html` pages / templates — no bundler, no npm.
122
+
123
+ ### 1. Get the dist files into your project
124
+ `dist/` is committed, so there's no build step. Easiest way — use npm once just to download, then
125
+ copy the files (vendor them):
126
+ ```bash
127
+ npm i @cahyo-dimas/freeday
128
+ cp -r node_modules/@cahyo-dimas/freeday/dist ./assets/freeday # copy into your project
129
+ ```
130
+ (or `git clone` the repo and copy `dist/`, or download the files one by one). The minimum you need:
131
+ `freeday.bundle.css` (tokens + components in one) and `freeday.js` (all enhancers).
132
+
133
+ ### 2. Set the theme on `<html>` + link the CSS
134
+ ```html
135
+ <!doctype html>
136
+ <html lang="en" data-theme="light" data-density="comfortable">
137
+ <head>
138
+ <meta charset="utf-8">
139
+ <meta name="viewport" content="width=device-width, initial-scale=1">
140
+ <link rel="stylesheet" href="assets/freeday/freeday.bundle.css">
141
+ </head>
142
+ ```
143
+ > Two-file alternative: `freeday.tokens.css` (tokens) + `freeday.css` (components).
144
+
145
+ ### 3. Load the enhancers before `</body>`
146
+ ```html
147
+ <script src="assets/freeday/freeday.js" defer></script>
148
+ <!-- or pick per-file: freeday-select.js, freeday-table.js, freeday-datepicker.js, … -->
149
+ </body>
150
+ ```
151
+
152
+ ### 4. Use `fdy-*` classes + `data-fdy-*` hooks
153
+ ```html
154
+ <button class="fdy-btn fdy-btn--primary" type="button">Save</button>
155
+
156
+ <div data-fdy-datepicker></div> <!-- enhancer auto-inits on DOMContentLoaded -->
157
+ ```
158
+ Listen for events as needed; for DOM you add **dynamically** after load, re-hydrate:
159
+ ```html
160
+ <script>
161
+ document.addEventListener('fdy-datepicker-change', (e) => console.log(e.detail.value));
162
+ // after inserting new markup dynamically:
163
+ // window.FreedayDatepicker.initAll(containerEl);
164
+ </script>
165
+ ```
166
+
167
+ ### 5. Copy component markup
168
+ From **[`COMPONENTS.md`](../COMPONENTS.md)** (every component's classes + minimal markup, shipped in
169
+ the package) or **[`reference-screen.html`](reference-screen.html)** for a whole assembled screen. The
170
+ live docs also have a copy button per component.
171
+
172
+ ---
173
+
174
+ ## Vue 3 (Vite)
175
+
176
+ ### 1. Install
177
+ ```bash
178
+ npm i @cahyo-dimas/freeday
179
+ ```
180
+ Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.20.0"` (public npm package). `dist/` is
181
+ committed and published → no build step; `npm ci` runs without auth.
182
+
183
+ ### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
184
+ ```ts
185
+ import { createApp } from 'vue';
186
+ import '@cahyo-dimas/freeday/css'; // tokens + components (single file)
187
+ import '@cahyo-dimas/freeday'; // side-effect: registers every window.Freeday* enhancer
188
+ import App from './App.vue';
189
+
190
+ createApp(App).mount('#app');
191
+ ```
192
+
193
+ ### 3. Set the theme on the root (`index.html`)
194
+ ```html
195
+ <html lang="en" data-theme="light" data-density="comfortable">
196
+ ```
197
+
198
+ ### 4. Use `fdy-*` + hydrate via `useFreeday`
199
+ Call `useFreeday(root)` **once** per component; put `ref="root"` on the subtree container. `fdy-*`
200
+ events are bubbling `CustomEvent`s → use native `v-on` (`@fdy-*`) and read `event.detail` (typed).
201
+ ```vue
202
+ <script setup lang="ts">
203
+ import { ref, reactive } from 'vue';
204
+ import { useFreeday } from '@cahyo-dimas/freeday/vue';
205
+ import type { FdyCascadeChangeDetail, FdyDatepickerChangeDetail } from '@cahyo-dimas/freeday/vue';
206
+
207
+ const root = ref<HTMLElement | null>(null);
208
+ useFreeday(root); // hydrate [data-fdy-*] in the subtree, on each mount + update (idempotent)
209
+
210
+ const form = reactive({ category: '', dueDate: '' });
211
+ const onCascade = (e: Event) => { form.category = (e as CustomEvent<FdyCascadeChangeDetail>).detail.value; };
212
+ const onDate = (e: Event) => { form.dueDate = (e as CustomEvent<FdyDatepickerChangeDetail>).detail.value; };
213
+ </script>
214
+
215
+ <template>
216
+ <div ref="root">
217
+ <button class="fdy-btn fdy-btn--primary" type="button">Save</button>
218
+ <div data-fdy-cascade @fdy-cascade-change="onCascade">…</div>
219
+ <div data-fdy-datepicker @fdy-datepicker-change="onDate">…</div>
220
+ </div>
221
+ </template>
222
+ ```
223
+
224
+ **Gotcha:** if TypeScript complains about `import '@cahyo-dimas/freeday/css'`, make sure `env.d.ts`
225
+ has `/// <reference types="vite/client" />`. For **Nuxt/SSR**, enhancers are client-only — wrap them
226
+ in `onMounted`/`<ClientOnly>`.
227
+
228
+ Full working example: [`examples/vue-faktur/`](../examples/vue-faktur/).
229
+
230
+ ---
231
+
232
+ ## React (Vite)
233
+
234
+ ### 1. Install
235
+ ```bash
236
+ npm i @cahyo-dimas/freeday
237
+ ```
238
+
239
+ ### 2. Import the CSS + enhancers **once** in your entry (`src/main.tsx`)
240
+ ```tsx
241
+ import { StrictMode } from 'react';
242
+ import { createRoot } from 'react-dom/client';
243
+ import '@cahyo-dimas/freeday/css'; // tokens + components
244
+ import '@cahyo-dimas/freeday'; // registers every window.Freeday* enhancer
245
+ import { App } from './App';
246
+
247
+ createRoot(document.getElementById('root')!).render(
248
+ <StrictMode><App /></StrictMode>,
249
+ );
250
+ ```
251
+
252
+ ### 3. Set the theme on the root (`index.html`)
253
+ ```html
254
+ <html lang="en" data-theme="light" data-density="comfortable">
255
+ ```
256
+
257
+ ### 4. Use `fdy-*` + hydrate via the `useFreeday` hook
258
+ React has no native `on:fdy-*` handler → since the events **bubble**, attach one set of listeners on
259
+ `root` via `useEffect` (clean up on unmount). Read `event.detail` (typed).
260
+ ```tsx
261
+ import { useRef, useEffect } from 'react';
262
+ import { useFreeday } from '@cahyo-dimas/freeday/react';
263
+ import type { FdyCascadeChangeDetail, FdyDatepickerChangeDetail } from '@cahyo-dimas/freeday/react';
264
+
265
+ export function Panel() {
266
+ const root = useRef<HTMLDivElement>(null);
267
+ useFreeday(root); // hydrate subtree on mount + every commit (idempotent)
268
+
269
+ useEffect(() => {
270
+ const el = root.current;
271
+ if (!el) return;
272
+ const onCascade = (e: Event) => { /* (e as CustomEvent<FdyCascadeChangeDetail>).detail.value */ };
273
+ const onDate = (e: Event) => { /* (e as CustomEvent<FdyDatepickerChangeDetail>).detail.value */ };
274
+ el.addEventListener('fdy-cascade-change', onCascade);
275
+ el.addEventListener('fdy-datepicker-change', onDate);
276
+ return () => {
277
+ el.removeEventListener('fdy-cascade-change', onCascade);
278
+ el.removeEventListener('fdy-datepicker-change', onDate);
279
+ };
280
+ }, []);
281
+
282
+ return (
283
+ <div ref={root}>
284
+ <button className="fdy-btn fdy-btn--primary" type="button">Save</button>
285
+ <div data-fdy-cascade />
286
+ <div data-fdy-datepicker />
287
+ </div>
288
+ );
289
+ }
290
+ ```
291
+
292
+ **Gotcha:** because the enhancer owns the widget DOM, don't double-control it from React — store the
293
+ value from `event.detail` in state/ref; don't set the DOM `value` back. `StrictMode` mounts twice in
294
+ dev; `useFreeday` is idempotent, so it's safe.
295
+
296
+ ### 5. Alternative: typed controlled components (`FdyCombo` · `FdyDatepicker` · `FdyDateRange` · `FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart`)
297
+ For fields you'd normally write as a native `<select>`/`<input type="date">`,
298
+ `@cahyo-dimas/freeday/react` also exports typed **controlled** components — plain `value`/`onChange`,
299
+ no manual event bubbling (parity with the Vue `v-model` components above):
300
+ ```tsx
301
+ import { FdyCombo } from '@cahyo-dimas/freeday/react';
302
+ import type { FdyComboOption } from '@cahyo-dimas/freeday/react';
303
+
304
+ type Status = 'draft' | 'sent' | 'paid';
305
+ const options: ReadonlyArray<FdyComboOption<Status>> = [
306
+ { value: 'draft', label: 'Draft' },
307
+ { value: 'sent', label: 'Sent' },
308
+ { value: 'paid', label: 'Paid' },
309
+ ];
310
+
311
+ function StatusField({ value, onChange }: { value: Status; onChange: (v: Status) => void }) {
312
+ return <FdyCombo<Status> value={value} options={options} onChange={onChange} ariaLabelledby="lbl-status" />;
313
+ }
314
+ ```
315
+ `FdyDatepicker`, `FdyCfl` (async choose-from-list), and `FdyChart` share the same shape (typed
316
+ `value`/`onChange`, or `series`/`values` for `FdyChart`) — see [`integrations.md`](integrations.md)
317
+ and `examples/react-faktur/src/App.tsx` for the full patterns. **Vite works with no extra config**
318
+ (esbuild transpiles the `.tsx` source directly); **Next.js** consumers may need
319
+ `transpilePackages: ['@cahyo-dimas/freeday']` in `next.config.js`.
320
+
321
+ Full working example: [`examples/react-faktur/`](../examples/react-faktur/).
322
+
323
+ ---
324
+
325
+ ## Blazor (WASM)
326
+
327
+ Blazor doesn't use npm — Freeday is served as **static files** in `wwwroot/`.
328
+
329
+ > **Prefer the native components?** Jump to [§4 — the `Freeday.Blazor` RCL](#4-recommended-native-typed-components-freedayblazor-rcl):
330
+ > typed `<FdyX>` with `@bind`, no manual JS interop. Steps 1–2 (assets + scripts) still apply; step 3
331
+ > below (the raw enhancer + event bridge) is the underlying mechanism and the fallback for markup the
332
+ > RCL doesn't cover.
333
+
334
+ ### 1. Place the assets in `wwwroot/freeday/`
335
+ Copy 3 files into `wwwroot/freeday/`: `freeday.bundle.css`, `freeday.js` (from `dist/`), and
336
+ `freeday-blazor.js` (from `adapters/blazor/`). Manually, **or** automatically via an MSBuild target
337
+ (put the Freeday repo near your project and adjust the path) in `.csproj`:
338
+ ```xml
339
+ <Target Name="CopyFreedayAssets" BeforeTargets="ResolveStaticWebAssetsInputs;Build">
340
+ <ItemGroup>
341
+ <_FreedaySrc Include="PATH\dist\freeday.bundle.css;PATH\dist\freeday.js;PATH\adapters\blazor\freeday-blazor.js" />
342
+ </ItemGroup>
343
+ <Copy SourceFiles="@(_FreedaySrc)" DestinationFolder="$(MSBuildProjectDirectory)\wwwroot\freeday" SkipUnchangedFiles="true" />
344
+ </Target>
345
+ ```
346
+
347
+ ### 2. Set the theme + load the assets in `wwwroot/index.html`
348
+ Load `freeday.js` then `freeday-blazor.js` **before** `blazor.webassembly.js`:
349
+ ```html
350
+ <html lang="en" data-theme="light" data-density="comfortable">
351
+ <head>
352
+ <link rel="stylesheet" href="freeday/freeday.bundle.css" />
353
+ </head>
354
+ <body>
355
+ <div id="app">Loading…</div>
356
+ <script src="freeday/freeday.js"></script>
357
+ <script src="freeday/freeday-blazor.js"></script>
358
+ <script src="_framework/blazor.webassembly.js"></script>
359
+ </body>
360
+ ```
361
+ > Use the global IIFE (`window.FreedayBlazor`), **not** an ES module — so it passes strict-MIME on static hosts.
362
+
363
+ ### 3. Hydrate + bridge events in code-behind (`.razor.cs`)
364
+ In `OnAfterRenderAsync(firstRender)`: `initAll`, then `on(...)` per event → `[JSInvokable]` methods.
365
+ Release them in `DisposeAsync`.
366
+ ```csharp
367
+ public partial class Panel : ComponentBase, IAsyncDisposable
368
+ {
369
+ [Inject] private IJSRuntime JS { get; set; } = default!;
370
+ private ElementReference _root;
371
+ private DotNetObjectReference<Panel>? _self;
372
+ private readonly List<int> _tokens = new();
373
+
374
+ protected override async Task OnAfterRenderAsync(bool firstRender)
375
+ {
376
+ if (!firstRender) return;
377
+ await JS.InvokeVoidAsync("FreedayBlazor.initAll", _root); // hydrate the Blazor markup
378
+ _self = DotNetObjectReference.Create(this);
379
+ _tokens.Add(await JS.InvokeAsync<int>("FreedayBlazor.on", _root, "fdy-cascade-change", _self, nameof(OnCascade)));
380
+ _tokens.Add(await JS.InvokeAsync<int>("FreedayBlazor.on", _root, "fdy-datepicker-change", _self, nameof(OnDate)));
381
+ }
382
+
383
+ [JSInvokable] public void OnCascade(CascadeDetail d) { /* d.Value / d.Path */ StateHasChanged(); }
384
+ [JSInvokable] public void OnDate(ValueDetail d) { /* d.Value */ StateHasChanged(); }
385
+
386
+ public async ValueTask DisposeAsync()
387
+ {
388
+ foreach (var t in _tokens)
389
+ try { await JS.InvokeVoidAsync("FreedayBlazor.off", t); } catch (JSDisconnectedException) { }
390
+ _self?.Dispose();
391
+ }
392
+
393
+ public sealed record CascadeDetail(string Value, string Path, string[] Labels);
394
+ public sealed record ValueDetail(string Value);
395
+ }
396
+ ```
397
+ ```razor
398
+ @* Panel.razor — @ref on the subtree container, fdy-* classes + data-fdy-* hooks in the markup *@
399
+ <div @ref="_root">
400
+ <button class="fdy-btn fdy-btn--primary" type="button">Save</button>
401
+ <div data-fdy-cascade></div>
402
+ <div data-fdy-datepicker></div>
403
+ </div>
404
+ ```
405
+ Extras: `FreedayBlazor.toast(new { variant, title, message })` for toasts; `FreedayBlazor.toggleTheme()`
406
+ to flip the theme. Event DTOs are deserialized case-insensitively by Blazor.
407
+
408
+ ### 4. Recommended: native typed components (`Freeday.Blazor` RCL)
409
+
410
+ Instead of hand-writing `fdy-*` markup + the interop above, reference the **Razor Class Library** and
411
+ use typed `<FdyX>` components with `@bind` — the Blazor equivalent of the Vue `v-model` / React
412
+ `value`/`onChange` adapters. Place the Freeday repo near your solution and add a project reference:
413
+ ```xml
414
+ <!-- YourApp.csproj -->
415
+ <ProjectReference Include="PATH\adapters\blazor\Freeday.Blazor.csproj" />
416
+ ```
417
+ ```razor
418
+ @* _Imports.razor *@
419
+ @using Freeday.Blazor
420
+ ```
421
+ Load `freeday.js` + `freeday-blazor.js` exactly as in step 2 (the components still hydrate over the
422
+ kit's CSS/enhancers), then bind:
423
+ ```razor
424
+ @* Invoice.razor — no @ref, no manual JS interop, no [JSInvokable] *@
425
+ <FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl-status" />
426
+ <FdyDatepicker @bind-Value="_dueDate" Label="Due date" />
427
+ <FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)"
428
+ PageSize="10" RowActivatable="true" RowActivate="OpenDetail" />
429
+ <FdyChart Type="donut" Values="_byCity" Labels="_cityLabels" AriaLabel="Revenue by city" />
430
+ <FdyDrawer @bind-Open="_drawerOpen" Title="Detail" Side="right">…</FdyDrawer>
431
+ ```
432
+ Ten components at parity with the Vue/React adapters: **`FdyModal`** · **`FdyDrawer`** (`@bind-Open`,
433
+ `Title`, `Size`/`Side`, `Dismissible`) · **`FdyCombo<TValue>`** · **`FdyDatepicker`** ·
434
+ **`FdyAutocomplete`** · **`FdyCascade`** · **`FdyDateRange`** (`@bind-From`/`@bind-To`) ·
435
+ **`FdyCfl<TRow>`** (async `LoadPage`) · **`FdyChart`** · **`FdyTable<TRow>`** (client sort/filter/page,
436
+ or controlled `Sort`/`Filters`/`Page` for a server-paged table; `RowActivatable`, `RowDetail`). Each
437
+ `select`-type control also takes `Disabled`/`Readonly`/`Invalid`. The RCL targets **net8.0** and is
438
+ consumed as source (`<ProjectReference>`); `.NET bin/obj` never ships in the npm tarball.
439
+
440
+ Full working example (all ten): [`examples/blazor-faktur/`](../examples/blazor-faktur/) —
441
+ `Pages/ComponentsDemo.razor`.
442
+
443
+ ---
444
+
445
+ ## Verify (every stack)
446
+
447
+ Run the project → check two things:
448
+ 1. **CSS connected** — buttons/cards are styled (not plain HTML).
449
+ 2. **Enhancers connected** — interactive components come alive (e.g. datepicker/combo open on
450
+ click), and `event.detail` reaches your state.
451
+
452
+ If the visuals are plain → the CSS didn't load. If visuals are fine but widgets are dead → the
453
+ enhancers aren't hydrated (make sure `import '@cahyo-dimas/freeday'` / `<script freeday.js>` is
454
+ present, and the adapter/`initAll` is called for dynamic DOM).