@cahyo-dimas/freeday 1.32.0 → 1.33.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 +67 -0
- package/COMPONENTS.md +20 -2
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/blazor/FdyTable.razor.cs +6 -1
- package/adapters/react/components/FdyTable.tsx +6 -1
- package/adapters/vue/components/FdyTable.vue +11 -3
- package/adapters/vue/index.d.ts +13 -0
- package/adapters/vue/index.js +1 -0
- package/dist/freeday.bundle.css +21 -2
- package/dist/freeday.css +21 -2
- package/docs/agent-onboarding.md +23 -0
- package/docs/getting-started.md +1 -1
- package/docs/integrations.md +14 -0
- package/package.json +1 -1
- package/src/components/app-shell.css +5 -1
- package/src/components/button.css +11 -0
- package/src/components/composition.css +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,73 @@
|
|
|
3
3
|
Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); tiap versi = git tag.
|
|
5
5
|
|
|
6
|
+
## [1.33.0] — 2026-08-18
|
|
7
|
+
Three notes from the back-office app, four findings, all four executed.
|
|
8
|
+
### Added
|
|
9
|
+
- **`pager={false}` on `FdyTable`** (Vue · React · Blazor). Server mode rendered its own footer
|
|
10
|
+
unconditionally: `hasPager` was `pageSize > 0 && totalPages > 1` with no way in, so a responsive
|
|
11
|
+
list — a table at `lg`, `.fdy-list` below it, one pager serving both so the views cannot disagree —
|
|
12
|
+
showed the kit's footer *and* the app's, stacked, on every list with more than one page. The only
|
|
13
|
+
workaround was `display:none` on `.fdy-table-footer`, an app reaching into a component's internals.
|
|
14
|
+
Client mode already had its hatch (`pageIndex`); this gives server mode one, and in server mode it
|
|
15
|
+
is the honest shape — the app owns the page there anyway, it was simply being handed a second
|
|
16
|
+
control.
|
|
17
|
+
- **`usePopover` is exported from the Vue adapter.** Every kit dropdown uses it to escape an
|
|
18
|
+
ancestor's overflow clip (`.fdy-card` is `overflow:hidden`, so a panel inside one is cut at the
|
|
19
|
+
card's edge), and a consuming app that had to build a control the kit does not ship had to
|
|
20
|
+
re-implement it — ~70 lines, from the kit's own description, that will not improve when the kit's
|
|
21
|
+
positioning does. *Correction to the report: React has exported it all along; only Vue was missing
|
|
22
|
+
it. Blazor has no composable layer — its panels go through the JS bridge.*
|
|
23
|
+
- **Quiet destructive buttons.** `--danger` was the solid treatment and nothing else, so
|
|
24
|
+
`fdy-btn--ghost fdy-btn--danger` — which every reader parses as "quiet destructive" — rendered a
|
|
25
|
+
second SOLID button. That is a hierarchy defect: the base class is already the one primary per
|
|
26
|
+
screen, and a solid Delete beside Save is a second primary in all but name.
|
|
27
|
+
`.fdy-menu__item--danger` has always modelled the quiet form; it now reaches `.fdy-btn`.
|
|
28
|
+
### Fixed
|
|
29
|
+
- **`.fdy-eyebrow` and `.fdy-nav__grouplabel` spend a text ink.** Both set type in
|
|
30
|
+
`--color-text-subtle`, which is gated at 3.0 *by design* (placeholders, separators, decorative
|
|
31
|
+
glyphs) and measures 4.41:1 — axe reports it as a serious `color-contrast` failure. Identical to
|
|
32
|
+
`.fdy-stat__label` in 1.30.0, one release earlier: same ink, same 4.41, same argument.
|
|
33
|
+
The nav group label is the one the report did not know about — the sweep it asked for found it,
|
|
34
|
+
and that rule was *already* brightening to `--color-text-muted` on hover, which is the CSS
|
|
35
|
+
admitting its resting state was too faint.
|
|
36
|
+
### Notes on the shape of the fix
|
|
37
|
+
- The report proposed scoping the solid danger with `:not(.fdy-btn--ghost):not(.fdy-btn--text)`.
|
|
38
|
+
Shipped without it: the pairing rules are more specific and come later, so they already win —
|
|
39
|
+
the `:not()` would be a second mechanism expressing the same intent, and the mutation run proved
|
|
40
|
+
it changed nothing.
|
|
41
|
+
- **A regression written and caught inside this change:** Vue casts an omitted Boolean prop to
|
|
42
|
+
`false`, not `undefined`, so `props.pager !== false` withheld the footer from every table that
|
|
43
|
+
never mentions `pager`. The existing controlled-`pageIndex` guard failed within seconds. `pager`
|
|
44
|
+
now goes through `withDefaults`, the same trap `FdyModal`'s `dismissible` hit in v1.18.0.
|
|
45
|
+
### Added — guards
|
|
46
|
+
- **The prose-ink invariant** note 007 asked for: any rule that sets a `font-size` *and* spends
|
|
47
|
+
`--color-text-subtle` is typesetting prose in an ink gated for decoration, and fails. Matched by
|
|
48
|
+
shape, not by a list of names, so the third instance cannot arrive the way the second did. Two
|
|
49
|
+
documented exemptions carry their reason.
|
|
50
|
+
- `pager={false}` is measured in a real browser with two tables sharing one server page state: the
|
|
51
|
+
footer must be **absent from the DOM**, not hidden (a visually hidden pager is still a tab stop
|
|
52
|
+
and still announced), and the other table's pager must still work.
|
|
53
|
+
- Quiet destructive is measured as a rendered `background-image`, the same reason the `aria-pressed`
|
|
54
|
+
fills are: a gradient is a background-*image*, so the CSS reads fine either way and only the engine
|
|
55
|
+
shows which treatment won.
|
|
56
|
+
|
|
57
|
+
## [1.32.1] — 2026-08-18
|
|
58
|
+
Docs only. Asked by a consumer: *"if I upgrade, will the agent in my project know what it gained?"*
|
|
59
|
+
The answer was no, and two reasons why.
|
|
60
|
+
### Fixed
|
|
61
|
+
- **`docs/agent-onboarding.md` never mentioned the changelog.** It is the file a consuming agent
|
|
62
|
+
reads first, and it listed everything in the package except the one file that says what changed
|
|
63
|
+
between the version the project had and the one it now has. It now points at
|
|
64
|
+
`node_modules/@cahyo-dimas/freeday/CHANGELOG.md` explicitly, plus a short *reach for this instead
|
|
65
|
+
of hand-rolling that* table covering the last nine releases — the additions most likely to replace
|
|
66
|
+
something an app already worked around.
|
|
67
|
+
- **Three classes existed only in shorthand** in `COMPONENTS.md` — `.fdy-label--required`,
|
|
68
|
+
`.fdy-cal__month` and `.fdy-cal__grid--months` were written as `(+--required)` and `__month`, so
|
|
69
|
+
the full name appeared **zero** times and a grep for the class found nothing. The drift guard
|
|
70
|
+
cannot see this either: it verifies fully-written names and skips shorthand by design, so a class
|
|
71
|
+
introduced only in shorthand is invisible to both the reader and the test.
|
|
72
|
+
|
|
6
73
|
## [1.32.0] — 2026-08-18
|
|
7
74
|
Improvement note 004, both halves: a calendar you could not steer, and a check mark that talked.
|
|
8
75
|
### Added
|
package/COMPONENTS.md
CHANGED
|
@@ -259,6 +259,11 @@ a compact root with one region opted back out (shared chrome that must match a s
|
|
|
259
259
|
</button>
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
+
**Quiet destructive:** combine `--danger` with `--ghost` or `--text`. The ground stays quiet and only
|
|
263
|
+
the ink turns red — `.fdy-menu__item--danger` has always worked this way. Use it whenever Delete
|
|
264
|
+
shares a screen with the one primary action; a solid Delete beside Save is a second primary in all
|
|
265
|
+
but name.
|
|
266
|
+
|
|
262
267
|
## FAB — `.fdy-fab`
|
|
263
268
|
Floating circular action. Modifiers: `--sm` `--accent` `--danger` `--extended` (pill with a label).
|
|
264
269
|
Always `aria-label` unless `--extended` carries visible text.
|
|
@@ -307,7 +312,7 @@ native control, otherwise a `<div>` + explicitly associated label.
|
|
|
307
312
|
</fieldset>
|
|
308
313
|
```
|
|
309
314
|
|
|
310
|
-
|
|
315
|
+
**`.fdy-label--required` marks the label, not the accessibility tree.** The control already carries
|
|
311
316
|
`required`; the asterisk is painted through `::after` with CSS alt text, so a screen reader never
|
|
312
317
|
reads "star" after the label — something a `<span>` you have to remember to mark `aria-hidden`
|
|
313
318
|
cannot guarantee.
|
|
@@ -517,7 +522,8 @@ Input-styled trigger + calendar popover. Needs `freeday-datepicker.js`. **Author
|
|
|
517
522
|
get `data-role="from"` / `"to"`; separator `.fdy-daterange__sep`. The end can't precede the start.
|
|
518
523
|
**Typed wrapper: `<FdyDateRange>`** (Vue/React/Blazor) — use it in those stacks.
|
|
519
524
|
**Getting to a distant month.** The title is a **button**: press it and the day grid becomes a
|
|
520
|
-
12-cell month grid for the current year, where the
|
|
525
|
+
12-cell month grid (`.fdy-cal__grid--months` of `.fdy-cal__month`) for the current year, where the
|
|
526
|
+
arrows step **years** instead of months. Pick a
|
|
521
527
|
month and you are back on days — choosing a month is navigation, so nothing is committed until a day
|
|
522
528
|
is selected. From August 2026 to March 2022 that is 7 clicks; walking month by month is 53.
|
|
523
529
|
|
|
@@ -716,6 +722,18 @@ accessible name (`Filter <column>`) — a dialog named after its trigger is the
|
|
|
716
722
|
Playwright/Testing-Library suite that means `getByLabel('Filter Name')` resolves to two elements;
|
|
717
723
|
reach for `getByRole('button', { name: 'Filter Name' })` instead.
|
|
718
724
|
|
|
725
|
+
**Who draws the pager.** The table renders its own footer (range + pager) whenever there is more
|
|
726
|
+
than one page. Two ways to take it over:
|
|
727
|
+
|
|
728
|
+
- **client mode** — pass `pageIndex` (with `pageSize`, without `page`) and drive it yourself;
|
|
729
|
+
- **either mode** — pass `pager={false}` (Vue `:pager="false"`, Blazor `Pager="false"`) and render
|
|
730
|
+
your own control. In server mode this is the only way: the app already owns the page there, and
|
|
731
|
+
was still being handed a second control. The typical shape is a responsive list — a table at `lg`,
|
|
732
|
+
`.fdy-list` below it — where one pager has to serve both views so they cannot disagree.
|
|
733
|
+
|
|
734
|
+
Do not hide the footer with CSS. `display:none` works, but it reaches into a component's internals
|
|
735
|
+
and breaks the moment the class changes.
|
|
736
|
+
|
|
719
737
|
## Pagination — `.fdy-pagination`
|
|
720
738
|
The block class on the `<nav>` is a **structural hook only** — it carries no rule of its own; the
|
|
721
739
|
`__list` / `__link` / `__ellipsis` elements do all the styling, and the data table targets
|
package/README.id.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Lebih banyak _free day_ buat dev — UI kit-nya sudah siap pakai.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.33.0)
|
|
9
9
|
|
|
10
10
|
UI KIT yang token-driven & framework-agnostic — satu sumber kebenaran untuk warna, tipografi,
|
|
11
11
|
spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **More free days for devs — the UI kit is ready to use.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.33.0)
|
|
9
9
|
|
|
10
10
|
A token-driven, framework-agnostic UI kit — one source of truth for color, typography,
|
|
11
11
|
spacing, and components. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
|
@@ -36,6 +36,11 @@ public partial class FdyTable<TRow>
|
|
|
36
36
|
/// the <c>md</c> breakpoint and renders a card list from <see cref="Process"/> can render one pager
|
|
37
37
|
/// for both breakpoints and bind it here. Leave null for the internal index (unchanged default).
|
|
38
38
|
/// </summary>
|
|
39
|
+
/// <summary>Withhold the table's own footer (pager + range) so the screen can render one.
|
|
40
|
+
/// Server mode had no way to do this: the app owns the page there anyway, and was still handed a
|
|
41
|
+
/// second control. Client mode's counterpart is <see cref="PageIndex"/>. Default true.</summary>
|
|
42
|
+
[Parameter] public bool Pager { get; set; } = true;
|
|
43
|
+
|
|
39
44
|
[Parameter] public int? PageIndex { get; set; }
|
|
40
45
|
|
|
41
46
|
/// <summary>Raised in client mode with <see cref="PageIndex"/> set: the table asks for a new
|
|
@@ -97,7 +102,7 @@ public partial class FdyTable<TRow>
|
|
|
97
102
|
private int CurrentPage1 => (ServerPaged ? Page!.Index : ClientPageIndex) + 1;
|
|
98
103
|
private int TotalCount => _totalCount;
|
|
99
104
|
private int TotalPages => PageSizeEff > 0 ? Math.Max(1, (int)Math.Ceiling((double)TotalCount / PageSizeEff)) : 1;
|
|
100
|
-
private bool HasPager => PageSizeEff > 0 && TotalPages > 1;
|
|
105
|
+
private bool HasPager => Pager && PageSizeEff > 0 && TotalPages > 1;
|
|
101
106
|
private List<int?> Pages => TableModel.PageWindow(CurrentPage1, TotalPages);
|
|
102
107
|
private int RangeFrom => TotalCount == 0 ? 0 : (CurrentPage1 - 1) * PageSizeEff + 1;
|
|
103
108
|
private int RangeTo => TotalCount == 0 ? 0 : RangeFrom - 1 + _displayRows.Count;
|
|
@@ -51,6 +51,11 @@ export interface FdyTableProps<Row extends object> {
|
|
|
51
51
|
* card list from `onProcess` can render one pager for both breakpoints and point it here.
|
|
52
52
|
* Omit for the internal index (unchanged default).
|
|
53
53
|
*/
|
|
54
|
+
/** Withhold the table's own footer (pager + range) so the screen can render one. Server mode had
|
|
55
|
+
* no way to do this: the app owns the page there ANYWAY, and was still handed a second control —
|
|
56
|
+
* a responsive list that shows a table at one breakpoint and cards at another ended up with the
|
|
57
|
+
* kit's pager stacked under its own. Client mode's counterpart is `pageIndex`. Default true. */
|
|
58
|
+
pager?: boolean;
|
|
54
59
|
pageIndex?: number;
|
|
55
60
|
/** Client mode with `pageIndex` provided: the table asks for a new 0-based index (pager click, a
|
|
56
61
|
* reset to 0 after sort/filter, or a clamp when filtering shrank the set). */
|
|
@@ -127,7 +132,7 @@ export function FdyTable<Row extends object>(props: FdyTableProps<Row>): JSX.Ele
|
|
|
127
132
|
const pageSizeEff: number = serverPaged ? (props.page as FdyPageState).size : (props.pageSize ?? 0);
|
|
128
133
|
const currentPage1: number = (serverPaged ? (props.page as FdyPageState).index : clientPageIndex) + 1;
|
|
129
134
|
const totalPages: number = pageSizeEff > 0 ? Math.max(1, Math.ceil(totalCount / pageSizeEff)) : 1;
|
|
130
|
-
const hasPager: boolean = pageSizeEff > 0 && totalPages > 1;
|
|
135
|
+
const hasPager: boolean = props.pager !== false && pageSizeEff > 0 && totalPages > 1;
|
|
131
136
|
const pages: Array<number | 'ellipsis'> = pageWindow(currentPage1, totalPages);
|
|
132
137
|
const rangeFrom: number = totalCount === 0 ? 0 : (currentPage1 - 1) * pageSizeEff + 1;
|
|
133
138
|
const rangeTo: number = totalCount === 0 ? 0 : rangeFrom - 1 + displayRows.length;
|
|
@@ -29,7 +29,10 @@ import FdyTableFilter from './FdyTableFilter.vue';
|
|
|
29
29
|
// (`update:sort` / `update:filters` / `update:page`) for the caller to feed back into its query.
|
|
30
30
|
// Column filters (text/enum/number/date) apply live; in server mode, debounce the emit if needed.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
/* `pager` MUST go through withDefaults: Vue's boolean-cast gives an omitted Boolean prop `false`,
|
|
33
|
+
not `undefined`, so a plain `props.pager !== false` would withhold the footer from every table that
|
|
34
|
+
never mentions it. Same trap as FdyModal's `dismissible`. */
|
|
35
|
+
const props = withDefaults(defineProps<{
|
|
33
36
|
columns: ReadonlyArray<FdyTableColumn<Row>>;
|
|
34
37
|
rows: ReadonlyArray<Row>;
|
|
35
38
|
rowKey: (row: Row) => string | number;
|
|
@@ -48,6 +51,11 @@ const props = defineProps<{
|
|
|
48
51
|
* card list from the `process` event can render one pager for both breakpoints and point it here.
|
|
49
52
|
* Omit for the internal index (unchanged default).
|
|
50
53
|
*/
|
|
54
|
+
/** Withhold the table's own footer (pager + range) so the screen can render one. Server mode had
|
|
55
|
+
* no way to do this: the app owns the page there ANYWAY, and was still handed a second control —
|
|
56
|
+
* a responsive list that shows a table at one breakpoint and cards at another ended up with the
|
|
57
|
+
* kit's pager stacked under its own. Client mode's counterpart is `pageIndex`. Default true. */
|
|
58
|
+
pager?: boolean;
|
|
51
59
|
pageIndex?: number;
|
|
52
60
|
loading?: boolean;
|
|
53
61
|
emptyText?: string;
|
|
@@ -58,7 +66,7 @@ const props = defineProps<{
|
|
|
58
66
|
rowClass?: (row: Row) => string | undefined;
|
|
59
67
|
/** Controlled: row keys whose `row-detail` slot is shown as a full-width row beneath them. */
|
|
60
68
|
expandedKeys?: ReadonlyArray<string | number>;
|
|
61
|
-
}>();
|
|
69
|
+
}>(), { pager: true });
|
|
62
70
|
|
|
63
71
|
const emit = defineEmits<{
|
|
64
72
|
'update:sort': [sort: FdySortState | null];
|
|
@@ -137,7 +145,7 @@ const currentPage1: ComputedRef<number> = computed((): number =>
|
|
|
137
145
|
const totalPages: ComputedRef<number> = computed((): number =>
|
|
138
146
|
pageSizeEff.value > 0 ? Math.max(1, Math.ceil(totalCount.value / pageSizeEff.value)) : 1,
|
|
139
147
|
);
|
|
140
|
-
const hasPager: ComputedRef<boolean> = computed((): boolean => pageSizeEff.value > 0 && totalPages.value > 1);
|
|
148
|
+
const hasPager: ComputedRef<boolean> = computed((): boolean => props.pager !== false && pageSizeEff.value > 0 && totalPages.value > 1);
|
|
141
149
|
const pages: ComputedRef<Array<number | 'ellipsis'>> = computed((): Array<number | 'ellipsis'> =>
|
|
142
150
|
pageWindow(currentPage1.value, totalPages.value),
|
|
143
151
|
);
|
package/adapters/vue/index.d.ts
CHANGED
|
@@ -12,6 +12,19 @@ export interface UseFreedayReturn {
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function useFreeday(rootRef?: Ref<HTMLElement | null>): UseFreedayReturn;
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Anchor a dropdown panel to its trigger in the top layer, so it escapes any ancestor overflow clip
|
|
17
|
+
* — `.fdy-card` is `overflow:hidden`, so a panel positioned inside one is otherwise cut at the card's
|
|
18
|
+
* edge. The panel element must carry `popover="manual"`. This is the plumbing every kit dropdown
|
|
19
|
+
* uses; it is exported so a control the kit does not ship can behave like one that it does, instead
|
|
20
|
+
* of each app re-implementing it from the description.
|
|
21
|
+
*/
|
|
22
|
+
export declare function usePopover(
|
|
23
|
+
panelRef: Ref<HTMLElement | null>,
|
|
24
|
+
triggerRef: Ref<HTMLElement | null>,
|
|
25
|
+
open: Ref<boolean>,
|
|
26
|
+
): void;
|
|
27
|
+
|
|
15
28
|
export { default as FdyCombo } from './components/FdyCombo.vue';
|
|
16
29
|
export { default as FdyDatepicker } from './components/FdyDatepicker.vue';
|
|
17
30
|
export { default as FdyDateRange } from './components/FdyDateRange.vue';
|
package/adapters/vue/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { useFreeday } from './useFreeday.js';
|
|
2
|
+
export { usePopover } from './usePopover.ts';
|
|
2
3
|
export { default as FdyCombo } from './components/FdyCombo.vue';
|
|
3
4
|
export { default as FdyDatepicker } from './components/FdyDatepicker.vue';
|
|
4
5
|
export { default as FdyDateRange } from './components/FdyDateRange.vue';
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -510,7 +510,11 @@ a { color: var(--color-primary); }
|
|
|
510
510
|
/* Collapsible section: <details class="fdy-nav__group"><summary class="fdy-nav__grouplabel"> */
|
|
511
511
|
.fdy-nav__group{margin:0;}
|
|
512
512
|
.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);}
|
|
513
|
-
|
|
513
|
+
/* Prose, and a control name: this is the <summary> of a nav group, at --text-xs uppercase. It is
|
|
514
|
+
the second class found spending the decorative ink on type (after .fdy-eyebrow) — and it was
|
|
515
|
+
already brightening to --color-text-muted on hover, which is the rule admitting its resting
|
|
516
|
+
state was too faint to read. */
|
|
517
|
+
.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);}
|
|
514
518
|
.fdy-nav__grouplabel::-webkit-details-marker{display:none;}
|
|
515
519
|
.fdy-nav__grouplabel:hover{color:var(--color-text-muted);}
|
|
516
520
|
.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);}
|
|
@@ -702,9 +706,20 @@ a { color: var(--color-primary); }
|
|
|
702
706
|
.fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
|
|
703
707
|
.fdy-btn--ghost:active{transform:translateY(1px);}
|
|
704
708
|
|
|
709
|
+
/* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger` —
|
|
710
|
+
which every reader parses as "quiet destructive" — rendered a second SOLID button beside Save.
|
|
711
|
+
That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
|
|
712
|
+
screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
|
|
713
|
+
the pairing rules below bring it to the button. They are more specific than this one and come
|
|
714
|
+
after it, so this rule needs no :not() guard of its own — one mechanism, not two. */
|
|
705
715
|
.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);}
|
|
706
716
|
.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);}
|
|
707
717
|
.fdy-btn--danger:active{transform:translateY(1px);}
|
|
718
|
+
/* Quiet destructive: the variant keeps its own ground and only the ink turns. */
|
|
719
|
+
.fdy-btn--ghost.fdy-btn--danger{background:transparent;color:var(--color-danger-strong);border-color:color-mix(in srgb,var(--color-danger) 40%,transparent);box-shadow:none;}
|
|
720
|
+
.fdy-btn--ghost.fdy-btn--danger:hover{background:var(--color-danger-soft);border-color:var(--color-danger);color:var(--color-danger-strong);}
|
|
721
|
+
.fdy-btn--text.fdy-btn--danger{background:none;color:var(--color-danger-strong);border-color:transparent;box-shadow:none;}
|
|
722
|
+
.fdy-btn--text.fdy-btn--danger:hover{background:var(--color-danger-soft);color:var(--color-danger-strong);}
|
|
708
723
|
.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);}
|
|
709
724
|
|
|
710
725
|
.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);}
|
|
@@ -1032,7 +1047,11 @@ a { color: var(--color-primary); }
|
|
|
1032
1047
|
/* ---- Type roles (three title levels + supporting text) -------------------------------------
|
|
1033
1048
|
* One role per level of hierarchy. Reach for these instead of re-using .fdy-card__title for a
|
|
1034
1049
|
* page title (the mistake that collapses three levels into one grey mush). */
|
|
1035
|
-
|
|
1050
|
+
/* --color-text-muted, not -subtle: an eyebrow is a small label naming the thing below it, i.e.
|
|
1051
|
+
prose. -subtle is gated at 3.0 on purpose (placeholders, separators, decorative glyphs) and
|
|
1052
|
+
measures 4.41 on the page ground — axe reports it as a serious color-contrast failure. Same
|
|
1053
|
+
argument, same ink, same 4.41 as .fdy-stat__label in 1.30.0. */
|
|
1054
|
+
.fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-muted);}
|
|
1036
1055
|
.fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
1037
1056
|
.fdy-title-section{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-snug);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
1038
1057
|
.fdy-title-card{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0;}
|
package/dist/freeday.css
CHANGED
|
@@ -127,7 +127,11 @@ a { color: var(--color-primary); }
|
|
|
127
127
|
/* Collapsible section: <details class="fdy-nav__group"><summary class="fdy-nav__grouplabel"> */
|
|
128
128
|
.fdy-nav__group{margin:0;}
|
|
129
129
|
.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);}
|
|
130
|
-
|
|
130
|
+
/* Prose, and a control name: this is the <summary> of a nav group, at --text-xs uppercase. It is
|
|
131
|
+
the second class found spending the decorative ink on type (after .fdy-eyebrow) — and it was
|
|
132
|
+
already brightening to --color-text-muted on hover, which is the rule admitting its resting
|
|
133
|
+
state was too faint to read. */
|
|
134
|
+
.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);}
|
|
131
135
|
.fdy-nav__grouplabel::-webkit-details-marker{display:none;}
|
|
132
136
|
.fdy-nav__grouplabel:hover{color:var(--color-text-muted);}
|
|
133
137
|
.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);}
|
|
@@ -319,9 +323,20 @@ a { color: var(--color-primary); }
|
|
|
319
323
|
.fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
|
|
320
324
|
.fdy-btn--ghost:active{transform:translateY(1px);}
|
|
321
325
|
|
|
326
|
+
/* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger` —
|
|
327
|
+
which every reader parses as "quiet destructive" — rendered a second SOLID button beside Save.
|
|
328
|
+
That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
|
|
329
|
+
screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
|
|
330
|
+
the pairing rules below bring it to the button. They are more specific than this one and come
|
|
331
|
+
after it, so this rule needs no :not() guard of its own — one mechanism, not two. */
|
|
322
332
|
.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);}
|
|
323
333
|
.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);}
|
|
324
334
|
.fdy-btn--danger:active{transform:translateY(1px);}
|
|
335
|
+
/* Quiet destructive: the variant keeps its own ground and only the ink turns. */
|
|
336
|
+
.fdy-btn--ghost.fdy-btn--danger{background:transparent;color:var(--color-danger-strong);border-color:color-mix(in srgb,var(--color-danger) 40%,transparent);box-shadow:none;}
|
|
337
|
+
.fdy-btn--ghost.fdy-btn--danger:hover{background:var(--color-danger-soft);border-color:var(--color-danger);color:var(--color-danger-strong);}
|
|
338
|
+
.fdy-btn--text.fdy-btn--danger{background:none;color:var(--color-danger-strong);border-color:transparent;box-shadow:none;}
|
|
339
|
+
.fdy-btn--text.fdy-btn--danger:hover{background:var(--color-danger-soft);color:var(--color-danger-strong);}
|
|
325
340
|
.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);}
|
|
326
341
|
|
|
327
342
|
.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);}
|
|
@@ -649,7 +664,11 @@ a { color: var(--color-primary); }
|
|
|
649
664
|
/* ---- Type roles (three title levels + supporting text) -------------------------------------
|
|
650
665
|
* One role per level of hierarchy. Reach for these instead of re-using .fdy-card__title for a
|
|
651
666
|
* page title (the mistake that collapses three levels into one grey mush). */
|
|
652
|
-
|
|
667
|
+
/* --color-text-muted, not -subtle: an eyebrow is a small label naming the thing below it, i.e.
|
|
668
|
+
prose. -subtle is gated at 3.0 on purpose (placeholders, separators, decorative glyphs) and
|
|
669
|
+
measures 4.41 on the page ground — axe reports it as a serious color-contrast failure. Same
|
|
670
|
+
argument, same ink, same 4.41 as .fdy-stat__label in 1.30.0. */
|
|
671
|
+
.fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-muted);}
|
|
653
672
|
.fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
654
673
|
.fdy-title-section{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-snug);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
655
674
|
.fdy-title-card{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0;}
|
package/docs/agent-onboarding.md
CHANGED
|
@@ -91,6 +91,7 @@ Blazor). Then verify the agent can actually read those files — an agent that c
|
|
|
91
91
|
| `docs/integrations.md` | How to bridge third-party libraries (validation, charts, dates, i18n…). |
|
|
92
92
|
| `docs/reference-screen.html` | A full screen assembled from the shell down. Open it in a browser. |
|
|
93
93
|
| `docs/agent-onboarding.md` | This file. |
|
|
94
|
+
| `CHANGELOG.md` | **What changed between the version this project had and the one it has now** — read it after every upgrade; each entry says what broke, what is new, and why. |
|
|
94
95
|
| `dist/` | Built CSS + enhancers. **`freeday.bundle.css` = tokens + components** (what `@cahyo-dimas/freeday/css` resolves to); `freeday.css` is components **only**, `freeday.tokens.css` tokens only — linking `freeday.css` alone leaves every `var(--…)` unresolved. Plus `freeday-*.js` and the `.d.ts` files. |
|
|
95
96
|
| `src/components/*.css` | The authoritative source for every class, when a doc is ambiguous. |
|
|
96
97
|
| `tokens/tokens.json` | Every token in W3C DTCG format — machine-readable. |
|
|
@@ -101,6 +102,28 @@ The live docs (with an interactive playground) are at
|
|
|
101
102
|
apps under `examples/` (Vue, React, Blazor) that are **not** in the npm tarball — is at
|
|
102
103
|
<https://github.com/cahyo-dimas/freeday-ui-kit>.
|
|
103
104
|
|
|
105
|
+
### After an upgrade, read the changelog first
|
|
106
|
+
|
|
107
|
+
`npm i @cahyo-dimas/freeday@latest` does not tell you what you gained. Read
|
|
108
|
+
`node_modules/@cahyo-dimas/freeday/CHANGELOG.md` down to the version this project was on before —
|
|
109
|
+
it is written for exactly this moment, and it is the difference between adopting a new affordance
|
|
110
|
+
and re-implementing it locally.
|
|
111
|
+
|
|
112
|
+
Recent additions most likely to replace something an app hand-rolled (all detailed in
|
|
113
|
+
`COMPONENTS.md`):
|
|
114
|
+
|
|
115
|
+
| Reach for | Instead of |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `.fdy-btn--stretch` | a hand-rolled overlay to make a card or list row clickable |
|
|
118
|
+
| `[data-fdy-number]` | a bare `<input type="number">` with the browser's own spin buttons |
|
|
119
|
+
| `clearable` on `FdyCfl` / `FdyDatepicker` | a value that can be set but never unset |
|
|
120
|
+
| `row.waiting(label)` on an upload row | a second status line saying the server is still working |
|
|
121
|
+
| `.fdy-label--required` | a `<span>` asterisk you have to remember to `aria-hidden` |
|
|
122
|
+
| `.fdy-text-warning` / `-danger` / `-success` | a caption class on a line that should stand out |
|
|
123
|
+
| `.fdy-icon` | your own `width:1em;height:1em` rule, once per project |
|
|
124
|
+
| `data-density="comfortable"` | restating the default spacing tokens on a wrapper |
|
|
125
|
+
| the calendar's month grid (press the title) | clicking "previous month" thirty times |
|
|
126
|
+
|
|
104
127
|
## 3. Starting a new screen
|
|
105
128
|
|
|
106
129
|
The order matters; skipping to components is what produces flat, identical-card screens.
|
package/docs/getting-started.md
CHANGED
|
@@ -184,7 +184,7 @@ live docs also have a copy button per component.
|
|
|
184
184
|
```bash
|
|
185
185
|
npm i @cahyo-dimas/freeday
|
|
186
186
|
```
|
|
187
|
-
Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.
|
|
187
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.33.0"` (public npm package). `dist/` is
|
|
188
188
|
committed and published → no build step; `npm ci` runs without auth.
|
|
189
189
|
|
|
190
190
|
### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
|
package/docs/integrations.md
CHANGED
|
@@ -319,3 +319,17 @@ web-based add-ons (Service Layer + .NET), Freeday + enhancers over JS interop wo
|
|
|
319
319
|
*Library recommendations reflect what's common and stable as of 2026; choose based on your
|
|
320
320
|
project's license and bundle-size budget. Freeday locks you into none of them — everything is
|
|
321
321
|
optional and replaceable.*
|
|
322
|
+
|
|
323
|
+
## Building a control the kit does not ship
|
|
324
|
+
|
|
325
|
+
Use the `app-` prefix for its classes, and reuse the kit's plumbing rather than re-deriving it:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { usePopover } from '@cahyo-dimas/freeday/vue'; // or '/react'
|
|
329
|
+
usePopover(panelRef, triggerRef, open); // panel needs popover="manual"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
This is what every kit dropdown uses to escape an ancestor's overflow clip — `.fdy-card` is
|
|
333
|
+
`overflow:hidden`, so a panel positioned inside one is otherwise cut at the card's edge. It is
|
|
334
|
+
exported so a control you build behaves like one the kit ships, and improves when the kit's
|
|
335
|
+
positioning does.
|
package/package.json
CHANGED
|
@@ -48,7 +48,11 @@
|
|
|
48
48
|
/* Collapsible section: <details class="fdy-nav__group"><summary class="fdy-nav__grouplabel"> */
|
|
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) — and it was
|
|
53
|
+
already brightening to --color-text-muted on hover, which is the rule admitting its resting
|
|
54
|
+
state was too faint to read. */
|
|
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);}
|
|
52
56
|
.fdy-nav__grouplabel::-webkit-details-marker{display:none;}
|
|
53
57
|
.fdy-nav__grouplabel:hover{color:var(--color-text-muted);}
|
|
54
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);}
|
|
@@ -23,9 +23,20 @@
|
|
|
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" — rendered a second SOLID button beside Save.
|
|
28
|
+
That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
|
|
29
|
+
screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
|
|
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 — one mechanism, not two. */
|
|
26
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);}
|
|
27
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);}
|
|
28
34
|
.fdy-btn--danger:active{transform:translateY(1px);}
|
|
35
|
+
/* Quiet destructive: the variant keeps its own ground and only the ink turns. */
|
|
36
|
+
.fdy-btn--ghost.fdy-btn--danger{background:transparent;color:var(--color-danger-strong);border-color:color-mix(in srgb,var(--color-danger) 40%,transparent);box-shadow:none;}
|
|
37
|
+
.fdy-btn--ghost.fdy-btn--danger:hover{background:var(--color-danger-soft);border-color:var(--color-danger);color:var(--color-danger-strong);}
|
|
38
|
+
.fdy-btn--text.fdy-btn--danger{background:none;color:var(--color-danger-strong);border-color:transparent;box-shadow:none;}
|
|
39
|
+
.fdy-btn--text.fdy-btn--danger:hover{background:var(--color-danger-soft);color:var(--color-danger-strong);}
|
|
29
40
|
.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);}
|
|
30
41
|
|
|
31
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);}
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
/* ---- Type roles (three title levels + supporting text) -------------------------------------
|
|
10
10
|
* One role per level of hierarchy. Reach for these instead of re-using .fdy-card__title for a
|
|
11
11
|
* page title (the mistake that collapses three levels into one grey mush). */
|
|
12
|
-
|
|
12
|
+
/* --color-text-muted, not -subtle: an eyebrow is a small label naming the thing below it, i.e.
|
|
13
|
+
prose. -subtle is gated at 3.0 on purpose (placeholders, separators, decorative glyphs) and
|
|
14
|
+
measures 4.41 on the page ground — axe reports it as a serious color-contrast failure. Same
|
|
15
|
+
argument, same ink, same 4.41 as .fdy-stat__label in 1.30.0. */
|
|
16
|
+
.fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-muted);}
|
|
13
17
|
.fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
14
18
|
.fdy-title-section{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-snug);color:var(--color-text);margin:0;text-wrap:balance;}
|
|
15
19
|
.fdy-title-card{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0;}
|