@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
|
@@ -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).
|