@milktop/react-ui 0.2.3 → 0.2.5
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/dist/command/Command.d.ts +4 -2
- package/dist/command/Command.js +12 -5
- package/dist/table/Table.d.ts +4 -2
- package/dist/table/Table.js +12 -5
- package/dist/table/table.css +2 -0
- package/package.json +1 -1
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
* the item is listed under), `icon`, `description`, `shortcut` text,
|
|
12
12
|
* `keywords` (extra words to match) and `disabled`. An item with `href`
|
|
13
13
|
* goes there when picked
|
|
14
|
-
* - `load`: optional async function(query) returning items, for server
|
|
15
|
-
* search (debounced by `debounce` ms, `loadingText` meanwhile)
|
|
14
|
+
* - `load`: optional async function(query) returning more items, for server
|
|
15
|
+
* search (debounced by `debounce` ms, `loadingText` meanwhile). They're
|
|
16
|
+
* listed after the matching `items`, which filter as you type while the
|
|
17
|
+
* search runs
|
|
16
18
|
* - `onSelect`: called with the item's `value` (or the item, if it has none)
|
|
17
19
|
* - `trigger`: the element that opens it (it gets the trigger's props)
|
|
18
20
|
* - `open`, `defaultOpen`; `onOpenChange`: called with the new open state
|
package/dist/command/Command.js
CHANGED
|
@@ -12,8 +12,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
* the item is listed under), `icon`, `description`, `shortcut` text,
|
|
13
13
|
* `keywords` (extra words to match) and `disabled`. An item with `href`
|
|
14
14
|
* goes there when picked
|
|
15
|
-
* - `load`: optional async function(query) returning items, for server
|
|
16
|
-
* search (debounced by `debounce` ms, `loadingText` meanwhile)
|
|
15
|
+
* - `load`: optional async function(query) returning more items, for server
|
|
16
|
+
* search (debounced by `debounce` ms, `loadingText` meanwhile). They're
|
|
17
|
+
* listed after the matching `items`, which filter as you type while the
|
|
18
|
+
* search runs
|
|
17
19
|
* - `onSelect`: called with the item's `value` (or the item, if it has none)
|
|
18
20
|
* - `trigger`: the element that opens it (it gets the trigger's props)
|
|
19
21
|
* - `open`, `defaultOpen`; `onOpenChange`: called with the new open state
|
|
@@ -41,9 +43,11 @@ export function formatHotkey(key = 'k') {
|
|
|
41
43
|
return mac ? `⌘${key.toUpperCase()}` : `Ctrl ${key.toUpperCase()}`;
|
|
42
44
|
}
|
|
43
45
|
const keyOf = (item) => String(item.value ?? item.href ?? item.label);
|
|
46
|
+
// Lowercase without accents, so "tache" finds "Tâches"
|
|
47
|
+
const fold = (text) => text.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase();
|
|
44
48
|
// Every word typed must appear in the label, description, group or keywords.
|
|
45
49
|
function matches(item, words) {
|
|
46
|
-
const text = [item.label, item.description, item.group, ...(item.keywords ?? [])].join(' ')
|
|
50
|
+
const text = fold([item.label, item.description, item.group, ...(item.keywords ?? [])].join(' '));
|
|
47
51
|
return words.every((word) => text.includes(word));
|
|
48
52
|
}
|
|
49
53
|
function go(href) {
|
|
@@ -68,8 +72,11 @@ export function Command({ items = [], load = null, debounce = 200, onSelect, tri
|
|
|
68
72
|
const listEl = useRef(null);
|
|
69
73
|
const ticket = useRef(0);
|
|
70
74
|
const timer = useRef(undefined);
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
// The items filter at once; loaded results from the last search stay
|
|
76
|
+
// below them until the new ones arrive.
|
|
77
|
+
const words = fold(query).split(/\s+/).filter(Boolean);
|
|
78
|
+
const local = words.length ? items.filter((item) => matches(item, words)) : items;
|
|
79
|
+
const found = load ? [...local, ...loaded] : local;
|
|
73
80
|
// Results in group order (by first appearance), so the list reads and the
|
|
74
81
|
// arrow keys move the same way.
|
|
75
82
|
const groups = [...new Set(found.map((item) => item.group ?? ''))]
|
package/dist/table/Table.d.ts
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* - `selectable`: a checkbox per row and a select-all; `selected`,
|
|
24
24
|
* `defaultSelected`: the selected rows' keys; `onSelectedChange`. Shift-click
|
|
25
25
|
* a row's checkbox to tick (or clear) the rows between it and the last one
|
|
26
|
-
* clicked
|
|
26
|
+
* clicked. Once a row is ticked, clicking a row ticks (or clears) it too,
|
|
27
|
+
* Shift a run, rather than calling `onRowClick`
|
|
27
28
|
* - `loading`: skeleton rows while there are none yet (`loadingRows`, 5),
|
|
28
29
|
* else dims the rows
|
|
29
30
|
* - `emptyText`, or `empty` (markup), for no rows
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
* - `label`: a caption for assistive tech
|
|
32
33
|
* - `size`: 'sm', 'md' (default) or 'lg' (roomier rows)
|
|
33
34
|
* - `flush`: no border or rounding, to sit edge to edge in a card
|
|
34
|
-
* - `onRowClick`: called with the row when a row is clicked (outside its
|
|
35
|
+
* - `onRowClick`: called with the row when a row is clicked (outside its
|
|
36
|
+
* controls), unless rows are ticked
|
|
35
37
|
*
|
|
36
38
|
* Or write the <table> yourself as its content, for the look alone (no
|
|
37
39
|
* sorting, selection or loading states); `data-align` on cells works too.
|
package/dist/table/Table.js
CHANGED
|
@@ -24,7 +24,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
24
24
|
* - `selectable`: a checkbox per row and a select-all; `selected`,
|
|
25
25
|
* `defaultSelected`: the selected rows' keys; `onSelectedChange`. Shift-click
|
|
26
26
|
* a row's checkbox to tick (or clear) the rows between it and the last one
|
|
27
|
-
* clicked
|
|
27
|
+
* clicked. Once a row is ticked, clicking a row ticks (or clears) it too,
|
|
28
|
+
* Shift a run, rather than calling `onRowClick`
|
|
28
29
|
* - `loading`: skeleton rows while there are none yet (`loadingRows`, 5),
|
|
29
30
|
* else dims the rows
|
|
30
31
|
* - `emptyText`, or `empty` (markup), for no rows
|
|
@@ -32,7 +33,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
32
33
|
* - `label`: a caption for assistive tech
|
|
33
34
|
* - `size`: 'sm', 'md' (default) or 'lg' (roomier rows)
|
|
34
35
|
* - `flush`: no border or rounding, to sit edge to edge in a card
|
|
35
|
-
* - `onRowClick`: called with the row when a row is clicked (outside its
|
|
36
|
+
* - `onRowClick`: called with the row when a row is clicked (outside its
|
|
37
|
+
* controls), unless rows are ticked
|
|
36
38
|
*
|
|
37
39
|
* Or write the <table> yourself as its content, for the look alone (no
|
|
38
40
|
* sorting, selection or loading states); `data-align` on cells works too.
|
|
@@ -97,6 +99,8 @@ export function Table({ columns = [], rows = [], rowKey = 'id', selectable = fal
|
|
|
97
99
|
const isSelected = (row) => selected.includes(keyOf(row));
|
|
98
100
|
const selectedCount = rows.filter(isSelected).length;
|
|
99
101
|
const allState = selectedCount === 0 ? false : selectedCount === rows.length ? true : 'indeterminate';
|
|
102
|
+
// Once a row is ticked, a click on a row ticks it rather than opening it.
|
|
103
|
+
const selecting = selectable && selected.length > 0;
|
|
100
104
|
// Change events don't carry the Shift key, so it's noted on the way in;
|
|
101
105
|
// Shift+mousedown would also select the text between.
|
|
102
106
|
const shift = useRef(false);
|
|
@@ -104,7 +108,7 @@ export function Table({ columns = [], rows = [], rowKey = 'id', selectable = fal
|
|
|
104
108
|
function noteShift(e) {
|
|
105
109
|
const event = e.nativeEvent;
|
|
106
110
|
shift.current = event.shiftKey;
|
|
107
|
-
if (event.shiftKey && e.type === 'mousedown' && e.target.closest('[data-part=select-cell]'))
|
|
111
|
+
if (event.shiftKey && e.type === 'mousedown' && (selecting || e.target.closest('[data-part=select-cell]')))
|
|
108
112
|
e.preventDefault();
|
|
109
113
|
}
|
|
110
114
|
function toggleRow(row, on) {
|
|
@@ -131,10 +135,13 @@ export function Table({ columns = [], rows = [], rowKey = 'id', selectable = fal
|
|
|
131
135
|
const control = e.target.closest('a, button, input, label, select, textarea, [data-scope]:not([data-scope=table])');
|
|
132
136
|
if (control && e.currentTarget.contains(control))
|
|
133
137
|
return;
|
|
134
|
-
|
|
138
|
+
if (selecting)
|
|
139
|
+
toggleRow(row, !isSelected(row));
|
|
140
|
+
else
|
|
141
|
+
onRowClick?.(row);
|
|
135
142
|
}
|
|
136
143
|
const columnCount = columns.length + (selectable ? 1 : 0);
|
|
137
|
-
return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, "data-flush": flush || undefined, "data-loading": (loading && rows.length > 0) || undefined, children: _jsx("div", { ...part('scroll'), "data-sticky": maxHeight ? '' : undefined, style: maxHeight ? { maxHeight } : undefined, children: !columns.length ? children : (_jsxs("table", { ...part('table'), "aria-busy": loading, children: [label && _jsx("caption", { ...part('caption'), children: label }), _jsx("thead", { children: _jsxs("tr", { children: [selectable && (_jsx("th", { ...part('select-cell'), scope: "col", children: _jsx(Checkbox, { label: t.table.selectAll, labelHidden: true, disabled: !rows.length, checked: allState, onCheckedChange: (on) => toggleAll(on) }) })), columns.map((column) => (_jsx("th", { scope: "col", "data-align": column.align ?? 'start', "aria-sort": ariaSort(column), style: column.width ? { width: column.width } : undefined, children: column.sortable
|
|
144
|
+
return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, "data-flush": flush || undefined, "data-selecting": selecting || undefined, "data-loading": (loading && rows.length > 0) || undefined, children: _jsx("div", { ...part('scroll'), "data-sticky": maxHeight ? '' : undefined, style: maxHeight ? { maxHeight } : undefined, children: !columns.length ? children : (_jsxs("table", { ...part('table'), "aria-busy": loading, children: [label && _jsx("caption", { ...part('caption'), children: label }), _jsx("thead", { children: _jsxs("tr", { children: [selectable && (_jsx("th", { ...part('select-cell'), scope: "col", children: _jsx(Checkbox, { label: t.table.selectAll, labelHidden: true, disabled: !rows.length, checked: allState, onCheckedChange: (on) => toggleAll(on) }) })), columns.map((column) => (_jsx("th", { scope: "col", "data-align": column.align ?? 'start', "aria-sort": ariaSort(column), style: column.width ? { width: column.width } : undefined, children: column.sortable
|
|
138
145
|
? _jsxs("button", { ...part('sort'), type: "button", "data-dir": sortDir(column) ?? undefined, onClick: () => toggleSort(column), children: [_jsx("span", { children: column.label }), _jsx("span", { ...part('sort-icon'), children: _jsx("iconify-icon", { icon: sortDir(column) === 'asc' ? 'lucide:chevron-up'
|
|
139
146
|
: sortDir(column) === 'desc' ? 'lucide:chevron-down' : 'lucide:chevrons-up-down' }) })] })
|
|
140
147
|
: column.label }, column.key)))] }) }), _jsx("tbody", { onPointerDownCapture: noteShift, onMouseDownCapture: noteShift, onKeyDownCapture: noteShift, children: loading && !rows.length
|
package/dist/table/table.css
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
& tbody tr:last-child td { border-bottom: none; }
|
|
24
24
|
& tbody tr:hover { background: color-mix(in srgb, var(--ui-hover) 60%, transparent); }
|
|
25
25
|
& tbody tr[data-selected] { background: color-mix(in srgb, var(--ui-accent-soft) 45%, transparent); }
|
|
26
|
+
/* While rows are ticked, a click on a row ticks it. */
|
|
27
|
+
[data-selecting] > & tbody tr { cursor: pointer; }
|
|
26
28
|
/* Outer cells line up with a card's padding. */
|
|
27
29
|
[data-flush] > & :is(th, td):first-child { padding-left: 1.25rem; }
|
|
28
30
|
[data-flush] > & :is(th, td):last-child { padding-right: 1.25rem; } }
|