@cahyo-dimas/freeday 1.18.0 → 1.21.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 (51) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/COMPONENTS.md +775 -0
  3. package/README.id.md +11 -1
  4. package/README.md +18 -2
  5. package/USAGE.md +55 -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 +91 -9
  11. package/dist/freeday.css +75 -6
  12. package/dist/freeday.tokens.css +16 -3
  13. package/docs/agent-onboarding.md +153 -0
  14. package/docs/getting-started.md +456 -0
  15. package/docs/integrations.md +321 -0
  16. package/docs/reference-screen.html +461 -0
  17. package/package.json +10 -4
  18. package/src/base.css +15 -0
  19. package/src/components/accordion.css +5 -1
  20. package/src/components/card.css +7 -1
  21. package/src/components/carousel.css +4 -1
  22. package/src/components/list.css +36 -0
  23. package/src/components/table.css +5 -2
  24. package/src/components/tabs.css +2 -1
  25. package/tokens/breakpoints.d.ts +3 -0
  26. package/tokens/breakpoints.mjs +8 -1
  27. package/src/components/.gitkeep +0 -0
  28. package/src/freeday-autocomplete.js +0 -135
  29. package/src/freeday-breakpoint.js +0 -51
  30. package/src/freeday-carousel.js +0 -111
  31. package/src/freeday-cascade.js +0 -256
  32. package/src/freeday-cfl.js +0 -213
  33. package/src/freeday-chart.js +0 -429
  34. package/src/freeday-chip.js +0 -83
  35. package/src/freeday-datepicker.js +0 -321
  36. package/src/freeday-datetime.js +0 -83
  37. package/src/freeday-drawer.js +0 -43
  38. package/src/freeday-form.js +0 -181
  39. package/src/freeday-mask.js +0 -114
  40. package/src/freeday-menu.js +0 -93
  41. package/src/freeday-popover.js +0 -69
  42. package/src/freeday-rating.js +0 -50
  43. package/src/freeday-select.js +0 -218
  44. package/src/freeday-slider.js +0 -34
  45. package/src/freeday-stepper.js +0 -90
  46. package/src/freeday-table.js +0 -475
  47. package/src/freeday-tabs.js +0 -68
  48. package/src/freeday-timepicker.js +0 -180
  49. package/src/freeday-toast.js +0 -105
  50. package/src/freeday-tree.js +0 -94
  51. package/src/freeday-upload.js +0 -206
@@ -0,0 +1,461 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="light" data-density="comfortable">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Freeday — reference screen</title>
7
+
8
+ <!-- The kit in one file: tokens + components (this is what @cahyo-dimas/freeday/css resolves to).
9
+ Note the file name: dist/freeday.css is components ONLY — link that alone and every
10
+ var(--color-*) / var(--space-*) silently falls back, which looks like a broken stylesheet.
11
+ Paths are relative, so this works both in the repo and in node_modules/@cahyo-dimas/freeday/docs/. -->
12
+ <link rel="stylesheet" href="../dist/freeday.bundle.css">
13
+
14
+ <!-- Fonts are NOT shipped by the package. Load them yourself (Fontsource, self-hosted, or a
15
+ <link> to a provider) or override --font-display / --font-body / --font-mono. Without them
16
+ the kit renders in the system fallback, which reads as an unfinished design. -->
17
+ </head>
18
+ <body>
19
+
20
+ <!-- ══════════════════════════════════════════════════════════════════════════════════════════
21
+ REFERENCE SCREEN — one business screen, assembled the intended way.
22
+
23
+ Structure, outermost to innermost:
24
+ .fdy-app the shell (topbar · sidebar · main)
25
+ └ .fdy-page the screen body — vertical section rhythm
26
+ ├ .fdy-page__header eyebrow + page title + desc | ONE primary action
27
+ ├ .fdy-page-section KPI strip (.fdy-stats — deliberately not cards)
28
+ ├ .fdy-page-section chart
29
+ └ .fdy-page-section the data table
30
+
31
+ Every class here exists in COMPONENTS.md. Every value comes from a token — search this file
32
+ for "#" or "px" and you will find none outside the demo-only sizing on the chart wrappers.
33
+ Doctrine behind the choices: USAGE.md.
34
+ ══════════════════════════════════════════════════════════════════════════════════════════ -->
35
+
36
+ <div class="fdy-app" id="app">
37
+ <a class="fdy-skip" href="#main">Skip to main content</a>
38
+
39
+ <!-- Sidebar comes FIRST: .fdy-app is a flex row of [sidebar | content]. The brand lives in the
40
+ sidebar (it is sized to match the topbar height), not in the topbar. -->
41
+ <aside class="fdy-app__sidebar" id="sidebar" aria-label="Main navigation">
42
+ <a class="fdy-app__brand" href="#app">
43
+ <span class="fdy-app__brand-mark">
44
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M4 12h10M4 17h6"/></svg>
45
+ </span>
46
+ <span class="fdy-app__brand-text">
47
+ <span class="fdy-app__brand-title">Acme Books</span>
48
+ <span class="fdy-app__brand-subtitle">Finance</span>
49
+ </span>
50
+ </a>
51
+ <nav class="fdy-nav">
52
+ <a class="fdy-nav__item" href="#app">
53
+ <span class="fdy-nav__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg></span>
54
+ <span class="fdy-nav__label">Dashboard</span>
55
+ </a>
56
+ <a class="fdy-nav__item" href="#app" aria-current="page">
57
+ <span class="fdy-nav__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M9 13h6M9 17h6"/></svg></span>
58
+ <span class="fdy-nav__label">Invoices</span>
59
+ <span class="fdy-nav__badge">12</span>
60
+ </a>
61
+ <a class="fdy-nav__item" href="#app">
62
+ <span class="fdy-nav__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg></span>
63
+ <span class="fdy-nav__label">Customers</span>
64
+ </a>
65
+ <details class="fdy-nav__group" open>
66
+ <summary class="fdy-nav__grouplabel">Reports</summary>
67
+ <a class="fdy-nav__item" href="#app"><span class="fdy-nav__label">Aging</span></a>
68
+ <a class="fdy-nav__item" href="#app"><span class="fdy-nav__label">Collections</span></a>
69
+ </details>
70
+ </nav>
71
+ </aside>
72
+
73
+ <!-- __content is the column wrapper holding the topbar + main. It exists so the sticky topbar
74
+ has a tall containing block to travel in — a bare flex cell cannot do that. -->
75
+ <div class="fdy-app__content">
76
+
77
+ <header class="fdy-app__topbar">
78
+ <button class="fdy-app__navtoggle" type="button" aria-label="Toggle navigation" aria-expanded="true" aria-controls="sidebar">
79
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
80
+ </button>
81
+
82
+ <!-- .fdy-app__title carries an auto-spacer (margin-right:auto): everything after it sits at
83
+ the right edge. -->
84
+ <h1 class="fdy-app__title fdy-hide-below-md">Receivables</h1>
85
+
86
+ <div class="fdy-input-group fdy-hide-below-md" style="max-width:14rem">
87
+ <span class="fdy-input-group__addon fdy-input-group__addon--icon">
88
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.35-4.35"/></svg>
89
+ </span>
90
+ <input class="fdy-input" type="search" placeholder="Search…" aria-label="Search the app">
91
+ </div>
92
+
93
+ <span class="fdy-badge-wrap">
94
+ <button class="fdy-btn fdy-btn--ghost fdy-btn--icon" type="button" aria-label="Notifications — 3 new">
95
+ <span class="fdy-btn__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>
96
+ </button>
97
+ <span class="fdy-badge-ov fdy-badge-ov--danger" aria-hidden="true">3</span>
98
+ </span>
99
+
100
+ <div class="fdy-menu-wrap" data-fdy-menu>
101
+ <button class="fdy-btn fdy-btn--ghost" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="User menu">
102
+ <span class="fdy-avatar fdy-avatar--sm fdy-avatar--tone-3">AK</span>
103
+ </button>
104
+ <ul class="fdy-menu fdy-menu--end" role="menu" hidden>
105
+ <li role="none"><button class="fdy-menu__item" role="menuitem" type="button">Profile</button></li>
106
+ <li role="none"><button class="fdy-menu__item" role="menuitem" type="button" id="toggle-theme">Toggle theme</button></li>
107
+ <li role="none"><button class="fdy-menu__item" role="menuitem" type="button" id="toggle-density">Toggle density</button></li>
108
+ <li role="none"><hr class="fdy-menu__sep"></li>
109
+ <li role="none"><button class="fdy-menu__item fdy-menu__item--danger" role="menuitem" type="button">Sign out</button></li>
110
+ </ul>
111
+ </div>
112
+ </header>
113
+
114
+ <!-- __main is the padded content area (--space-8). The screen goes straight inside it. -->
115
+ <main class="fdy-app__main" id="main" tabindex="-1">
116
+
117
+ <!-- ═══ THE SCREEN ═══ -->
118
+ <div class="fdy-page">
119
+
120
+ <!-- One page header. One primary action — everything else is ghost. -->
121
+ <header class="fdy-page__header">
122
+ <div class="fdy-page__heading">
123
+ <p class="fdy-eyebrow">Sales</p>
124
+ <h2 class="fdy-title-page">Receivables</h2>
125
+ <p class="fdy-page__desc">Every open invoice, with the amount still outstanding per customer.</p>
126
+ </div>
127
+ <div class="fdy-page__actions">
128
+ <button class="fdy-btn fdy-btn--ghost" type="button" id="btn-export">Export</button>
129
+ <button class="fdy-btn" type="button" id="btn-new">New invoice</button>
130
+ </div>
131
+ </header>
132
+
133
+ <!-- KPI strip: .fdy-stat tiles, NOT cards — a metric row must not read as a card grid. -->
134
+ <section class="fdy-page-section" aria-labelledby="kpi-h">
135
+ <h3 class="fdy-title-section fdy-visually-hidden" id="kpi-h">Key figures</h3>
136
+ <div class="fdy-stats fdy-stats--boxed">
137
+ <div class="fdy-stat">
138
+ <span class="fdy-stat__label">Outstanding</span>
139
+ <span class="fdy-stat__value">8.42<small> M</small></span>
140
+ <span class="fdy-stat__meta">across 34 invoices</span>
141
+ </div>
142
+ <div class="fdy-stat">
143
+ <span class="fdy-stat__label">Overdue</span>
144
+ <span class="fdy-stat__value">1.19<small> M</small></span>
145
+ <span class="fdy-stat__meta"><span class="fdy-badge fdy-badge--warning">7 invoices</span></span>
146
+ </div>
147
+ <div class="fdy-stat">
148
+ <span class="fdy-stat__label">Collected (MTD)</span>
149
+ <span class="fdy-stat__value">5.60<small> M</small></span>
150
+ <span class="fdy-stat__meta">target 6.00 M</span>
151
+ </div>
152
+ <div class="fdy-stat">
153
+ <span class="fdy-stat__label">Avg. days to pay</span>
154
+ <span class="fdy-stat__value">27</span>
155
+ <span class="fdy-stat__meta">terms: 30 days</span>
156
+ </div>
157
+ </div>
158
+ </section>
159
+
160
+ <!-- A section with a chart. Flat section, one card holding the chart. -->
161
+ <section class="fdy-page-section" aria-labelledby="trend-h">
162
+ <div class="fdy-page-section__head">
163
+ <h3 class="fdy-title-section" id="trend-h">Collection trend</h3>
164
+ <p class="fdy-text-caption">Last six months</p>
165
+ </div>
166
+ <div class="fdy-card">
167
+ <div class="fdy-card__body">
168
+ <div data-fdy-chart="line"
169
+ data-labels="Feb,Mar,Apr,May,Jun,Jul"
170
+ data-series='[{"label":"Invoiced","values":[62,68,75,71,83,88]},{"label":"Collected","values":[54,60,66,69,74,81]}]'
171
+ data-fdy-format="number"
172
+ role="img"
173
+ aria-label="Invoiced versus collected, in millions: invoiced rose from 62 to 88, collected from 54 to 81">
174
+ <table>
175
+ <caption>Invoiced vs collected (millions)</caption>
176
+ <tr><th scope="col">Month</th><th scope="col">Invoiced</th><th scope="col">Collected</th></tr>
177
+ <tr><td>Jul</td><td>88</td><td>81</td></tr>
178
+ </table>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </section>
183
+
184
+ <!-- The data table, with its filter bar composed onto the toolbar baseline.
185
+ KNOWN GAP: the vanilla freeday-table.js writes its footer/bulk strings in Indonesian
186
+ ("Menampilkan 1–5 dari 7", "N dipilih") and offers no override, so they stay Indonesian
187
+ on this English page. The Vue/React/Blazor <FdyTable> components default to English.
188
+ If you need English in a static page, render those two nodes yourself from the
189
+ fdy-table-change event instead of using data-fdy-table-info / -bulk-count. -->
190
+ <section class="fdy-page-section" aria-labelledby="list-h">
191
+ <div class="fdy-page-section__head">
192
+ <h3 class="fdy-title-section" id="list-h">Open invoices</h3>
193
+ </div>
194
+
195
+ <!-- md and up: the table. Below md it is hidden and the flat list below takes over. -->
196
+ <div class="fdy-datatable fdy-hide-below-md" data-fdy-table data-page-size="5">
197
+ <div class="fdy-table-toolbar fdy-filterbar">
198
+ <div class="fdy-field fdy-field--w-grow">
199
+ <span class="fdy-label" id="f-q">Search</span>
200
+ <div class="fdy-input-group">
201
+ <span class="fdy-input-group__addon fdy-input-group__addon--icon">
202
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.35-4.35"/></svg>
203
+ </span>
204
+ <input class="fdy-input" type="search" placeholder="Invoice or customer…" data-fdy-table-search aria-labelledby="f-q">
205
+ </div>
206
+ </div>
207
+ <div class="fdy-field fdy-field--w-sm">
208
+ <span class="fdy-label" id="f-st">Status</span>
209
+ <div class="fdy-combo" data-fdy-combo data-value="all">
210
+ <button type="button" class="fdy-combo__button" role="combobox" aria-haspopup="listbox" aria-expanded="false" aria-labelledby="f-st f-st-v">
211
+ <span class="fdy-combo__value" id="f-st-v">All</span>
212
+ </button>
213
+ <ul class="fdy-combo__listbox" role="listbox" aria-labelledby="f-st" hidden>
214
+ <li class="fdy-combo__option" role="option" data-value="all" aria-selected="true"><span class="fdy-combo__check">✓</span>All</li>
215
+ <li class="fdy-combo__option" role="option" data-value="open" aria-selected="false"><span class="fdy-combo__check"></span>Open</li>
216
+ <li class="fdy-combo__option" role="option" data-value="overdue" aria-selected="false"><span class="fdy-combo__check"></span>Overdue</li>
217
+ </ul>
218
+ </div>
219
+ </div>
220
+ <div class="fdy-field fdy-field--w-2xl">
221
+ <span class="fdy-label">Issued between</span>
222
+ <div class="fdy-daterange" data-fdy-daterange role="group" aria-label="Issued between">
223
+ <div data-fdy-datepicker data-role="from" data-placeholder="From" data-label="Issued from"></div>
224
+ <span class="fdy-daterange__sep" aria-hidden="true">–</span>
225
+ <div data-fdy-datepicker data-role="to" data-placeholder="To" data-label="Issued to"></div>
226
+ </div>
227
+ </div>
228
+ <div class="fdy-filterbar__actions">
229
+ <button class="fdy-btn fdy-btn--ghost" type="button">Reset</button>
230
+ </div>
231
+ </div>
232
+
233
+ <div class="fdy-table-bulkbar" data-fdy-table-bulk hidden role="region" aria-label="Bulk actions">
234
+ <span class="fdy-table-bulkbar__count" data-fdy-table-bulk-count aria-live="polite">0 selected</span>
235
+ <span class="fdy-table-bulkbar__spacer"></span>
236
+ <div class="fdy-table-bulkbar__actions">
237
+ <button class="fdy-btn fdy-btn--ghost fdy-btn--sm" type="button">Send reminder</button>
238
+ <button class="fdy-btn fdy-btn--ghost fdy-btn--sm" type="button" data-fdy-table-bulk-clear>Clear</button>
239
+ </div>
240
+ </div>
241
+
242
+ <div class="fdy-table-scroll">
243
+ <table class="fdy-table">
244
+ <caption class="fdy-visually-hidden">Open invoices, sortable and filterable</caption>
245
+ <thead>
246
+ <tr>
247
+ <th class="fdy-table__selcol" scope="col"><input type="checkbox" class="fdy-checkbox" data-fdy-select-all aria-label="Select all rows"></th>
248
+ <th scope="col"><button type="button" class="fdy-table__sortbtn" data-fdy-sort>Invoice</button></th>
249
+ <th scope="col" data-fdy-filter="text"><button type="button" class="fdy-table__sortbtn" data-fdy-sort>Customer</button></th>
250
+ <th scope="col" data-fdy-filter="enum"><button type="button" class="fdy-table__sortbtn" data-fdy-sort>Status</button></th>
251
+ <th scope="col">Due</th>
252
+ <th scope="col" class="fdy-table__num" data-fdy-filter="number"><button type="button" class="fdy-table__sortbtn" data-fdy-sort="number">Amount</button></th>
253
+ </tr>
254
+ </thead>
255
+ <tbody>
256
+ <tr>
257
+ <td class="fdy-table__selcol"><input type="checkbox" class="fdy-checkbox" data-fdy-row-select aria-label="Select INV-2041"></td>
258
+ <td class="fdy-mono">INV-2041</td><td>Acme Ltd</td>
259
+ <td><span class="fdy-badge fdy-badge--success">Paid</span></td>
260
+ <td>30 Jul 2026</td><td class="fdy-table__num" data-sort-value="1240000">1,240,000</td>
261
+ </tr>
262
+ <tr>
263
+ <td class="fdy-table__selcol"><input type="checkbox" class="fdy-checkbox" data-fdy-row-select aria-label="Select INV-2042"></td>
264
+ <td class="fdy-mono">INV-2042</td><td>Northwind Trading</td>
265
+ <td><span class="fdy-badge fdy-badge--warning">Overdue</span></td>
266
+ <td>12 Jul 2026</td><td class="fdy-table__num" data-sort-value="290000">290,000</td>
267
+ </tr>
268
+ <tr>
269
+ <td class="fdy-table__selcol"><input type="checkbox" class="fdy-checkbox" data-fdy-row-select aria-label="Select INV-2043"></td>
270
+ <td class="fdy-mono">INV-2043</td><td>Globex Supply</td>
271
+ <td><span class="fdy-badge fdy-badge--info">Draft</span></td>
272
+ <td>—</td><td class="fdy-table__num" data-sort-value="0">0</td>
273
+ </tr>
274
+ <tr>
275
+ <td class="fdy-table__selcol"><input type="checkbox" class="fdy-checkbox" data-fdy-row-select aria-label="Select INV-2044"></td>
276
+ <td class="fdy-mono">INV-2044</td><td>Initech Partners</td>
277
+ <td><span class="fdy-badge fdy-badge--success">Paid</span></td>
278
+ <td>02 Aug 2026</td><td class="fdy-table__num" data-sort-value="4750000">4,750,000</td>
279
+ </tr>
280
+ <tr>
281
+ <td class="fdy-table__selcol"><input type="checkbox" class="fdy-checkbox" data-fdy-row-select aria-label="Select INV-2045"></td>
282
+ <td class="fdy-mono">INV-2045</td><td>Umbra Logistics</td>
283
+ <td><span class="fdy-badge fdy-badge--warning">Overdue</span></td>
284
+ <td>08 Jul 2026</td><td class="fdy-table__num" data-sort-value="820000">820,000</td>
285
+ </tr>
286
+ <tr>
287
+ <td class="fdy-table__selcol"><input type="checkbox" class="fdy-checkbox" data-fdy-row-select aria-label="Select INV-2046"></td>
288
+ <td class="fdy-mono">INV-2046</td><td>Stark Materials</td>
289
+ <td><span class="fdy-badge fdy-badge--danger">Cancelled</span></td>
290
+ <td>—</td><td class="fdy-table__num" data-sort-value="150000">150,000</td>
291
+ </tr>
292
+ <tr>
293
+ <td class="fdy-table__selcol"><input type="checkbox" class="fdy-checkbox" data-fdy-row-select aria-label="Select INV-2047"></td>
294
+ <td class="fdy-mono">INV-2047</td><td>Acme Ltd</td>
295
+ <td><span class="fdy-badge fdy-badge--success">Paid</span></td>
296
+ <td>21 Aug 2026</td><td class="fdy-table__num" data-sort-value="3120000">3,120,000</td>
297
+ </tr>
298
+ </tbody>
299
+ </table>
300
+ </div>
301
+
302
+ <div class="fdy-table-footer">
303
+ <span class="fdy-table-footer__info" data-fdy-table-info></span>
304
+ <nav class="fdy-pagination" aria-label="Table pages" data-fdy-table-pagination></nav>
305
+ </div>
306
+ </div>
307
+
308
+ <!-- Below md: the SAME rows as a flat list. .fdy-list is the flat container the doctrine
309
+ asks for — a stack of .fdy-card would put a 34px lift under every row.
310
+ In a framework app you would not duplicate the markup: hide <FdyTable> below md, feed
311
+ this list from its `process` event, and drive both from a controlled `pageIndex` with
312
+ ONE pager rendered outside the table. See COMPONENTS.md §Data table. -->
313
+ <ul class="fdy-list fdy-hide-above-md">
314
+ <li>
315
+ <button type="button" class="fdy-list__row fdy-list__row--button">
316
+ <span class="fdy-list__main">
317
+ <span class="fdy-list__title">Acme Ltd</span>
318
+ <span class="fdy-list__meta"><span class="fdy-mono">INV-2041</span> · due 30 Jul <span class="fdy-badge fdy-badge--success">Paid</span></span>
319
+ </span>
320
+ <span class="fdy-list__aside fdy-mono">1,240,000</span>
321
+ </button>
322
+ </li>
323
+ <li>
324
+ <button type="button" class="fdy-list__row fdy-list__row--button">
325
+ <span class="fdy-list__main">
326
+ <span class="fdy-list__title">Northwind Trading</span>
327
+ <span class="fdy-list__meta"><span class="fdy-mono">INV-2042</span> · due 12 Jul <span class="fdy-badge fdy-badge--warning">Overdue</span></span>
328
+ </span>
329
+ <span class="fdy-list__aside fdy-mono">290,000</span>
330
+ </button>
331
+ </li>
332
+ <li>
333
+ <button type="button" class="fdy-list__row fdy-list__row--button">
334
+ <span class="fdy-list__main">
335
+ <span class="fdy-list__title">Globex Supply</span>
336
+ <span class="fdy-list__meta"><span class="fdy-mono">INV-2043</span> · draft <span class="fdy-badge fdy-badge--info">Draft</span></span>
337
+ </span>
338
+ <span class="fdy-list__aside fdy-mono">0</span>
339
+ </button>
340
+ </li>
341
+ </ul>
342
+ </section>
343
+
344
+ <!-- A detail region: key-value pairs + a timeline. Flat section, one card each. -->
345
+ <section class="fdy-page-section" aria-labelledby="detail-h">
346
+ <div class="fdy-page-section__head">
347
+ <h3 class="fdy-title-section" id="detail-h">INV-2042 — detail</h3>
348
+ <button class="fdy-btn fdy-btn--text" type="button">Open full record</button>
349
+ </div>
350
+ <div class="fdy-card">
351
+ <div class="fdy-card__body">
352
+ <h4 class="fdy-title-card">Northwind Trading</h4>
353
+ <dl class="fdy-dl">
354
+ <dt>Invoice</dt><dd class="fdy-mono">INV-2042</dd>
355
+ <dt>Status</dt><dd><span class="fdy-badge fdy-badge--warning">Overdue</span></dd>
356
+ <dt>Issued</dt><dd>12 Jun 2026</dd>
357
+ <dt>Due</dt><dd>12 Jul 2026</dd>
358
+ <dt>Amount</dt><dd>290,000</dd>
359
+ </dl>
360
+ </div>
361
+ </div>
362
+ </section>
363
+
364
+ </div>
365
+ <!-- ═══ /THE SCREEN ═══ -->
366
+
367
+ </main>
368
+
369
+ </div><!-- /.fdy-app__content -->
370
+
371
+ <div class="fdy-app__backdrop"></div>
372
+ </div>
373
+
374
+ <!-- Native <dialog>: focus trap, Esc and backdrop are the browser's job, not yours. -->
375
+ <dialog class="fdy-modal" id="new-invoice" aria-labelledby="new-invoice-t">
376
+ <form method="dialog" data-fdy-validate>
377
+ <div class="fdy-modal__header">
378
+ <h2 class="fdy-modal__title" id="new-invoice-t">New invoice</h2>
379
+ <button class="fdy-modal__close" type="button" aria-label="Close" data-close>&times;</button>
380
+ </div>
381
+ <div class="fdy-modal__body">
382
+ <div class="fdy-form-grid">
383
+ <label class="fdy-field">
384
+ <span class="fdy-label">Customer</span>
385
+ <input class="fdy-input" name="customer" required data-fdy-msg-required="Customer is required.">
386
+ </label>
387
+ <label class="fdy-field">
388
+ <span class="fdy-label">Invoice no.</span>
389
+ <input class="fdy-input fdy-mono" name="no" value="INV-2048" readonly>
390
+ </label>
391
+ <div class="fdy-field">
392
+ <span class="fdy-label">Issue date</span>
393
+ <div data-fdy-datepicker data-value="2026-08-12" data-label="Issue date"></div>
394
+ </div>
395
+ <label class="fdy-field">
396
+ <span class="fdy-label">Amount</span>
397
+ <div class="fdy-input-group">
398
+ <span class="fdy-input-group__addon">Rp</span>
399
+ <input class="fdy-input" name="amount" inputmode="numeric" required data-fdy-msg-required="Amount is required.">
400
+ </div>
401
+ </label>
402
+ <label class="fdy-field fdy-field--full fdy-switch">
403
+ <input type="checkbox" checked> Email the invoice to the customer
404
+ </label>
405
+ </div>
406
+ </div>
407
+ <div class="fdy-modal__footer">
408
+ <button class="fdy-btn fdy-btn--ghost" type="button" data-close>Cancel</button>
409
+ <button class="fdy-btn" type="submit">Save invoice</button>
410
+ </div>
411
+ </form>
412
+ </dialog>
413
+
414
+ <!-- Enhancers: only the ones this screen actually uses. Zero dependencies, load order free. -->
415
+ <script src="../dist/freeday-popover.js"></script>
416
+ <script src="../dist/freeday-select.js"></script>
417
+ <script src="../dist/freeday-datepicker.js"></script>
418
+ <script src="../dist/freeday-menu.js"></script>
419
+ <script src="../dist/freeday-table.js"></script>
420
+ <script src="../dist/freeday-chart.js"></script>
421
+ <script src="../dist/freeday-form.js"></script>
422
+ <script src="../dist/freeday-toast.js"></script>
423
+
424
+ <script>
425
+ // The shell's only wiring: the nav toggle. Two state classes, no library.
426
+ // Above 720px the sidebar collapses to zero width (--nav-collapsed); at or below it slides in as
427
+ // an off-canvas drawer (--nav-open). 720px is where app-shell.css switches — keep them in step.
428
+ const app = document.getElementById('app');
429
+ const toggle = app.querySelector('.fdy-app__navtoggle');
430
+ const wide = () => window.matchMedia('(min-width: 721px)').matches;
431
+ toggle.addEventListener('click', () => {
432
+ const cls = wide() ? 'fdy-app--nav-collapsed' : 'fdy-app--nav-open';
433
+ const open = app.classList.toggle(cls);
434
+ toggle.setAttribute('aria-expanded', String(wide() ? !open : open));
435
+ });
436
+ app.querySelector('.fdy-app__backdrop').addEventListener('click', () => {
437
+ app.classList.remove('fdy-app--nav-open');
438
+ toggle.setAttribute('aria-expanded', 'false');
439
+ });
440
+
441
+ // Theme + density live on <html>, set once — never per component.
442
+ const root = document.documentElement;
443
+ document.getElementById('toggle-theme').addEventListener('click', () => {
444
+ root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';
445
+ });
446
+ document.getElementById('toggle-density').addEventListener('click', () => {
447
+ root.dataset.density = root.dataset.density === 'compact' ? 'comfortable' : 'compact';
448
+ });
449
+
450
+ document.getElementById('btn-new').addEventListener('click', () => {
451
+ document.getElementById('new-invoice').showModal();
452
+ });
453
+ document.querySelectorAll('#new-invoice [data-close]').forEach(b => {
454
+ b.addEventListener('click', () => document.getElementById('new-invoice').close());
455
+ });
456
+ document.getElementById('btn-export').addEventListener('click', () => {
457
+ Freeday.toast({ variant: 'info', title: 'Export queued', message: 'The file will download shortly.', key: 'export' });
458
+ });
459
+ </script>
460
+ </body>
461
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "1.18.0",
3
+ "version": "1.21.0",
4
4
  "description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,7 +21,8 @@
21
21
  },
22
22
  "files": [
23
23
  "dist",
24
- "src",
24
+ "src/base.css",
25
+ "src/components",
25
26
  "adapters",
26
27
  "tokens/tokens.json",
27
28
  "tokens/breakpoints.mjs",
@@ -29,7 +30,12 @@
29
30
  "README.md",
30
31
  "README.id.md",
31
32
  "USAGE.md",
32
- "CHANGELOG.md"
33
+ "COMPONENTS.md",
34
+ "CHANGELOG.md",
35
+ "docs/getting-started.md",
36
+ "docs/integrations.md",
37
+ "docs/agent-onboarding.md",
38
+ "docs/reference-screen.html"
33
39
  ],
34
40
  "exports": {
35
41
  ".": { "types": "./dist/freeday.d.ts", "default": "./dist/freeday.js" },
@@ -65,7 +71,7 @@
65
71
  "scripts": {
66
72
  "build": "node tokens/build.mjs",
67
73
  "test": "node --test",
68
- "test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs",
74
+ "test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs",
69
75
  "prepack": "node tokens/build.mjs",
70
76
  "version": "node tokens/build.mjs && git add dist",
71
77
  "typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit"
package/src/base.css CHANGED
@@ -14,6 +14,21 @@ h1, h2, h3, h4 { font-family: var(--font-display); font-weight: var(--weight-bol
14
14
  a { color: var(--color-primary); }
15
15
  :focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
16
16
  :where(button, input, select, textarea) { font: inherit; }
17
+ /* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
18
+ * has a position, and because it is position:absolute its containing block is the nearest
19
+ * POSITIONED ancestor — with none, the initial containing block (the document itself).
20
+ *
21
+ * That matters, because `overflow` only clips a descendant whose containing block is inside the
22
+ * overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
23
+ * to name an icon button) parks at its static position — possibly thousands of px to the right —
24
+ * and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
25
+ * wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
26
+ *
27
+ * The fix is not here — it is on the containers: every clipping/scrolling container in the kit
28
+ * that holds consumer markup declares `position:relative` so it becomes the containing block for
29
+ * its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
30
+ * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not —
31
+ * the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
17
32
  .fdy-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
18
33
  /* Opt-in list reset. base.css is intentionally a *light* reset — it does NOT strip list/paragraph
19
34
  * margins (see the kit's own list components, which each reset themselves). If you run a utility
@@ -1,5 +1,9 @@
1
1
  /* Freeday — Accordion (native <details>/<summary>, zero-JS) */
2
- .fdy-accordion{border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
2
+ /* position:relative — containing block for out-of-flow panel content (see base.css). NOT redundant
3
+ with the panel's reveal animation: that animation gives the panel a transform (which happens to
4
+ make it a containing block too), but it lives behind prefers-reduced-motion:no-preference — so
5
+ without this line the escape bug appears ONLY for readers who asked for reduced motion. */
6
+ .fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
3
7
  .fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
4
8
  .fdy-accordion__item > summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);font-weight:var(--weight-medium);color:var(--color-text);}
5
9
  .fdy-accordion__item > summary::-webkit-details-marker{display:none;}
@@ -1,5 +1,7 @@
1
1
  /* Freeday — Card */
2
- .fdy-card{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
2
+ /* position:relative — containing block for out-of-flow card content (see base.css); it also gives
3
+ consumers the anchor a corner ribbon/badge needs. */
4
+ .fdy-card{position:relative;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
3
5
  .fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
4
6
  .fdy-card--interactive{cursor:pointer;}
5
7
  .fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
@@ -10,6 +12,10 @@
10
12
  * announced — reset the UA button box. Note: it does NOT reset background/border, because
11
13
  * .fdy-card already sets its own surface + border (resetting them here would strip the card). */
12
14
  .fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
15
+ /* Disabled card-as-control — same contract as .fdy-list__row--button: dim + not-allowed, and the
16
+ --interactive lift is withdrawn so a refusing control stops answering the pointer. */
17
+ .fdy-card--button:disabled,.fdy-card--button[aria-disabled="true"],.fdy-card--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
18
+ .fdy-card--interactive:disabled:hover,.fdy-card--interactive[aria-disabled="true"]:hover{box-shadow:var(--shadow-lift);transform:none;}
13
19
  .fdy-card__body{padding:var(--space-5);}
14
20
  .fdy-card__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0 0 var(--space-1);}
15
21
  .fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
@@ -1,6 +1,9 @@
1
1
  /* Freeday — Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
2
2
  .fdy-carousel{position:relative;}
3
- .fdy-carousel__viewport{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;border-radius:var(--radius-lg);scrollbar-width:none;-ms-overflow-style:none;}
3
+ /* position:relative — the arrows are positioned against .fdy-carousel (the parent), so this only
4
+ catches out-of-flow content inside the SLIDES, which would otherwise escape the viewport
5
+ entirely and scroll the page by one slide-offset per slide (see base.css). */
6
+ .fdy-carousel__viewport{position:relative;display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;border-radius:var(--radius-lg);scrollbar-width:none;-ms-overflow-style:none;}
4
7
  .fdy-carousel__viewport::-webkit-scrollbar{display:none;}
5
8
  .fdy-carousel__slide{flex:0 0 100%;min-width:0;scroll-snap-align:center;}
6
9
  .fdy-carousel__arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:2;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:0;border-radius:var(--radius-full);background:color-mix(in srgb,var(--color-surface) 86%,transparent);color:var(--color-text);box-shadow:var(--shadow-2);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard);}
@@ -0,0 +1,36 @@
1
+ /* Freeday — List: the FLAT row container.
2
+ *
3
+ * USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
4
+ * container the kit shipped was .fdy-card — which carries --shadow-lift (a real 34px lift). A
5
+ * responsive table that becomes a list below `md` therefore had to choose between a stack of
6
+ * shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
7
+ * the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
8
+ *
9
+ * Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
10
+ * Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
11
+ /* position:relative — containing block for out-of-flow row content (see base.css). */
12
+ .fdy-list{position:relative;list-style:none;margin:0;padding:0;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;}
13
+ .fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
14
+ /* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
15
+ * of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
16
+ * <li>s — a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
17
+ .fdy-list__row + .fdy-list__row,
18
+ .fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
19
+ /* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
20
+ * touching the list surface (same contract as .fdy-card--button). */
21
+ .fdy-list__row--button{width:100%;text-align:inherit;color:inherit;font:inherit;background:none;border:0;appearance:none;-webkit-appearance:none;cursor:pointer;}
22
+ .fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
23
+ .fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
24
+ .fdy-list__row--interactive{cursor:pointer;}
25
+ /* Disabled row. The reset above adopts the UA button box, but not its disabled state — so a row
26
+ disabled mid-flight (a redirect in progress, say) kept lighting up and kept a pointer cursor:
27
+ a control answering the pointer while refusing input. Same contract as every other disabled
28
+ control in the kit (dim + not-allowed), hover withdrawn, both the native and the aria- form. */
29
+ .fdy-list__row--button:disabled,.fdy-list__row--button[aria-disabled="true"],.fdy-list__row--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
30
+ .fdy-list__row--button:disabled:hover,.fdy-list__row--button[aria-disabled="true"]:hover,.fdy-list__row--interactive[aria-disabled="true"]:hover{background:none;}
31
+ /* Row internals: a title/meta stack that truncates, and a trailing slot pinned right. */
32
+ .fdy-list__main{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;flex:1;}
33
+ .fdy-list__title{font-weight:var(--weight-medium);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
34
+ .fdy-list__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
35
+ .fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
36
+ /* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */