@cahyo-dimas/freeday 1.21.0 → 1.23.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 +111 -0
- package/COMPONENTS.md +83 -3
- package/README.id.md +23 -1
- package/README.md +22 -1
- package/USAGE.md +21 -0
- package/adapters/react/useFreeday.js +5 -0
- package/adapters/vue/useFreeday.js +5 -0
- package/dist/freeday-autocomplete.js +4 -1
- package/dist/freeday-carousel.js +4 -1
- package/dist/freeday-cascade.js +4 -1
- package/dist/freeday-cfl.js +25 -20
- package/dist/freeday-chart.js +4 -1
- package/dist/freeday-chip.js +3 -0
- package/dist/freeday-datepicker.js +6 -3
- package/dist/freeday-datetime.js +4 -1
- package/dist/freeday-drawer.js +16 -11
- package/dist/freeday-form.js +4 -1
- package/dist/freeday-mask.js +3 -0
- package/dist/freeday-menu.js +4 -1
- package/dist/freeday-rating.js +4 -1
- package/dist/freeday-select.js +4 -5
- package/dist/freeday-slider.js +4 -1
- package/dist/freeday-stepper.js +4 -1
- package/dist/freeday-table.js +4 -4
- package/dist/freeday-tabs.js +4 -4
- package/dist/freeday-timepicker.js +4 -1
- package/dist/freeday-tree.js +4 -1
- package/dist/freeday-upload.js +4 -1
- package/dist/freeday.bundle.css +50 -2
- package/dist/freeday.css +50 -2
- package/dist/freeday.js +117 -60
- package/docs/agent-onboarding.md +35 -4
- package/docs/getting-started.md +10 -5
- package/package.json +2 -2
- package/src/components/app-shell.css +9 -0
- package/src/components/appbar.css +9 -0
- package/src/components/avatar.css +5 -0
- package/src/components/button.css +14 -0
- package/src/components/stepper.css +6 -1
- package/src/components/tabs.css +7 -1
package/dist/freeday-drawer.js
CHANGED
|
@@ -16,19 +16,24 @@
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function bindTrigger(btn) {
|
|
20
|
+
if (btn.dataset.fdyDrawerBound === '1') return;
|
|
21
|
+
var dialog = document.getElementById(btn.getAttribute('data-fdy-drawer'));
|
|
22
|
+
if (!dialog) return;
|
|
23
|
+
initDrawer(dialog);
|
|
24
|
+
btn.dataset.fdyDrawerBound = '1';
|
|
25
|
+
btn.addEventListener('click', function () {
|
|
26
|
+
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
27
|
+
else dialog.setAttribute('open', '');
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
function initTriggers(context) {
|
|
20
32
|
var ctx = context || document;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
initDrawer(dialog);
|
|
26
|
-
btn.dataset.fdyDrawerBound = '1';
|
|
27
|
-
btn.addEventListener('click', function () {
|
|
28
|
-
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
29
|
-
else dialog.setAttribute('open', '');
|
|
30
|
-
});
|
|
31
|
-
});
|
|
33
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
34
|
+
if (ctx.matches && ctx.matches('[data-fdy-drawer]')) bindTrigger(ctx);
|
|
35
|
+
if (ctx.matches && ctx.matches('.fdy-drawer')) initDrawer(ctx);
|
|
36
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-drawer]'), bindTrigger);
|
|
32
37
|
// Init any drawer dialogs even without a wired trigger (backdrop/close still work).
|
|
33
38
|
Array.prototype.forEach.call(ctx.querySelectorAll('.fdy-drawer'), initDrawer);
|
|
34
39
|
}
|
package/dist/freeday-form.js
CHANGED
|
@@ -168,7 +168,10 @@
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function initAll(context) {
|
|
171
|
-
|
|
171
|
+
var root = context || document;
|
|
172
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
173
|
+
if (root.matches && root.matches('[data-fdy-validate]')) initForm(root);
|
|
174
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-validate]'), initForm);
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
if (document.readyState === 'loading') {
|
package/dist/freeday-mask.js
CHANGED
|
@@ -100,6 +100,9 @@
|
|
|
100
100
|
|
|
101
101
|
function initAll(context) {
|
|
102
102
|
var root = context || document;
|
|
103
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
104
|
+
if (root.matches && root.matches('[data-fdy-mask]')) initMask(root);
|
|
105
|
+
if (root.matches && root.matches('[data-fdy-password]')) initReveal(root);
|
|
103
106
|
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-mask]'), initMask);
|
|
104
107
|
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-password]'), initReveal);
|
|
105
108
|
}
|
package/dist/freeday-menu.js
CHANGED
|
@@ -80,7 +80,10 @@
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function initAll(ctx) {
|
|
83
|
-
|
|
83
|
+
var root = ctx || document;
|
|
84
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
85
|
+
if (root.matches && root.matches('[data-fdy-menu]')) initMenu(root);
|
|
86
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-menu]'), initMenu);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
if (document.readyState === 'loading') {
|
package/dist/freeday-rating.js
CHANGED
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function initAll(context) {
|
|
40
|
-
|
|
40
|
+
var root = context || document;
|
|
41
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
42
|
+
if (root.matches && root.matches('[data-fdy-rating]')) initRating(root);
|
|
43
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-rating]'), initRating);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
if (document.readyState === 'loading') {
|
package/dist/freeday-select.js
CHANGED
|
@@ -189,11 +189,10 @@
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
function initAll(context) {
|
|
192
|
-
var
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
192
|
+
var root = context || document;
|
|
193
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
194
|
+
if (root.matches && root.matches('[data-fdy-combo]')) initCombo(root);
|
|
195
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-combo]'), initCombo);
|
|
197
196
|
}
|
|
198
197
|
|
|
199
198
|
// One global handler closes the open combo on an outside click.
|
package/dist/freeday-slider.js
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function initAll(context) {
|
|
24
|
-
|
|
24
|
+
var root = context || document;
|
|
25
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
26
|
+
if (root.matches && root.matches('[data-fdy-slider]')) initSlider(root);
|
|
27
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-slider]'), initSlider);
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
if (document.readyState === 'loading') {
|
package/dist/freeday-stepper.js
CHANGED
|
@@ -77,7 +77,10 @@
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function initAll(context) {
|
|
80
|
-
|
|
80
|
+
var root = context || document;
|
|
81
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
82
|
+
if (root.matches && root.matches('[data-fdy-stepper]')) initStepper(root);
|
|
83
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-stepper]'), initStepper);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
if (document.readyState === 'loading') {
|
package/dist/freeday-table.js
CHANGED
|
@@ -459,10 +459,10 @@
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
function initAll(context) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
);
|
|
462
|
+
var root = context || document;
|
|
463
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
464
|
+
if (root.matches && root.matches('[data-fdy-table]')) initTable(root);
|
|
465
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-table]'), initTable);
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
if (document.readyState === 'loading') {
|
package/dist/freeday-tabs.js
CHANGED
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function initAll(context) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
55
|
+
var root = context || document;
|
|
56
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
57
|
+
if (root.matches && root.matches('[data-fdy-tabs]')) initTabs(root);
|
|
58
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-tabs]'), initTabs);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (document.readyState === 'loading') {
|
|
@@ -167,7 +167,10 @@
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
function initAll(context) {
|
|
170
|
-
|
|
170
|
+
var root = context || document;
|
|
171
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
172
|
+
if (root.matches && root.matches('[data-fdy-timepicker]')) initTime(root);
|
|
173
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-timepicker]'), initTime);
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
if (document.readyState === 'loading') {
|
package/dist/freeday-tree.js
CHANGED
|
@@ -81,7 +81,10 @@
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function initAll(context) {
|
|
84
|
-
|
|
84
|
+
var root = context || document;
|
|
85
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
86
|
+
if (root.matches && root.matches('[data-fdy-tree]')) initTree(root);
|
|
87
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-tree]'), initTree);
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
if (document.readyState === 'loading') {
|
package/dist/freeday-upload.js
CHANGED
|
@@ -193,7 +193,10 @@
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function initAll(context) {
|
|
196
|
-
|
|
196
|
+
var root = context || document;
|
|
197
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
198
|
+
if (root.matches && root.matches('[data-fdy-dropzone]')) initDropzone(root);
|
|
199
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-dropzone]'), initDropzone);
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
if (document.readyState === 'loading') {
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -473,6 +473,15 @@ a { color: var(--color-primary); }
|
|
|
473
473
|
.fdy-app__brand-title{display:block;font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
474
474
|
.fdy-app__brand-subtitle{display:block;font-size:var(--text-xs);font-weight:var(--weight-regular);color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
475
475
|
.fdy-nav{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;gap:2px;padding:var(--space-3) var(--space-3) var(--space-6);}
|
|
476
|
+
/* Horizontal variant — the SAME nav links laid out as a row, for a top-nav application (one that
|
|
477
|
+
* puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
|
|
478
|
+
* a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
|
|
479
|
+
* is the point — a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
|
|
480
|
+
* ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
|
|
481
|
+
* (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
|
|
482
|
+
* row scrolls; see base.css on why a scroller must be a containing block. */
|
|
483
|
+
.fdy-nav--horizontal{position:relative;flex:0 1 auto;flex-direction:row;align-items:center;gap:var(--space-1);padding:0;min-height:0;overflow-x:auto;}
|
|
484
|
+
.fdy-nav--horizontal .fdy-nav__item{white-space:nowrap;}
|
|
476
485
|
.fdy-nav__item{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);text-decoration:none;cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
|
|
477
486
|
.fdy-nav__item:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
478
487
|
/* Active indicator stays in the primary ramp so it never clashes with a re-themed accent. */
|
|
@@ -548,9 +557,18 @@ a { color: var(--color-primary); }
|
|
|
548
557
|
.fdy-appbar__brand svg{display:block;width:1.5rem;height:1.5rem;}
|
|
549
558
|
.fdy-appbar__spacer{flex:1;}
|
|
550
559
|
.fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
|
|
560
|
+
/* Primary navigation inside the bar: `<nav class="fdy-nav fdy-nav--horizontal">` with
|
|
561
|
+
`.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own — a nav link
|
|
562
|
+
is the same component whether the nav is a sidebar column or a top row. */
|
|
551
563
|
/* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
|
|
552
564
|
.fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
|
|
553
565
|
.fdy-appbar--primary .fdy-btn--ghost:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);transform:none;}
|
|
566
|
+
/* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
|
|
567
|
+
the contrast the bar's own token pair guarantees). The current page is carried by the background
|
|
568
|
+
wash plus the semibold weight the base rule already applies — and by `aria-current` itself. */
|
|
569
|
+
.fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
|
|
570
|
+
.fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
|
|
571
|
+
.fdy-appbar--primary .fdy-nav__item[aria-current="page"]{background:color-mix(in srgb,var(--color-on-primary) 24%,transparent);color:var(--color-on-primary);}
|
|
554
572
|
|
|
555
573
|
/* Freeday — Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
|
|
556
574
|
* Enhanced by freeday-autocomplete.js. */
|
|
@@ -568,6 +586,11 @@ a { color: var(--color-primary); }
|
|
|
568
586
|
/* Freeday — Avatar */
|
|
569
587
|
.fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;user-select:none;}
|
|
570
588
|
.fdy-avatar img{width:100%;height:100%;object-fit:cover;}
|
|
589
|
+
/* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
|
|
590
|
+
`--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
|
|
591
|
+
crosses the circle. 1.5rem leaves the same ~4px breathing room the kit's controls give their
|
|
592
|
+
icons. Uses `--text-xs` (the smallest type token the kit has — there is no `2xs`). */
|
|
593
|
+
.fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
|
|
571
594
|
.fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
|
|
572
595
|
.fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
|
|
573
596
|
.fdy-avatar-group{display:inline-flex;}
|
|
@@ -653,6 +676,14 @@ a { color: var(--color-primary); }
|
|
|
653
676
|
.fdy-btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-primary) 60%,transparent),inset 0 1px 0 rgba(255,255,255,.26);}
|
|
654
677
|
.fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
|
|
655
678
|
.fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
|
|
679
|
+
/* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
|
|
680
|
+
no class and no new markup contract — and it is what makes .fdy-btn-group a complete segmented
|
|
681
|
+
control: the group joins the borders, this says which segment you are on. Each fill variant
|
|
682
|
+
defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
|
|
683
|
+
background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
|
|
684
|
+
and sink the shadow inward, so a held button reads as depressed, not merely primary-coloured. */
|
|
685
|
+
.fdy-btn[aria-pressed="true"]{background:linear-gradient(180deg,color-mix(in srgb,#000 12%,var(--color-primary)),var(--color-primary));box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
|
|
686
|
+
.fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
|
|
656
687
|
|
|
657
688
|
.fdy-btn--ghost{background:var(--color-surface);color:var(--color-primary-strong);border-color:var(--color-border-strong);box-shadow:var(--shadow-1);}
|
|
658
689
|
.fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
|
|
@@ -661,6 +692,7 @@ a { color: var(--color-primary); }
|
|
|
661
692
|
.fdy-btn--danger{background:linear-gradient(180deg,var(--color-danger-btn),color-mix(in srgb,#000 12%,var(--color-danger-btn)));color:var(--color-on-danger);box-shadow:0 2px 9px -1px color-mix(in srgb,var(--color-danger-btn) 46%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
|
|
662
693
|
.fdy-btn--danger:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-danger-btn) 54%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
|
|
663
694
|
.fdy-btn--danger:active{transform:translateY(1px);}
|
|
695
|
+
.fdy-btn--danger[aria-pressed="true"]{background:linear-gradient(180deg,color-mix(in srgb,#000 14%,var(--color-danger-btn)),var(--color-danger-btn));box-shadow:inset 0 2px 6px color-mix(in srgb,#000 24%,transparent);}
|
|
664
696
|
|
|
665
697
|
.fdy-btn--sm{height:calc(var(--control-h) - var(--space-2));padding:0 var(--space-3);font-size:var(--text-xs);border-radius:var(--radius-sm);}
|
|
666
698
|
.fdy-btn--lg{height:calc(var(--control-h) + var(--space-2));padding:0 var(--space-6);font-size:var(--text-base);border-radius:var(--radius-lg);}
|
|
@@ -673,6 +705,11 @@ a { color: var(--color-primary); }
|
|
|
673
705
|
.fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
|
|
674
706
|
.fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
|
|
675
707
|
.fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
|
|
708
|
+
/* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink) — the same pairing
|
|
709
|
+
.fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
|
|
710
|
+
the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
|
|
711
|
+
.fdy-btn--ghost[aria-pressed="true"],.fdy-btn--text[aria-pressed="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);border-color:var(--color-primary-border);box-shadow:none;}
|
|
712
|
+
.fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}
|
|
676
713
|
|
|
677
714
|
/* Icon-only — square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
|
|
678
715
|
.fdy-btn--icon{width:var(--control-h);padding:0;}
|
|
@@ -1425,7 +1462,12 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1425
1462
|
.fdy-step.is-active::before,.fdy-step.is-complete::before{background:var(--color-primary);}
|
|
1426
1463
|
.fdy-step__btn{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:0 var(--space-2);background:none;border:0;font:inherit;color:inherit;cursor:pointer;max-width:100%;}
|
|
1427
1464
|
.fdy-step__btn:disabled{cursor:default;}
|
|
1428
|
-
|
|
1465
|
+
/* The marker carries the lift itself, NOT `__btn`. The connector above is position:absolute with
|
|
1466
|
+
z-index:0, and a positioned box paints after in-flow inline content in the same stacking context —
|
|
1467
|
+
so a marker that is merely inline (an indicator-only stepper with no `__btn`) gets painted THROUGH
|
|
1468
|
+
by the connector line. Lifting the part that must always exist makes that assembly impossible;
|
|
1469
|
+
`__btn`'s own z-index stays, harmlessly, for the navigable case. */
|
|
1470
|
+
.fdy-step__marker{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--radius-full);background:var(--color-surface);border:2px solid var(--color-border-strong);color:var(--color-text-muted);font-size:var(--text-sm);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;}
|
|
1429
1471
|
.fdy-step__marker svg{display:block;width:1rem;height:1rem;}
|
|
1430
1472
|
.fdy-step.is-active .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
|
|
1431
1473
|
.fdy-step.is-complete .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary);color:var(--color-on-primary);}
|
|
@@ -1518,7 +1560,13 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1518
1560
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
1519
1561
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
1520
1562
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
|
1521
|
-
|
|
1563
|
+
/* `aria-current="page"` is honoured alongside `aria-selected="true"` so the tab look can be used for
|
|
1564
|
+
ROUTE-driven sub-navigation (/settings/profile · /settings/billing), where the tabs are real links.
|
|
1565
|
+
A link cannot be `aria-selected` — that is invalid ARIA on an anchor; `aria-current` is the only
|
|
1566
|
+
correct marker, and it is what a router sets. Use `.fdy-tabs` + `freeday-tabs.js` (roving tabindex,
|
|
1567
|
+
a panel per tab) for in-page tabs; use these classes on plain links for routed ones, and
|
|
1568
|
+
`.fdy-nav--horizontal` for an application's PRIMARY navigation. */
|
|
1569
|
+
.fdy-tabs__tab[aria-selected="true"],.fdy-tabs__tab[aria-current="page"]{color:var(--color-primary-strong);border-bottom-color:var(--color-primary);}
|
|
1522
1570
|
.fdy-tabs__tab:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
1523
1571
|
.fdy-tabs__panel{padding:var(--space-5) 0;}
|
|
1524
1572
|
.fdy-tabs__panel[hidden]{display:none;}
|
package/dist/freeday.css
CHANGED
|
@@ -103,6 +103,15 @@ a { color: var(--color-primary); }
|
|
|
103
103
|
.fdy-app__brand-title{display:block;font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
104
104
|
.fdy-app__brand-subtitle{display:block;font-size:var(--text-xs);font-weight:var(--weight-regular);color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
105
105
|
.fdy-nav{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;gap:2px;padding:var(--space-3) var(--space-3) var(--space-6);}
|
|
106
|
+
/* Horizontal variant — the SAME nav links laid out as a row, for a top-nav application (one that
|
|
107
|
+
* puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
|
|
108
|
+
* a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
|
|
109
|
+
* is the point — a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
|
|
110
|
+
* ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
|
|
111
|
+
* (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
|
|
112
|
+
* row scrolls; see base.css on why a scroller must be a containing block. */
|
|
113
|
+
.fdy-nav--horizontal{position:relative;flex:0 1 auto;flex-direction:row;align-items:center;gap:var(--space-1);padding:0;min-height:0;overflow-x:auto;}
|
|
114
|
+
.fdy-nav--horizontal .fdy-nav__item{white-space:nowrap;}
|
|
106
115
|
.fdy-nav__item{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);text-decoration:none;cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
|
|
107
116
|
.fdy-nav__item:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
108
117
|
/* Active indicator stays in the primary ramp so it never clashes with a re-themed accent. */
|
|
@@ -178,9 +187,18 @@ a { color: var(--color-primary); }
|
|
|
178
187
|
.fdy-appbar__brand svg{display:block;width:1.5rem;height:1.5rem;}
|
|
179
188
|
.fdy-appbar__spacer{flex:1;}
|
|
180
189
|
.fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
|
|
190
|
+
/* Primary navigation inside the bar: `<nav class="fdy-nav fdy-nav--horizontal">` with
|
|
191
|
+
`.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own — a nav link
|
|
192
|
+
is the same component whether the nav is a sidebar column or a top row. */
|
|
181
193
|
/* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
|
|
182
194
|
.fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
|
|
183
195
|
.fdy-appbar--primary .fdy-btn--ghost:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);transform:none;}
|
|
196
|
+
/* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
|
|
197
|
+
the contrast the bar's own token pair guarantees). The current page is carried by the background
|
|
198
|
+
wash plus the semibold weight the base rule already applies — and by `aria-current` itself. */
|
|
199
|
+
.fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
|
|
200
|
+
.fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
|
|
201
|
+
.fdy-appbar--primary .fdy-nav__item[aria-current="page"]{background:color-mix(in srgb,var(--color-on-primary) 24%,transparent);color:var(--color-on-primary);}
|
|
184
202
|
|
|
185
203
|
/* Freeday — Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
|
|
186
204
|
* Enhanced by freeday-autocomplete.js. */
|
|
@@ -198,6 +216,11 @@ a { color: var(--color-primary); }
|
|
|
198
216
|
/* Freeday — Avatar */
|
|
199
217
|
.fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;user-select:none;}
|
|
200
218
|
.fdy-avatar img{width:100%;height:100%;object-fit:cover;}
|
|
219
|
+
/* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
|
|
220
|
+
`--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
|
|
221
|
+
crosses the circle. 1.5rem leaves the same ~4px breathing room the kit's controls give their
|
|
222
|
+
icons. Uses `--text-xs` (the smallest type token the kit has — there is no `2xs`). */
|
|
223
|
+
.fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
|
|
201
224
|
.fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
|
|
202
225
|
.fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
|
|
203
226
|
.fdy-avatar-group{display:inline-flex;}
|
|
@@ -283,6 +306,14 @@ a { color: var(--color-primary); }
|
|
|
283
306
|
.fdy-btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-primary) 60%,transparent),inset 0 1px 0 rgba(255,255,255,.26);}
|
|
284
307
|
.fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
|
|
285
308
|
.fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
|
|
309
|
+
/* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
|
|
310
|
+
no class and no new markup contract — and it is what makes .fdy-btn-group a complete segmented
|
|
311
|
+
control: the group joins the borders, this says which segment you are on. Each fill variant
|
|
312
|
+
defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
|
|
313
|
+
background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
|
|
314
|
+
and sink the shadow inward, so a held button reads as depressed, not merely primary-coloured. */
|
|
315
|
+
.fdy-btn[aria-pressed="true"]{background:linear-gradient(180deg,color-mix(in srgb,#000 12%,var(--color-primary)),var(--color-primary));box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
|
|
316
|
+
.fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
|
|
286
317
|
|
|
287
318
|
.fdy-btn--ghost{background:var(--color-surface);color:var(--color-primary-strong);border-color:var(--color-border-strong);box-shadow:var(--shadow-1);}
|
|
288
319
|
.fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
|
|
@@ -291,6 +322,7 @@ a { color: var(--color-primary); }
|
|
|
291
322
|
.fdy-btn--danger{background:linear-gradient(180deg,var(--color-danger-btn),color-mix(in srgb,#000 12%,var(--color-danger-btn)));color:var(--color-on-danger);box-shadow:0 2px 9px -1px color-mix(in srgb,var(--color-danger-btn) 46%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
|
|
292
323
|
.fdy-btn--danger:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-danger-btn) 54%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
|
|
293
324
|
.fdy-btn--danger:active{transform:translateY(1px);}
|
|
325
|
+
.fdy-btn--danger[aria-pressed="true"]{background:linear-gradient(180deg,color-mix(in srgb,#000 14%,var(--color-danger-btn)),var(--color-danger-btn));box-shadow:inset 0 2px 6px color-mix(in srgb,#000 24%,transparent);}
|
|
294
326
|
|
|
295
327
|
.fdy-btn--sm{height:calc(var(--control-h) - var(--space-2));padding:0 var(--space-3);font-size:var(--text-xs);border-radius:var(--radius-sm);}
|
|
296
328
|
.fdy-btn--lg{height:calc(var(--control-h) + var(--space-2));padding:0 var(--space-6);font-size:var(--text-base);border-radius:var(--radius-lg);}
|
|
@@ -303,6 +335,11 @@ a { color: var(--color-primary); }
|
|
|
303
335
|
.fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
|
|
304
336
|
.fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
|
|
305
337
|
.fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
|
|
338
|
+
/* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink) — the same pairing
|
|
339
|
+
.fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
|
|
340
|
+
the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
|
|
341
|
+
.fdy-btn--ghost[aria-pressed="true"],.fdy-btn--text[aria-pressed="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);border-color:var(--color-primary-border);box-shadow:none;}
|
|
342
|
+
.fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}
|
|
306
343
|
|
|
307
344
|
/* Icon-only — square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
|
|
308
345
|
.fdy-btn--icon{width:var(--control-h);padding:0;}
|
|
@@ -1055,7 +1092,12 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1055
1092
|
.fdy-step.is-active::before,.fdy-step.is-complete::before{background:var(--color-primary);}
|
|
1056
1093
|
.fdy-step__btn{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:0 var(--space-2);background:none;border:0;font:inherit;color:inherit;cursor:pointer;max-width:100%;}
|
|
1057
1094
|
.fdy-step__btn:disabled{cursor:default;}
|
|
1058
|
-
|
|
1095
|
+
/* The marker carries the lift itself, NOT `__btn`. The connector above is position:absolute with
|
|
1096
|
+
z-index:0, and a positioned box paints after in-flow inline content in the same stacking context —
|
|
1097
|
+
so a marker that is merely inline (an indicator-only stepper with no `__btn`) gets painted THROUGH
|
|
1098
|
+
by the connector line. Lifting the part that must always exist makes that assembly impossible;
|
|
1099
|
+
`__btn`'s own z-index stays, harmlessly, for the navigable case. */
|
|
1100
|
+
.fdy-step__marker{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--radius-full);background:var(--color-surface);border:2px solid var(--color-border-strong);color:var(--color-text-muted);font-size:var(--text-sm);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;}
|
|
1059
1101
|
.fdy-step__marker svg{display:block;width:1rem;height:1rem;}
|
|
1060
1102
|
.fdy-step.is-active .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
|
|
1061
1103
|
.fdy-step.is-complete .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary);color:var(--color-on-primary);}
|
|
@@ -1148,7 +1190,13 @@ fieldset.fdy-field>legend{padding:0;float:none;}
|
|
|
1148
1190
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
1149
1191
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
1150
1192
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
|
1151
|
-
|
|
1193
|
+
/* `aria-current="page"` is honoured alongside `aria-selected="true"` so the tab look can be used for
|
|
1194
|
+
ROUTE-driven sub-navigation (/settings/profile · /settings/billing), where the tabs are real links.
|
|
1195
|
+
A link cannot be `aria-selected` — that is invalid ARIA on an anchor; `aria-current` is the only
|
|
1196
|
+
correct marker, and it is what a router sets. Use `.fdy-tabs` + `freeday-tabs.js` (roving tabindex,
|
|
1197
|
+
a panel per tab) for in-page tabs; use these classes on plain links for routed ones, and
|
|
1198
|
+
`.fdy-nav--horizontal` for an application's PRIMARY navigation. */
|
|
1199
|
+
.fdy-tabs__tab[aria-selected="true"],.fdy-tabs__tab[aria-current="page"]{color:var(--color-primary-strong);border-bottom-color:var(--color-primary);}
|
|
1152
1200
|
.fdy-tabs__tab:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
1153
1201
|
.fdy-tabs__panel{padding:var(--space-5) 0;}
|
|
1154
1202
|
.fdy-tabs__panel[hidden]{display:none;}
|