@cahyo-dimas/freeday 1.54.0 → 2.1.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 +713 -545
- package/COMPONENTS.md +417 -139
- package/README.id.md +46 -45
- package/README.md +46 -43
- package/USAGE.md +34 -34
- package/adapters/blazor/FdyAppShell.razor +2 -2
- package/adapters/blazor/FdyAppShell.razor.cs +6 -6
- package/adapters/blazor/FdyCfl.razor.cs +1 -1
- package/adapters/blazor/FdyChart.razor +1 -1
- package/adapters/blazor/FdyChart.razor.cs +3 -3
- package/adapters/blazor/FdyChartSeries.cs +1 -1
- package/adapters/blazor/FdyCombo.razor.cs +2 -2
- package/adapters/blazor/FdyModal.razor +1 -1
- package/adapters/blazor/FdyTable.razor.cs +7 -7
- package/adapters/blazor/FdyTableFilter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
- package/adapters/blazor/Freeday.Blazor.csproj +1 -1
- package/adapters/blazor/FreedayComponentBase.cs +1 -1
- package/adapters/blazor/TableModel.cs +3 -3
- package/adapters/blazor/TableTypes.cs +3 -3
- package/adapters/blazor/freeday-blazor.js +5 -5
- package/adapters/core/app-shell.d.ts +1 -1
- package/adapters/core/app-shell.js +6 -6
- package/adapters/core/cfl-value.d.ts +11 -0
- package/adapters/core/cfl-value.js +31 -0
- package/adapters/core/table-model.d.ts +2 -2
- package/adapters/core/table-model.js +8 -8
- package/adapters/react/components/FdyAppShell.tsx +5 -5
- package/adapters/react/components/FdyAutocomplete.tsx +1 -1
- package/adapters/react/components/FdyCascade.tsx +1 -1
- package/adapters/react/components/FdyCfl.tsx +11 -11
- package/adapters/react/components/FdyDateRange.tsx +2 -2
- package/adapters/react/components/FdyDatepicker.tsx +5 -5
- package/adapters/react/components/FdyDrawer.tsx +1 -1
- package/adapters/react/components/FdyModal.tsx +2 -2
- package/adapters/react/components/FdyTable.tsx +7 -7
- package/adapters/react/components/FdyTableFilter.tsx +1 -1
- package/adapters/react/components/FdyTableFooter.tsx +5 -5
- package/adapters/react/index.d.ts +4 -1
- package/adapters/react/index.js +1 -0
- package/adapters/react/useFreeday.js +3 -3
- package/adapters/vue/components/FdyAppShell.vue +5 -5
- package/adapters/vue/components/FdyAutocomplete.vue +1 -1
- package/adapters/vue/components/FdyCascade.vue +1 -1
- package/adapters/vue/components/FdyCfl.vue +8 -8
- package/adapters/vue/components/FdyChart.vue +2 -2
- package/adapters/vue/components/FdyCombo.vue +1 -1
- package/adapters/vue/components/FdyDateRange.vue +2 -2
- package/adapters/vue/components/FdyDatepicker.vue +5 -5
- package/adapters/vue/components/FdyDrawer.vue +3 -3
- package/adapters/vue/components/FdyModal.vue +3 -3
- package/adapters/vue/components/FdyTable.vue +7 -7
- package/adapters/vue/components/FdyTableFilter.vue +1 -1
- package/adapters/vue/components/FdyTableFooter.vue +5 -5
- package/adapters/vue/index.d.ts +5 -2
- package/adapters/vue/index.js +1 -0
- package/adapters/vue/useFreeday.js +3 -3
- package/dist/asset.d.ts +3 -3
- package/dist/freeday-app-shell.js +9 -9
- package/dist/freeday-autocomplete.js +2 -2
- package/dist/freeday-breakpoint.js +1 -1
- package/dist/freeday-carousel.js +8 -7
- package/dist/freeday-cascade.js +10 -9
- package/dist/freeday-cfl.js +12 -11
- package/dist/freeday-chart.js +11 -11
- package/dist/freeday-chip.js +3 -3
- package/dist/freeday-datepicker.js +11 -7
- package/dist/freeday-datetime.js +5 -5
- package/dist/freeday-drawer.js +1 -1
- package/dist/freeday-form.js +17 -16
- package/dist/freeday-mask.js +9 -8
- package/dist/freeday-menu.js +1 -1
- package/dist/freeday-number.js +3 -3
- package/dist/freeday-popover.js +3 -3
- package/dist/freeday-rating.js +2 -2
- package/dist/freeday-select.js +5 -5
- package/dist/freeday-slider.js +2 -2
- package/dist/freeday-stepper.js +10 -9
- package/dist/freeday-table.js +18 -17
- package/dist/freeday-tabs.js +1 -1
- package/dist/freeday-timepicker.js +2 -2
- package/dist/freeday-toast.js +11 -10
- package/dist/freeday-tree.js +3 -3
- package/dist/freeday-upload.js +28 -27
- package/dist/freeday.bundle.css +176 -176
- package/dist/freeday.css +170 -170
- package/dist/freeday.d.ts +2 -2
- package/dist/freeday.js +189 -176
- package/dist/freeday.tokens.css +5 -5
- package/docs/agent-onboarding.md +49 -46
- package/docs/getting-started.md +49 -40
- package/docs/integrations.md +18 -18
- package/docs/reference-screen.html +14 -14
- package/package.json +4 -3
- package/src/base.css +6 -6
- package/src/components/accordion.css +4 -4
- package/src/components/alert.css +1 -1
- package/src/components/app-shell.css +10 -10
- package/src/components/appbar.css +3 -3
- package/src/components/autocomplete.css +1 -1
- package/src/components/avatar.css +3 -3
- package/src/components/badge.css +5 -5
- package/src/components/breadcrumb.css +1 -1
- package/src/components/breakpoints.css +4 -4
- package/src/components/button.css +13 -13
- package/src/components/card.css +8 -8
- package/src/components/carousel.css +2 -2
- package/src/components/cascade.css +2 -2
- package/src/components/cfl.css +4 -4
- package/src/components/chart.css +3 -3
- package/src/components/chip.css +2 -2
- package/src/components/combo.css +3 -3
- package/src/components/composition.css +7 -7
- package/src/components/datepicker.css +6 -6
- package/src/components/datetimepicker.css +1 -1
- package/src/components/description-list.css +1 -1
- package/src/components/divider.css +1 -1
- package/src/components/drawer.css +1 -1
- package/src/components/file-upload.css +5 -5
- package/src/components/filterbar.css +7 -7
- package/src/components/form-grid.css +2 -2
- package/src/components/input-group.css +6 -6
- package/src/components/input.css +6 -6
- package/src/components/kbd.css +1 -1
- package/src/components/list.css +6 -6
- package/src/components/menu.css +3 -3
- package/src/components/modal.css +1 -1
- package/src/components/pagination.css +1 -1
- package/src/components/progress.css +1 -1
- package/src/components/rating.css +2 -2
- package/src/components/selection.css +4 -4
- package/src/components/skeleton.css +1 -1
- package/src/components/slider.css +1 -1
- package/src/components/spinner.css +1 -1
- package/src/components/states.css +1 -1
- package/src/components/stepper.css +2 -2
- package/src/components/table.css +15 -15
- package/src/components/tabs.css +3 -3
- package/src/components/timeline.css +1 -1
- package/src/components/timepicker.css +1 -1
- package/src/components/toast.css +3 -3
- package/src/components/tooltip.css +1 -1
- package/src/components/tree.css +2 -2
- package/tokens/breakpoints.d.ts +2 -2
- package/tokens/breakpoints.mjs +3 -3
- package/tokens/tokens.json +1 -1
package/docs/integrations.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Freeday is a **dependency-free foundation**: tokens + accessible markup + vanilla enhancers. This
|
|
4
4
|
document maps each area to the ecosystem library you'd normally install in a real project, **when**
|
|
5
5
|
Freeday's built-ins are enough, and **how to bridge**. The goal: when you start a new project, open
|
|
6
|
-
this one file
|
|
6
|
+
this one file, so there's no hunting around.
|
|
7
7
|
|
|
8
8
|
> Golden rule: **Freeday owns the look (tokens + markup + a11y); the library owns the engine
|
|
9
9
|
> (heavy logic).** Don't duplicate. Connect through the 3 mechanisms below.
|
|
@@ -14,11 +14,11 @@ this one file — no more hunting around.
|
|
|
14
14
|
|
|
15
15
|
Every integration comes down to one of these:
|
|
16
16
|
|
|
17
|
-
1. **`fdy-*` events
|
|
17
|
+
1. **`fdy-*` events:** listen to enhancer output, forward it to framework/library state.
|
|
18
18
|
All events are bubbling `CustomEvent`s; the data is in `event.detail`.
|
|
19
|
-
2. **Init hook `window.Freeday<X>.initAll(el)
|
|
19
|
+
2. **Init hook `window.Freeday<X>.initAll(el)`:** call it again after DOM is rendered
|
|
20
20
|
dynamically (Vue `onMounted`, React `useEffect`, Blazor `OnAfterRenderAsync`). Idempotent.
|
|
21
|
-
3. **Color tokens
|
|
21
|
+
3. **Color tokens:** match a library's colors to the active theme:
|
|
22
22
|
```js
|
|
23
23
|
// Read a live semantic token so a 3rd-party lib matches the current theme
|
|
24
24
|
const token = (name) =>
|
|
@@ -79,7 +79,7 @@ library only.
|
|
|
79
79
|
| Virtual scroll | 🔌 | — | **TanStack Virtual** · Vue: **vue-virtual-scroller** | — |
|
|
80
80
|
|
|
81
81
|
> **Monospace data cells:** add `.fdy-mono` to any identifier / code / IP / timestamp cell (or an
|
|
82
|
-
> inline `<span>`) to render it in the data font with tabular figures. It is alignment-neutral
|
|
82
|
+
> inline `<span>`) to render it in the data font with tabular figures. It is alignment-neutral,
|
|
83
83
|
> unlike `.fdy-table__num`, which is also right-aligned. `FdyTable` applies it automatically to any
|
|
84
84
|
> column with `mono: true`.
|
|
85
85
|
|
|
@@ -115,7 +115,7 @@ library only.
|
|
|
115
115
|
|
|
116
116
|
| Area | Freeday enough? | When the built-in is enough | If you need more | Bridge |
|
|
117
117
|
|---|---|---|---|---|
|
|
118
|
-
| Icons | ➕ (inline SVG) | A small built-in set | A full set: **Lucide** (best fit
|
|
118
|
+
| Icons | ➕ (inline SVG) | A small built-in set | A full set: **Lucide** (best fit, since Freeday uses this stroke style), **Heroicons**, **Tabler**, **Phosphor** | Drop the SVG into a slot: `[data-fdy-icon]`, `.fdy-input-group__addon--icon`, `.fdy-combo__icon`. |
|
|
119
119
|
| Drag & drop / sortable | 🔌 | — | **SortableJS** (vanilla) · React: **dnd-kit** · Vue: **vuedraggable** | Lay it out with Freeday markup, DnD from the library. |
|
|
120
120
|
| Command palette | 🔌 | — | **cmdk** (React) · **kbar** | Style with tokens + `.fdy-kbd`. |
|
|
121
121
|
| Routing | 🔌 | — | Vue Router · React Router · Blazor Router | After a route change → `window.FreedayTable.initAll()`, etc. |
|
|
@@ -124,8 +124,8 @@ library only.
|
|
|
124
124
|
|
|
125
125
|
## Per-framework binding
|
|
126
126
|
|
|
127
|
-
> **Recommended path:** use the ready-made adapters
|
|
128
|
-
> `useFreeday` hook (React), or `window.FreedayBlazor` (Blazor)
|
|
127
|
+
> **Recommended path:** use the ready-made adapters: the `useFreeday` composable (Vue), the
|
|
128
|
+
> `useFreeday` hook (React), or `window.FreedayBlazor` (Blazor). They wrap hydration + event
|
|
129
129
|
> bridging. Full steps per stack are in **[`getting-started.md`](getting-started.md)**. The raw
|
|
130
130
|
> `initAll()` pattern below is the underlying mechanism (and a fallback if you don't use an adapter).
|
|
131
131
|
|
|
@@ -154,7 +154,7 @@ function Panel(): JSX.Element {
|
|
|
154
154
|
```
|
|
155
155
|
> **Controlled alternative (parity with Vue's `v-model`):** `@cahyo-dimas/freeday/react` also exports
|
|
156
156
|
> the typed components `FdyCombo` / `FdyDatepicker` / `FdyDateRange` / `FdyAutocomplete` /
|
|
157
|
-
> `FdyCascade` / `FdyCfl` / `FdyChart` / `FdyTable` / `FdyModal` / `FdyDrawer`
|
|
157
|
+
> `FdyCascade` / `FdyCfl` / `FdyChart` / `FdyTable` / `FdyModal` / `FdyDrawer` / `FdyAppShell`, with plain
|
|
158
158
|
> `value`/`onChange` (props in, events out), no `data-fdy-*` + manual event listener:
|
|
159
159
|
> ```tsx
|
|
160
160
|
> import { FdyCombo } from '@cahyo-dimas/freeday/react';
|
|
@@ -168,22 +168,22 @@ function Panel(): JSX.Element {
|
|
|
168
168
|
> `row-detail`/`renderRowDetail` + controlled `expandedKeys`); `FdyModal`/`FdyDrawer` take `open` +
|
|
169
169
|
> `onClose`.
|
|
170
170
|
>
|
|
171
|
-
> **Responsive list screens
|
|
171
|
+
> **Responsive list screens: drive the page from outside the table.** `FdyTable`'s pager renders
|
|
172
172
|
> inside `.fdy-datatable`, so a screen that hides the table below `md` and shows a card list loses it.
|
|
173
173
|
> Pass **`pageIndex`** (+ `update:pageIndex` / `onPageIndexChange` / `PageIndexChanged`) to own the
|
|
174
174
|
> client-side index while the table still does filter/sort/paginate: render **one** pager outside for
|
|
175
175
|
> both breakpoints, feed the card list from the **`process`** event (`{ rows, total }`), and build the
|
|
176
176
|
> pager's page window with `pageWindow` from `@cahyo-dimas/freeday/table-model` so it is literally the
|
|
177
|
-
> same computation the table uses. One page index can then span several tables
|
|
177
|
+
> same computation the table uses. One page index can then span several tables, which is what a
|
|
178
178
|
> grouped list needs. The same set is available
|
|
179
|
-
> in `@cahyo-dimas/freeday/vue` via `v-model` / props
|
|
179
|
+
> in `@cahyo-dimas/freeday/vue` via `v-model` / props, and both adapters are fully symmetric. See
|
|
180
180
|
> [`getting-started.md` §React](getting-started.md#react-vite) and
|
|
181
181
|
> `examples/react-faktur/src/App.tsx`. **Vite** transpiles the `.tsx` source with no extra config;
|
|
182
182
|
> **Next.js** may need `transpilePackages: ['@cahyo-dimas/freeday']`.
|
|
183
183
|
|
|
184
184
|
**Blazor**
|
|
185
185
|
```csharp
|
|
186
|
-
// Component.razor.cs
|
|
186
|
+
// Component.razor.cs: call the enhancer after Blazor renders the markup.
|
|
187
187
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
188
188
|
{
|
|
189
189
|
if (firstRender)
|
|
@@ -200,7 +200,7 @@ document.addEventListener('fdy-form-invalid', (e) =>
|
|
|
200
200
|
> **Native components (parity with Vue's `v-model` / React's `value`/`onChange`):** the
|
|
201
201
|
> **`Freeday.Blazor`** RCL (net8.0, `adapters/blazor/`) ships the typed components `FdyCombo<TValue>` /
|
|
202
202
|
> `FdyDatepicker` / `FdyDateRange` / `FdyAutocomplete` / `FdyCascade` / `FdyCfl<TRow>` / `FdyChart` /
|
|
203
|
-
> `FdyTable<TRow>` / `FdyModal` / `FdyDrawer`
|
|
203
|
+
> `FdyTable<TRow>` / `FdyModal` / `FdyDrawer` / `FdyAppShell` with `@bind`, no `@ref` / manual `initAll` / `[JSInvokable]`:
|
|
204
204
|
> ```razor
|
|
205
205
|
> @using Freeday.Blazor
|
|
206
206
|
> <FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl-status" />
|
|
@@ -297,7 +297,7 @@ computePosition(trigger, tip, { placement: 'top', middleware: [offset(8), flip()
|
|
|
297
297
|
```
|
|
298
298
|
|
|
299
299
|
### 6. Large async select → `freeday-cfl`
|
|
300
|
-
For thousands of server-side rows, don't force a `<select
|
|
300
|
+
For thousands of server-side rows, don't force a `<select>`. Use choose-from-list:
|
|
301
301
|
```js
|
|
302
302
|
window.FreedayCfl.init(el, {
|
|
303
303
|
// Server owns paging/search; Freeday owns the accessible dialog UI.
|
|
@@ -311,13 +311,13 @@ el.addEventListener('fdy-cfl-select', (e) => store.setCustomer(e.detail.row));
|
|
|
311
311
|
## SAP B1 note
|
|
312
312
|
|
|
313
313
|
Freeday is purely for the **web** (companion apps, .NET web add-ons, portals). A **UI API
|
|
314
|
-
(SAPBouiCOM)** add-on is WinForms/COM
|
|
314
|
+
(SAPBouiCOM)** add-on is WinForms/COM, a different world where Freeday doesn't apply. For
|
|
315
315
|
web-based add-ons (Service Layer + .NET), Freeday + enhancers over JS interop work normally.
|
|
316
316
|
|
|
317
317
|
---
|
|
318
318
|
|
|
319
319
|
*Library recommendations reflect what's common and stable as of 2026; choose based on your
|
|
320
|
-
project's license and bundle-size budget. Freeday locks you into none of them
|
|
320
|
+
project's license and bundle-size budget. Freeday locks you into none of them. Everything is
|
|
321
321
|
optional and replaceable.*
|
|
322
322
|
|
|
323
323
|
## Building a control the kit does not ship
|
|
@@ -329,7 +329,7 @@ import { usePopover } from '@cahyo-dimas/freeday/vue'; // or '/react'
|
|
|
329
329
|
usePopover(panelRef, triggerRef, open); // panel needs popover="manual"
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
This is what every kit dropdown uses to escape an ancestor's overflow clip
|
|
332
|
+
This is what every kit dropdown uses to escape an ancestor's overflow clip, because `.fdy-card` is
|
|
333
333
|
`overflow:hidden`, so a panel positioned inside one is otherwise cut at the card's edge. It is
|
|
334
334
|
exported so a control you build behaves like one the kit ships, and improves when the kit's
|
|
335
335
|
positioning does.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Freeday — reference screen</title>
|
|
7
7
|
|
|
8
8
|
<!-- The kit in one file: tokens + components (this is what @cahyo-dimas/freeday/css resolves to).
|
|
9
|
-
Note the file name: dist/freeday.css is components ONLY
|
|
9
|
+
Note the file name: dist/freeday.css is components ONLY. Link that alone and every
|
|
10
10
|
var(--color-*) / var(--space-*) silently falls back, which looks like a broken stylesheet.
|
|
11
11
|
Paths are relative, so this works both in the repo and in node_modules/@cahyo-dimas/freeday/docs/. -->
|
|
12
12
|
<link rel="stylesheet" href="../dist/freeday.bundle.css">
|
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
<body>
|
|
19
19
|
|
|
20
20
|
<!-- ══════════════════════════════════════════════════════════════════════════════════════════
|
|
21
|
-
REFERENCE SCREEN
|
|
21
|
+
REFERENCE SCREEN: one business screen, assembled the intended way.
|
|
22
22
|
|
|
23
23
|
Structure, outermost to innermost:
|
|
24
24
|
.fdy-app the shell (topbar · sidebar · main)
|
|
25
|
-
└ .fdy-page the screen body
|
|
25
|
+
└ .fdy-page the screen body, vertical section rhythm
|
|
26
26
|
├ .fdy-page__header eyebrow + page title + desc | ONE primary action
|
|
27
|
-
├ .fdy-page-section KPI strip (.fdy-stats
|
|
27
|
+
├ .fdy-page-section KPI strip (.fdy-stats, deliberately not cards)
|
|
28
28
|
├ .fdy-page-section chart
|
|
29
29
|
└ .fdy-page-section the data table
|
|
30
30
|
|
|
31
|
-
Every class here exists in COMPONENTS.md. Every value comes from a token
|
|
31
|
+
Every class here exists in COMPONENTS.md. Every value comes from a token. Search this file
|
|
32
32
|
for "#" or "px" and you will find none outside the demo-only sizing on the chart wrappers.
|
|
33
33
|
Doctrine behind the choices: USAGE.md.
|
|
34
34
|
══════════════════════════════════════════════════════════════════════════════════════════ -->
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
</aside>
|
|
72
72
|
|
|
73
73
|
<!-- __content is the column wrapper holding the topbar + main. It exists so the sticky topbar
|
|
74
|
-
has a tall containing block to travel in
|
|
74
|
+
has a tall containing block to travel in, which a bare flex cell cannot give it. -->
|
|
75
75
|
<div class="fdy-app__content">
|
|
76
76
|
|
|
77
77
|
<header class="fdy-app__topbar">
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
</div>
|
|
92
92
|
|
|
93
93
|
<span class="fdy-badge-wrap">
|
|
94
|
-
<button class="fdy-btn fdy-btn--ghost fdy-btn--icon" type="button" aria-label="Notifications
|
|
94
|
+
<button class="fdy-btn fdy-btn--ghost fdy-btn--icon" type="button" aria-label="Notifications, 3 new">
|
|
95
95
|
<span class="fdy-btn__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>
|
|
96
96
|
</button>
|
|
97
97
|
<span class="fdy-badge-ov fdy-badge-ov--danger" aria-hidden="true">3</span>
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
<!-- ═══ THE SCREEN ═══ -->
|
|
118
118
|
<div class="fdy-page">
|
|
119
119
|
|
|
120
|
-
<!-- One page header. One primary action
|
|
120
|
+
<!-- One page header. One primary action; everything else is ghost. -->
|
|
121
121
|
<header class="fdy-page__header">
|
|
122
122
|
<div class="fdy-page__heading">
|
|
123
123
|
<p class="fdy-eyebrow">Sales</p>
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
</div>
|
|
131
131
|
</header>
|
|
132
132
|
|
|
133
|
-
<!-- KPI strip: .fdy-stat tiles, NOT cards
|
|
133
|
+
<!-- KPI strip: .fdy-stat tiles, NOT cards, because a metric row must not read as a card grid. -->
|
|
134
134
|
<section class="fdy-page-section" aria-labelledby="kpi-h">
|
|
135
135
|
<h3 class="fdy-title-section fdy-visually-hidden" id="kpi-h">Key figures</h3>
|
|
136
136
|
<div class="fdy-stats fdy-stats--boxed">
|
|
@@ -301,7 +301,7 @@
|
|
|
301
301
|
</div>
|
|
302
302
|
|
|
303
303
|
<!-- Below md: the SAME rows as a flat list. .fdy-list is the flat container the doctrine
|
|
304
|
-
asks for
|
|
304
|
+
asks for. A stack of .fdy-card would put a 34px lift under every row.
|
|
305
305
|
In a framework app you would not duplicate the markup: hide <FdyTable> below md, feed
|
|
306
306
|
this list from its `process` event, and drive both from a controlled `pageIndex` with
|
|
307
307
|
ONE pager rendered outside the table. See COMPONENTS.md §Data table. -->
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
<!-- A detail region: key-value pairs + a timeline. Flat section, one card each. -->
|
|
340
340
|
<section class="fdy-page-section" aria-labelledby="detail-h">
|
|
341
341
|
<div class="fdy-page-section__head">
|
|
342
|
-
<h3 class="fdy-title-section" id="detail-h">INV-2042
|
|
342
|
+
<h3 class="fdy-title-section" id="detail-h">INV-2042 detail</h3>
|
|
343
343
|
<button class="fdy-btn fdy-btn--text" type="button">Open full record</button>
|
|
344
344
|
</div>
|
|
345
345
|
<div class="fdy-card">
|
|
@@ -419,10 +419,10 @@
|
|
|
419
419
|
|
|
420
420
|
<script>
|
|
421
421
|
// The nav toggle is the kit's now: `data-fdy-app` on the shell is the whole wiring. What used to
|
|
422
|
-
// live here handled the two state classes and nothing else
|
|
423
|
-
// background, no focus restore
|
|
422
|
+
// live here handled the two state classes and nothing else: no Escape, no focus trap, no inert
|
|
423
|
+
// background, no focus restore. That is why the behaviour moved into the kit.
|
|
424
424
|
|
|
425
|
-
// Theme + density live on <html>, set once
|
|
425
|
+
// Theme + density live on <html>, set once, never per component.
|
|
426
426
|
const root = document.documentElement;
|
|
427
427
|
document.getElementById('toggle-theme').addEventListener('click', () => {
|
|
428
428
|
root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Freeday
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"sideEffects": true,
|
|
@@ -95,7 +95,8 @@
|
|
|
95
95
|
"test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs",
|
|
96
96
|
"prepack": "node tokens/build.mjs",
|
|
97
97
|
"version": "node tokens/build.mjs && git add dist",
|
|
98
|
-
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit"
|
|
98
|
+
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
|
|
99
|
+
"test:blazor": "dotnet test test/blazor -v quiet --nologo"
|
|
99
100
|
},
|
|
100
101
|
"devDependencies": {
|
|
101
102
|
"@types/react": "^19.2.17",
|
package/src/base.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday base layer
|
|
1
|
+
/* Freeday base layer: reset ringan + default via token */
|
|
2
2
|
*, *::before, *::after { box-sizing: border-box; }
|
|
3
3
|
html { -webkit-text-size-adjust: 100%; }
|
|
4
4
|
body {
|
|
@@ -16,21 +16,21 @@ a { color: var(--color-primary); }
|
|
|
16
16
|
:where(button, input, select, textarea) { font: inherit; }
|
|
17
17
|
/* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
|
|
18
18
|
* has a position, and because it is position:absolute its containing block is the nearest
|
|
19
|
-
* POSITIONED ancestor
|
|
19
|
+
* POSITIONED ancestor, and with none, the initial containing block (the document itself).
|
|
20
20
|
*
|
|
21
21
|
* That matters, because `overflow` only clips a descendant whose containing block is inside the
|
|
22
22
|
* overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
|
|
23
|
-
* to name an icon button) parks at its static position
|
|
23
|
+
* to name an icon button) parks at its static position, possibly thousands of px to the right —
|
|
24
24
|
* and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
|
|
25
25
|
* wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
|
|
26
26
|
*
|
|
27
|
-
* The fix is not here
|
|
27
|
+
* The fix is not here but on the containers: every clipping/scrolling container in the kit
|
|
28
28
|
* that holds consumer markup declares `position:relative` so it becomes the containing block for
|
|
29
29
|
* its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
|
|
30
|
-
* Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not
|
|
30
|
+
* Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not,
|
|
31
31
|
* the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
|
|
32
32
|
.fdy-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
33
|
-
/* Opt-in list reset. base.css is intentionally a *light* reset
|
|
33
|
+
/* Opt-in list reset. base.css is intentionally a *light* reset: it does NOT strip list/paragraph
|
|
34
34
|
* margins (see the kit's own list components, which each reset themselves). If you run a utility
|
|
35
35
|
* framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
|
|
36
36
|
* bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/* Freeday
|
|
2
|
-
/* position:relative
|
|
1
|
+
/* Freeday. Accordion (native <details>/<summary>, zero-JS) */
|
|
2
|
+
/* position:relative, containing block for out-of-flow panel content (see base.css). NOT redundant
|
|
3
3
|
with the panel's reveal animation: that animation gives the panel a transform (which happens to
|
|
4
|
-
make it a containing block too), but it lives behind prefers-reduced-motion:no-preference
|
|
4
|
+
make it a containing block too), but it lives behind prefers-reduced-motion:no-preference, so
|
|
5
5
|
without this line the escape bug appears ONLY for readers who asked for reduced motion. */
|
|
6
6
|
.fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
|
|
7
7
|
.fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
.fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
|
|
14
14
|
.fdy-accordion__panel{padding:0 var(--space-4) var(--space-4);color:var(--color-text-muted);font-size:var(--text-sm);line-height:var(--leading-normal);}
|
|
15
15
|
/* Content reveal on open (fade + rise). Native <details> can't animate its own height cross-
|
|
16
|
-
* browser without JS, so we animate the revealed panel instead
|
|
16
|
+
* browser without JS, so we animate the revealed panel instead, cheap, safe, reduced-motion gated. */
|
|
17
17
|
@media (prefers-reduced-motion:no-preference){.fdy-accordion__item[open] > .fdy-accordion__panel{animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}
|
package/src/components/alert.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. Alert (inline) */
|
|
2
2
|
.fdy-alert{display:flex;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);border:var(--bw) solid var(--color-border);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface-2);}
|
|
3
3
|
.fdy-alert__icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-top:.05rem;}
|
|
4
4
|
.fdy-alert__icon svg{width:100%;height:100%;}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. App Shell.
|
|
2
2
|
* Natural document scroll: the shell grows with content and the PAGE scrolls,
|
|
3
3
|
* while the sidebar and topbar are position:sticky so they stay in view. No
|
|
4
4
|
* fixed-height / internal-scroll trickery (that leaves a white gap if the host
|
|
5
5
|
* page can still scroll). Structure: .fdy-app > (.fdy-app__sidebar, .fdy-app__content
|
|
6
6
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
7
|
-
* topbar a tall containing block so it can travel
|
|
7
|
+
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
8
8
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
9
9
|
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
10
10
|
.fdy-app__content{flex:1;min-width:0;display:flex;flex-direction:column;}
|
|
11
|
-
/* Brand is a fixed header
|
|
11
|
+
/* Brand is a fixed header, same height as the topbar so they align, and only
|
|
12
12
|
* the nav below it scrolls. Bare text (no children) renders as one bold
|
|
13
13
|
* display line via the rules on .fdy-app__brand itself. For a mark + two-line
|
|
14
14
|
* title/subtitle, add structured children mirroring .fdy-appbar__brand:
|
|
@@ -24,10 +24,10 @@
|
|
|
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);margin:0;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);margin:0;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
|
|
27
|
+
/* Horizontal variant, the SAME nav links laid out as a row, for a top-nav application (one that
|
|
28
28
|
* puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
|
|
29
29
|
* a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
|
|
30
|
-
* is the point
|
|
30
|
+
* is the point, a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
|
|
31
31
|
* ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
|
|
32
32
|
* (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
|
|
33
33
|
* row scrolls; see base.css on why a scroller must be a containing block. */
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
.fdy-nav__item[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
|
|
40
40
|
.fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
|
|
41
41
|
/* Sidebar-menu extras: per-node icon (leading), count badge (trailing), and a
|
|
42
|
-
* text label that truncates. Any of these are optional
|
|
42
|
+
* text label that truncates. Any of these are optional, a bare text item works. */
|
|
43
43
|
.fdy-nav__icon{flex:none;display:inline-flex;width:1.15rem;height:1.15rem;}
|
|
44
44
|
.fdy-nav__icon svg{display:block;width:100%;height:100%;}
|
|
45
45
|
.fdy-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
.fdy-nav__group{margin:0;}
|
|
50
50
|
.fdy-nav__group + .fdy-nav__group,.fdy-nav__item + .fdy-nav__group{border-top:var(--bw) solid var(--color-border-muted);margin-top:var(--space-1);padding-top:var(--space-1);}
|
|
51
51
|
/* Prose, and a control name: this is the <summary> of a nav group, at --text-xs uppercase. It is
|
|
52
|
-
the second class found spending the decorative ink on type (after .fdy-eyebrow)
|
|
52
|
+
the second class found spending the decorative ink on type (after .fdy-eyebrow), and it was
|
|
53
53
|
already brightening to --color-text-muted on hover, which is the rule admitting its resting
|
|
54
54
|
state was too faint to read. */
|
|
55
55
|
.fdy-nav__grouplabel{list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:var(--space-3) var(--space-3) var(--space-1);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:var(--tracking-wide);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
.fdy-nav__grouplabel:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
|
|
59
59
|
.fdy-nav__grouplabel::after{content:"";flex:none;width:.45rem;height:.45rem;margin-right:var(--space-1);border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform var(--dur-fast) var(--ease-standard);opacity:.7;}
|
|
60
60
|
.fdy-nav__group[open] > .fdy-nav__grouplabel::after{transform:rotate(45deg);}
|
|
61
|
-
/* --flat: calmer, non-collapsible grouped nav
|
|
61
|
+
/* --flat: calmer, non-collapsible grouped nav, hide the caret, drop the
|
|
62
62
|
* inter-group divider and the collapse cursor. Works on native <details> groups
|
|
63
63
|
* (caret just hidden) and on static <div>/<p> groups (never collapsible). */
|
|
64
64
|
.fdy-nav--flat .fdy-nav__grouplabel::after{display:none;}
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
.fdy-nav__tree > summary .fdy-nav__caret{flex:none;margin-left:auto;width:.4rem;height:.4rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform var(--dur-fast) var(--ease-standard);opacity:.6;}
|
|
72
72
|
.fdy-nav__tree[open] > summary .fdy-nav__caret{transform:rotate(45deg);}
|
|
73
73
|
.fdy-nav__sub{display:flex;flex-direction:column;gap:2px;padding-left:calc(var(--space-3) + 1.15rem);margin-top:2px;}
|
|
74
|
-
/* Collapse toggle (hamburger)
|
|
74
|
+
/* Collapse toggle (hamburger), sits in the topbar; collapses the sidebar. */
|
|
75
75
|
.fdy-app__navtoggle{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;flex:none;padding:0;border:0;border-radius:var(--radius-md);background:transparent;color:var(--color-text-muted);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
|
|
76
76
|
.fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
77
77
|
.fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
.fdy-app__main{flex:1;padding:var(--space-8);}
|
|
85
85
|
.fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
|
|
86
86
|
.fdy-skip:focus{transform:translateY(0);}
|
|
87
|
-
/* --static: for a bounded/embedded shell (a demo box, not the whole page)
|
|
87
|
+
/* --static: for a bounded/embedded shell (a demo box, not the whole page),
|
|
88
88
|
* drop sticky so nothing escapes the box; sidebar stretches to fill it. */
|
|
89
89
|
.fdy-app--static .fdy-app__sidebar{position:static;height:auto;align-self:stretch;}
|
|
90
90
|
.fdy-app--static .fdy-app__topbar{position:static;}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. App bar (standalone top bar: brand · actions · spacer).
|
|
2
2
|
* Distinct from .fdy-app__topbar (which is bound to the shell grid). Use anywhere;
|
|
3
3
|
* sticky / elevated / dense / coloured via modifiers. Pure CSS. */
|
|
4
4
|
.fdy-appbar{display:flex;align-items:center;gap:var(--space-3);height:var(--space-16);padding:0 var(--space-4);background:var(--color-surface);color:var(--color-text);border-bottom:var(--bw) solid var(--color-border);}
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
.fdy-appbar__spacer{flex:1;}
|
|
12
12
|
.fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
|
|
13
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
|
|
14
|
+
`.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own, a nav link
|
|
15
15
|
is the same component whether the nav is a sidebar column or a top row. */
|
|
16
16
|
/* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
|
|
17
17
|
.fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
|
|
18
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
19
|
/* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
|
|
20
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
|
|
21
|
+
wash plus the semibold weight the base rule already applies, and by `aria-current` itself. */
|
|
22
22
|
.fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
|
|
23
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
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,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
|
|
2
2
|
* Enhanced by freeday-autocomplete.js. */
|
|
3
3
|
.fdy-autocomplete{position:relative;display:block;max-width:22rem;}
|
|
4
4
|
.fdy-autocomplete__listbox{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:120;min-width:100%;max-height:16rem;overflow-y:auto;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);}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
/* Freeday
|
|
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
4
|
/* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
|
|
5
5
|
`--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
|
|
6
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
|
|
7
|
+
icons. Uses `--text-xs` (the smallest type token the kit has, there is no `2xs`). */
|
|
8
8
|
.fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
|
|
9
9
|
.fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
|
|
10
10
|
.fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
|
|
11
11
|
.fdy-avatar-group{display:inline-flex;}
|
|
12
12
|
.fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
|
|
13
13
|
.fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
|
|
14
|
-
/* Decorative identity tones
|
|
14
|
+
/* Decorative identity tones, a soft tint of a categorical chart colour with a theme-aware,
|
|
15
15
|
* text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
|
|
16
16
|
* test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
|
|
17
17
|
* hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
|
package/src/components/badge.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. Badge */
|
|
2
2
|
.fdy-badge{display:inline-flex;align-items:center;gap:var(--space-1);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;padding:.35em .7em;border-radius:var(--radius-full);color:var(--color-primary-strong);background:var(--color-primary-soft);border:var(--bw) solid color-mix(in srgb,var(--color-primary) 22%,transparent);box-shadow:0 1px 1.5px rgba(16,14,30,.05);}
|
|
3
3
|
.fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
|
|
4
4
|
.fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
.fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
|
|
7
7
|
.fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
|
|
8
8
|
|
|
9
|
-
/* Categorical tone badges
|
|
9
|
+
/* Categorical tone badges, a status vocabulary larger than the semantic palette, from the
|
|
10
10
|
* general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
|
|
11
11
|
* .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
|
|
12
12
|
*
|
|
13
13
|
* Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
|
|
14
|
-
* --warning. These are for the rest
|
|
14
|
+
* --warning. These are for the rest, the states a workflow distinguishes but a palette of five
|
|
15
15
|
* cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
|
|
16
16
|
* Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
|
|
17
17
|
* which is the report this shipped for. */
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
.fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
|
|
26
26
|
.fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
|
|
27
27
|
|
|
28
|
-
/* Overlay badge
|
|
28
|
+
/* Overlay badge, anchors a small count/dot/icon badge to a corner of any element
|
|
29
29
|
* (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
|
|
30
30
|
* sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */
|
|
31
31
|
.fdy-badge-wrap{position:relative;display:inline-flex;vertical-align:middle;}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
/* Solid colours safe for count/text (on-* tokens, AA in light & dark) */
|
|
36
36
|
.fdy-badge-ov--primary{background:var(--color-primary);color:var(--color-on-primary);}
|
|
37
37
|
.fdy-badge-ov--danger{background:var(--color-danger-btn);color:var(--color-on-danger);}
|
|
38
|
-
/* Dot-oriented accents (for dots
|
|
38
|
+
/* Dot-oriented accents (for dots, plain text on these is not guaranteed AA) */
|
|
39
39
|
.fdy-badge-ov--accent{background:var(--color-accent);}
|
|
40
40
|
.fdy-badge-ov--info{background:var(--color-info);}
|
|
41
41
|
.fdy-badge-ov--success{background:var(--color-success);}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. Breadcrumb */
|
|
2
2
|
.fdy-breadcrumb{font-size:var(--text-sm);}
|
|
3
3
|
.fdy-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);list-style:none;margin:0;padding:0;}
|
|
4
4
|
.fdy-breadcrumb__item{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--color-text-muted);}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. Responsive display utilities (pairs with freeday-breakpoint.js).
|
|
2
2
|
* Breakpoint scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920.
|
|
3
|
-
* NOTE: media-query breakpoints must be literal
|
|
3
|
+
* NOTE: media-query breakpoints must be literal. CSS can't read custom properties
|
|
4
4
|
* inside @media, so these px are structural, not stray magic numbers. */
|
|
5
5
|
.fdy-hidden{display:none !important;}
|
|
6
6
|
|
|
7
|
-
/* hide-below-X
|
|
7
|
+
/* hide-below-X, hidden when the viewport is narrower than X (content shows at X and up) */
|
|
8
8
|
@media (max-width:599.98px){.fdy-hide-below-sm{display:none !important;}}
|
|
9
9
|
@media (max-width:959.98px){.fdy-hide-below-md{display:none !important;}}
|
|
10
10
|
@media (max-width:1279.98px){.fdy-hide-below-lg{display:none !important;}}
|
|
11
11
|
|
|
12
|
-
/* hide-above-X
|
|
12
|
+
/* hide-above-X, hidden when the viewport is X and wider (content shows below X) */
|
|
13
13
|
@media (min-width:600px){.fdy-hide-above-sm{display:none !important;}}
|
|
14
14
|
@media (min-width:960px){.fdy-hide-above-md{display:none !important;}}
|
|
15
15
|
@media (min-width:1280px){.fdy-hide-above-lg{display:none !important;}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. Button */
|
|
2
2
|
.fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
|
|
3
3
|
height:var(--control-h);padding:0 var(--space-4);
|
|
4
4
|
font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
|
@@ -11,7 +11,7 @@
|
|
|
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
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
|
|
14
|
+
no class and no new markup contract, and it is what makes .fdy-btn-group a complete segmented
|
|
15
15
|
control: the group joins the borders, this says which segment you are on. Each fill variant
|
|
16
16
|
defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
|
|
17
17
|
background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
.fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
|
|
24
24
|
.fdy-btn--ghost:active{transform:translateY(1px);}
|
|
25
25
|
|
|
26
|
-
/* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger
|
|
27
|
-
which every reader parses as "quiet destructive"
|
|
26
|
+
/* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger`,
|
|
27
|
+
which every reader parses as "quiet destructive", rendered a second SOLID button beside Save.
|
|
28
28
|
That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
|
|
29
29
|
screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
|
|
30
30
|
the pairing rules below bring it to the button. They are more specific than this one and come
|
|
31
|
-
after it, so this rule needs no :not() guard of its own
|
|
31
|
+
after it, so this rule needs no :not() guard of its own, one mechanism, not two. */
|
|
32
32
|
.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);}
|
|
33
33
|
.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);}
|
|
34
34
|
.fdy-btn--danger:active{transform:translateY(1px);}
|
|
@@ -42,21 +42,21 @@
|
|
|
42
42
|
.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);}
|
|
43
43
|
.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);}
|
|
44
44
|
|
|
45
|
-
/* Icon inside a button
|
|
45
|
+
/* Icon inside a button, scales with the button's font size (works for text+icon & icon-only) */
|
|
46
46
|
.fdy-btn__icon{display:inline-flex;flex:none;}
|
|
47
47
|
.fdy-btn__icon svg{display:block;width:1.15em;height:1.15em;}
|
|
48
48
|
|
|
49
|
-
/* Text button
|
|
49
|
+
/* Text button, lowest emphasis (no fill/border), colored label, subtle hover tint */
|
|
50
50
|
.fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
|
|
51
51
|
.fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
|
|
52
52
|
.fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
|
|
53
|
-
/* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink)
|
|
53
|
+
/* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink), the same pairing
|
|
54
54
|
.fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
|
|
55
55
|
the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
|
|
56
56
|
.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;}
|
|
57
57
|
.fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}
|
|
58
58
|
|
|
59
|
-
/* Stretched hit area (#007)
|
|
59
|
+
/* Stretched hit area (#007), spread this button's click target over its nearest positioned
|
|
60
60
|
* ancestor, so "one card, one primary action, one escape hatch" needs no click handler on the
|
|
61
61
|
* <article>: the real control stays a real <button>/<a> and keeps its keyboard semantics.
|
|
62
62
|
* `.fdy-card` is already position:relative, so it is the anchor.
|
|
@@ -65,19 +65,19 @@
|
|
|
65
65
|
* a transformed element becomes the containing block for its own absolutely-positioned descendants,
|
|
66
66
|
* so `inset:0` re-anchors from the card to the button's own box the moment the button is hovered or
|
|
67
67
|
* pressed. Measured: mousedown lands on the button, the overlay collapses under the pointer, mouseup
|
|
68
|
-
* lands on some other element, and the browser fires `click` on their common ancestor
|
|
68
|
+
* lands on some other element, and the browser fires `click` on their common ancestor, the button
|
|
69
69
|
* never gets one. It demos fine on hover for --text/--ghost (their hover is already transform:none)
|
|
70
70
|
* and only a real press reveals it; the default and --danger fills break one step earlier, on hover.
|
|
71
71
|
* Press feedback moves to the card, which is the object actually being pressed (see card.css). */
|
|
72
72
|
.fdy-btn--stretch::after{content:'';position:absolute;inset:0;}
|
|
73
73
|
.fdy-btn--stretch:hover,.fdy-btn--stretch:active{transform:none;}
|
|
74
74
|
|
|
75
|
-
/* Icon-only
|
|
75
|
+
/* Icon-only, square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
|
|
76
76
|
.fdy-btn--icon{width:var(--control-h);padding:0;}
|
|
77
77
|
.fdy-btn--icon.fdy-btn--sm{width:calc(var(--control-h) - var(--space-2));}
|
|
78
78
|
.fdy-btn--icon.fdy-btn--lg{width:calc(var(--control-h) + var(--space-2));}
|
|
79
79
|
|
|
80
|
-
/* Button group
|
|
80
|
+
/* Button group, segmented, joined borders, rounded only at the ends */
|
|
81
81
|
.fdy-btn-group{display:inline-flex;}
|
|
82
82
|
.fdy-btn-group .fdy-btn{border-radius:0;}
|
|
83
83
|
.fdy-btn-group .fdy-btn:not(:first-child){margin-left:calc(var(--bw) * -1);}
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
.fdy-btn-group .fdy-btn:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md);}
|
|
86
86
|
.fdy-btn-group .fdy-btn:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);}
|
|
87
87
|
|
|
88
|
-
/* FAB
|
|
88
|
+
/* FAB, circular elevated action button (icon), optional extended pill with label */
|
|
89
89
|
.fdy-fab{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);width:3.5rem;height:3.5rem;padding:0;border:var(--bw) solid transparent;border-radius:var(--radius-full);cursor:pointer;color:var(--color-on-primary);background:linear-gradient(180deg,var(--color-primary),color-mix(in srgb,#000 10%,var(--color-primary)));box-shadow:var(--shadow-3);transition:transform var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
|
|
90
90
|
.fdy-fab:hover{transform:translateY(-1px);box-shadow:var(--shadow-4);}
|
|
91
91
|
.fdy-fab:active{transform:translateY(1px);}
|