@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.js
CHANGED
|
@@ -124,7 +124,10 @@
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
function initAll(context) {
|
|
127
|
-
|
|
127
|
+
var root = context || document;
|
|
128
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
129
|
+
if (root.matches && root.matches('[data-fdy-autocomplete]')) initAutocomplete(root);
|
|
130
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-autocomplete]'), initAutocomplete);
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
if (document.readyState === 'loading') {
|
|
@@ -288,7 +291,10 @@
|
|
|
288
291
|
}
|
|
289
292
|
|
|
290
293
|
function initAll(context) {
|
|
291
|
-
|
|
294
|
+
var root = context || document;
|
|
295
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
296
|
+
if (root.matches && root.matches('[data-fdy-carousel]')) initCarousel(root);
|
|
297
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-carousel]'), initCarousel);
|
|
292
298
|
}
|
|
293
299
|
|
|
294
300
|
if (document.readyState === 'loading') {
|
|
@@ -545,7 +551,10 @@
|
|
|
545
551
|
}
|
|
546
552
|
|
|
547
553
|
function initAll(context) {
|
|
548
|
-
|
|
554
|
+
var root = context || document;
|
|
555
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
556
|
+
if (root.matches && root.matches('[data-fdy-cascade]')) initCascade(root);
|
|
557
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-cascade]'), initCascade);
|
|
549
558
|
}
|
|
550
559
|
|
|
551
560
|
if (document.readyState === 'loading') {
|
|
@@ -738,28 +747,33 @@
|
|
|
738
747
|
};
|
|
739
748
|
}
|
|
740
749
|
|
|
741
|
-
function
|
|
742
|
-
|
|
750
|
+
function bindGroup(group) {
|
|
751
|
+
if (group.dataset.fdyCflBound === '1') return;
|
|
752
|
+
var dialog = document.getElementById(group.getAttribute('data-fdy-cfl'));
|
|
753
|
+
if (!dialog) return;
|
|
754
|
+
initDialog(dialog);
|
|
755
|
+
var opener = group.querySelector('[data-fdy-cfl-trigger], .fdy-input-group__btn');
|
|
756
|
+
if (!opener) return;
|
|
757
|
+
group.dataset.fdyCflBound = '1';
|
|
758
|
+
opener.addEventListener('click', function () { dialog.fdyCflOpen(group, opener); });
|
|
759
|
+
}
|
|
743
760
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
opener.addEventListener('click', function () { dialog.fdyCflOpen(group, opener); });
|
|
753
|
-
});
|
|
761
|
+
function bindOpener(opener) {
|
|
762
|
+
if (opener.dataset.fdyCflBound === '1') return;
|
|
763
|
+
var dialog = document.getElementById(opener.getAttribute('data-fdy-cfl-open'));
|
|
764
|
+
if (!dialog) return;
|
|
765
|
+
initDialog(dialog);
|
|
766
|
+
opener.dataset.fdyCflBound = '1';
|
|
767
|
+
opener.addEventListener('click', function () { dialog.fdyCflOpen(null, opener); });
|
|
768
|
+
}
|
|
754
769
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
});
|
|
770
|
+
function initTriggers(context) {
|
|
771
|
+
var ctx = context || document;
|
|
772
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
773
|
+
if (ctx.matches && ctx.matches('[data-fdy-cfl]')) bindGroup(ctx);
|
|
774
|
+
if (ctx.matches && ctx.matches('[data-fdy-cfl-open]')) bindOpener(ctx);
|
|
775
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-cfl]'), bindGroup);
|
|
776
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-cfl-open]'), bindOpener);
|
|
763
777
|
}
|
|
764
778
|
|
|
765
779
|
if (document.readyState === 'loading') {
|
|
@@ -1189,7 +1203,10 @@
|
|
|
1189
1203
|
}
|
|
1190
1204
|
|
|
1191
1205
|
function initAll(context) {
|
|
1192
|
-
|
|
1206
|
+
var root = context || document;
|
|
1207
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
1208
|
+
if (root.matches && root.matches('[data-fdy-chart]')) initChart(root);
|
|
1209
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-chart]'), initChart);
|
|
1193
1210
|
}
|
|
1194
1211
|
|
|
1195
1212
|
if (document.readyState === 'loading') {
|
|
@@ -1272,6 +1289,9 @@
|
|
|
1272
1289
|
|
|
1273
1290
|
function initAll(context) {
|
|
1274
1291
|
var root = context || document;
|
|
1292
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
1293
|
+
if (root.matches && root.matches('[data-fdy-chips]')) initGroup(root);
|
|
1294
|
+
if (root.matches && root.matches('.fdy-chip__remove')) initRemove(root);
|
|
1275
1295
|
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-chips]'), initGroup);
|
|
1276
1296
|
Array.prototype.forEach.call(root.querySelectorAll('.fdy-chip__remove'), initRemove);
|
|
1277
1297
|
}
|
|
@@ -1593,9 +1613,12 @@
|
|
|
1593
1613
|
}
|
|
1594
1614
|
|
|
1595
1615
|
function initAll(context) {
|
|
1596
|
-
var
|
|
1597
|
-
|
|
1598
|
-
|
|
1616
|
+
var root = context || document;
|
|
1617
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
1618
|
+
if (root.matches && root.matches('[data-fdy-daterange]')) initRange(root);
|
|
1619
|
+
if (root.matches && root.matches('[data-fdy-datepicker]')) initPicker(root);
|
|
1620
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-daterange]'), initRange);
|
|
1621
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-datepicker]'), initPicker);
|
|
1599
1622
|
}
|
|
1600
1623
|
|
|
1601
1624
|
if (document.readyState === 'loading') {
|
|
@@ -1679,7 +1702,10 @@
|
|
|
1679
1702
|
}
|
|
1680
1703
|
|
|
1681
1704
|
function initAll(context) {
|
|
1682
|
-
|
|
1705
|
+
var root = context || document;
|
|
1706
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
1707
|
+
if (root.matches && root.matches('[data-fdy-datetimepicker]')) initDatetime(root);
|
|
1708
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-datetimepicker]'), initDatetime);
|
|
1683
1709
|
}
|
|
1684
1710
|
|
|
1685
1711
|
if (document.readyState === 'loading') {
|
|
@@ -1709,19 +1735,24 @@
|
|
|
1709
1735
|
});
|
|
1710
1736
|
}
|
|
1711
1737
|
|
|
1738
|
+
function bindTrigger(btn) {
|
|
1739
|
+
if (btn.dataset.fdyDrawerBound === '1') return;
|
|
1740
|
+
var dialog = document.getElementById(btn.getAttribute('data-fdy-drawer'));
|
|
1741
|
+
if (!dialog) return;
|
|
1742
|
+
initDrawer(dialog);
|
|
1743
|
+
btn.dataset.fdyDrawerBound = '1';
|
|
1744
|
+
btn.addEventListener('click', function () {
|
|
1745
|
+
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
1746
|
+
else dialog.setAttribute('open', '');
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1712
1750
|
function initTriggers(context) {
|
|
1713
1751
|
var ctx = context || document;
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
initDrawer(dialog);
|
|
1719
|
-
btn.dataset.fdyDrawerBound = '1';
|
|
1720
|
-
btn.addEventListener('click', function () {
|
|
1721
|
-
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
1722
|
-
else dialog.setAttribute('open', '');
|
|
1723
|
-
});
|
|
1724
|
-
});
|
|
1752
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
1753
|
+
if (ctx.matches && ctx.matches('[data-fdy-drawer]')) bindTrigger(ctx);
|
|
1754
|
+
if (ctx.matches && ctx.matches('.fdy-drawer')) initDrawer(ctx);
|
|
1755
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-drawer]'), bindTrigger);
|
|
1725
1756
|
// Init any drawer dialogs even without a wired trigger (backdrop/close still work).
|
|
1726
1757
|
Array.prototype.forEach.call(ctx.querySelectorAll('.fdy-drawer'), initDrawer);
|
|
1727
1758
|
}
|
|
@@ -1905,7 +1936,10 @@
|
|
|
1905
1936
|
}
|
|
1906
1937
|
|
|
1907
1938
|
function initAll(context) {
|
|
1908
|
-
|
|
1939
|
+
var root = context || document;
|
|
1940
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
1941
|
+
if (root.matches && root.matches('[data-fdy-validate]')) initForm(root);
|
|
1942
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-validate]'), initForm);
|
|
1909
1943
|
}
|
|
1910
1944
|
|
|
1911
1945
|
if (document.readyState === 'loading') {
|
|
@@ -2019,6 +2053,9 @@
|
|
|
2019
2053
|
|
|
2020
2054
|
function initAll(context) {
|
|
2021
2055
|
var root = context || document;
|
|
2056
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
2057
|
+
if (root.matches && root.matches('[data-fdy-mask]')) initMask(root);
|
|
2058
|
+
if (root.matches && root.matches('[data-fdy-password]')) initReveal(root);
|
|
2022
2059
|
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-mask]'), initMask);
|
|
2023
2060
|
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-password]'), initReveal);
|
|
2024
2061
|
}
|
|
@@ -2114,7 +2151,10 @@
|
|
|
2114
2151
|
}
|
|
2115
2152
|
|
|
2116
2153
|
function initAll(ctx) {
|
|
2117
|
-
|
|
2154
|
+
var root = ctx || document;
|
|
2155
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
2156
|
+
if (root.matches && root.matches('[data-fdy-menu]')) initMenu(root);
|
|
2157
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-menu]'), initMenu);
|
|
2118
2158
|
}
|
|
2119
2159
|
|
|
2120
2160
|
if (document.readyState === 'loading') {
|
|
@@ -2235,7 +2275,10 @@
|
|
|
2235
2275
|
}
|
|
2236
2276
|
|
|
2237
2277
|
function initAll(context) {
|
|
2238
|
-
|
|
2278
|
+
var root = context || document;
|
|
2279
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
2280
|
+
if (root.matches && root.matches('[data-fdy-rating]')) initRating(root);
|
|
2281
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-rating]'), initRating);
|
|
2239
2282
|
}
|
|
2240
2283
|
|
|
2241
2284
|
if (document.readyState === 'loading') {
|
|
@@ -2438,11 +2481,10 @@
|
|
|
2438
2481
|
}
|
|
2439
2482
|
|
|
2440
2483
|
function initAll(context) {
|
|
2441
|
-
var
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
);
|
|
2484
|
+
var root = context || document;
|
|
2485
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
2486
|
+
if (root.matches && root.matches('[data-fdy-combo]')) initCombo(root);
|
|
2487
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-combo]'), initCombo);
|
|
2446
2488
|
}
|
|
2447
2489
|
|
|
2448
2490
|
// One global handler closes the open combo on an outside click.
|
|
@@ -2489,7 +2531,10 @@
|
|
|
2489
2531
|
}
|
|
2490
2532
|
|
|
2491
2533
|
function initAll(context) {
|
|
2492
|
-
|
|
2534
|
+
var root = context || document;
|
|
2535
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
2536
|
+
if (root.matches && root.matches('[data-fdy-slider]')) initSlider(root);
|
|
2537
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-slider]'), initSlider);
|
|
2493
2538
|
}
|
|
2494
2539
|
|
|
2495
2540
|
if (document.readyState === 'loading') {
|
|
@@ -2580,7 +2625,10 @@
|
|
|
2580
2625
|
}
|
|
2581
2626
|
|
|
2582
2627
|
function initAll(context) {
|
|
2583
|
-
|
|
2628
|
+
var root = context || document;
|
|
2629
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
2630
|
+
if (root.matches && root.matches('[data-fdy-stepper]')) initStepper(root);
|
|
2631
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-stepper]'), initStepper);
|
|
2584
2632
|
}
|
|
2585
2633
|
|
|
2586
2634
|
if (document.readyState === 'loading') {
|
|
@@ -3053,10 +3101,10 @@
|
|
|
3053
3101
|
}
|
|
3054
3102
|
|
|
3055
3103
|
function initAll(context) {
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
);
|
|
3104
|
+
var root = context || document;
|
|
3105
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
3106
|
+
if (root.matches && root.matches('[data-fdy-table]')) initTable(root);
|
|
3107
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-table]'), initTable);
|
|
3060
3108
|
}
|
|
3061
3109
|
|
|
3062
3110
|
if (document.readyState === 'loading') {
|
|
@@ -3122,10 +3170,10 @@
|
|
|
3122
3170
|
}
|
|
3123
3171
|
|
|
3124
3172
|
function initAll(context) {
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
);
|
|
3173
|
+
var root = context || document;
|
|
3174
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
3175
|
+
if (root.matches && root.matches('[data-fdy-tabs]')) initTabs(root);
|
|
3176
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-tabs]'), initTabs);
|
|
3129
3177
|
}
|
|
3130
3178
|
|
|
3131
3179
|
if (document.readyState === 'loading') {
|
|
@@ -3306,7 +3354,10 @@
|
|
|
3306
3354
|
}
|
|
3307
3355
|
|
|
3308
3356
|
function initAll(context) {
|
|
3309
|
-
|
|
3357
|
+
var root = context || document;
|
|
3358
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
3359
|
+
if (root.matches && root.matches('[data-fdy-timepicker]')) initTime(root);
|
|
3360
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-timepicker]'), initTime);
|
|
3310
3361
|
}
|
|
3311
3362
|
|
|
3312
3363
|
if (document.readyState === 'loading') {
|
|
@@ -3507,7 +3558,10 @@
|
|
|
3507
3558
|
}
|
|
3508
3559
|
|
|
3509
3560
|
function initAll(context) {
|
|
3510
|
-
|
|
3561
|
+
var root = context || document;
|
|
3562
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
3563
|
+
if (root.matches && root.matches('[data-fdy-tree]')) initTree(root);
|
|
3564
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-tree]'), initTree);
|
|
3511
3565
|
}
|
|
3512
3566
|
|
|
3513
3567
|
if (document.readyState === 'loading') {
|
|
@@ -3714,7 +3768,10 @@
|
|
|
3714
3768
|
}
|
|
3715
3769
|
|
|
3716
3770
|
function initAll(context) {
|
|
3717
|
-
|
|
3771
|
+
var root = context || document;
|
|
3772
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
3773
|
+
if (root.matches && root.matches('[data-fdy-dropzone]')) initDropzone(root);
|
|
3774
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-dropzone]'), initDropzone);
|
|
3718
3775
|
}
|
|
3719
3776
|
|
|
3720
3777
|
if (document.readyState === 'loading') {
|
package/docs/agent-onboarding.md
CHANGED
|
@@ -18,8 +18,38 @@ the **root of the consuming project**:
|
|
|
18
18
|
```markdown
|
|
19
19
|
## UI: Freeday design system (@cahyo-dimas/freeday)
|
|
20
20
|
|
|
21
|
-
All UI in this project is built from Freeday
|
|
22
|
-
|
|
21
|
+
All UI in this project is built from Freeday: a **token-driven CSS kit** (`fdy-*` classes on plain
|
|
22
|
+
markup) with **typed components for Vue, React and Blazor** layered on top. Most of the kit is
|
|
23
|
+
markup + classes; ten interactive components also ship a typed wrapper, and in those three stacks
|
|
24
|
+
the wrapper is the correct way to use them.
|
|
25
|
+
|
|
26
|
+
**0. First decide which entry point this project uses. This is not an optimisation — get it wrong
|
|
27
|
+
and the code looks correct and fails later.**
|
|
28
|
+
|
|
29
|
+
| This project's stack | Import the ten components from | Binding |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Vue 3 | `@cahyo-dimas/freeday/vue` | `v-model` |
|
|
32
|
+
| React 18/19 | `@cahyo-dimas/freeday/react` | `value` + `onChange` |
|
|
33
|
+
| Blazor (net8.0) | `@using Freeday.Blazor` (RCL) | `@bind-Value` |
|
|
34
|
+
| Static HTML, Svelte, server-rendered templates… | no wrapper — raw markup + the enhancer script | `fdy-*` DOM events |
|
|
35
|
+
|
|
36
|
+
The ten: **FdyCombo · FdyDatepicker · FdyDateRange · FdyAutocomplete · FdyCascade · FdyCfl ·
|
|
37
|
+
FdyChart · FdyTable · FdyModal · FdyDrawer**. In Vue/React/Blazor, **never hand-write the raw
|
|
38
|
+
markup + enhancer for these ten.** The raw path *appears* to work — the enhancer auto-initialises
|
|
39
|
+
once on `DOMContentLoaded` and the first render is correct — then fails quietly: DOM your framework
|
|
40
|
+
renders later is never hydrated, and the widget's state lives in the DOM instead of in your
|
|
41
|
+
framework's state.
|
|
42
|
+
|
|
43
|
+
Everything else is the same in every stack: plain `fdy-*` markup (button, card, badge, alert,
|
|
44
|
+
table markup, layout…). For the *other* interactive components (chips, stepper, input mask, file
|
|
45
|
+
upload, tree, tabs, menu, rating, slider, form validation, carousel, timepicker) there is no
|
|
46
|
+
wrapper — use the raw markup and hydrate it:
|
|
47
|
+
|
|
48
|
+
- **Vue / React** — `useFreeday(rootRef)` from the same import path, plus `import '@cahyo-dimas/freeday'`
|
|
49
|
+
once at app entry to register the enhancers. (The ten typed components do **not** need this: they
|
|
50
|
+
are native Vue/React implementations of the same markup, not wrappers over the enhancer.)
|
|
51
|
+
- **Blazor** — `FreedayBlazor.initAll` interop. (Here the typed components *are* thin wrappers over
|
|
52
|
+
the enhancers, so the enhancer script is always required.)
|
|
23
53
|
|
|
24
54
|
**Before writing or editing any markup/CSS, read these (they ship inside the package):**
|
|
25
55
|
- `node_modules/@cahyo-dimas/freeday/COMPONENTS.md` — every class that exists, with minimal markup
|
|
@@ -40,8 +70,9 @@ enhancers**, not a component framework — components are plain markup with `fdy
|
|
|
40
70
|
5. Assemble from the frame down: `.fdy-app` → `.fdy-page` → `.fdy-page-section` → components.
|
|
41
71
|
6. Form errors: `aria-invalid="true"` + `aria-describedby` → a `.fdy-help.fdy-help--error`.
|
|
42
72
|
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
|
-
|
|
73
|
+
7. Interactive components need their enhancer script loaded (see the table in COMPONENTS.md); in an
|
|
74
|
+
SPA, re-hydrate dynamic DOM with `useFreeday` / `FreedayBlazor.initAll`. This applies to the
|
|
75
|
+
components **without** a typed wrapper — for the ten in step 0, use the wrapper instead.
|
|
45
76
|
8. Freeday owns components + tokens, **not layout**. Grids/stacks/one-off gaps come from our own
|
|
46
77
|
layout layer — build its theme on `var(--space-N)` so both systems stay in step.
|
|
47
78
|
```
|
package/docs/getting-started.md
CHANGED
|
@@ -17,10 +17,15 @@ Freeday = **CSS** (semantic tokens + `fdy-*` classes) + **zero-dependency JS enh
|
|
|
17
17
|
1. **Static vs interactive.** Static components (button, card, badge, plain input, layout) need
|
|
18
18
|
only the **`fdy-*` classes** — no JS. Interactive components (select/combo, cascade, date/time
|
|
19
19
|
picker, table, dropzone, form validation, input mask, chip) need the **JS enhancers**.
|
|
20
|
-
2. **The enhancer is the source of truth
|
|
21
|
-
the widget's DOM. You **listen for `fdy-*` events** (all bubbling
|
|
22
|
-
`event.detail`) → store them in your framework state. Event/API contract
|
|
23
|
-
[`integrations.md` §Event & API contract](integrations.md).
|
|
20
|
+
2. **The enhancer is the source of truth — *on the raw path*.** You don't re-implement components;
|
|
21
|
+
the enhancer owns the widget's DOM. You **listen for `fdy-*` events** (all bubbling
|
|
22
|
+
`CustomEvent`s, data in `event.detail`) → store them in your framework state. Event/API contract
|
|
23
|
+
table: [`integrations.md` §Event & API contract](integrations.md).
|
|
24
|
+
**On Vue, React or Blazor this is not the path to take for ten of the components** — `FdyCombo`,
|
|
25
|
+
`FdyDatepicker`, `FdyDateRange`, `FdyAutocomplete`, `FdyCascade`, `FdyCfl`, `FdyChart`,
|
|
26
|
+
`FdyTable`, `FdyModal`, `FdyDrawer` ship typed wrappers that own the state properly (Vue and
|
|
27
|
+
React re-implement the interaction natively; Blazor wraps the enhancer over interop). Use them;
|
|
28
|
+
the raw path is for the components without a wrapper, and for stacks without an adapter.
|
|
24
29
|
3. **Hydrate dynamic DOM.** Enhancers auto-init once on `DOMContentLoaded`. DOM an SPA renders
|
|
25
30
|
**after** that must be re-hydrated: `window.Freeday<X>.initAll(el)` (idempotent, safe to repeat).
|
|
26
31
|
Each framework's adapter wraps this — you don't call it manually.
|
|
@@ -179,7 +184,7 @@ live docs also have a copy button per component.
|
|
|
179
184
|
```bash
|
|
180
185
|
npm i @cahyo-dimas/freeday
|
|
181
186
|
```
|
|
182
|
-
Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.
|
|
187
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.23.0"` (public npm package). `dist/` is
|
|
183
188
|
committed and published → no build step; `npm ci` runs without auth.
|
|
184
189
|
|
|
185
190
|
### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "node tokens/build.mjs",
|
|
73
73
|
"test": "node --test",
|
|
74
|
-
"test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs",
|
|
74
|
+
"test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs",
|
|
75
75
|
"prepack": "node tokens/build.mjs",
|
|
76
76
|
"version": "node tokens/build.mjs && git add dist",
|
|
77
77
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit"
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
.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;}
|
|
25
25
|
.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;}
|
|
26
26
|
.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);}
|
|
27
|
+
/* Horizontal variant — the SAME nav links laid out as a row, for a top-nav application (one that
|
|
28
|
+
* puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
|
|
29
|
+
* a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
|
|
30
|
+
* is the point — a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
|
|
31
|
+
* ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
|
|
32
|
+
* (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
|
|
33
|
+
* row scrolls; see base.css on why a scroller must be a containing block. */
|
|
34
|
+
.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;}
|
|
35
|
+
.fdy-nav--horizontal .fdy-nav__item{white-space:nowrap;}
|
|
27
36
|
.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);}
|
|
28
37
|
.fdy-nav__item:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
29
38
|
/* Active indicator stays in the primary ramp so it never clashes with a re-themed accent. */
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
.fdy-appbar__brand svg{display:block;width:1.5rem;height:1.5rem;}
|
|
11
11
|
.fdy-appbar__spacer{flex:1;}
|
|
12
12
|
.fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
|
|
13
|
+
/* Primary navigation inside the bar: `<nav class="fdy-nav fdy-nav--horizontal">` with
|
|
14
|
+
`.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own — a nav link
|
|
15
|
+
is the same component whether the nav is a sidebar column or a top row. */
|
|
13
16
|
/* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
|
|
14
17
|
.fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
|
|
15
18
|
.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;}
|
|
19
|
+
/* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
|
|
20
|
+
the contrast the bar's own token pair guarantees). The current page is carried by the background
|
|
21
|
+
wash plus the semibold weight the base rule already applies — and by `aria-current` itself. */
|
|
22
|
+
.fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
|
|
23
|
+
.fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
|
|
24
|
+
.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);}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/* Freeday — Avatar */
|
|
2
2
|
.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;}
|
|
3
3
|
.fdy-avatar img{width:100%;height:100%;object-fit:cover;}
|
|
4
|
+
/* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
|
|
5
|
+
`--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
|
|
6
|
+
crosses the circle. 1.5rem leaves the same ~4px breathing room the kit's controls give their
|
|
7
|
+
icons. Uses `--text-xs` (the smallest type token the kit has — there is no `2xs`). */
|
|
8
|
+
.fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
|
|
4
9
|
.fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
|
|
5
10
|
.fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
|
|
6
11
|
.fdy-avatar-group{display:inline-flex;}
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
.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);}
|
|
11
11
|
.fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
|
|
12
12
|
.fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
|
|
13
|
+
/* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
|
|
14
|
+
no class and no new markup contract — and it is what makes .fdy-btn-group a complete segmented
|
|
15
|
+
control: the group joins the borders, this says which segment you are on. Each fill variant
|
|
16
|
+
defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
|
|
17
|
+
background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
|
|
18
|
+
and sink the shadow inward, so a held button reads as depressed, not merely primary-coloured. */
|
|
19
|
+
.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);}
|
|
20
|
+
.fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
|
|
13
21
|
|
|
14
22
|
.fdy-btn--ghost{background:var(--color-surface);color:var(--color-primary-strong);border-color:var(--color-border-strong);box-shadow:var(--shadow-1);}
|
|
15
23
|
.fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
|
|
@@ -18,6 +26,7 @@
|
|
|
18
26
|
.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);}
|
|
19
27
|
.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);}
|
|
20
28
|
.fdy-btn--danger:active{transform:translateY(1px);}
|
|
29
|
+
.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);}
|
|
21
30
|
|
|
22
31
|
.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);}
|
|
23
32
|
.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);}
|
|
@@ -30,6 +39,11 @@
|
|
|
30
39
|
.fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
|
|
31
40
|
.fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
|
|
32
41
|
.fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
|
|
42
|
+
/* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink) — the same pairing
|
|
43
|
+
.fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
|
|
44
|
+
the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
|
|
45
|
+
.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;}
|
|
46
|
+
.fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}
|
|
33
47
|
|
|
34
48
|
/* Icon-only — square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
|
|
35
49
|
.fdy-btn--icon{width:var(--control-h);padding:0;}
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
.fdy-step.is-active::before,.fdy-step.is-complete::before{background:var(--color-primary);}
|
|
8
8
|
.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%;}
|
|
9
9
|
.fdy-step__btn:disabled{cursor:default;}
|
|
10
|
-
|
|
10
|
+
/* The marker carries the lift itself, NOT `__btn`. The connector above is position:absolute with
|
|
11
|
+
z-index:0, and a positioned box paints after in-flow inline content in the same stacking context —
|
|
12
|
+
so a marker that is merely inline (an indicator-only stepper with no `__btn`) gets painted THROUGH
|
|
13
|
+
by the connector line. Lifting the part that must always exist makes that assembly impossible;
|
|
14
|
+
`__btn`'s own z-index stays, harmlessly, for the navigable case. */
|
|
15
|
+
.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;}
|
|
11
16
|
.fdy-step__marker svg{display:block;width:1rem;height:1rem;}
|
|
12
17
|
.fdy-step.is-active .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
|
|
13
18
|
.fdy-step.is-complete .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary);color:var(--color-on-primary);}
|
package/src/components/tabs.css
CHANGED
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
6
6
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
7
7
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
|
8
|
-
|
|
8
|
+
/* `aria-current="page"` is honoured alongside `aria-selected="true"` so the tab look can be used for
|
|
9
|
+
ROUTE-driven sub-navigation (/settings/profile · /settings/billing), where the tabs are real links.
|
|
10
|
+
A link cannot be `aria-selected` — that is invalid ARIA on an anchor; `aria-current` is the only
|
|
11
|
+
correct marker, and it is what a router sets. Use `.fdy-tabs` + `freeday-tabs.js` (roving tabindex,
|
|
12
|
+
a panel per tab) for in-page tabs; use these classes on plain links for routed ones, and
|
|
13
|
+
`.fdy-nav--horizontal` for an application's PRIMARY navigation. */
|
|
14
|
+
.fdy-tabs__tab[aria-selected="true"],.fdy-tabs__tab[aria-current="page"]{color:var(--color-primary-strong);border-bottom-color:var(--color-primary);}
|
|
9
15
|
.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);}
|
|
10
16
|
.fdy-tabs__panel{padding:var(--space-5) 0;}
|
|
11
17
|
.fdy-tabs__panel[hidden]{display:none;}
|