@bunnix/components 0.9.0 → 0.9.2
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/@types/index.d.ts +134 -30
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/components/AccordionGroup.mjs +2 -1
- package/src/components/Badge.mjs +18 -4
- package/src/components/Button.mjs +7 -9
- package/src/components/Card.mjs +37 -0
- package/src/components/Checkbox.mjs +5 -7
- package/src/components/CodeBlock.mjs +31 -0
- package/src/components/ComboBox.mjs +22 -14
- package/src/components/Container.mjs +8 -10
- package/src/components/DatePicker.mjs +13 -15
- package/src/components/Dialog.mjs +35 -4
- package/src/components/DropdownMenu.mjs +16 -14
- package/src/components/HStack.mjs +11 -3
- package/src/components/Icon.mjs +9 -5
- package/src/components/InputField.mjs +12 -4
- package/src/components/NavigationBar.mjs +55 -25
- package/src/components/PageHeader.mjs +11 -8
- package/src/components/PageSection.mjs +20 -10
- package/src/components/PopoverMenu.mjs +94 -50
- package/src/components/RadioCheckbox.mjs +5 -7
- package/src/components/SearchBox.mjs +12 -21
- package/src/components/Sidebar.mjs +142 -67
- package/src/components/Table.mjs +145 -96
- package/src/components/Text.mjs +52 -21
- package/src/components/TimePicker.mjs +13 -15
- package/src/components/ToastNotification.mjs +16 -13
- package/src/components/ToggleSwitch.mjs +5 -7
- package/src/components/VStack.mjs +7 -6
- package/src/index.mjs +2 -0
- package/src/styles/buttons.css +8 -0
- package/src/styles/colors.css +8 -0
- package/src/styles/controls.css +61 -0
- package/src/styles/layout.css +64 -5
- package/src/styles/media.css +11 -0
- package/src/styles/menu.css +39 -21
- package/src/styles/table.css +2 -2
- package/src/styles/typography.css +25 -0
- package/src/styles/variables.css +3 -0
- package/src/utils/iconUtils.mjs +10 -0
- package/src/utils/sizeUtils.mjs +87 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Bunnix from "@bunnix/core";
|
|
2
|
+
import { clampSize, toSizeToken } from "../utils/sizeUtils.mjs";
|
|
2
3
|
const { label, input, span } = Bunnix;
|
|
3
4
|
|
|
4
5
|
export default function RadioCheckbox({
|
|
@@ -10,14 +11,11 @@ export default function RadioCheckbox({
|
|
|
10
11
|
class: className = "",
|
|
11
12
|
...inputProps
|
|
12
13
|
}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (value === "sm") return "sm";
|
|
16
|
-
if (value === "lg" || value === "xl") return value;
|
|
17
|
-
return value;
|
|
18
|
-
};
|
|
14
|
+
// RadioCheckbox supports all sizes
|
|
15
|
+
const normalizeSize = (value) => clampSize(value, ["xsmall", "small", "regular", "large", "xlarge"], "regular");
|
|
19
16
|
const normalizedSize = normalizeSize(size);
|
|
20
|
-
const
|
|
17
|
+
const sizeToken = toSizeToken(normalizedSize);
|
|
18
|
+
const sizeClass = sizeToken === "xl" ? "checkbox-xl" : sizeToken === "lg" ? "checkbox-lg" : "";
|
|
21
19
|
const nativeChange = onChange ?? inputProps.change;
|
|
22
20
|
const checkHandler = onCheck ?? check;
|
|
23
21
|
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import Bunnix, { ForEach, useEffect, useMemo, useRef, useState } from "@bunnix/core";
|
|
2
|
+
import { clampSize, toSizeToken } from "../utils/sizeUtils.mjs";
|
|
2
3
|
import InputField from "./InputField.mjs";
|
|
3
4
|
import Icon from "./Icon.mjs";
|
|
4
5
|
|
|
5
|
-
const sizeClassMap = {
|
|
6
|
-
sm: "",
|
|
7
|
-
md: "",
|
|
8
|
-
lg: "input-lg",
|
|
9
|
-
xl: "input-xl"
|
|
10
|
-
};
|
|
11
|
-
|
|
12
6
|
const { div, button, span } = Bunnix;
|
|
13
7
|
|
|
14
8
|
export default function SearchBox({
|
|
@@ -24,14 +18,11 @@ export default function SearchBox({
|
|
|
24
18
|
select,
|
|
25
19
|
...rest
|
|
26
20
|
} = {}) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (value === "sm") return "sm";
|
|
30
|
-
if (value === "lg" || value === "xl") return value;
|
|
31
|
-
return value;
|
|
32
|
-
};
|
|
21
|
+
// SearchBox supports all sizes
|
|
22
|
+
const normalizeSize = (value) => clampSize(value, ["xsmall", "small", "regular", "large", "xlarge"], "regular");
|
|
33
23
|
const normalizedSize = normalizeSize(size);
|
|
34
|
-
const
|
|
24
|
+
const sizeToken = toSizeToken(normalizedSize);
|
|
25
|
+
const sizeClass = sizeToken === "xl" ? "input-xl" : sizeToken === "lg" ? "input-lg" : "";
|
|
35
26
|
const variantClass = variant === "rounded" ? "rounded-full" : "";
|
|
36
27
|
const combinedClass = `${sizeClass} ${variantClass} ${className}`.trim();
|
|
37
28
|
|
|
@@ -122,13 +113,13 @@ export default function SearchBox({
|
|
|
122
113
|
}
|
|
123
114
|
};
|
|
124
115
|
|
|
125
|
-
const itemSizeClass =
|
|
126
|
-
const iconSizeValue = normalizedSize === "
|
|
127
|
-
? "
|
|
128
|
-
: normalizedSize === "
|
|
129
|
-
? "
|
|
130
|
-
: normalizedSize === "
|
|
131
|
-
? "
|
|
116
|
+
const itemSizeClass = sizeToken === "lg" ? "btn-lg" : sizeToken === "xl" ? "btn-xl" : "";
|
|
117
|
+
const iconSizeValue = normalizedSize === "small"
|
|
118
|
+
? "small"
|
|
119
|
+
: normalizedSize === "large"
|
|
120
|
+
? "large"
|
|
121
|
+
: normalizedSize === "xlarge"
|
|
122
|
+
? "xlarge"
|
|
132
123
|
: undefined;
|
|
133
124
|
const hasResults = indexedData.map((list) => (list || []).length > 0);
|
|
134
125
|
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import Bunnix, { useMemo, useState, ForEach, Show } from "@bunnix/core";
|
|
2
2
|
import SearchBox from "./SearchBox.mjs";
|
|
3
3
|
import Badge from "./Badge.mjs";
|
|
4
|
+
import { resolveIconClass } from "../utils/iconUtils.mjs";
|
|
4
5
|
const { div, a, span, h4, h6, hr } = Bunnix;
|
|
5
6
|
|
|
6
7
|
export default function Sidebar({
|
|
7
|
-
items
|
|
8
|
+
items,
|
|
8
9
|
selection,
|
|
9
|
-
onSelect,
|
|
10
10
|
onItemSelect,
|
|
11
11
|
searchable = false,
|
|
12
|
-
searchProps = {}
|
|
12
|
+
searchProps = {},
|
|
13
|
+
leading,
|
|
14
|
+
trailing,
|
|
15
|
+
class: className = "",
|
|
13
16
|
} = {}) {
|
|
14
|
-
const
|
|
17
|
+
const selectionState =
|
|
18
|
+
selection &&
|
|
19
|
+
typeof selection.map === "function" &&
|
|
20
|
+
typeof selection.get === "function" &&
|
|
21
|
+
typeof selection.set === "function"
|
|
22
|
+
? selection
|
|
23
|
+
: null;
|
|
24
|
+
const selected = selectionState ?? useState(selection ?? "home");
|
|
15
25
|
const searchValue = useState("");
|
|
16
26
|
|
|
27
|
+
const resolveItems = (value) => {
|
|
28
|
+
const resolved =
|
|
29
|
+
value && typeof value.get === "function" ? value.get() : value;
|
|
30
|
+
return Array.isArray(resolved) ? resolved : [];
|
|
31
|
+
};
|
|
32
|
+
|
|
17
33
|
// Initialize expanded state from items' isExpanded property
|
|
18
|
-
const initialExpanded = items.reduce((acc, item) => {
|
|
34
|
+
const initialExpanded = resolveItems(items).reduce((acc, item) => {
|
|
19
35
|
if (item.children && item.isExpanded) {
|
|
20
36
|
acc[item.id] = true;
|
|
21
37
|
}
|
|
@@ -32,7 +48,6 @@ export default function Sidebar({
|
|
|
32
48
|
window.location.hash = target;
|
|
33
49
|
}
|
|
34
50
|
}
|
|
35
|
-
if (onSelect) onSelect(id);
|
|
36
51
|
if (onItemSelect) onItemSelect(id);
|
|
37
52
|
};
|
|
38
53
|
|
|
@@ -48,16 +63,22 @@ export default function Sidebar({
|
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
if (item.isHeader) {
|
|
51
|
-
return div(
|
|
52
|
-
|
|
66
|
+
return div(
|
|
67
|
+
{ class: "row-container px-base py-sm pt-md select-none sticky-top" },
|
|
68
|
+
span(
|
|
69
|
+
{
|
|
70
|
+
class:
|
|
71
|
+
"no-margin text-tertiary text-sm bold text-uppercase no-selectable",
|
|
72
|
+
},
|
|
73
|
+
item.label,
|
|
74
|
+
),
|
|
53
75
|
);
|
|
54
76
|
}
|
|
55
77
|
|
|
56
78
|
const hasChildren = item.children && item.children.length > 0;
|
|
57
|
-
const isSelected = selected.map(v => v === item.id);
|
|
58
|
-
const isExpanded = useMemo(
|
|
59
|
-
[
|
|
60
|
-
(ex, query) => (String(query ?? "").trim() ? true : !!ex[item.id])
|
|
79
|
+
const isSelected = selected.map((v) => v === item.id);
|
|
80
|
+
const isExpanded = useMemo([expanded, searchValue], (ex, query) =>
|
|
81
|
+
String(query ?? "").trim() ? true : !!ex[item.id],
|
|
61
82
|
);
|
|
62
83
|
|
|
63
84
|
const handleItemClick = (e) => {
|
|
@@ -69,53 +90,95 @@ export default function Sidebar({
|
|
|
69
90
|
};
|
|
70
91
|
|
|
71
92
|
return div({ class: "column-container" }, [
|
|
72
|
-
div(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
div(
|
|
94
|
+
{ class: `box-sm no-selectable ${isChild ? "pl-md" : ""}` },
|
|
95
|
+
div(
|
|
96
|
+
{
|
|
97
|
+
class: isSelected.map(
|
|
98
|
+
(s) => `box-control hoverable ${s ? "selected" : ""}`,
|
|
99
|
+
),
|
|
100
|
+
click: handleItemClick,
|
|
101
|
+
},
|
|
102
|
+
[
|
|
103
|
+
div(
|
|
104
|
+
{ class: "row-container items-center gap-sm no-margin w-full" },
|
|
105
|
+
[
|
|
106
|
+
item.icon
|
|
107
|
+
? (() => {
|
|
108
|
+
const resolvedIconClass = resolveIconClass(item.icon);
|
|
109
|
+
return span({
|
|
110
|
+
class: isSelected.map(
|
|
111
|
+
(s) =>
|
|
112
|
+
`icon ${resolvedIconClass} ${s ? "bg-white" : "icon-base"}`,
|
|
113
|
+
),
|
|
114
|
+
});
|
|
115
|
+
})()
|
|
116
|
+
: null,
|
|
117
|
+
h4({ class: "no-margin text-base font-inherit" }, item.label),
|
|
118
|
+
item.badge || hasChildren ? div({ class: "spacer-h" }) : null,
|
|
119
|
+
(() => {
|
|
120
|
+
if (!item.badge) return null;
|
|
121
|
+
if (
|
|
122
|
+
typeof item.badge === "string" ||
|
|
123
|
+
typeof item.badge === "number"
|
|
124
|
+
) {
|
|
125
|
+
return Badge(
|
|
126
|
+
{ tone: "accent", size: "xsmall", shape: "capsule" },
|
|
127
|
+
String(item.badge),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const value = item.badge.value;
|
|
131
|
+
if (value === undefined || value === null || value === "")
|
|
132
|
+
return null;
|
|
133
|
+
return Badge(
|
|
134
|
+
{
|
|
135
|
+
tone: item.badge.tone || "accent",
|
|
136
|
+
variant: item.badge.variant || "solid",
|
|
137
|
+
size: item.badge.size || "xsmall",
|
|
138
|
+
shape: "capsule",
|
|
139
|
+
},
|
|
140
|
+
String(value),
|
|
141
|
+
);
|
|
142
|
+
})(),
|
|
143
|
+
hasChildren &&
|
|
144
|
+
span({
|
|
145
|
+
class: isExpanded.map(
|
|
146
|
+
(ex) =>
|
|
147
|
+
`icon icon-chevron-down icon-base ml-auto transition-transform ${ex ? "rotate-180" : ""}`,
|
|
148
|
+
),
|
|
149
|
+
}),
|
|
150
|
+
],
|
|
151
|
+
),
|
|
152
|
+
],
|
|
153
|
+
),
|
|
100
154
|
),
|
|
101
|
-
hasChildren &&
|
|
102
|
-
|
|
103
|
-
|
|
155
|
+
hasChildren &&
|
|
156
|
+
Show(
|
|
157
|
+
isExpanded,
|
|
158
|
+
div(
|
|
159
|
+
{ class: "column-container py-xs" },
|
|
160
|
+
item.children.map((child) => renderItem(child, true)),
|
|
161
|
+
),
|
|
162
|
+
),
|
|
104
163
|
]);
|
|
105
164
|
};
|
|
106
165
|
|
|
107
166
|
const filterSidebarItems = (rawItems, query) => {
|
|
108
|
-
|
|
167
|
+
const list = Array.isArray(rawItems) ? rawItems : [];
|
|
168
|
+
if (!query) return list;
|
|
109
169
|
const normalized = query.trim().toLowerCase();
|
|
110
|
-
if (!normalized) return
|
|
170
|
+
if (!normalized) return list;
|
|
111
171
|
|
|
112
172
|
const filterItem = (item) => {
|
|
113
173
|
if (item.isHeader || item.isSeparator) return item;
|
|
114
174
|
const label = (item.label ?? "").toLowerCase();
|
|
115
175
|
const hasChildren = item.children && item.children.length > 0;
|
|
116
176
|
if (hasChildren) {
|
|
117
|
-
const filteredChildren = item.children
|
|
118
|
-
|
|
177
|
+
const filteredChildren = item.children
|
|
178
|
+
.map((child) => filterItem(child))
|
|
179
|
+
.filter(Boolean);
|
|
180
|
+
const matched =
|
|
181
|
+
label.includes(normalized) || filteredChildren.length > 0;
|
|
119
182
|
if (!matched) return null;
|
|
120
183
|
return { ...item, children: filteredChildren };
|
|
121
184
|
}
|
|
@@ -137,7 +200,7 @@ export default function Sidebar({
|
|
|
137
200
|
currentGroup = [];
|
|
138
201
|
};
|
|
139
202
|
|
|
140
|
-
for (const item of
|
|
203
|
+
for (const item of list) {
|
|
141
204
|
if (item.isHeader) {
|
|
142
205
|
flush();
|
|
143
206
|
currentHeader = item;
|
|
@@ -158,30 +221,42 @@ export default function Sidebar({
|
|
|
158
221
|
return result;
|
|
159
222
|
};
|
|
160
223
|
|
|
161
|
-
const filteredItems = useMemo(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
224
|
+
const filteredItems = useMemo([items, searchValue], (list, query) => {
|
|
225
|
+
return filterSidebarItems(resolveItems(list), (query ?? "").trim());
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const leadingContent = typeof leading === "function" ? leading() : leading;
|
|
229
|
+
const trailingContent =
|
|
230
|
+
typeof trailing === "function" ? trailing() : trailing;
|
|
165
231
|
|
|
166
232
|
const content = [];
|
|
167
233
|
if (searchable) {
|
|
168
|
-
content.push(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
234
|
+
content.push(
|
|
235
|
+
div(
|
|
236
|
+
{ class: "px-base py-xs" },
|
|
237
|
+
SearchBox({
|
|
238
|
+
placeholder: "Search",
|
|
239
|
+
variant: "rounded",
|
|
240
|
+
class: "w-full",
|
|
241
|
+
value: searchValue.get(),
|
|
242
|
+
onInput: (event) => {
|
|
243
|
+
const value = event?.target?.value ?? "";
|
|
244
|
+
searchValue.set(value);
|
|
245
|
+
},
|
|
246
|
+
...searchProps,
|
|
247
|
+
}),
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (leadingContent) {
|
|
252
|
+
content.push(div({ class: "px-base py-xs" }, leadingContent));
|
|
181
253
|
}
|
|
182
254
|
content.push(ForEach(filteredItems, "id", (item) => renderItem(item)));
|
|
255
|
+
if (trailingContent) {
|
|
256
|
+
content.push(div({ class: "px-base py-xs" }, trailingContent));
|
|
257
|
+
}
|
|
183
258
|
|
|
184
|
-
return div({ class:
|
|
185
|
-
div({ class: "column-container py-xs" }, content),
|
|
259
|
+
return div({ class: `sidebar ${className}` }, [
|
|
260
|
+
div({ class: "column-container py-xs w-full h-full" }, content),
|
|
186
261
|
]);
|
|
187
262
|
}
|
package/src/components/Table.mjs
CHANGED
|
@@ -51,7 +51,9 @@ const compareValues = (aValue, bValue, sortType) => {
|
|
|
51
51
|
return aDate - bDate;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
return String(aValue).localeCompare(String(bValue), undefined, {
|
|
54
|
+
return String(aValue).localeCompare(String(bValue), undefined, {
|
|
55
|
+
sensitivity: "base",
|
|
56
|
+
});
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
export default function Table({
|
|
@@ -66,47 +68,59 @@ export default function Table({
|
|
|
66
68
|
sort,
|
|
67
69
|
variant = "regular",
|
|
68
70
|
interactive = false,
|
|
69
|
-
|
|
71
|
+
hideHeaders = false,
|
|
72
|
+
class: className = "",
|
|
70
73
|
} = {}) {
|
|
71
74
|
const renderer = renderCell || cell;
|
|
72
75
|
const searchField = searchable?.field;
|
|
73
76
|
const searchText = searchable?.searchText;
|
|
74
|
-
const searchTextState =
|
|
77
|
+
const searchTextState =
|
|
78
|
+
searchText && typeof searchText.map === "function" ? searchText : null;
|
|
75
79
|
const sortableConfig = Array.isArray(sortable) ? sortable : [];
|
|
76
80
|
const initialSort = sortableConfig.find((entry) => entry.sorted);
|
|
77
81
|
const sortState = useState(
|
|
78
82
|
initialSort
|
|
79
83
|
? {
|
|
80
84
|
field: initialSort.field,
|
|
81
|
-
direction: initialSort.direction === "desc" ? "desc" : "asc"
|
|
85
|
+
direction: initialSort.direction === "desc" ? "desc" : "asc",
|
|
82
86
|
}
|
|
83
|
-
: null
|
|
87
|
+
: null,
|
|
84
88
|
);
|
|
85
89
|
const selectionEnabled = typeof selection === "function";
|
|
86
90
|
const selectedKeys = useState([]);
|
|
87
91
|
|
|
88
|
-
const variantClass =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const variantClass =
|
|
93
|
+
variant === "background"
|
|
94
|
+
? "table-bg"
|
|
95
|
+
: variant === "bordered"
|
|
96
|
+
? "table-bordered"
|
|
97
|
+
: "";
|
|
98
|
+
const interactiveClass = interactive
|
|
99
|
+
? "table-hover-rows table-interactive"
|
|
100
|
+
: "";
|
|
94
101
|
|
|
95
102
|
const filterRows = (rows, textValue) => {
|
|
96
103
|
if (!searchField || textValue == null || textValue === "") return rows;
|
|
97
104
|
const needle = String(textValue).toLowerCase();
|
|
98
105
|
return (rows || []).filter((row) => {
|
|
99
106
|
const value = row && typeof row === "object" ? row[searchField] : "";
|
|
100
|
-
return String(value ?? "")
|
|
107
|
+
return String(value ?? "")
|
|
108
|
+
.toLowerCase()
|
|
109
|
+
.includes(needle);
|
|
101
110
|
});
|
|
102
111
|
};
|
|
103
112
|
|
|
104
|
-
const resolvedSearchText = searchTextState
|
|
105
|
-
|
|
113
|
+
const resolvedSearchText = searchTextState
|
|
114
|
+
? searchTextState.get()
|
|
115
|
+
: searchText;
|
|
116
|
+
const isDataState =
|
|
117
|
+
data && typeof data.get === "function" && typeof data.map === "function";
|
|
106
118
|
|
|
107
119
|
const applySort = (rows, sortValue) => {
|
|
108
120
|
if (!sortValue || !sortValue.field) return rows;
|
|
109
|
-
const sortableEntry = sortableConfig.find(
|
|
121
|
+
const sortableEntry = sortableConfig.find(
|
|
122
|
+
(entry) => entry.field === sortValue.field,
|
|
123
|
+
);
|
|
110
124
|
if (!sortableEntry) return rows;
|
|
111
125
|
|
|
112
126
|
const direction = sortValue.direction === "desc" ? -1 : 1;
|
|
@@ -127,22 +141,28 @@ export default function Table({
|
|
|
127
141
|
|
|
128
142
|
const buildRows = (rows, textValue, sortValue) =>
|
|
129
143
|
applySort(filterRows(rows, textValue), sortValue).map((row, index) => ({
|
|
130
|
-
__key:
|
|
131
|
-
|
|
144
|
+
__key:
|
|
145
|
+
keyField && row && row[keyField] != null
|
|
146
|
+
? row[keyField]
|
|
147
|
+
: fallbackKey(row, index),
|
|
148
|
+
__row: row,
|
|
132
149
|
}));
|
|
133
150
|
|
|
134
151
|
const normalizedRows = useMemo(
|
|
135
152
|
[data, searchTextState ?? searchText, sortState],
|
|
136
|
-
(rows, textValue, sortValue) => buildRows(rows, textValue, sortValue)
|
|
153
|
+
(rows, textValue, sortValue) => buildRows(rows, textValue, sortValue),
|
|
137
154
|
);
|
|
138
155
|
|
|
139
|
-
const visibleKeysState =
|
|
140
|
-
|
|
141
|
-
|
|
156
|
+
const visibleKeysState =
|
|
157
|
+
normalizedRows && typeof normalizedRows.map === "function"
|
|
158
|
+
? normalizedRows.map((rows) => (rows || []).map((row) => row.__key))
|
|
159
|
+
: null;
|
|
142
160
|
|
|
143
161
|
const isAllSelected = visibleKeysState
|
|
144
|
-
? useMemo(
|
|
145
|
-
|
|
162
|
+
? useMemo(
|
|
163
|
+
[selectedKeys, visibleKeysState],
|
|
164
|
+
(keys, visible) =>
|
|
165
|
+
visible.length > 0 && visible.every((key) => keys.includes(key)),
|
|
146
166
|
)
|
|
147
167
|
: selectedKeys.map((keys) => keys.length > 0);
|
|
148
168
|
|
|
@@ -173,82 +193,111 @@ export default function Table({
|
|
|
173
193
|
}
|
|
174
194
|
sortState.set({
|
|
175
195
|
field,
|
|
176
|
-
direction: current.direction === "asc" ? "desc" : "asc"
|
|
196
|
+
direction: current.direction === "asc" ? "desc" : "asc",
|
|
177
197
|
});
|
|
178
198
|
};
|
|
179
199
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
...columns.map((column) => col({ style: `width: ${resolveColumnWidth(column.size)};` }))
|
|
185
|
-
].filter(Boolean)
|
|
186
|
-
),
|
|
187
|
-
thead([
|
|
188
|
-
tr(
|
|
189
|
-
[
|
|
190
|
-
selectionEnabled ? th({ class: "table-checkbox-cell" }, [
|
|
191
|
-
Checkbox({
|
|
192
|
-
class: "table-checkbox",
|
|
193
|
-
checked: isAllSelected,
|
|
194
|
-
change: handleToggleAll
|
|
195
|
-
})
|
|
196
|
-
]) : null,
|
|
197
|
-
...columns.map((column) => {
|
|
198
|
-
const sortableEntry = sortableConfig.find((entry) => entry.field === column.field);
|
|
199
|
-
if (!sortableEntry) {
|
|
200
|
-
return th(column.label ?? column.field ?? "");
|
|
201
|
-
}
|
|
202
|
-
const iconClass = sortState.map((sortValue) => {
|
|
203
|
-
const isSorted = sortValue && sortValue.field === column.field;
|
|
204
|
-
const isAsc = isSorted && sortValue.direction === "asc";
|
|
205
|
-
return `icon icon-chevron-down table-sort-icon ${isSorted ? "icon-base" : "icon-quaternary"} ${isAsc ? "rotate-180" : ""}`.trim();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
return th({
|
|
209
|
-
class: sortState.map((sortValue) => {
|
|
210
|
-
const isSorted = sortValue && sortValue.field === column.field;
|
|
211
|
-
return `table-sortable hoverable ${isSorted ? "is-sorted" : ""}`.trim();
|
|
212
|
-
}),
|
|
213
|
-
click: () => handleSort(column.field)
|
|
214
|
-
}, [
|
|
215
|
-
span({ class: "row-container items-center gap-xs w-full" }, [
|
|
216
|
-
span(column.label ?? column.field ?? ""),
|
|
217
|
-
span({ class: iconClass.map(cls => `${cls} ml-auto`.trim()) })
|
|
218
|
-
])
|
|
219
|
-
]);
|
|
220
|
-
})
|
|
221
|
-
].filter(Boolean)
|
|
222
|
-
)
|
|
223
|
-
]),
|
|
224
|
-
tbody([
|
|
225
|
-
ForEach(normalizedRows, "__key", (item, rowIndex) => {
|
|
226
|
-
const row = item.__row;
|
|
227
|
-
return tr(
|
|
200
|
+
const header = hideHeaders
|
|
201
|
+
? null
|
|
202
|
+
: thead([
|
|
203
|
+
tr(
|
|
228
204
|
[
|
|
229
|
-
selectionEnabled
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (value && typeof value.map === "function") {
|
|
245
|
-
return td(value.map((val) => span(val)));
|
|
205
|
+
selectionEnabled
|
|
206
|
+
? th({ class: "table-checkbox-cell" }, [
|
|
207
|
+
Checkbox({
|
|
208
|
+
class: "table-checkbox",
|
|
209
|
+
checked: isAllSelected,
|
|
210
|
+
change: handleToggleAll,
|
|
211
|
+
}),
|
|
212
|
+
])
|
|
213
|
+
: null,
|
|
214
|
+
...columns.map((column) => {
|
|
215
|
+
const sortableEntry = sortableConfig.find(
|
|
216
|
+
(entry) => entry.field === column.field,
|
|
217
|
+
);
|
|
218
|
+
if (!sortableEntry) {
|
|
219
|
+
return th(column.label ?? column.field ?? "");
|
|
246
220
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
221
|
+
const iconClass = sortState.map((sortValue) => {
|
|
222
|
+
const isSorted = sortValue && sortValue.field === column.field;
|
|
223
|
+
const isAsc = isSorted && sortValue.direction === "asc";
|
|
224
|
+
return `icon icon-chevron-down table-sort-icon ${isSorted ? "icon-base" : "icon-quaternary"} ${isAsc ? "rotate-180" : ""}`.trim();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return th(
|
|
228
|
+
{
|
|
229
|
+
class: sortState.map((sortValue) => {
|
|
230
|
+
const isSorted =
|
|
231
|
+
sortValue && sortValue.field === column.field;
|
|
232
|
+
return `table-sortable hoverable ${isSorted ? "is-sorted" : ""}`.trim();
|
|
233
|
+
}),
|
|
234
|
+
click: () => handleSort(column.field),
|
|
235
|
+
},
|
|
236
|
+
[
|
|
237
|
+
span({ class: "row-container items-center gap-xs w-full" }, [
|
|
238
|
+
span(column.label ?? column.field ?? ""),
|
|
239
|
+
span({
|
|
240
|
+
class: iconClass.map((cls) => `${cls} ml-auto`.trim()),
|
|
241
|
+
}),
|
|
242
|
+
]),
|
|
243
|
+
],
|
|
244
|
+
);
|
|
245
|
+
}),
|
|
246
|
+
].filter(Boolean),
|
|
247
|
+
),
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
return table(
|
|
251
|
+
{ class: `table ${variantClass} ${interactiveClass} ${className}`.trim() },
|
|
252
|
+
[
|
|
253
|
+
colgroup(
|
|
254
|
+
[
|
|
255
|
+
selectionEnabled ? col({ style: "width: 40px;" }) : null,
|
|
256
|
+
...columns.map((column) =>
|
|
257
|
+
col({ style: `width: ${resolveColumnWidth(column.size)};` }),
|
|
258
|
+
),
|
|
259
|
+
].filter(Boolean),
|
|
260
|
+
),
|
|
261
|
+
header,
|
|
262
|
+
tbody([
|
|
263
|
+
ForEach(normalizedRows, "__key", (item, rowIndex) => {
|
|
264
|
+
const row = item.__row;
|
|
265
|
+
return tr(
|
|
266
|
+
[
|
|
267
|
+
selectionEnabled
|
|
268
|
+
? td({ class: "table-checkbox-cell" }, [
|
|
269
|
+
Checkbox({
|
|
270
|
+
class: "table-checkbox",
|
|
271
|
+
checked: selectedKeys.map((keys) =>
|
|
272
|
+
keys.includes(item.__key),
|
|
273
|
+
),
|
|
274
|
+
change: () => handleToggleRow(item.__key),
|
|
275
|
+
}),
|
|
276
|
+
])
|
|
277
|
+
: null,
|
|
278
|
+
...columns.map((column, columnIndex) => {
|
|
279
|
+
if (renderer) {
|
|
280
|
+
const rendered = renderer(
|
|
281
|
+
columnIndex,
|
|
282
|
+
column.field,
|
|
283
|
+
row,
|
|
284
|
+
column,
|
|
285
|
+
);
|
|
286
|
+
if (rendered !== undefined && rendered !== null) {
|
|
287
|
+
return td(rendered);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const value =
|
|
291
|
+
row && typeof row === "object" ? row[column.field] : "";
|
|
292
|
+
if (value && typeof value.map === "function") {
|
|
293
|
+
return td(value.map((val) => span(val)));
|
|
294
|
+
}
|
|
295
|
+
return td(String(value ?? ""));
|
|
296
|
+
}),
|
|
297
|
+
].filter(Boolean),
|
|
298
|
+
);
|
|
299
|
+
}),
|
|
300
|
+
]),
|
|
301
|
+
],
|
|
302
|
+
);
|
|
254
303
|
}
|