@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.
- package/CHANGELOG.md +227 -0
- package/COMPONENTS.md +775 -0
- package/README.id.md +11 -1
- package/README.md +18 -2
- package/USAGE.md +55 -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 +91 -9
- package/dist/freeday.css +75 -6
- package/dist/freeday.tokens.css +16 -3
- package/docs/agent-onboarding.md +153 -0
- package/docs/getting-started.md +456 -0
- package/docs/integrations.md +321 -0
- package/docs/reference-screen.html +461 -0
- package/package.json +10 -4
- package/src/base.css +15 -0
- package/src/components/accordion.css +5 -1
- package/src/components/card.css +7 -1
- package/src/components/carousel.css +4 -1
- package/src/components/list.css +36 -0
- package/src/components/table.css +5 -2
- package/src/components/tabs.css +2 -1
- package/tokens/breakpoints.d.ts +3 -0
- package/tokens/breakpoints.mjs +8 -1
- package/src/components/.gitkeep +0 -0
- package/src/freeday-autocomplete.js +0 -135
- package/src/freeday-breakpoint.js +0 -51
- package/src/freeday-carousel.js +0 -111
- package/src/freeday-cascade.js +0 -256
- package/src/freeday-cfl.js +0 -213
- package/src/freeday-chart.js +0 -429
- package/src/freeday-chip.js +0 -83
- package/src/freeday-datepicker.js +0 -321
- package/src/freeday-datetime.js +0 -83
- package/src/freeday-drawer.js +0 -43
- package/src/freeday-form.js +0 -181
- package/src/freeday-mask.js +0 -114
- package/src/freeday-menu.js +0 -93
- package/src/freeday-popover.js +0 -69
- package/src/freeday-rating.js +0 -50
- package/src/freeday-select.js +0 -218
- package/src/freeday-slider.js +0 -34
- package/src/freeday-stepper.js +0 -90
- package/src/freeday-table.js +0 -475
- package/src/freeday-tabs.js +0 -68
- package/src/freeday-timepicker.js +0 -180
- package/src/freeday-toast.js +0 -105
- package/src/freeday-tree.js +0 -94
- package/src/freeday-upload.js +0 -206
package/dist/freeday.bundle.css
CHANGED
|
@@ -177,6 +177,9 @@
|
|
|
177
177
|
--focus-ring: var(--azure-600);
|
|
178
178
|
--focus-ring-width: 2px;
|
|
179
179
|
}
|
|
180
|
+
/* The SYSTEM default stays root-scoped: it is a statement about the document, and dropping :root
|
|
181
|
+
* here would match every element that is not itself [data-theme="light"] — which would re-darken
|
|
182
|
+
* the children of a light panel, since they carry no attribute of their own. */
|
|
180
183
|
@media (prefers-color-scheme: dark) {
|
|
181
184
|
:root:not([data-theme="light"]) {
|
|
182
185
|
--shadow-1: 0 1px 2px rgba(0,0,0,.4);
|
|
@@ -235,7 +238,13 @@
|
|
|
235
238
|
--focus-ring: var(--azure-400);
|
|
236
239
|
}
|
|
237
240
|
}
|
|
238
|
-
:root
|
|
241
|
+
/* The two EXPLICIT opt-ins are deliberately NOT scoped to :root, for the same reason as density
|
|
242
|
+
* below: these are inheriting custom properties, so data-theme on any ancestor re-themes just that
|
|
243
|
+
* subtree — a dark brand panel beside a light form is an ordinary layout, and it should not require
|
|
244
|
+
* re-colouring each component by hand. The root still matches, so data-theme on <html> is unchanged.
|
|
245
|
+
* Both keep the same specificity (0,1,0) as :root and come after it, so they still win there; and
|
|
246
|
+
* --light after --dark means a light island inside a dark region wins in turn. */
|
|
247
|
+
[data-theme="dark"] {
|
|
239
248
|
--shadow-1: 0 1px 2px rgba(0,0,0,.4);
|
|
240
249
|
--shadow-2: 0 2px 6px rgba(0,0,0,.45);
|
|
241
250
|
--shadow-3: 0 6px 20px -2px rgba(0,0,0,.6),0 1px 3px rgba(0,0,0,.5);
|
|
@@ -291,7 +300,7 @@
|
|
|
291
300
|
--chart-tick: var(--slate-600);
|
|
292
301
|
--focus-ring: var(--azure-400);
|
|
293
302
|
}
|
|
294
|
-
|
|
303
|
+
[data-theme="light"] {
|
|
295
304
|
--shadow-1: 0 1px 2px rgba(16,14,30,.06);
|
|
296
305
|
--shadow-2: 0 2px 6px rgba(16,14,30,.07),0 1px 2px rgba(16,14,30,.05);
|
|
297
306
|
--shadow-3: 0 4px 16px -2px rgba(16,14,30,.14),0 1px 3px rgba(16,14,30,.10);
|
|
@@ -347,7 +356,11 @@
|
|
|
347
356
|
--chart-tick: var(--slate-400);
|
|
348
357
|
--focus-ring: var(--azure-600);
|
|
349
358
|
}
|
|
350
|
-
:root
|
|
359
|
+
/* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit —
|
|
360
|
+
* putting the attribute on any ancestor (a route wrapper, one section) densifies just that
|
|
361
|
+
* subtree, which is how density is actually decided: per screen, not per app. The root still
|
|
362
|
+
* matches, so setting data-density on the html element keeps working exactly as before. */
|
|
363
|
+
[data-density="compact"] {
|
|
351
364
|
--space-3: 0.625rem;
|
|
352
365
|
--space-4: 0.75rem;
|
|
353
366
|
--space-5: 1rem;
|
|
@@ -372,6 +385,21 @@ h1, h2, h3, h4 { font-family: var(--font-display); font-weight: var(--weight-bol
|
|
|
372
385
|
a { color: var(--color-primary); }
|
|
373
386
|
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
|
|
374
387
|
:where(button, input, select, textarea) { font: inherit; }
|
|
388
|
+
/* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
|
|
389
|
+
* has a position, and because it is position:absolute its containing block is the nearest
|
|
390
|
+
* POSITIONED ancestor — with none, the initial containing block (the document itself).
|
|
391
|
+
*
|
|
392
|
+
* That matters, because `overflow` only clips a descendant whose containing block is inside the
|
|
393
|
+
* overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
|
|
394
|
+
* to name an icon button) parks at its static position — possibly thousands of px to the right —
|
|
395
|
+
* and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
|
|
396
|
+
* wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
|
|
397
|
+
*
|
|
398
|
+
* The fix is not here — it is on the containers: every clipping/scrolling container in the kit
|
|
399
|
+
* that holds consumer markup declares `position:relative` so it becomes the containing block for
|
|
400
|
+
* its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
|
|
401
|
+
* Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not —
|
|
402
|
+
* the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
|
|
375
403
|
.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; }
|
|
376
404
|
/* Opt-in list reset. base.css is intentionally a *light* reset — it does NOT strip list/paragraph
|
|
377
405
|
* margins (see the kit's own list components, which each reset themselves). If you run a utility
|
|
@@ -383,7 +411,11 @@ a { color: var(--color-primary); }
|
|
|
383
411
|
}
|
|
384
412
|
|
|
385
413
|
/* Freeday — Accordion (native <details>/<summary>, zero-JS) */
|
|
386
|
-
|
|
414
|
+
/* position:relative — containing block for out-of-flow panel content (see base.css). NOT redundant
|
|
415
|
+
with the panel's reveal animation: that animation gives the panel a transform (which happens to
|
|
416
|
+
make it a containing block too), but it lives behind prefers-reduced-motion:no-preference — so
|
|
417
|
+
without this line the escape bug appears ONLY for readers who asked for reduced motion. */
|
|
418
|
+
.fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
|
|
387
419
|
.fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
|
|
388
420
|
.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);}
|
|
389
421
|
.fdy-accordion__item > summary::-webkit-details-marker{display:none;}
|
|
@@ -669,7 +701,9 @@ a { color: var(--color-primary); }
|
|
|
669
701
|
.fdy-fab--accent{background:linear-gradient(180deg,var(--color-accent),color-mix(in srgb,#000 10%,var(--color-accent)));color:var(--color-on-accent);}
|
|
670
702
|
|
|
671
703
|
/* Freeday — Card */
|
|
672
|
-
|
|
704
|
+
/* position:relative — containing block for out-of-flow card content (see base.css); it also gives
|
|
705
|
+
consumers the anchor a corner ribbon/badge needs. */
|
|
706
|
+
.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);}
|
|
673
707
|
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
|
|
674
708
|
.fdy-card--interactive{cursor:pointer;}
|
|
675
709
|
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
|
|
@@ -680,6 +714,10 @@ a { color: var(--color-primary); }
|
|
|
680
714
|
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
681
715
|
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
682
716
|
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
717
|
+
/* Disabled card-as-control — same contract as .fdy-list__row--button: dim + not-allowed, and the
|
|
718
|
+
--interactive lift is withdrawn so a refusing control stops answering the pointer. */
|
|
719
|
+
.fdy-card--button:disabled,.fdy-card--button[aria-disabled="true"],.fdy-card--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
|
|
720
|
+
.fdy-card--interactive:disabled:hover,.fdy-card--interactive[aria-disabled="true"]:hover{box-shadow:var(--shadow-lift);transform:none;}
|
|
683
721
|
.fdy-card__body{padding:var(--space-5);}
|
|
684
722
|
.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);}
|
|
685
723
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -687,7 +725,10 @@ a { color: var(--color-primary); }
|
|
|
687
725
|
|
|
688
726
|
/* Freeday — Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
|
|
689
727
|
.fdy-carousel{position:relative;}
|
|
690
|
-
.fdy-
|
|
728
|
+
/* position:relative — the arrows are positioned against .fdy-carousel (the parent), so this only
|
|
729
|
+
catches out-of-flow content inside the SLIDES, which would otherwise escape the viewport
|
|
730
|
+
entirely and scroll the page by one slide-offset per slide (see base.css). */
|
|
731
|
+
.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;}
|
|
691
732
|
.fdy-carousel__viewport::-webkit-scrollbar{display:none;}
|
|
692
733
|
.fdy-carousel__slide{flex:0 0 100%;min-width:0;scroll-snap-align:center;}
|
|
693
734
|
.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);}
|
|
@@ -1184,6 +1225,43 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1184
1225
|
/* Freeday — Keyboard key */
|
|
1185
1226
|
.fdy-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 var(--space-2);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text);background:var(--color-surface-2);border:var(--bw) solid var(--color-border-strong);border-bottom-width:2px;border-radius:var(--radius-sm);}
|
|
1186
1227
|
|
|
1228
|
+
/* Freeday — List: the FLAT row container.
|
|
1229
|
+
*
|
|
1230
|
+
* USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
|
|
1231
|
+
* container the kit shipped was .fdy-card — which carries --shadow-lift (a real 34px lift). A
|
|
1232
|
+
* responsive table that becomes a list below `md` therefore had to choose between a stack of
|
|
1233
|
+
* shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
|
|
1234
|
+
* the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
|
|
1235
|
+
*
|
|
1236
|
+
* Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
|
|
1237
|
+
* Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
|
|
1238
|
+
/* position:relative — containing block for out-of-flow row content (see base.css). */
|
|
1239
|
+
.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;}
|
|
1240
|
+
.fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
|
|
1241
|
+
/* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
|
|
1242
|
+
* of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
|
|
1243
|
+
* <li>s — a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
|
|
1244
|
+
.fdy-list__row + .fdy-list__row,
|
|
1245
|
+
.fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
|
|
1246
|
+
/* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
|
|
1247
|
+
* touching the list surface (same contract as .fdy-card--button). */
|
|
1248
|
+
.fdy-list__row--button{width:100%;text-align:inherit;color:inherit;font:inherit;background:none;border:0;appearance:none;-webkit-appearance:none;cursor:pointer;}
|
|
1249
|
+
.fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
|
|
1250
|
+
.fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
|
|
1251
|
+
.fdy-list__row--interactive{cursor:pointer;}
|
|
1252
|
+
/* Disabled row. The reset above adopts the UA button box, but not its disabled state — so a row
|
|
1253
|
+
disabled mid-flight (a redirect in progress, say) kept lighting up and kept a pointer cursor:
|
|
1254
|
+
a control answering the pointer while refusing input. Same contract as every other disabled
|
|
1255
|
+
control in the kit (dim + not-allowed), hover withdrawn, both the native and the aria- form. */
|
|
1256
|
+
.fdy-list__row--button:disabled,.fdy-list__row--button[aria-disabled="true"],.fdy-list__row--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
|
|
1257
|
+
.fdy-list__row--button:disabled:hover,.fdy-list__row--button[aria-disabled="true"]:hover,.fdy-list__row--interactive[aria-disabled="true"]:hover{background:none;}
|
|
1258
|
+
/* Row internals: a title/meta stack that truncates, and a trailing slot pinned right. */
|
|
1259
|
+
.fdy-list__main{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;flex:1;}
|
|
1260
|
+
.fdy-list__title{font-weight:var(--weight-medium);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1261
|
+
.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;}
|
|
1262
|
+
.fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
|
|
1263
|
+
/* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */
|
|
1264
|
+
|
|
1187
1265
|
/* Freeday — Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
|
|
1188
1266
|
.fdy-menu-wrap{position:relative;display:inline-flex;}
|
|
1189
1267
|
.fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
|
|
@@ -1361,7 +1439,10 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1361
1439
|
.fdy-step-nav{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-5);}
|
|
1362
1440
|
|
|
1363
1441
|
/* Freeday — Table */
|
|
1364
|
-
|
|
1442
|
+
/* position:relative on every scroller/clipper — it makes the box the containing block for its own
|
|
1443
|
+
absolutely positioned content (.fdy-visually-hidden above all), which `overflow` alone does NOT
|
|
1444
|
+
clip. Without it a hidden label in a wide table scrolls the whole page. See base.css. */
|
|
1445
|
+
.fdy-table-wrap{position:relative;overflow-x:auto;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);}
|
|
1365
1446
|
.fdy-table{width:100%;border-collapse:collapse;font-size:var(--text-sm);}
|
|
1366
1447
|
.fdy-table caption{text-align:left;padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
1367
1448
|
.fdy-table th{text-align:left;padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:var(--weight-semibold);text-transform:uppercase;letter-spacing:var(--tracking-wide);color:var(--color-text-muted);background:var(--color-surface-2);border-bottom:var(--bw) solid var(--color-border);white-space:nowrap;}
|
|
@@ -1398,7 +1479,7 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1398
1479
|
|
|
1399
1480
|
/* Data-table shell (toolbar + scroll + footer stay put while the table scrolls) */
|
|
1400
1481
|
.fdy-datatable{border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);overflow:hidden;}
|
|
1401
|
-
.fdy-table-scroll{overflow-x:auto;}
|
|
1482
|
+
.fdy-table-scroll{position:relative;overflow-x:auto;}
|
|
1402
1483
|
.fdy-table-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border);}
|
|
1403
1484
|
.fdy-table-toolbar__search{max-width:18rem;}
|
|
1404
1485
|
.fdy-table-toolbar__spacer{flex:1;}
|
|
@@ -1431,7 +1512,8 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1431
1512
|
.fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}
|
|
1432
1513
|
|
|
1433
1514
|
/* Freeday — Tabs (WAI-ARIA APG) */
|
|
1434
|
-
|
|
1515
|
+
/* position:relative — containing block for out-of-flow content in the tabs (see base.css). */
|
|
1516
|
+
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);border-bottom:var(--bw) solid var(--color-border);overflow-x:auto;}
|
|
1435
1517
|
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;margin-bottom:-1px;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1436
1518
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
1437
1519
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
package/dist/freeday.css
CHANGED
|
@@ -15,6 +15,21 @@ h1, h2, h3, h4 { font-family: var(--font-display); font-weight: var(--weight-bol
|
|
|
15
15
|
a { color: var(--color-primary); }
|
|
16
16
|
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
|
|
17
17
|
:where(button, input, select, textarea) { font: inherit; }
|
|
18
|
+
/* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
|
|
19
|
+
* has a position, and because it is position:absolute its containing block is the nearest
|
|
20
|
+
* POSITIONED ancestor — with none, the initial containing block (the document itself).
|
|
21
|
+
*
|
|
22
|
+
* That matters, because `overflow` only clips a descendant whose containing block is inside the
|
|
23
|
+
* overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
|
|
24
|
+
* to name an icon button) parks at its static position — possibly thousands of px to the right —
|
|
25
|
+
* and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
|
|
26
|
+
* wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
|
|
27
|
+
*
|
|
28
|
+
* The fix is not here — it is on the containers: every clipping/scrolling container in the kit
|
|
29
|
+
* that holds consumer markup declares `position:relative` so it becomes the containing block for
|
|
30
|
+
* its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
|
|
31
|
+
* Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not —
|
|
32
|
+
* the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
|
|
18
33
|
.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; }
|
|
19
34
|
/* Opt-in list reset. base.css is intentionally a *light* reset — it does NOT strip list/paragraph
|
|
20
35
|
* margins (see the kit's own list components, which each reset themselves). If you run a utility
|
|
@@ -26,7 +41,11 @@ a { color: var(--color-primary); }
|
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
/* Freeday — Accordion (native <details>/<summary>, zero-JS) */
|
|
29
|
-
|
|
44
|
+
/* position:relative — containing block for out-of-flow panel content (see base.css). NOT redundant
|
|
45
|
+
with the panel's reveal animation: that animation gives the panel a transform (which happens to
|
|
46
|
+
make it a containing block too), but it lives behind prefers-reduced-motion:no-preference — so
|
|
47
|
+
without this line the escape bug appears ONLY for readers who asked for reduced motion. */
|
|
48
|
+
.fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
|
|
30
49
|
.fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
|
|
31
50
|
.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);}
|
|
32
51
|
.fdy-accordion__item > summary::-webkit-details-marker{display:none;}
|
|
@@ -312,7 +331,9 @@ a { color: var(--color-primary); }
|
|
|
312
331
|
.fdy-fab--accent{background:linear-gradient(180deg,var(--color-accent),color-mix(in srgb,#000 10%,var(--color-accent)));color:var(--color-on-accent);}
|
|
313
332
|
|
|
314
333
|
/* Freeday — Card */
|
|
315
|
-
|
|
334
|
+
/* position:relative — containing block for out-of-flow card content (see base.css); it also gives
|
|
335
|
+
consumers the anchor a corner ribbon/badge needs. */
|
|
336
|
+
.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);}
|
|
316
337
|
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
|
|
317
338
|
.fdy-card--interactive{cursor:pointer;}
|
|
318
339
|
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
|
|
@@ -323,6 +344,10 @@ a { color: var(--color-primary); }
|
|
|
323
344
|
* announced — reset the UA button box. Note: it does NOT reset background/border, because
|
|
324
345
|
* .fdy-card already sets its own surface + border (resetting them here would strip the card). */
|
|
325
346
|
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
|
|
347
|
+
/* Disabled card-as-control — same contract as .fdy-list__row--button: dim + not-allowed, and the
|
|
348
|
+
--interactive lift is withdrawn so a refusing control stops answering the pointer. */
|
|
349
|
+
.fdy-card--button:disabled,.fdy-card--button[aria-disabled="true"],.fdy-card--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
|
|
350
|
+
.fdy-card--interactive:disabled:hover,.fdy-card--interactive[aria-disabled="true"]:hover{box-shadow:var(--shadow-lift);transform:none;}
|
|
326
351
|
.fdy-card__body{padding:var(--space-5);}
|
|
327
352
|
.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);}
|
|
328
353
|
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
|
|
@@ -330,7 +355,10 @@ a { color: var(--color-primary); }
|
|
|
330
355
|
|
|
331
356
|
/* Freeday — Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
|
|
332
357
|
.fdy-carousel{position:relative;}
|
|
333
|
-
.fdy-
|
|
358
|
+
/* position:relative — the arrows are positioned against .fdy-carousel (the parent), so this only
|
|
359
|
+
catches out-of-flow content inside the SLIDES, which would otherwise escape the viewport
|
|
360
|
+
entirely and scroll the page by one slide-offset per slide (see base.css). */
|
|
361
|
+
.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;}
|
|
334
362
|
.fdy-carousel__viewport::-webkit-scrollbar{display:none;}
|
|
335
363
|
.fdy-carousel__slide{flex:0 0 100%;min-width:0;scroll-snap-align:center;}
|
|
336
364
|
.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);}
|
|
@@ -827,6 +855,43 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
827
855
|
/* Freeday — Keyboard key */
|
|
828
856
|
.fdy-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 var(--space-2);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text);background:var(--color-surface-2);border:var(--bw) solid var(--color-border-strong);border-bottom-width:2px;border-radius:var(--radius-sm);}
|
|
829
857
|
|
|
858
|
+
/* Freeday — List: the FLAT row container.
|
|
859
|
+
*
|
|
860
|
+
* USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
|
|
861
|
+
* container the kit shipped was .fdy-card — which carries --shadow-lift (a real 34px lift). A
|
|
862
|
+
* responsive table that becomes a list below `md` therefore had to choose between a stack of
|
|
863
|
+
* shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
|
|
864
|
+
* the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
|
|
865
|
+
*
|
|
866
|
+
* Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
|
|
867
|
+
* Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
|
|
868
|
+
/* position:relative — containing block for out-of-flow row content (see base.css). */
|
|
869
|
+
.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;}
|
|
870
|
+
.fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
|
|
871
|
+
/* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
|
|
872
|
+
* of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
|
|
873
|
+
* <li>s — a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
|
|
874
|
+
.fdy-list__row + .fdy-list__row,
|
|
875
|
+
.fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
|
|
876
|
+
/* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
|
|
877
|
+
* touching the list surface (same contract as .fdy-card--button). */
|
|
878
|
+
.fdy-list__row--button{width:100%;text-align:inherit;color:inherit;font:inherit;background:none;border:0;appearance:none;-webkit-appearance:none;cursor:pointer;}
|
|
879
|
+
.fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
|
|
880
|
+
.fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
|
|
881
|
+
.fdy-list__row--interactive{cursor:pointer;}
|
|
882
|
+
/* Disabled row. The reset above adopts the UA button box, but not its disabled state — so a row
|
|
883
|
+
disabled mid-flight (a redirect in progress, say) kept lighting up and kept a pointer cursor:
|
|
884
|
+
a control answering the pointer while refusing input. Same contract as every other disabled
|
|
885
|
+
control in the kit (dim + not-allowed), hover withdrawn, both the native and the aria- form. */
|
|
886
|
+
.fdy-list__row--button:disabled,.fdy-list__row--button[aria-disabled="true"],.fdy-list__row--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
|
|
887
|
+
.fdy-list__row--button:disabled:hover,.fdy-list__row--button[aria-disabled="true"]:hover,.fdy-list__row--interactive[aria-disabled="true"]:hover{background:none;}
|
|
888
|
+
/* Row internals: a title/meta stack that truncates, and a trailing slot pinned right. */
|
|
889
|
+
.fdy-list__main{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;flex:1;}
|
|
890
|
+
.fdy-list__title{font-weight:var(--weight-medium);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
891
|
+
.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;}
|
|
892
|
+
.fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
|
|
893
|
+
/* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */
|
|
894
|
+
|
|
830
895
|
/* Freeday — Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
|
|
831
896
|
.fdy-menu-wrap{position:relative;display:inline-flex;}
|
|
832
897
|
.fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
|
|
@@ -1004,7 +1069,10 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1004
1069
|
.fdy-step-nav{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-5);}
|
|
1005
1070
|
|
|
1006
1071
|
/* Freeday — Table */
|
|
1007
|
-
|
|
1072
|
+
/* position:relative on every scroller/clipper — it makes the box the containing block for its own
|
|
1073
|
+
absolutely positioned content (.fdy-visually-hidden above all), which `overflow` alone does NOT
|
|
1074
|
+
clip. Without it a hidden label in a wide table scrolls the whole page. See base.css. */
|
|
1075
|
+
.fdy-table-wrap{position:relative;overflow-x:auto;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);}
|
|
1008
1076
|
.fdy-table{width:100%;border-collapse:collapse;font-size:var(--text-sm);}
|
|
1009
1077
|
.fdy-table caption{text-align:left;padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
1010
1078
|
.fdy-table th{text-align:left;padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:var(--weight-semibold);text-transform:uppercase;letter-spacing:var(--tracking-wide);color:var(--color-text-muted);background:var(--color-surface-2);border-bottom:var(--bw) solid var(--color-border);white-space:nowrap;}
|
|
@@ -1041,7 +1109,7 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1041
1109
|
|
|
1042
1110
|
/* Data-table shell (toolbar + scroll + footer stay put while the table scrolls) */
|
|
1043
1111
|
.fdy-datatable{border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);overflow:hidden;}
|
|
1044
|
-
.fdy-table-scroll{overflow-x:auto;}
|
|
1112
|
+
.fdy-table-scroll{position:relative;overflow-x:auto;}
|
|
1045
1113
|
.fdy-table-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border);}
|
|
1046
1114
|
.fdy-table-toolbar__search{max-width:18rem;}
|
|
1047
1115
|
.fdy-table-toolbar__spacer{flex:1;}
|
|
@@ -1074,7 +1142,8 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1074
1142
|
.fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}
|
|
1075
1143
|
|
|
1076
1144
|
/* Freeday — Tabs (WAI-ARIA APG) */
|
|
1077
|
-
|
|
1145
|
+
/* position:relative — containing block for out-of-flow content in the tabs (see base.css). */
|
|
1146
|
+
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);border-bottom:var(--bw) solid var(--color-border);overflow-x:auto;}
|
|
1078
1147
|
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;margin-bottom:-1px;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1079
1148
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
1080
1149
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
package/dist/freeday.tokens.css
CHANGED
|
@@ -176,6 +176,9 @@
|
|
|
176
176
|
--focus-ring: var(--azure-600);
|
|
177
177
|
--focus-ring-width: 2px;
|
|
178
178
|
}
|
|
179
|
+
/* The SYSTEM default stays root-scoped: it is a statement about the document, and dropping :root
|
|
180
|
+
* here would match every element that is not itself [data-theme="light"] — which would re-darken
|
|
181
|
+
* the children of a light panel, since they carry no attribute of their own. */
|
|
179
182
|
@media (prefers-color-scheme: dark) {
|
|
180
183
|
:root:not([data-theme="light"]) {
|
|
181
184
|
--shadow-1: 0 1px 2px rgba(0,0,0,.4);
|
|
@@ -234,7 +237,13 @@
|
|
|
234
237
|
--focus-ring: var(--azure-400);
|
|
235
238
|
}
|
|
236
239
|
}
|
|
237
|
-
:root
|
|
240
|
+
/* The two EXPLICIT opt-ins are deliberately NOT scoped to :root, for the same reason as density
|
|
241
|
+
* below: these are inheriting custom properties, so data-theme on any ancestor re-themes just that
|
|
242
|
+
* subtree — a dark brand panel beside a light form is an ordinary layout, and it should not require
|
|
243
|
+
* re-colouring each component by hand. The root still matches, so data-theme on <html> is unchanged.
|
|
244
|
+
* Both keep the same specificity (0,1,0) as :root and come after it, so they still win there; and
|
|
245
|
+
* --light after --dark means a light island inside a dark region wins in turn. */
|
|
246
|
+
[data-theme="dark"] {
|
|
238
247
|
--shadow-1: 0 1px 2px rgba(0,0,0,.4);
|
|
239
248
|
--shadow-2: 0 2px 6px rgba(0,0,0,.45);
|
|
240
249
|
--shadow-3: 0 6px 20px -2px rgba(0,0,0,.6),0 1px 3px rgba(0,0,0,.5);
|
|
@@ -290,7 +299,7 @@
|
|
|
290
299
|
--chart-tick: var(--slate-600);
|
|
291
300
|
--focus-ring: var(--azure-400);
|
|
292
301
|
}
|
|
293
|
-
|
|
302
|
+
[data-theme="light"] {
|
|
294
303
|
--shadow-1: 0 1px 2px rgba(16,14,30,.06);
|
|
295
304
|
--shadow-2: 0 2px 6px rgba(16,14,30,.07),0 1px 2px rgba(16,14,30,.05);
|
|
296
305
|
--shadow-3: 0 4px 16px -2px rgba(16,14,30,.14),0 1px 3px rgba(16,14,30,.10);
|
|
@@ -346,7 +355,11 @@
|
|
|
346
355
|
--chart-tick: var(--slate-400);
|
|
347
356
|
--focus-ring: var(--azure-600);
|
|
348
357
|
}
|
|
349
|
-
:root
|
|
358
|
+
/* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit —
|
|
359
|
+
* putting the attribute on any ancestor (a route wrapper, one section) densifies just that
|
|
360
|
+
* subtree, which is how density is actually decided: per screen, not per app. The root still
|
|
361
|
+
* matches, so setting data-density on the html element keeps working exactly as before. */
|
|
362
|
+
[data-density="compact"] {
|
|
350
363
|
--space-3: 0.625rem;
|
|
351
364
|
--space-4: 0.75rem;
|
|
352
365
|
--space-5: 1rem;
|
|
@@ -0,0 +1,153 @@
|
|
|
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"`, normally on
|
|
86
|
+
`<html>`, set once at the root. Use `compact` for table-heavy back-office screens. Both attributes
|
|
87
|
+
also work on **any ancestor**: `<section data-theme="dark">` inverts that region and every
|
|
88
|
+
component inside it follows, so never hand-colour an inverted panel.
|
|
89
|
+
3. **Fonts** — the package ships **no** `@font-face`. Load Sora / IBM Plex Sans / JetBrains Mono
|
|
90
|
+
yourself, or override `--font-display`/`--font-body`/`--font-mono`. Skipping this reads as
|
|
91
|
+
"unfinished design", not "missing dependency".
|
|
92
|
+
4. **Page frame** — `.fdy-page` + `.fdy-page__header` (eyebrow + `.fdy-title-page` + desc on the
|
|
93
|
+
left, **one** primary action on the right).
|
|
94
|
+
5. **Sections** — one `.fdy-page-section` per region, each with a `.fdy-title-section`.
|
|
95
|
+
6. **Components** — from `COMPONENTS.md`, inside the sections.
|
|
96
|
+
7. **Verify** — the checklist in §5.
|
|
97
|
+
|
|
98
|
+
## 4. Migrating an existing UI to Freeday
|
|
99
|
+
|
|
100
|
+
Migration is a **class-and-structure swap**, not a rewrite. Keep the app's DOM semantics; replace
|
|
101
|
+
the styling layer. Rough equivalents — always confirm the target class in `COMPONENTS.md`, and note
|
|
102
|
+
that Freeday deliberately has **no** layout/spacing utilities, so grid/flex/margin classes stay with
|
|
103
|
+
your own layout layer:
|
|
104
|
+
|
|
105
|
+
| Coming from | Freeday |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `btn btn-primary` / `MudButton Variant=Filled` | `fdy-btn` |
|
|
108
|
+
| `btn btn-secondary` / `btn-outline-*` | `fdy-btn fdy-btn--ghost` |
|
|
109
|
+
| `btn btn-danger` | `fdy-btn fdy-btn--danger` |
|
|
110
|
+
| `btn btn-link` | `fdy-btn fdy-btn--text` |
|
|
111
|
+
| `btn-sm` / `btn-lg` | `fdy-btn--sm` / `fdy-btn--lg` |
|
|
112
|
+
| `form-control` / `MudTextField` | `fdy-field` + `fdy-label` + `fdy-input` |
|
|
113
|
+
| `form-select` / `<select>` / `MudSelect` | `fdy-combo` + `data-fdy-combo` (+ `freeday-select.js`) |
|
|
114
|
+
| `invalid-feedback` / `is-invalid` | `aria-invalid="true"` + `fdy-help fdy-help--error` |
|
|
115
|
+
| `input-group` / `input-group-text` | `fdy-input-group` + `__addon` / `__btn` |
|
|
116
|
+
| `form-check` / `form-switch` | `fdy-check` / `fdy-radio` / `fdy-switch` |
|
|
117
|
+
| `card` / `card-body` / `card-title` | `fdy-card` / `__body` / `__title` |
|
|
118
|
+
| `table table-striped` / `MudTable` | `fdy-table` in `fdy-table-wrap`; interactive → `fdy-datatable` |
|
|
119
|
+
| `badge bg-success` / `MudChip` (status) | `fdy-badge fdy-badge--success` |
|
|
120
|
+
| `alert alert-danger` | `fdy-alert fdy-alert--danger` + `role="alert"` |
|
|
121
|
+
| `modal` / `MudDialog` | `<dialog class="fdy-modal">` (native — drop the JS backdrop plumbing) |
|
|
122
|
+
| `offcanvas` / `MudDrawer` | `<dialog class="fdy-drawer">` + `data-fdy-drawer` |
|
|
123
|
+
| `nav nav-tabs` | `fdy-tabs` + `data-fdy-tabs` |
|
|
124
|
+
| `breadcrumb` / `pagination` | `fdy-breadcrumb` / `fdy-pagination` (same `<nav><ol>` structure) |
|
|
125
|
+
| `spinner-border` / `progress` | `fdy-spinner` / `fdy-progress` + `__bar` |
|
|
126
|
+
| `toast` container + JS | `Freeday.toast({…})` — imperative, no markup to author |
|
|
127
|
+
| `text-muted` | `fdy-text-muted` |
|
|
128
|
+
| `d-none` / `visually-hidden` | `fdy-hidden` / `fdy-visually-hidden` |
|
|
129
|
+
| `container` / `row` / `col-*` / `mb-3` / `gap-2` | **stays yours** — Freeday ships no layout utilities |
|
|
130
|
+
|
|
131
|
+
Order of work that avoids a half-migrated mess:
|
|
132
|
+
|
|
133
|
+
1. Load Freeday's CSS and **turn off the old framework's reset/preflight** — `base.css` is the
|
|
134
|
+
reset now. Two resets fighting is the usual source of "everything looks slightly off".
|
|
135
|
+
2. Shell + theme attributes first, so tokens resolve everywhere.
|
|
136
|
+
3. Then screen by screen: page frame → sections → controls. Convert a whole screen at a time;
|
|
137
|
+
half-converted screens can't be reviewed visually.
|
|
138
|
+
4. Delete the old framework's CSS only when no screen references it, then grep for leftover class
|
|
139
|
+
prefixes.
|
|
140
|
+
5. Replace hand-rolled modal/drawer/dropdown JS with the native-`<dialog>` components and the
|
|
141
|
+
enhancers — that is usually where the most code disappears.
|
|
142
|
+
|
|
143
|
+
## 5. Verification checklist (before claiming a screen is done)
|
|
144
|
+
|
|
145
|
+
- Every `fdy-*` class used appears in `COMPONENTS.md`. Grep the diff for `fdy-` and check.
|
|
146
|
+
- No raw hex/rgb/px in the diff's CSS. Grep for `#` and `px`.
|
|
147
|
+
- Exactly one `.fdy-btn` without a variant modifier on the screen; one `.fdy-title-page`.
|
|
148
|
+
- Toggle `data-theme="dark"` on `<html>` — nothing becomes unreadable, no hard-coded white/black.
|
|
149
|
+
- Toggle `data-density="compact"` — the layout still holds.
|
|
150
|
+
- Keyboard: Tab reaches every control, focus is always visible, Esc closes overlays.
|
|
151
|
+
- Form errors carry `aria-invalid` + a linked message; icon-only buttons have `aria-label`.
|
|
152
|
+
- The interactive components on the screen have their enhancer loaded, and SPA-rendered DOM is
|
|
153
|
+
re-hydrated.
|