@cahyo-dimas/freeday 1.8.0 → 1.9.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 +22 -0
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/react/components/FdyTable.tsx +32 -3
- package/adapters/vue/components/FdyTable.vue +32 -2
- package/dist/freeday.bundle.css +4 -0
- package/dist/freeday.css +4 -0
- package/package.json +1 -1
- package/src/components/table.css +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.9.0] — 2026-07-28
|
|
7
|
+
Follow-ups to 1.8.0's `FdyTable` (improvement notes #25/#26). Additive — the #25 change **widens** a
|
|
8
|
+
generic bound, so every call site that compiled before still compiles.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **`FdyTable` now accepts `interface`-typed rows.** The row generic was constrained to
|
|
12
|
+
`Record<string, unknown>`, to which a TypeScript `interface` is never assignable (interfaces get no
|
|
13
|
+
implicit index signature) — so a normally-typed DTO array failed to compile and `FdyTable` was
|
|
14
|
+
effectively unusable in a strict-TS app. Widened the bound to `object` in both adapters; the row type
|
|
15
|
+
now **infers** correctly (`cell-*` slots / `renderCell` are properly typed) and no cast is pushed
|
|
16
|
+
onto consumers. The component never indexes rows directly — all access goes through the
|
|
17
|
+
already-unconstrained core, which is unchanged.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **`FdyTable` row activation (opt-in).** `rowActivatable` makes rows focusable and emits
|
|
21
|
+
`row-activate` (Vue) / calls `onRowActivate` (React) on click, or Enter/Space while the row itself is
|
|
22
|
+
focused — a control inside a cell keeps its own event (the `target !== currentTarget` guard). Adds a
|
|
23
|
+
`rowClass(row)` hook for per-row state (e.g. a selected row) and a `.fdy-table__row--activatable`
|
|
24
|
+
class (pointer + `:focus-visible` ring; hover tint is already inherited from the base row rule).
|
|
25
|
+
Replaces the hand-rolled `<tr tabindex="0" @click @keydown>` + duplicated row CSS consumers were
|
|
26
|
+
each re-writing.
|
|
27
|
+
|
|
6
28
|
## [1.8.0] — 2026-07-28
|
|
7
29
|
Release **1.8 — framework-safe data table, modal/drawer wrappers, and a monospace utility**.
|
|
8
30
|
Non-breaking, purely additive. No existing selector, markup, or enhancer changed.
|
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.9.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.9.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`.
|
|
@@ -29,7 +29,7 @@ import { FdyTableFilter } from './FdyTableFilter';
|
|
|
29
29
|
// `onSortChange` / `onFiltersChange` / `onPageChange` for the caller to feed back into its query.
|
|
30
30
|
// Column filters (text/enum/number/date) apply live; in server mode, debounce the callback if needed.
|
|
31
31
|
|
|
32
|
-
export interface FdyTableProps<Row extends
|
|
32
|
+
export interface FdyTableProps<Row extends object> {
|
|
33
33
|
columns: ReadonlyArray<FdyTableColumn<Row>>;
|
|
34
34
|
rows: ReadonlyArray<Row>;
|
|
35
35
|
rowKey: (row: Row) => string | number;
|
|
@@ -51,9 +51,15 @@ export interface FdyTableProps<Row extends Record<string, unknown>> {
|
|
|
51
51
|
renderCell?: (column: FdyTableColumn<Row>, row: Row, value: unknown) => ReactNode;
|
|
52
52
|
toolbar?: ReactNode;
|
|
53
53
|
empty?: ReactNode;
|
|
54
|
+
/** Opt in to row activation: rows become focusable and call `onRowActivate` on click/Enter/Space. */
|
|
55
|
+
rowActivatable?: boolean;
|
|
56
|
+
/** Per-row class hook, e.g. to mark a selected row. */
|
|
57
|
+
rowClass?: (row: Row) => string | undefined;
|
|
58
|
+
/** A row was activated (click, or Enter/Space while the row itself is focused). */
|
|
59
|
+
onRowActivate?: (row: Row) => void;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
export function FdyTable<Row extends
|
|
62
|
+
export function FdyTable<Row extends object>(props: FdyTableProps<Row>): JSX.Element {
|
|
57
63
|
const serverPaged: boolean = props.page != null;
|
|
58
64
|
const sortControlled: boolean = serverPaged || props.sort !== undefined;
|
|
59
65
|
const filtersControlled: boolean = serverPaged || props.filters !== undefined;
|
|
@@ -143,6 +149,21 @@ export function FdyTable<Row extends Record<string, unknown>>(props: FdyTablePro
|
|
|
143
149
|
function alignStyle(col: FdyTableColumn<Row>): CSSProperties | undefined {
|
|
144
150
|
return col.align !== undefined ? { textAlign: col.align } : undefined;
|
|
145
151
|
}
|
|
152
|
+
function rowClassName(row: Row): string | undefined {
|
|
153
|
+
const cls: string = [props.rowClass?.(row), props.rowActivatable === true ? 'fdy-table__row--activatable' : undefined]
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join(' ');
|
|
156
|
+
return cls === '' ? undefined : cls;
|
|
157
|
+
}
|
|
158
|
+
// Enter/Space activate only when the row itself is focused — a control inside a cell keeps its own
|
|
159
|
+
// event (the `event.target !== event.currentTarget` guard). Click relies on inner controls calling
|
|
160
|
+
// stopPropagation, matching the pattern consumers hand-roll today.
|
|
161
|
+
function onRowKeydown(e: React.KeyboardEvent<HTMLTableRowElement>, row: Row): void {
|
|
162
|
+
if (props.rowActivatable !== true || e.target !== e.currentTarget) return;
|
|
163
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
props.onRowActivate?.(row);
|
|
166
|
+
}
|
|
146
167
|
function renderCellContent(col: FdyTableColumn<Row>, row: Row): ReactNode {
|
|
147
168
|
if (props.renderCell !== undefined) {
|
|
148
169
|
const custom: ReactNode = props.renderCell(col, row, cellValue(row, col));
|
|
@@ -192,7 +213,15 @@ export function FdyTable<Row extends Record<string, unknown>>(props: FdyTablePro
|
|
|
192
213
|
</tr>
|
|
193
214
|
) : (
|
|
194
215
|
displayRows.map((row: Row): JSX.Element => (
|
|
195
|
-
<tr
|
|
216
|
+
<tr
|
|
217
|
+
key={props.rowKey(row)}
|
|
218
|
+
className={rowClassName(row)}
|
|
219
|
+
tabIndex={props.rowActivatable ? 0 : undefined}
|
|
220
|
+
onClick={(): void => {
|
|
221
|
+
if (props.rowActivatable === true) props.onRowActivate?.(row);
|
|
222
|
+
}}
|
|
223
|
+
onKeyDown={(e): void => onRowKeydown(e, row)}
|
|
224
|
+
>
|
|
196
225
|
{props.columns.map((col: FdyTableColumn<Row>): JSX.Element => (
|
|
197
226
|
<td key={col.key} className={cellClass(col)} style={alignStyle(col)}>{renderCellContent(col, row)}</td>
|
|
198
227
|
))}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup lang="ts" generic="Row extends
|
|
1
|
+
<script setup lang="ts" generic="Row extends object">
|
|
2
2
|
import { computed, ref, watch, type ComputedRef, type Ref } from 'vue';
|
|
3
3
|
import {
|
|
4
4
|
cellValue,
|
|
@@ -44,12 +44,18 @@ const props = defineProps<{
|
|
|
44
44
|
loading?: boolean;
|
|
45
45
|
emptyText?: string;
|
|
46
46
|
ariaLabel?: string;
|
|
47
|
+
/** Opt in to row activation: rows become focusable and emit `row-activate` on click/Enter/Space. */
|
|
48
|
+
rowActivatable?: boolean;
|
|
49
|
+
/** Per-row class hook, e.g. to mark a selected row. */
|
|
50
|
+
rowClass?: (row: Row) => string | undefined;
|
|
47
51
|
}>();
|
|
48
52
|
|
|
49
53
|
const emit = defineEmits<{
|
|
50
54
|
'update:sort': [sort: FdySortState | null];
|
|
51
55
|
'update:filters': [filters: FdyFilterMap];
|
|
52
56
|
'update:page': [page: FdyPageState];
|
|
57
|
+
/** A row was activated (click, or Enter/Space while the row itself is focused). */
|
|
58
|
+
'row-activate': [row: Row];
|
|
53
59
|
}>();
|
|
54
60
|
|
|
55
61
|
const internalSort: Ref<FdySortState | null> = ref(null);
|
|
@@ -162,6 +168,22 @@ function cellClass(col: FdyTableColumn<Row>): string | undefined {
|
|
|
162
168
|
function alignStyle(col: FdyTableColumn<Row>): Record<string, string> | undefined {
|
|
163
169
|
return col.align !== undefined ? { textAlign: col.align } : undefined;
|
|
164
170
|
}
|
|
171
|
+
|
|
172
|
+
function rowClasses(row: Row): Array<string | undefined> {
|
|
173
|
+
return [props.rowClass?.(row), props.rowActivatable === true ? 'fdy-table__row--activatable' : undefined];
|
|
174
|
+
}
|
|
175
|
+
function onRowClick(row: Row): void {
|
|
176
|
+
if (props.rowActivatable === true) emit('row-activate', row);
|
|
177
|
+
}
|
|
178
|
+
// Enter/Space activate only when the row itself is focused — a control inside a cell keeps its own
|
|
179
|
+
// event (the `event.target !== event.currentTarget` guard). Click relies on inner controls calling
|
|
180
|
+
// stopPropagation, matching the pattern consumers hand-roll today.
|
|
181
|
+
function onRowKeydown(e: KeyboardEvent, row: Row): void {
|
|
182
|
+
if (props.rowActivatable !== true || e.target !== e.currentTarget) return;
|
|
183
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
emit('row-activate', row);
|
|
186
|
+
}
|
|
165
187
|
</script>
|
|
166
188
|
|
|
167
189
|
<template>
|
|
@@ -202,7 +224,15 @@ function alignStyle(col: FdyTableColumn<Row>): Record<string, string> | undefine
|
|
|
202
224
|
<slot name="empty">{{ emptyText ?? 'No data' }}</slot>
|
|
203
225
|
</td>
|
|
204
226
|
</tr>
|
|
205
|
-
<tr
|
|
227
|
+
<tr
|
|
228
|
+
v-for="row in displayRows"
|
|
229
|
+
v-else
|
|
230
|
+
:key="rowKey(row)"
|
|
231
|
+
:class="rowClasses(row)"
|
|
232
|
+
:tabindex="rowActivatable ? 0 : undefined"
|
|
233
|
+
@click="onRowClick(row)"
|
|
234
|
+
@keydown="onRowKeydown($event, row)"
|
|
235
|
+
>
|
|
206
236
|
<td v-for="col in columns" :key="col.key" :class="cellClass(col)" :style="alignStyle(col)">
|
|
207
237
|
<slot :name="`cell-${col.key}`" :row="row" :value="cellValue(row, col)">{{ cellText(row, col) }}</slot>
|
|
208
238
|
</td>
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -1219,6 +1219,10 @@ a { color: var(--color-primary); }
|
|
|
1219
1219
|
.fdy-table__num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--font-mono);}
|
|
1220
1220
|
/* Loading / empty state cell (FdyTable spans it across all columns). */
|
|
1221
1221
|
.fdy-table__state{text-align:center;color:var(--color-text-muted);padding:var(--space-6) var(--space-4);}
|
|
1222
|
+
/* Activatable row (FdyTable rowActivatable): pointer + keyboard focus ring. The hover tint already
|
|
1223
|
+
comes from the base `.fdy-table tbody tr:hover` rule, so this only adds the affordance + focus. */
|
|
1224
|
+
.fdy-table__row--activatable{cursor:pointer;}
|
|
1225
|
+
.fdy-table__row--activatable:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;}
|
|
1222
1226
|
.fdy-table th[aria-sort]{cursor:pointer;}
|
|
1223
1227
|
.fdy-table th[aria-sort="ascending"]::after{content:" ↑";color:var(--color-primary);}
|
|
1224
1228
|
.fdy-table th[aria-sort="descending"]::after{content:" ↓";color:var(--color-primary);}
|
package/dist/freeday.css
CHANGED
|
@@ -874,6 +874,10 @@ a { color: var(--color-primary); }
|
|
|
874
874
|
.fdy-table__num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--font-mono);}
|
|
875
875
|
/* Loading / empty state cell (FdyTable spans it across all columns). */
|
|
876
876
|
.fdy-table__state{text-align:center;color:var(--color-text-muted);padding:var(--space-6) var(--space-4);}
|
|
877
|
+
/* Activatable row (FdyTable rowActivatable): pointer + keyboard focus ring. The hover tint already
|
|
878
|
+
comes from the base `.fdy-table tbody tr:hover` rule, so this only adds the affordance + focus. */
|
|
879
|
+
.fdy-table__row--activatable{cursor:pointer;}
|
|
880
|
+
.fdy-table__row--activatable:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;}
|
|
877
881
|
.fdy-table th[aria-sort]{cursor:pointer;}
|
|
878
882
|
.fdy-table th[aria-sort="ascending"]::after{content:" ↑";color:var(--color-primary);}
|
|
879
883
|
.fdy-table th[aria-sort="descending"]::after{content:" ↓";color:var(--color-primary);}
|
package/package.json
CHANGED
package/src/components/table.css
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
.fdy-table__num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--font-mono);}
|
|
16
16
|
/* Loading / empty state cell (FdyTable spans it across all columns). */
|
|
17
17
|
.fdy-table__state{text-align:center;color:var(--color-text-muted);padding:var(--space-6) var(--space-4);}
|
|
18
|
+
/* Activatable row (FdyTable rowActivatable): pointer + keyboard focus ring. The hover tint already
|
|
19
|
+
comes from the base `.fdy-table tbody tr:hover` rule, so this only adds the affordance + focus. */
|
|
20
|
+
.fdy-table__row--activatable{cursor:pointer;}
|
|
21
|
+
.fdy-table__row--activatable:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;}
|
|
18
22
|
.fdy-table th[aria-sort]{cursor:pointer;}
|
|
19
23
|
.fdy-table th[aria-sort="ascending"]::after{content:" ↑";color:var(--color-primary);}
|
|
20
24
|
.fdy-table th[aria-sort="descending"]::after{content:" ↓";color:var(--color-primary);}
|