@cahyo-dimas/freeday 1.32.1 → 1.34.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 +168 -0
- package/COMPONENTS.md +43 -2
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/blazor/FdyTable.razor +2 -35
- package/adapters/blazor/FdyTable.razor.cs +61 -9
- package/adapters/blazor/FdyTableFooter.razor +60 -0
- package/adapters/blazor/FdyTableFooter.razor.cs +59 -0
- package/adapters/blazor/TableModel.cs +12 -0
- package/adapters/core/table-model.d.ts +2 -0
- package/adapters/core/table-model.js +21 -0
- package/adapters/react/components/FdyAutocomplete.tsx +1 -1
- package/adapters/react/components/FdyCascade.tsx +2 -2
- package/adapters/react/components/FdyCfl.tsx +25 -11
- package/adapters/react/components/FdyTable.tsx +54 -33
- package/adapters/react/components/FdyTableFooter.tsx +102 -0
- package/adapters/react/index.d.ts +3 -0
- package/adapters/react/index.js +1 -0
- package/adapters/vue/components/FdyAutocomplete.vue +1 -1
- package/adapters/vue/components/FdyCascade.vue +2 -2
- package/adapters/vue/components/FdyCfl.vue +25 -11
- package/adapters/vue/components/FdyTable.vue +61 -48
- package/adapters/vue/components/FdyTableFooter.vue +121 -0
- package/adapters/vue/index.d.ts +16 -0
- package/adapters/vue/index.js +2 -0
- package/dist/freeday-table.js +24 -1
- package/dist/freeday.bundle.css +49 -14
- package/dist/freeday.css +49 -14
- package/dist/freeday.js +24 -1
- package/docs/agent-onboarding.md +2 -0
- package/docs/getting-started.md +1 -1
- package/docs/integrations.md +14 -0
- package/package.json +29 -8
- package/src/components/alert.css +1 -1
- package/src/components/app-shell.css +7 -3
- package/src/components/button.css +11 -0
- package/src/components/cfl.css +1 -1
- package/src/components/composition.css +5 -1
- package/src/components/datepicker.css +8 -4
- package/src/components/file-upload.css +1 -1
- package/src/components/list.css +1 -1
- package/src/components/states.css +1 -1
- package/src/components/table.css +12 -0
- package/src/components/toast.css +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useId, type ComputedRef } from 'vue';
|
|
3
|
+
import { pageWindow, pageIndexForSize } from '../../core/table-model.js';
|
|
4
|
+
import type { FdyPageState } from '../../core/table-model';
|
|
5
|
+
import FdyCombo from './FdyCombo.vue';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* The band under a table: what you are looking at, how much of it you see, where you are.
|
|
9
|
+
*
|
|
10
|
+
* `FdyTable` renders this itself, so most screens never import it. It is exported for the one shape
|
|
11
|
+
* that cannot use the table's own — a RESPONSIVE list, where a `.fdy-datatable` at `lg` and a
|
|
12
|
+
* `.fdy-list` below it are two renderings of ONE page of rows. A footer inside the table is inside
|
|
13
|
+
* the half that is hidden on a phone, so those screens need to render it once, outside both
|
|
14
|
+
* (improvement notes #005 and #008, from IDU_EMATE_APPL_WEB).
|
|
15
|
+
*
|
|
16
|
+
* It owns nothing. `page` in, `update:page` out — the same contract as the table's server mode, so a
|
|
17
|
+
* screen already wired for that switches by moving the prop.
|
|
18
|
+
*/
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
/** The page being shown. `size` drives the range AND the rows-per-page control's value. */
|
|
21
|
+
page: FdyPageState;
|
|
22
|
+
/**
|
|
23
|
+
* Offer a rows-per-page control. Omit for none — the footer is then range + pager, exactly as
|
|
24
|
+
* before. Picking a size emits `update:page` carrying the new `size` and the index that still
|
|
25
|
+
* holds the row you were looking at.
|
|
26
|
+
*/
|
|
27
|
+
pageSizes?: readonly number[];
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{ 'update:page': [page: FdyPageState] }>();
|
|
31
|
+
|
|
32
|
+
/* The combo is labelled by the visible word beside it — a `<label for>` cannot reach inside a
|
|
33
|
+
* component, and an aria-label would leave that word unattached to anything. */
|
|
34
|
+
const sizeLabelId: string = `fdy-rows-${useId()}`;
|
|
35
|
+
|
|
36
|
+
const totalPages: ComputedRef<number> = computed((): number =>
|
|
37
|
+
props.page.size > 0 ? Math.max(1, Math.ceil(props.page.total / props.page.size)) : 1,
|
|
38
|
+
);
|
|
39
|
+
const currentPage1: ComputedRef<number> = computed((): number => props.page.index + 1);
|
|
40
|
+
const rangeFrom: ComputedRef<number> = computed((): number =>
|
|
41
|
+
props.page.total === 0 ? 0 : props.page.index * props.page.size + 1,
|
|
42
|
+
);
|
|
43
|
+
const rangeTo: ComputedRef<number> = computed((): number =>
|
|
44
|
+
Math.min(props.page.total, (props.page.index + 1) * props.page.size),
|
|
45
|
+
);
|
|
46
|
+
const pages: ComputedRef<Array<number | 'ellipsis'>> = computed((): Array<number | 'ellipsis'> =>
|
|
47
|
+
pageWindow(currentPage1.value, totalPages.value),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const hasPager: ComputedRef<boolean> = computed((): boolean => props.page.size > 0 && totalPages.value > 1);
|
|
51
|
+
const sizes: ComputedRef<readonly number[]> = computed((): readonly number[] => props.pageSizes ?? []);
|
|
52
|
+
/* Just the number. The control sits beside a label reading "Rows", so "20 per page" states the same
|
|
53
|
+
* fact twice and truncates doing it. */
|
|
54
|
+
const sizeOptions: ComputedRef<Array<{ value: string; label: string }>> = computed(() =>
|
|
55
|
+
sizes.value.map((size: number) => ({ value: String(size), label: String(size) })),
|
|
56
|
+
);
|
|
57
|
+
/* One page and no size control means there is nothing here to say — the table has always withheld
|
|
58
|
+
* the whole band in that case, and this is where that decision now lives. */
|
|
59
|
+
const visible: ComputedRef<boolean> = computed((): boolean => hasPager.value || sizes.value.length > 0);
|
|
60
|
+
|
|
61
|
+
function goTo(page1: number): void {
|
|
62
|
+
const clamped: number = Math.min(Math.max(1, page1), totalPages.value);
|
|
63
|
+
emit('update:page', { index: clamped - 1, size: props.page.size, total: props.page.total });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function onSize(value: string): void {
|
|
67
|
+
const size: number = Number(value);
|
|
68
|
+
if (!Number.isFinite(size) || size <= 0 || size === props.page.size) return;
|
|
69
|
+
emit('update:page', {
|
|
70
|
+
index: pageIndexForSize(props.page.index, props.page.size, size),
|
|
71
|
+
size,
|
|
72
|
+
total: props.page.total,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<div v-if="visible" class="fdy-table-footer">
|
|
79
|
+
<span class="fdy-table-footer__info">Showing {{ rangeFrom }}–{{ rangeTo }} of {{ page.total }}</span>
|
|
80
|
+
|
|
81
|
+
<div v-if="sizes.length > 0" class="fdy-table-footer__size">
|
|
82
|
+
<span :id="sizeLabelId">Rows</span>
|
|
83
|
+
<!-- The kit's own listbox, not a native <select>: an OS menu is unthemeable, and on macOS it
|
|
84
|
+
drops a dark panel into a light page. FdyCombo's own header says exactly that. -->
|
|
85
|
+
<FdyCombo
|
|
86
|
+
:model-value="String(page.size)"
|
|
87
|
+
:options="sizeOptions"
|
|
88
|
+
:aria-labelledby="sizeLabelId"
|
|
89
|
+
@update:model-value="onSize"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<nav v-if="hasPager" aria-label="Pagination">
|
|
94
|
+
<ul class="fdy-pagination__list">
|
|
95
|
+
<li>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
class="fdy-pagination__link"
|
|
99
|
+
aria-label="Previous page"
|
|
100
|
+
:disabled="currentPage1 === 1"
|
|
101
|
+
@click="goTo(currentPage1 - 1)"
|
|
102
|
+
>‹</button>
|
|
103
|
+
</li>
|
|
104
|
+
<li v-for="(p, i) in pages" :key="typeof p === 'number' ? p : `gap-${i}`">
|
|
105
|
+
<span v-if="p === 'ellipsis'" class="fdy-pagination__ellipsis">…</span>
|
|
106
|
+
<span v-else-if="p === currentPage1" class="fdy-pagination__link" aria-current="page">{{ p }}</span>
|
|
107
|
+
<button v-else type="button" class="fdy-pagination__link" :aria-label="`Go to page ${p}`" @click="goTo(p)">{{ p }}</button>
|
|
108
|
+
</li>
|
|
109
|
+
<li>
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
class="fdy-pagination__link"
|
|
113
|
+
aria-label="Next page"
|
|
114
|
+
:disabled="currentPage1 === totalPages"
|
|
115
|
+
@click="goTo(currentPage1 + 1)"
|
|
116
|
+
>›</button>
|
|
117
|
+
</li>
|
|
118
|
+
</ul>
|
|
119
|
+
</nav>
|
|
120
|
+
</div>
|
|
121
|
+
</template>
|
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';
|
|
@@ -22,6 +35,9 @@ export type { CascadeNode } from './components/FdyCascade.vue';
|
|
|
22
35
|
export { default as FdyCfl } from './components/FdyCfl.vue';
|
|
23
36
|
export { default as FdyChart } from './components/FdyChart.vue';
|
|
24
37
|
export { default as FdyTable } from './components/FdyTable.vue';
|
|
38
|
+
/** The table's own footer, standalone — for a responsive list whose table is hidden at some
|
|
39
|
+
* breakpoints and so cannot host it. */
|
|
40
|
+
export { default as FdyTableFooter } from './components/FdyTableFooter.vue';
|
|
25
41
|
export { default as FdyModal } from './components/FdyModal.vue';
|
|
26
42
|
export { default as FdyDrawer } from './components/FdyDrawer.vue';
|
|
27
43
|
|
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';
|
|
@@ -7,5 +8,6 @@ export { default as FdyCascade } from './components/FdyCascade.vue';
|
|
|
7
8
|
export { default as FdyCfl } from './components/FdyCfl.vue';
|
|
8
9
|
export { default as FdyChart } from './components/FdyChart.vue';
|
|
9
10
|
export { default as FdyTable } from './components/FdyTable.vue';
|
|
11
|
+
export { default as FdyTableFooter } from './components/FdyTableFooter.vue';
|
|
10
12
|
export { default as FdyModal } from './components/FdyModal.vue';
|
|
11
13
|
export { default as FdyDrawer } from './components/FdyDrawer.vue';
|
package/dist/freeday-table.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* a `<table class="fdy-table">` whose sortable headers hold
|
|
9
9
|
* `<button class="fdy-table__sortbtn" data-fdy-sort[="number"]>`, optional
|
|
10
10
|
* selection checkboxes (`[data-fdy-select-all]`, `[data-fdy-row-select]`), and a
|
|
11
|
-
* `.fdy-table-footer` (with `[data-fdy-table-info]`, `[data-fdy-table-pagination]
|
|
11
|
+
* `.fdy-table-footer` (with `[data-fdy-table-info]`, `[data-fdy-table-pagination]`, and an
|
|
12
|
+
* optional `<select data-fdy-table-page-size>` whose options are row counts).
|
|
12
13
|
* Numeric columns should give each cell a `data-sort-value`.
|
|
13
14
|
*
|
|
14
15
|
* Column filters: mark a header `<th data-fdy-filter="text|enum|number">` and a funnel
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
var countEl = root.querySelector('[data-fdy-table-count]');
|
|
45
46
|
var infoEl = root.querySelector('[data-fdy-table-info]');
|
|
46
47
|
var pagerEl = root.querySelector('[data-fdy-table-pagination]');
|
|
48
|
+
var pageSizeEl = root.querySelector('[data-fdy-table-page-size]');
|
|
47
49
|
var selectAll = root.querySelector('[data-fdy-select-all]');
|
|
48
50
|
var pageSize = parseInt(root.getAttribute('data-page-size'), 10) || 0; // 0 = no pagination
|
|
49
51
|
|
|
@@ -440,6 +442,27 @@
|
|
|
440
442
|
updateBulk();
|
|
441
443
|
});
|
|
442
444
|
|
|
445
|
+
/* Rows per page. The select is app-authored markup (its options ARE the offer), so this only
|
|
446
|
+
* wires it: adopt whatever it currently shows, and on a change keep the reader on the row they
|
|
447
|
+
* were looking at rather than sending them back to page 1. */
|
|
448
|
+
if (pageSizeEl) {
|
|
449
|
+
var chosen = parseInt(pageSizeEl.value, 10);
|
|
450
|
+
if (chosen > 0) pageSize = chosen;
|
|
451
|
+
var onPageSize = function (e) {
|
|
452
|
+
var raw = (e && e.detail && e.detail.value != null) ? e.detail.value : pageSizeEl.value;
|
|
453
|
+
var next = parseInt(raw, 10);
|
|
454
|
+
if (!(next > 0) || next === pageSize) return;
|
|
455
|
+
var firstRow = (page - 1) * pageSize;
|
|
456
|
+
pageSize = next;
|
|
457
|
+
page = Math.floor(firstRow / next) + 1;
|
|
458
|
+
render();
|
|
459
|
+
};
|
|
460
|
+
/* Both kinds: a native <select> fires `change`, and `.fdy-combo` (freeday-select.js) fires
|
|
461
|
+
* `fdy-change` with the value in `detail`. A raw page may use either. */
|
|
462
|
+
pageSizeEl.addEventListener('change', onPageSize);
|
|
463
|
+
pageSizeEl.addEventListener('fdy-change', onPageSize);
|
|
464
|
+
}
|
|
465
|
+
|
|
443
466
|
if (bulkClear) bulkClear.addEventListener('click', clearSelection);
|
|
444
467
|
|
|
445
468
|
// Dismiss an open filter popover on outside click or Escape.
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -446,7 +446,7 @@ a { color: var(--color-primary); }
|
|
|
446
446
|
.fdy-alert__icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-top:.05rem;}
|
|
447
447
|
.fdy-alert__icon svg{width:100%;height:100%;}
|
|
448
448
|
.fdy-alert__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-1);}
|
|
449
|
-
.fdy-alert__title{font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
449
|
+
.fdy-alert__title{font-size:inherit;font-weight:var(--weight-semibold);color:var(--color-text);margin:0;}
|
|
450
450
|
.fdy-alert__text{color:var(--color-text);line-height:var(--leading-normal);}
|
|
451
451
|
.fdy-alert__close{flex:none;align-self:flex-start;border:0;background:transparent;color:var(--color-text-muted);cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
|
|
452
452
|
.fdy-alert__close:hover{color:var(--color-text);}
|
|
@@ -483,8 +483,8 @@ a { color: var(--color-primary); }
|
|
|
483
483
|
.fdy-app__brand-mark{flex:none;width:1.5rem;height:1.5rem;}
|
|
484
484
|
.fdy-app__brand-mark svg{display:block;width:100%;height:100%;}
|
|
485
485
|
.fdy-app__brand-text{display:flex;flex-direction:column;min-width:0;}
|
|
486
|
-
.fdy-app__brand-title{display:block;font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
487
|
-
.fdy-app__brand-subtitle{display:block;font-size:var(--text-xs);font-weight:var(--weight-regular);color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
486
|
+
.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;}
|
|
487
|
+
.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;}
|
|
488
488
|
.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);}
|
|
489
489
|
/* Horizontal variant — the SAME nav links laid out as a row, for a top-nav application (one that
|
|
490
490
|
* puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
|
|
@@ -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);}
|
|
@@ -886,7 +901,7 @@ a { color: var(--color-primary); }
|
|
|
886
901
|
.fdy-cfl__check input{pointer-events:none;}
|
|
887
902
|
|
|
888
903
|
/* Empty result state. */
|
|
889
|
-
.fdy-cfl__empty{padding:var(--space-8) var(--space-5);text-align:center;color:var(--color-text-muted);font-size:var(--text-sm);}
|
|
904
|
+
.fdy-cfl__empty{padding:var(--space-8) var(--space-5);text-align:center;color:var(--color-text-muted);font-size:var(--text-sm);margin:0;}
|
|
890
905
|
.fdy-cfl__empty[hidden]{display:none;}
|
|
891
906
|
|
|
892
907
|
/* Footer — selection count (multi) sits left, actions right. */
|
|
@@ -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);margin:0;}
|
|
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;}
|
|
@@ -1123,12 +1142,9 @@ a { color: var(--color-primary); }
|
|
|
1123
1142
|
.fdy-cal__head{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2);}
|
|
1124
1143
|
/* The title is a CONTROL now (#004 §1): it drills to the month grid, so it needs the UA button box
|
|
1125
1144
|
reset and the same hover/focus treatment as the arrows beside it. */
|
|
1126
|
-
.fdy-cal__title{flex:1;appearance:none;border:0;background:transparent;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);text-align:center;font-family:inherit;font-weight:var(--weight-semibold);font-size:var(--text-sm);color:var(--color-text);text-transform:capitalize;cursor:pointer;}
|
|
1145
|
+
.fdy-cal__title{flex:1;appearance:none;border:0;background:transparent;margin:0;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);text-align:center;font-family:inherit;font-weight:var(--weight-semibold);font-size:var(--text-sm);color:var(--color-text);text-transform:capitalize;cursor:pointer;}
|
|
1127
1146
|
.fdy-cal__title:hover{background:var(--color-surface-2);}
|
|
1128
1147
|
.fdy-cal__title:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
1129
|
-
/* Month grid — 3×4 over the same track width as the 7-column day grid, so the panel does not resize
|
|
1130
|
-
when you drill and the pointer does not have to travel. */
|
|
1131
|
-
.fdy-cal__grid--months{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
|
|
1132
1148
|
.fdy-cal__month{display:inline-flex;align-items:center;justify-content:center;height:2.25rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-family:inherit;font-size:var(--text-sm);text-transform:capitalize;cursor:pointer;}
|
|
1133
1149
|
.fdy-cal__month:hover{background:var(--color-surface-2);}
|
|
1134
1150
|
.fdy-cal__month:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
@@ -1139,6 +1155,13 @@ a { color: var(--color-primary); }
|
|
|
1139
1155
|
.fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
1140
1156
|
.fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
1141
1157
|
.fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
|
|
1158
|
+
/* Month grid — 3×4 over the same track width as the 7-column day grid, so the panel does not resize
|
|
1159
|
+
when you drill and the pointer does not have to travel.
|
|
1160
|
+
|
|
1161
|
+
AFTER `.fdy-cal__grid`, and that is the whole rule: the element carries both classes, they weigh
|
|
1162
|
+
the same, so source order decides. Declared earlier, the modifier lost to the base it modifies and
|
|
1163
|
+
twelve months rendered seven across — the layout the comment above described, never once applied. */
|
|
1164
|
+
.fdy-cal__grid--months{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
|
|
1142
1165
|
.fdy-cal__dow{display:flex;align-items:center;justify-content:center;height:1.75rem;font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-transform:capitalize;}
|
|
1143
1166
|
.fdy-cal__day{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-size:var(--text-sm);font-variant-numeric:tabular-nums;cursor:pointer;}
|
|
1144
1167
|
.fdy-cal__day:hover{background:var(--color-surface-2);}
|
|
@@ -1219,7 +1242,7 @@ a { color: var(--color-primary); }
|
|
|
1219
1242
|
.fdy-dropzone__icon{color:var(--color-text-subtle);}
|
|
1220
1243
|
.fdy-dropzone.is-dragover .fdy-dropzone__icon{color:var(--color-primary);}
|
|
1221
1244
|
.fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
|
|
1222
|
-
.fdy-dropzone__title{font-weight:var(--weight-medium);color:var(--color-text);}
|
|
1245
|
+
.fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
|
|
1223
1246
|
.fdy-dropzone__hint{font-size:var(--text-xs);}
|
|
1224
1247
|
/* Wide (horizontal) layout — icon beside the copy instead of stacked, for full-width
|
|
1225
1248
|
* sections. Wrap the title + hint in .fdy-dropzone__text. */
|
|
@@ -1406,7 +1429,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1406
1429
|
.fdy-list__row--button:disabled:hover,.fdy-list__row--button[aria-disabled="true"]:hover,.fdy-list__row--interactive[aria-disabled="true"]:hover{background:none;}
|
|
1407
1430
|
/* Row internals: a title/meta stack that truncates, and a trailing slot pinned right. */
|
|
1408
1431
|
.fdy-list__main{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;flex:1;}
|
|
1409
|
-
.fdy-list__title{font-weight:var(--weight-medium);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1432
|
+
.fdy-list__title{font-family:var(--font-body);font-size:var(--text-base);font-weight:var(--weight-medium);letter-spacing:normal;color:var(--color-text);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1410
1433
|
.fdy-list__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
|
|
1411
1434
|
.fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
|
|
1412
1435
|
/* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */
|
|
@@ -1563,7 +1586,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1563
1586
|
.fdy-state{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-2);padding:var(--space-10) var(--space-6);}
|
|
1564
1587
|
.fdy-state__icon{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;color:var(--color-text-subtle);}
|
|
1565
1588
|
.fdy-state__icon svg{width:100%;height:100%;}
|
|
1566
|
-
.fdy-state__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
1589
|
+
.fdy-state__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);margin:0;}
|
|
1567
1590
|
.fdy-state__text{font-size:var(--text-sm);color:var(--color-text-muted);max-width:24rem;line-height:var(--leading-normal);}
|
|
1568
1591
|
.fdy-state__actions{margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap;justify-content:center;}
|
|
1569
1592
|
.fdy-state--danger .fdy-state__icon{color:var(--color-danger-strong);}
|
|
@@ -1643,6 +1666,18 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1643
1666
|
.fdy-table-toolbar__count{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;}
|
|
1644
1667
|
.fdy-table-footer{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-top:var(--bw) solid var(--color-border);}
|
|
1645
1668
|
.fdy-table-footer__info{font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
1669
|
+
/* Rows per page. `margin-left:auto` eats the free space, so the size control and the pager travel
|
|
1670
|
+
together on the right while the range stays left — no wrapper element, which is what keeps the
|
|
1671
|
+
footer's DOM byte-identical for the tables that don't offer one (#008). The control is the kit's
|
|
1672
|
+
own `.fdy-combo`, never a native <select>: an OS menu is unthemeable, and on macOS it drops a
|
|
1673
|
+
dark panel into a light page — which is the reason FdyCombo exists at all. Sized to its content:
|
|
1674
|
+
`.fdy-combo` is display:block at max-width 22rem, which in a footer is a bar-wide box for two
|
|
1675
|
+
digits. Its HEIGHT is left alone on purpose — `--control-h`, the same as every other control on
|
|
1676
|
+
the page. It briefly carried 2.25rem to match the pager links beside it, which made the one
|
|
1677
|
+
combobox in a compact app 4px taller than all the others; a pager link is a nav button, and the
|
|
1678
|
+
thing this must agree with is the form controls. */
|
|
1679
|
+
.fdy-table-footer__size{display:inline-flex;align-items:center;gap:var(--space-2);margin-left:auto;font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
1680
|
+
.fdy-table-footer__size .fdy-combo{width:5.5rem;}
|
|
1646
1681
|
|
|
1647
1682
|
/* Column filter — funnel button injected (enhancer) or rendered (FdyTable) into a filterable <th> */
|
|
1648
1683
|
.fdy-table__filterwrap{display:inline-flex;align-items:center;vertical-align:middle;}
|
|
@@ -1734,7 +1769,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1734
1769
|
.fdy-toast--danger .fdy-toast__accent{background:var(--color-danger);}
|
|
1735
1770
|
.fdy-toast--info .fdy-toast__accent{background:var(--color-info);}
|
|
1736
1771
|
.fdy-toast__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);}
|
|
1737
|
-
.fdy-toast__title{font-weight:var(--weight-semibold);}
|
|
1772
|
+
.fdy-toast__title{font-size:inherit;font-weight:var(--weight-semibold);margin:0;}
|
|
1738
1773
|
.fdy-toast__text{opacity:.88;line-height:var(--leading-normal);}
|
|
1739
1774
|
.fdy-toast__close{flex:none;border:0;background:transparent;color:var(--color-inverse-text);opacity:.7;cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
|
|
1740
1775
|
.fdy-toast__close:hover{opacity:1;}
|
package/dist/freeday.css
CHANGED
|
@@ -63,7 +63,7 @@ a { color: var(--color-primary); }
|
|
|
63
63
|
.fdy-alert__icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-top:.05rem;}
|
|
64
64
|
.fdy-alert__icon svg{width:100%;height:100%;}
|
|
65
65
|
.fdy-alert__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-1);}
|
|
66
|
-
.fdy-alert__title{font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
66
|
+
.fdy-alert__title{font-size:inherit;font-weight:var(--weight-semibold);color:var(--color-text);margin:0;}
|
|
67
67
|
.fdy-alert__text{color:var(--color-text);line-height:var(--leading-normal);}
|
|
68
68
|
.fdy-alert__close{flex:none;align-self:flex-start;border:0;background:transparent;color:var(--color-text-muted);cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
|
|
69
69
|
.fdy-alert__close:hover{color:var(--color-text);}
|
|
@@ -100,8 +100,8 @@ a { color: var(--color-primary); }
|
|
|
100
100
|
.fdy-app__brand-mark{flex:none;width:1.5rem;height:1.5rem;}
|
|
101
101
|
.fdy-app__brand-mark svg{display:block;width:100%;height:100%;}
|
|
102
102
|
.fdy-app__brand-text{display:flex;flex-direction:column;min-width:0;}
|
|
103
|
-
.fdy-app__brand-title{display:block;font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
104
|
-
.fdy-app__brand-subtitle{display:block;font-size:var(--text-xs);font-weight:var(--weight-regular);color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
103
|
+
.fdy-app__brand-title{display:block;font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
104
|
+
.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;}
|
|
105
105
|
.fdy-nav{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;gap:2px;padding:var(--space-3) var(--space-3) var(--space-6);}
|
|
106
106
|
/* Horizontal variant — the SAME nav links laid out as a row, for a top-nav application (one that
|
|
107
107
|
* puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
|
|
@@ -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);}
|
|
@@ -503,7 +518,7 @@ a { color: var(--color-primary); }
|
|
|
503
518
|
.fdy-cfl__check input{pointer-events:none;}
|
|
504
519
|
|
|
505
520
|
/* Empty result state. */
|
|
506
|
-
.fdy-cfl__empty{padding:var(--space-8) var(--space-5);text-align:center;color:var(--color-text-muted);font-size:var(--text-sm);}
|
|
521
|
+
.fdy-cfl__empty{padding:var(--space-8) var(--space-5);text-align:center;color:var(--color-text-muted);font-size:var(--text-sm);margin:0;}
|
|
507
522
|
.fdy-cfl__empty[hidden]{display:none;}
|
|
508
523
|
|
|
509
524
|
/* Footer — selection count (multi) sits left, actions right. */
|
|
@@ -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);margin:0;}
|
|
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;}
|
|
@@ -740,12 +759,9 @@ a { color: var(--color-primary); }
|
|
|
740
759
|
.fdy-cal__head{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2);}
|
|
741
760
|
/* The title is a CONTROL now (#004 §1): it drills to the month grid, so it needs the UA button box
|
|
742
761
|
reset and the same hover/focus treatment as the arrows beside it. */
|
|
743
|
-
.fdy-cal__title{flex:1;appearance:none;border:0;background:transparent;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);text-align:center;font-family:inherit;font-weight:var(--weight-semibold);font-size:var(--text-sm);color:var(--color-text);text-transform:capitalize;cursor:pointer;}
|
|
762
|
+
.fdy-cal__title{flex:1;appearance:none;border:0;background:transparent;margin:0;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);text-align:center;font-family:inherit;font-weight:var(--weight-semibold);font-size:var(--text-sm);color:var(--color-text);text-transform:capitalize;cursor:pointer;}
|
|
744
763
|
.fdy-cal__title:hover{background:var(--color-surface-2);}
|
|
745
764
|
.fdy-cal__title:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
746
|
-
/* Month grid — 3×4 over the same track width as the 7-column day grid, so the panel does not resize
|
|
747
|
-
when you drill and the pointer does not have to travel. */
|
|
748
|
-
.fdy-cal__grid--months{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
|
|
749
765
|
.fdy-cal__month{display:inline-flex;align-items:center;justify-content:center;height:2.25rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-family:inherit;font-size:var(--text-sm);text-transform:capitalize;cursor:pointer;}
|
|
750
766
|
.fdy-cal__month:hover{background:var(--color-surface-2);}
|
|
751
767
|
.fdy-cal__month:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
@@ -756,6 +772,13 @@ a { color: var(--color-primary); }
|
|
|
756
772
|
.fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
757
773
|
.fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
758
774
|
.fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
|
|
775
|
+
/* Month grid — 3×4 over the same track width as the 7-column day grid, so the panel does not resize
|
|
776
|
+
when you drill and the pointer does not have to travel.
|
|
777
|
+
|
|
778
|
+
AFTER `.fdy-cal__grid`, and that is the whole rule: the element carries both classes, they weigh
|
|
779
|
+
the same, so source order decides. Declared earlier, the modifier lost to the base it modifies and
|
|
780
|
+
twelve months rendered seven across — the layout the comment above described, never once applied. */
|
|
781
|
+
.fdy-cal__grid--months{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
|
|
759
782
|
.fdy-cal__dow{display:flex;align-items:center;justify-content:center;height:1.75rem;font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-transform:capitalize;}
|
|
760
783
|
.fdy-cal__day{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-size:var(--text-sm);font-variant-numeric:tabular-nums;cursor:pointer;}
|
|
761
784
|
.fdy-cal__day:hover{background:var(--color-surface-2);}
|
|
@@ -836,7 +859,7 @@ a { color: var(--color-primary); }
|
|
|
836
859
|
.fdy-dropzone__icon{color:var(--color-text-subtle);}
|
|
837
860
|
.fdy-dropzone.is-dragover .fdy-dropzone__icon{color:var(--color-primary);}
|
|
838
861
|
.fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
|
|
839
|
-
.fdy-dropzone__title{font-weight:var(--weight-medium);color:var(--color-text);}
|
|
862
|
+
.fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
|
|
840
863
|
.fdy-dropzone__hint{font-size:var(--text-xs);}
|
|
841
864
|
/* Wide (horizontal) layout — icon beside the copy instead of stacked, for full-width
|
|
842
865
|
* sections. Wrap the title + hint in .fdy-dropzone__text. */
|
|
@@ -1023,7 +1046,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1023
1046
|
.fdy-list__row--button:disabled:hover,.fdy-list__row--button[aria-disabled="true"]:hover,.fdy-list__row--interactive[aria-disabled="true"]:hover{background:none;}
|
|
1024
1047
|
/* Row internals: a title/meta stack that truncates, and a trailing slot pinned right. */
|
|
1025
1048
|
.fdy-list__main{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;flex:1;}
|
|
1026
|
-
.fdy-list__title{font-weight:var(--weight-medium);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1049
|
+
.fdy-list__title{font-family:var(--font-body);font-size:var(--text-base);font-weight:var(--weight-medium);letter-spacing:normal;color:var(--color-text);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1027
1050
|
.fdy-list__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
|
|
1028
1051
|
.fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
|
|
1029
1052
|
/* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */
|
|
@@ -1180,7 +1203,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1180
1203
|
.fdy-state{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-2);padding:var(--space-10) var(--space-6);}
|
|
1181
1204
|
.fdy-state__icon{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;color:var(--color-text-subtle);}
|
|
1182
1205
|
.fdy-state__icon svg{width:100%;height:100%;}
|
|
1183
|
-
.fdy-state__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
1206
|
+
.fdy-state__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);margin:0;}
|
|
1184
1207
|
.fdy-state__text{font-size:var(--text-sm);color:var(--color-text-muted);max-width:24rem;line-height:var(--leading-normal);}
|
|
1185
1208
|
.fdy-state__actions{margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap;justify-content:center;}
|
|
1186
1209
|
.fdy-state--danger .fdy-state__icon{color:var(--color-danger-strong);}
|
|
@@ -1260,6 +1283,18 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1260
1283
|
.fdy-table-toolbar__count{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;}
|
|
1261
1284
|
.fdy-table-footer{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-top:var(--bw) solid var(--color-border);}
|
|
1262
1285
|
.fdy-table-footer__info{font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
1286
|
+
/* Rows per page. `margin-left:auto` eats the free space, so the size control and the pager travel
|
|
1287
|
+
together on the right while the range stays left — no wrapper element, which is what keeps the
|
|
1288
|
+
footer's DOM byte-identical for the tables that don't offer one (#008). The control is the kit's
|
|
1289
|
+
own `.fdy-combo`, never a native <select>: an OS menu is unthemeable, and on macOS it drops a
|
|
1290
|
+
dark panel into a light page — which is the reason FdyCombo exists at all. Sized to its content:
|
|
1291
|
+
`.fdy-combo` is display:block at max-width 22rem, which in a footer is a bar-wide box for two
|
|
1292
|
+
digits. Its HEIGHT is left alone on purpose — `--control-h`, the same as every other control on
|
|
1293
|
+
the page. It briefly carried 2.25rem to match the pager links beside it, which made the one
|
|
1294
|
+
combobox in a compact app 4px taller than all the others; a pager link is a nav button, and the
|
|
1295
|
+
thing this must agree with is the form controls. */
|
|
1296
|
+
.fdy-table-footer__size{display:inline-flex;align-items:center;gap:var(--space-2);margin-left:auto;font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
1297
|
+
.fdy-table-footer__size .fdy-combo{width:5.5rem;}
|
|
1263
1298
|
|
|
1264
1299
|
/* Column filter — funnel button injected (enhancer) or rendered (FdyTable) into a filterable <th> */
|
|
1265
1300
|
.fdy-table__filterwrap{display:inline-flex;align-items:center;vertical-align:middle;}
|
|
@@ -1351,7 +1386,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1351
1386
|
.fdy-toast--danger .fdy-toast__accent{background:var(--color-danger);}
|
|
1352
1387
|
.fdy-toast--info .fdy-toast__accent{background:var(--color-info);}
|
|
1353
1388
|
.fdy-toast__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);}
|
|
1354
|
-
.fdy-toast__title{font-weight:var(--weight-semibold);}
|
|
1389
|
+
.fdy-toast__title{font-size:inherit;font-weight:var(--weight-semibold);margin:0;}
|
|
1355
1390
|
.fdy-toast__text{opacity:.88;line-height:var(--leading-normal);}
|
|
1356
1391
|
.fdy-toast__close{flex:none;border:0;background:transparent;color:var(--color-inverse-text);opacity:.7;cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
|
|
1357
1392
|
.fdy-toast__close:hover{opacity:1;}
|
package/dist/freeday.js
CHANGED
|
@@ -2871,7 +2871,8 @@
|
|
|
2871
2871
|
* a `<table class="fdy-table">` whose sortable headers hold
|
|
2872
2872
|
* `<button class="fdy-table__sortbtn" data-fdy-sort[="number"]>`, optional
|
|
2873
2873
|
* selection checkboxes (`[data-fdy-select-all]`, `[data-fdy-row-select]`), and a
|
|
2874
|
-
* `.fdy-table-footer` (with `[data-fdy-table-info]`, `[data-fdy-table-pagination]
|
|
2874
|
+
* `.fdy-table-footer` (with `[data-fdy-table-info]`, `[data-fdy-table-pagination]`, and an
|
|
2875
|
+
* optional `<select data-fdy-table-page-size>` whose options are row counts).
|
|
2875
2876
|
* Numeric columns should give each cell a `data-sort-value`.
|
|
2876
2877
|
*
|
|
2877
2878
|
* Column filters: mark a header `<th data-fdy-filter="text|enum|number">` and a funnel
|
|
@@ -2907,6 +2908,7 @@
|
|
|
2907
2908
|
var countEl = root.querySelector('[data-fdy-table-count]');
|
|
2908
2909
|
var infoEl = root.querySelector('[data-fdy-table-info]');
|
|
2909
2910
|
var pagerEl = root.querySelector('[data-fdy-table-pagination]');
|
|
2911
|
+
var pageSizeEl = root.querySelector('[data-fdy-table-page-size]');
|
|
2910
2912
|
var selectAll = root.querySelector('[data-fdy-select-all]');
|
|
2911
2913
|
var pageSize = parseInt(root.getAttribute('data-page-size'), 10) || 0; // 0 = no pagination
|
|
2912
2914
|
|
|
@@ -3303,6 +3305,27 @@
|
|
|
3303
3305
|
updateBulk();
|
|
3304
3306
|
});
|
|
3305
3307
|
|
|
3308
|
+
/* Rows per page. The select is app-authored markup (its options ARE the offer), so this only
|
|
3309
|
+
* wires it: adopt whatever it currently shows, and on a change keep the reader on the row they
|
|
3310
|
+
* were looking at rather than sending them back to page 1. */
|
|
3311
|
+
if (pageSizeEl) {
|
|
3312
|
+
var chosen = parseInt(pageSizeEl.value, 10);
|
|
3313
|
+
if (chosen > 0) pageSize = chosen;
|
|
3314
|
+
var onPageSize = function (e) {
|
|
3315
|
+
var raw = (e && e.detail && e.detail.value != null) ? e.detail.value : pageSizeEl.value;
|
|
3316
|
+
var next = parseInt(raw, 10);
|
|
3317
|
+
if (!(next > 0) || next === pageSize) return;
|
|
3318
|
+
var firstRow = (page - 1) * pageSize;
|
|
3319
|
+
pageSize = next;
|
|
3320
|
+
page = Math.floor(firstRow / next) + 1;
|
|
3321
|
+
render();
|
|
3322
|
+
};
|
|
3323
|
+
/* Both kinds: a native <select> fires `change`, and `.fdy-combo` (freeday-select.js) fires
|
|
3324
|
+
* `fdy-change` with the value in `detail`. A raw page may use either. */
|
|
3325
|
+
pageSizeEl.addEventListener('change', onPageSize);
|
|
3326
|
+
pageSizeEl.addEventListener('fdy-change', onPageSize);
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3306
3329
|
if (bulkClear) bulkClear.addEventListener('click', clearSelection);
|
|
3307
3330
|
|
|
3308
3331
|
// Dismiss an open filter popover on outside click or Escape.
|
package/docs/agent-onboarding.md
CHANGED
|
@@ -122,6 +122,8 @@ Recent additions most likely to replace something an app hand-rolled (all detail
|
|
|
122
122
|
| `.fdy-text-warning` / `-danger` / `-success` | a caption class on a line that should stand out |
|
|
123
123
|
| `.fdy-icon` | your own `width:1em;height:1em` rule, once per project |
|
|
124
124
|
| `data-density="comfortable"` | restating the default spacing tokens on a wrapper |
|
|
125
|
+
| `pageSizes` on `FdyTable` | a rows-per-page control bolted above the table, away from the pager |
|
|
126
|
+
| `FdyTableFooter` | rebuilding the range + pager to add a size control, on a responsive list |
|
|
125
127
|
| the calendar's month grid (press the title) | clicking "previous month" thirty times |
|
|
126
128
|
|
|
127
129
|
## 3. Starting a new screen
|
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.34.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.
|