@alepha/ui 0.10.6 → 0.11.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/dist/AlephaMantineProvider-DDbIijPF.js +96 -0
- package/dist/AlephaMantineProvider-DDbIijPF.js.map +1 -0
- package/dist/AlephaMantineProvider-pOu8hOzK.js +3 -0
- package/dist/index.d.ts +560 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1341 -109
- package/dist/index.js.map +1 -1
- package/package.json +21 -12
- package/src/RootRouter.ts +8 -0
- package/src/components/Action.tsx +296 -135
- package/src/components/AlephaMantineProvider.tsx +34 -31
- package/src/components/Control.tsx +251 -236
- package/src/components/ControlDate.tsx +104 -0
- package/src/components/ControlSelect.tsx +196 -0
- package/src/components/DarkModeButton.tsx +82 -0
- package/src/components/DataTable.css +199 -0
- package/src/components/DataTable.tsx +724 -0
- package/src/components/Omnibar.tsx +77 -0
- package/src/components/Sidebar.css +217 -0
- package/src/components/Sidebar.tsx +255 -0
- package/src/components/TypeForm.tsx +158 -0
- package/src/components/dialogs/AlertDialog.tsx +13 -0
- package/src/components/dialogs/ConfirmDialog.tsx +21 -0
- package/src/components/dialogs/PromptDialog.tsx +52 -0
- package/src/hooks/useDialog.ts +15 -0
- package/src/hooks/useToast.ts +14 -0
- package/src/index.ts +54 -10
- package/src/services/DialogService.tsx +207 -0
- package/src/services/ToastService.tsx +71 -0
- package/src/utils/icons.tsx +121 -0
- package/src/utils/parseInput.ts +125 -0
- package/src/utils/string.ts +21 -0
package/dist/index.js
CHANGED
|
@@ -1,71 +1,372 @@
|
|
|
1
|
+
import { n as Omnibar_default, t as AlephaMantineProvider_default } from "./AlephaMantineProvider-DDbIijPF.js";
|
|
1
2
|
import { $module, TypeBoxError } from "@alepha/core";
|
|
2
|
-
import {
|
|
3
|
-
import "@mantine/
|
|
4
|
-
import "@mantine/
|
|
5
|
-
import "@mantine/spotlight/styles.css";
|
|
6
|
-
import "@mantine/notifications/styles.css";
|
|
7
|
-
import { useFormState } from "@alepha/react-form";
|
|
8
|
-
import { Autocomplete, Button, ColorSchemeScript, Flex, Input, MantineProvider, PasswordInput, SegmentedControl, Select, Switch, TextInput, Textarea } from "@mantine/core";
|
|
9
|
-
import { useState } from "react";
|
|
3
|
+
import { $page, AlephaReact, useActive, useAlepha, useInject, useRouter } from "@alepha/react";
|
|
4
|
+
import { modals } from "@mantine/modals";
|
|
5
|
+
import { ActionIcon, Autocomplete, Badge, Box, Button, Center, Checkbox, ColorInput, FileInput, Flex, Flex as Flex$1, Grid, Group, Input, Loader, Menu, MultiSelect, NumberInput, Pagination, Paper, PasswordInput, ScrollArea, SegmentedControl, Select, Switch, Table, TagsInput, Text, TextInput, Textarea, Tooltip, UnstyledButton, useComputedColorScheme, useMantineColorScheme } from "@mantine/core";
|
|
10
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
7
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { notifications } from "@mantine/notifications";
|
|
9
|
+
import { IconAlertTriangle, IconAt, IconCalendar, IconCheck, IconChevronDown, IconChevronRight, IconChevronUp, IconCircle, IconClock, IconColorPicker, IconColumns, IconDownload, IconFile, IconHash, IconInfoCircle, IconKey, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconRefresh, IconSearch, IconSelector, IconSun, IconToggleLeft, IconX } from "@tabler/icons-react";
|
|
10
|
+
import { useFormState } from "@alepha/react-form";
|
|
11
|
+
import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
|
|
12
|
+
import { useActive as useActive$1 } from "@alepha/react/src/hooks/useActive";
|
|
13
|
+
|
|
14
|
+
//#region src/RootRouter.ts
|
|
15
|
+
var RootRouter = class {
|
|
16
|
+
root = $page({
|
|
17
|
+
path: "/",
|
|
18
|
+
lazy: () => import("./AlephaMantineProvider-pOu8hOzK.js")
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/components/dialogs/AlertDialog.tsx
|
|
24
|
+
function AlertDialog({ options, onClose }) {
|
|
25
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text, {
|
|
26
|
+
mb: "md",
|
|
27
|
+
children: options.message
|
|
28
|
+
}), /* @__PURE__ */ jsx(Group, {
|
|
29
|
+
justify: "flex-end",
|
|
30
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
31
|
+
onClick: onClose,
|
|
32
|
+
children: options?.okLabel || "OK"
|
|
33
|
+
})
|
|
34
|
+
})] });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/components/dialogs/ConfirmDialog.tsx
|
|
39
|
+
function ConfirmDialog({ options, onConfirm }) {
|
|
40
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text, {
|
|
41
|
+
mb: "md",
|
|
42
|
+
children: options.message
|
|
43
|
+
}), /* @__PURE__ */ jsxs(Group, {
|
|
44
|
+
justify: "flex-end",
|
|
45
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
46
|
+
variant: "subtle",
|
|
47
|
+
onClick: () => onConfirm(false),
|
|
48
|
+
children: options?.cancelLabel || "Cancel"
|
|
49
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
50
|
+
color: options?.confirmColor || "blue",
|
|
51
|
+
onClick: () => onConfirm(true),
|
|
52
|
+
children: options?.confirmLabel || "Confirm"
|
|
53
|
+
})]
|
|
54
|
+
})] });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/components/dialogs/PromptDialog.tsx
|
|
59
|
+
function PromptDialog({ options, onSubmit }) {
|
|
60
|
+
const [value, setValue] = useState(options?.defaultValue || "");
|
|
61
|
+
const inputRef = useRef(null);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
inputRef.current?.focus();
|
|
64
|
+
}, []);
|
|
65
|
+
const handleSubmit = () => {
|
|
66
|
+
if (!options?.required || value.trim()) onSubmit(value);
|
|
67
|
+
};
|
|
68
|
+
const handleKeyDown = (event) => {
|
|
69
|
+
if (event.key === "Enter") handleSubmit();
|
|
70
|
+
};
|
|
71
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
72
|
+
options?.message && /* @__PURE__ */ jsx(Text, {
|
|
73
|
+
mb: "md",
|
|
74
|
+
children: options.message
|
|
75
|
+
}),
|
|
76
|
+
/* @__PURE__ */ jsx(TextInput, {
|
|
77
|
+
ref: inputRef,
|
|
78
|
+
label: options?.label,
|
|
79
|
+
placeholder: options?.placeholder,
|
|
80
|
+
value,
|
|
81
|
+
onChange: (event) => setValue(event.currentTarget.value),
|
|
82
|
+
onKeyDown: handleKeyDown,
|
|
83
|
+
required: options?.required,
|
|
84
|
+
mb: "md"
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ jsxs(Group, {
|
|
87
|
+
justify: "flex-end",
|
|
88
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
89
|
+
variant: "subtle",
|
|
90
|
+
onClick: () => onSubmit(null),
|
|
91
|
+
children: options?.cancelLabel || "Cancel"
|
|
92
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
93
|
+
onClick: handleSubmit,
|
|
94
|
+
disabled: options?.required && !value.trim(),
|
|
95
|
+
children: options?.submitLabel || "OK"
|
|
96
|
+
})]
|
|
97
|
+
})
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/services/DialogService.tsx
|
|
103
|
+
var DialogService = class {
|
|
104
|
+
options = { default: {
|
|
105
|
+
centered: true,
|
|
106
|
+
withCloseButton: true,
|
|
107
|
+
size: "md",
|
|
108
|
+
overlayProps: {
|
|
109
|
+
backgroundOpacity: .55,
|
|
110
|
+
blur: 3
|
|
111
|
+
},
|
|
112
|
+
transitionProps: {
|
|
113
|
+
transition: "pop",
|
|
114
|
+
duration: 200
|
|
115
|
+
}
|
|
116
|
+
} };
|
|
117
|
+
/**
|
|
118
|
+
* Show an alert dialog with a message
|
|
119
|
+
*/
|
|
120
|
+
alert(options) {
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const modalId = this.open({
|
|
123
|
+
...options,
|
|
124
|
+
title: options?.title || "Alert",
|
|
125
|
+
content: /* @__PURE__ */ jsx(AlertDialog, {
|
|
126
|
+
options,
|
|
127
|
+
onClose: () => {
|
|
128
|
+
this.close(modalId);
|
|
129
|
+
resolve();
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Show a confirmation dialog that returns a promise
|
|
137
|
+
*/
|
|
138
|
+
confirm(options) {
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
const modalId = this.open({
|
|
141
|
+
...options,
|
|
142
|
+
title: options?.title || "Confirm",
|
|
143
|
+
closeOnClickOutside: false,
|
|
144
|
+
closeOnEscape: false,
|
|
145
|
+
content: /* @__PURE__ */ jsx(ConfirmDialog, {
|
|
146
|
+
options,
|
|
147
|
+
onConfirm: (confirmed) => {
|
|
148
|
+
this.close(modalId);
|
|
149
|
+
resolve(confirmed);
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Show a prompt dialog to get user input
|
|
157
|
+
*/
|
|
158
|
+
prompt(options) {
|
|
159
|
+
return new Promise((resolve) => {
|
|
160
|
+
const modalId = this.open({
|
|
161
|
+
...options,
|
|
162
|
+
title: options?.title || "Input",
|
|
163
|
+
closeOnClickOutside: false,
|
|
164
|
+
closeOnEscape: false,
|
|
165
|
+
content: /* @__PURE__ */ jsx(PromptDialog, {
|
|
166
|
+
options,
|
|
167
|
+
onSubmit: (value) => {
|
|
168
|
+
this.close(modalId);
|
|
169
|
+
resolve(value);
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Open a custom dialog with provided content
|
|
177
|
+
*/
|
|
178
|
+
open(options) {
|
|
179
|
+
return modals.open({
|
|
180
|
+
...this.options.default,
|
|
181
|
+
...options,
|
|
182
|
+
children: options?.content || options?.message
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Show a JSON editor/viewer dialog
|
|
187
|
+
*/
|
|
188
|
+
json(data, options) {}
|
|
189
|
+
/**
|
|
190
|
+
* Show a form dialog for structured input
|
|
191
|
+
*/
|
|
192
|
+
form(options) {
|
|
193
|
+
return Promise.resolve(null);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Close the currently open dialog or a specific dialog by ID
|
|
197
|
+
*/
|
|
198
|
+
close(modalId) {
|
|
199
|
+
if (modalId) modals.close(modalId);
|
|
200
|
+
else modals.closeAll();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Show a loading/progress dialog with optional progress percentage
|
|
204
|
+
*/
|
|
205
|
+
loading(options) {}
|
|
206
|
+
/**
|
|
207
|
+
* Show an image viewer/gallery dialog
|
|
208
|
+
*/
|
|
209
|
+
image(src, options) {}
|
|
210
|
+
/**
|
|
211
|
+
* Show a table/data grid dialog for displaying tabular data
|
|
212
|
+
*/
|
|
213
|
+
table(data, options) {}
|
|
214
|
+
/**
|
|
215
|
+
* Show a multi-step wizard dialog
|
|
216
|
+
*/
|
|
217
|
+
wizard(steps, options) {
|
|
218
|
+
return Promise.resolve(null);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/services/ToastService.tsx
|
|
224
|
+
var ToastService = class {
|
|
225
|
+
raw = notifications;
|
|
226
|
+
options = { default: {
|
|
227
|
+
autoClose: 5e3,
|
|
228
|
+
withCloseButton: true,
|
|
229
|
+
position: "top-center"
|
|
230
|
+
} };
|
|
231
|
+
show(options) {
|
|
232
|
+
notifications.show({
|
|
233
|
+
...this.options.default,
|
|
234
|
+
...options
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
info(options) {
|
|
238
|
+
this.show({
|
|
239
|
+
color: "blue",
|
|
240
|
+
icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 20 }),
|
|
241
|
+
title: "Info",
|
|
242
|
+
message: "Information notification",
|
|
243
|
+
...options
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
success(options) {
|
|
247
|
+
this.show({
|
|
248
|
+
color: "green",
|
|
249
|
+
icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }),
|
|
250
|
+
title: "Success",
|
|
251
|
+
message: "Operation completed successfully",
|
|
252
|
+
...options
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
warning(options) {
|
|
256
|
+
this.show({
|
|
257
|
+
color: "yellow",
|
|
258
|
+
icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20 }),
|
|
259
|
+
title: "Warning",
|
|
260
|
+
message: "Please review this warning",
|
|
261
|
+
...options
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
danger(options) {
|
|
265
|
+
this.show({
|
|
266
|
+
color: "red",
|
|
267
|
+
icon: /* @__PURE__ */ jsx(IconX, { size: 20 }),
|
|
268
|
+
title: "Error",
|
|
269
|
+
message: "An error occurred",
|
|
270
|
+
...options
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
};
|
|
14
274
|
|
|
275
|
+
//#endregion
|
|
15
276
|
//#region src/components/Action.tsx
|
|
277
|
+
const renderMenuItem = (item, index) => {
|
|
278
|
+
if (item.type === "divider") return /* @__PURE__ */ jsx(Menu.Divider, {}, index);
|
|
279
|
+
if (item.type === "label") return /* @__PURE__ */ jsx(Menu.Label, { children: item.label }, index);
|
|
280
|
+
if (item.children && item.children.length > 0) return /* @__PURE__ */ jsxs(Menu, {
|
|
281
|
+
trigger: "hover",
|
|
282
|
+
position: "right-start",
|
|
283
|
+
offset: 2,
|
|
284
|
+
children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Menu.Item, {
|
|
285
|
+
leftSection: item.icon,
|
|
286
|
+
rightSection: /* @__PURE__ */ jsx(IconChevronRight, { size: 14 }),
|
|
287
|
+
children: item.label
|
|
288
|
+
}) }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: item.children.map((child, childIndex) => renderMenuItem(child, childIndex)) })]
|
|
289
|
+
}, index);
|
|
290
|
+
return /* @__PURE__ */ jsx(Menu.Item, {
|
|
291
|
+
leftSection: item.icon,
|
|
292
|
+
onClick: item.onClick,
|
|
293
|
+
color: item.color,
|
|
294
|
+
children: item.label
|
|
295
|
+
}, index);
|
|
296
|
+
};
|
|
16
297
|
const Action = (_props) => {
|
|
17
298
|
const props = {
|
|
18
299
|
variant: "subtle",
|
|
19
300
|
..._props
|
|
20
301
|
};
|
|
302
|
+
const { tooltip, menu,...restProps } = props;
|
|
21
303
|
if (props.leftSection && !props.children) {
|
|
22
|
-
|
|
23
|
-
|
|
304
|
+
restProps.className ??= "mantine-Action-iconOnly";
|
|
305
|
+
restProps.p ??= "xs";
|
|
24
306
|
}
|
|
25
307
|
if (props.textVisibleFrom) {
|
|
26
|
-
const { children, textVisibleFrom, leftSection,...rest } =
|
|
27
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex, {
|
|
308
|
+
const { children, textVisibleFrom, leftSection,...rest } = restProps;
|
|
309
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex$1, {
|
|
28
310
|
w: "100%",
|
|
29
311
|
visibleFrom: textVisibleFrom,
|
|
30
312
|
children: /* @__PURE__ */ jsx(Action, {
|
|
31
313
|
flex: 1,
|
|
32
314
|
...rest,
|
|
33
315
|
leftSection,
|
|
316
|
+
tooltip,
|
|
317
|
+
menu,
|
|
34
318
|
children
|
|
35
319
|
})
|
|
36
|
-
}), /* @__PURE__ */ jsx(Flex, {
|
|
320
|
+
}), /* @__PURE__ */ jsx(Flex$1, {
|
|
37
321
|
w: "100%",
|
|
38
322
|
hiddenFrom: textVisibleFrom,
|
|
39
323
|
children: /* @__PURE__ */ jsx(Action, {
|
|
40
324
|
px: "xs",
|
|
41
325
|
...rest,
|
|
326
|
+
tooltip,
|
|
327
|
+
menu,
|
|
42
328
|
children: leftSection
|
|
43
329
|
})
|
|
44
330
|
})] });
|
|
45
331
|
}
|
|
46
332
|
const renderAction = () => {
|
|
47
|
-
if ("href" in
|
|
48
|
-
...
|
|
49
|
-
href:
|
|
50
|
-
children:
|
|
333
|
+
if ("href" in restProps && restProps.href) return /* @__PURE__ */ jsx(ActionHref, {
|
|
334
|
+
...restProps,
|
|
335
|
+
href: restProps.href,
|
|
336
|
+
children: restProps.children
|
|
51
337
|
});
|
|
52
|
-
if ("onClick" in
|
|
53
|
-
...
|
|
54
|
-
onClick:
|
|
55
|
-
children:
|
|
338
|
+
if ("onClick" in restProps && restProps.onClick) return /* @__PURE__ */ jsx(ActionClick, {
|
|
339
|
+
...restProps,
|
|
340
|
+
onClick: restProps.onClick,
|
|
341
|
+
children: restProps.children
|
|
56
342
|
});
|
|
57
|
-
if ("form" in
|
|
58
|
-
...
|
|
59
|
-
form:
|
|
60
|
-
children:
|
|
343
|
+
if ("form" in restProps && restProps.form) return /* @__PURE__ */ jsx(ActionSubmit, {
|
|
344
|
+
...restProps,
|
|
345
|
+
form: restProps.form,
|
|
346
|
+
children: restProps.children
|
|
61
347
|
});
|
|
62
348
|
return /* @__PURE__ */ jsx(Button, {
|
|
63
|
-
...
|
|
64
|
-
children:
|
|
349
|
+
...restProps,
|
|
350
|
+
children: restProps.children
|
|
65
351
|
});
|
|
66
352
|
};
|
|
67
|
-
|
|
353
|
+
let actionElement = renderAction();
|
|
354
|
+
if (menu) actionElement = /* @__PURE__ */ jsxs(Menu, {
|
|
355
|
+
position: menu.position || "bottom-start",
|
|
356
|
+
width: menu.width || 200,
|
|
357
|
+
shadow: menu.shadow || "md",
|
|
358
|
+
children: [/* @__PURE__ */ jsx(Menu.Target, { children: actionElement }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: menu.items.map((item, index) => renderMenuItem(item, index)) })]
|
|
359
|
+
});
|
|
360
|
+
if (tooltip) return /* @__PURE__ */ jsx(Tooltip, { ...typeof tooltip === "string" ? {
|
|
361
|
+
label: tooltip,
|
|
362
|
+
children: actionElement
|
|
363
|
+
} : {
|
|
364
|
+
...tooltip,
|
|
365
|
+
children: actionElement
|
|
366
|
+
} });
|
|
367
|
+
return actionElement;
|
|
68
368
|
};
|
|
369
|
+
var Action_default = Action;
|
|
69
370
|
/**
|
|
70
371
|
* Action button that submits a form with loading and disabled state handling.
|
|
71
372
|
*/
|
|
@@ -75,7 +376,7 @@ const ActionSubmit = (props) => {
|
|
|
75
376
|
return /* @__PURE__ */ jsx(Button, {
|
|
76
377
|
...buttonProps,
|
|
77
378
|
loading: state.loading,
|
|
78
|
-
disabled: state.loading
|
|
379
|
+
disabled: state.loading,
|
|
79
380
|
type: "submit",
|
|
80
381
|
children: props.children
|
|
81
382
|
});
|
|
@@ -118,11 +419,10 @@ const ActionHref = (props) => {
|
|
|
118
419
|
href: props.href,
|
|
119
420
|
...options
|
|
120
421
|
} : { href: props.href });
|
|
121
|
-
const anchorProps = router.anchor(props.href, routerGoOptions);
|
|
122
422
|
return /* @__PURE__ */ jsx(Button, {
|
|
123
423
|
component: "a",
|
|
124
424
|
loading: isPending,
|
|
125
|
-
...
|
|
425
|
+
...router.anchor(props.href, routerGoOptions),
|
|
126
426
|
...buttonProps,
|
|
127
427
|
variant: isActive && options !== false ? "filled" : "subtle",
|
|
128
428
|
children: props.children
|
|
@@ -130,59 +430,98 @@ const ActionHref = (props) => {
|
|
|
130
430
|
};
|
|
131
431
|
|
|
132
432
|
//#endregion
|
|
133
|
-
//#region src/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
433
|
+
//#region src/utils/icons.tsx
|
|
434
|
+
/**
|
|
435
|
+
* Icon size presets following Mantine's size conventions
|
|
436
|
+
*/
|
|
437
|
+
const ICON_SIZES = {
|
|
438
|
+
xs: 12,
|
|
439
|
+
sm: 16,
|
|
440
|
+
md: 20,
|
|
441
|
+
lg: 24,
|
|
442
|
+
xl: 28
|
|
443
|
+
};
|
|
444
|
+
/**
|
|
445
|
+
* Get the default icon for an input based on its type, format, or name.
|
|
446
|
+
*/
|
|
447
|
+
const getDefaultIcon = (params) => {
|
|
448
|
+
const { type, format, name, isEnum, isArray, size = "sm" } = params;
|
|
449
|
+
const iconSize = ICON_SIZES[size];
|
|
450
|
+
if (format) switch (format) {
|
|
451
|
+
case "email": return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
|
|
452
|
+
case "url":
|
|
453
|
+
case "uri": return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
|
|
454
|
+
case "tel":
|
|
455
|
+
case "phone": return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
|
|
456
|
+
case "date": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
457
|
+
case "date-time": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
458
|
+
case "time": return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
|
|
459
|
+
case "color": return /* @__PURE__ */ jsx(IconColorPicker, { size: iconSize });
|
|
460
|
+
case "uuid": return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
|
|
461
|
+
}
|
|
462
|
+
if (name) {
|
|
463
|
+
const nameLower = name.toLowerCase();
|
|
464
|
+
if (nameLower.includes("password") || nameLower.includes("secret")) return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
|
|
465
|
+
if (nameLower.includes("email") || nameLower.includes("mail")) return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
|
|
466
|
+
if (nameLower.includes("url") || nameLower.includes("link")) return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
|
|
467
|
+
if (nameLower.includes("phone") || nameLower.includes("tel")) return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
|
|
468
|
+
if (nameLower.includes("color")) return /* @__PURE__ */ jsx(IconPalette, { size: iconSize });
|
|
469
|
+
if (nameLower.includes("file") || nameLower.includes("upload")) return /* @__PURE__ */ jsx(IconFile, { size: iconSize });
|
|
470
|
+
if (nameLower.includes("date")) return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
471
|
+
if (nameLower.includes("time")) return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
|
|
472
|
+
}
|
|
473
|
+
if (isEnum || isArray) return /* @__PURE__ */ jsx(IconSelector, { size: iconSize });
|
|
474
|
+
if (type) switch (type) {
|
|
475
|
+
case "boolean": return /* @__PURE__ */ jsx(IconToggleLeft, { size: iconSize });
|
|
476
|
+
case "number":
|
|
477
|
+
case "integer": return /* @__PURE__ */ jsx(IconHash, { size: iconSize });
|
|
478
|
+
case "array": return /* @__PURE__ */ jsx(IconList, { size: iconSize });
|
|
479
|
+
case "string": return /* @__PURE__ */ jsx(IconLetterCase, { size: iconSize });
|
|
480
|
+
}
|
|
481
|
+
return /* @__PURE__ */ jsx(IconAt, { size: iconSize });
|
|
157
482
|
};
|
|
158
483
|
|
|
159
484
|
//#endregion
|
|
160
|
-
//#region src/
|
|
485
|
+
//#region src/utils/string.ts
|
|
161
486
|
/**
|
|
162
|
-
*
|
|
487
|
+
* Capitalizes the first letter of a string.
|
|
163
488
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
*
|
|
172
|
-
*
|
|
489
|
+
* @example
|
|
490
|
+
* capitalize("hello") // "Hello"
|
|
491
|
+
*/
|
|
492
|
+
const capitalize = (str) => {
|
|
493
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
494
|
+
};
|
|
495
|
+
/**
|
|
496
|
+
* Converts a path or identifier string into a pretty display name.
|
|
497
|
+
* Removes slashes and capitalizes the first letter.
|
|
173
498
|
*
|
|
174
|
-
*
|
|
499
|
+
* @example
|
|
500
|
+
* prettyName("/userName") // "UserName"
|
|
501
|
+
* prettyName("email") // "Email"
|
|
175
502
|
*/
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
503
|
+
const prettyName = (name) => {
|
|
504
|
+
return capitalize(name.replaceAll("/", ""));
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/utils/parseInput.ts
|
|
509
|
+
const parseInput = (props, form) => {
|
|
179
510
|
const disabled = false;
|
|
180
511
|
const id = props.input.props.id;
|
|
181
512
|
const label = props.title ?? ("title" in props.input.schema && typeof props.input.schema.title === "string" ? props.input.schema.title : void 0) ?? prettyName(props.input.path);
|
|
182
513
|
const description = props.description ?? ("description" in props.input.schema && typeof props.input.schema.description === "string" ? props.input.schema.description : void 0);
|
|
183
514
|
const error = form.error && form.error instanceof TypeBoxError ? form.error.value.message : void 0;
|
|
184
|
-
const icon = props.icon
|
|
515
|
+
const icon = props.icon ?? getDefaultIcon({
|
|
516
|
+
type: props.input.schema && "type" in props.input.schema ? String(props.input.schema.type) : void 0,
|
|
517
|
+
format: props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0,
|
|
518
|
+
name: props.input.props.name,
|
|
519
|
+
isEnum: props.input.schema && "enum" in props.input.schema && Boolean(props.input.schema.enum),
|
|
520
|
+
isArray: props.input.schema && "type" in props.input.schema && props.input.schema.type === "array"
|
|
521
|
+
});
|
|
522
|
+
const format = props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0;
|
|
185
523
|
const required = props.input.required;
|
|
524
|
+
const schema = props.input.schema;
|
|
186
525
|
const inputProps = {
|
|
187
526
|
label,
|
|
188
527
|
description,
|
|
@@ -190,40 +529,122 @@ const Control = (props) => {
|
|
|
190
529
|
required,
|
|
191
530
|
disabled
|
|
192
531
|
};
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
532
|
+
if ("minLength" in schema && typeof schema.minLength === "number") inputProps.minLength = schema.minLength;
|
|
533
|
+
if ("maxLength" in schema && typeof schema.maxLength === "number") inputProps.maxLength = schema.maxLength;
|
|
534
|
+
if ("minimum" in schema && typeof schema.minimum === "number") inputProps.minimum = schema.minimum;
|
|
535
|
+
if ("maximum" in schema && typeof schema.maximum === "number") inputProps.maximum = schema.maximum;
|
|
536
|
+
return {
|
|
537
|
+
id,
|
|
538
|
+
icon,
|
|
539
|
+
format,
|
|
540
|
+
schema: props.input.schema,
|
|
541
|
+
inputProps
|
|
542
|
+
};
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/components/ControlDate.tsx
|
|
547
|
+
/**
|
|
548
|
+
* ControlDate component for handling date, datetime, and time inputs.
|
|
549
|
+
*
|
|
550
|
+
* Features:
|
|
551
|
+
* - DateInput for date format
|
|
552
|
+
* - DateTimePicker for date-time format
|
|
553
|
+
* - TimeInput for time format
|
|
554
|
+
*
|
|
555
|
+
* Automatically detects date formats from schema and renders appropriate picker.
|
|
556
|
+
*/
|
|
557
|
+
const ControlDate = (props) => {
|
|
558
|
+
const { inputProps, id, icon, format } = parseInput(props, useFormState(props.input));
|
|
559
|
+
if (!props.input?.props) return null;
|
|
560
|
+
if (props.datetime || format === "date-time") {
|
|
561
|
+
const dateTimePickerProps = typeof props.datetime === "object" ? props.datetime : {};
|
|
562
|
+
return /* @__PURE__ */ jsx(DateTimePicker, {
|
|
196
563
|
...inputProps,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
})
|
|
206
|
-
})
|
|
564
|
+
id,
|
|
565
|
+
leftSection: icon,
|
|
566
|
+
defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
|
|
567
|
+
onChange: (value) => {
|
|
568
|
+
props.input.set(value ? new Date(value).toISOString() : void 0);
|
|
569
|
+
},
|
|
570
|
+
...dateTimePickerProps
|
|
207
571
|
});
|
|
208
572
|
}
|
|
573
|
+
if (props.date || format === "date") {
|
|
574
|
+
const dateInputProps = typeof props.date === "object" ? props.date : {};
|
|
575
|
+
return /* @__PURE__ */ jsx(DateInput, {
|
|
576
|
+
...inputProps,
|
|
577
|
+
id,
|
|
578
|
+
leftSection: icon,
|
|
579
|
+
defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
|
|
580
|
+
onChange: (value) => {
|
|
581
|
+
props.input.set(value ? new Date(value).toISOString().slice(0, 10) : void 0);
|
|
582
|
+
},
|
|
583
|
+
...dateInputProps
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
if (props.time || format === "time") {
|
|
587
|
+
const timeInputProps = typeof props.time === "object" ? props.time : {};
|
|
588
|
+
return /* @__PURE__ */ jsx(TimeInput, {
|
|
589
|
+
...inputProps,
|
|
590
|
+
id,
|
|
591
|
+
leftSection: icon,
|
|
592
|
+
defaultValue: props.input.props.defaultValue,
|
|
593
|
+
onChange: (event) => {
|
|
594
|
+
props.input.set(event.currentTarget.value);
|
|
595
|
+
},
|
|
596
|
+
...timeInputProps
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
};
|
|
601
|
+
var ControlDate_default = ControlDate;
|
|
602
|
+
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/components/ControlSelect.tsx
|
|
605
|
+
/**
|
|
606
|
+
* ControlSelect component for handling Select, MultiSelect, and TagsInput.
|
|
607
|
+
*
|
|
608
|
+
* Features:
|
|
609
|
+
* - Basic Select with enum support
|
|
610
|
+
* - MultiSelect for array of enums
|
|
611
|
+
* - TagsInput for array of strings (no enum)
|
|
612
|
+
* - Future: Lazy loading
|
|
613
|
+
* - Future: Searchable/filterable options
|
|
614
|
+
* - Future: Custom option rendering
|
|
615
|
+
*
|
|
616
|
+
* Automatically detects enum values and array types from schema.
|
|
617
|
+
*/
|
|
618
|
+
const ControlSelect = (props) => {
|
|
619
|
+
const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
|
|
620
|
+
const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
|
|
621
|
+
let itemsEnum;
|
|
622
|
+
if (isArray && "items" in props.input.schema && props.input.schema.items) {
|
|
623
|
+
const items = props.input.schema.items;
|
|
624
|
+
if ("enum" in items && Array.isArray(items.enum)) itemsEnum = items.enum;
|
|
625
|
+
}
|
|
626
|
+
const enumValues = props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum : [];
|
|
627
|
+
const [data, setData] = useState([]);
|
|
628
|
+
useEffect(() => {
|
|
629
|
+
if (!props.input?.props) return;
|
|
630
|
+
if (props.loader) props.loader().then(setData);
|
|
631
|
+
else setData(enumValues);
|
|
632
|
+
}, [props.input, props.loader]);
|
|
633
|
+
if (!props.input?.props) return null;
|
|
209
634
|
if (props.segmented) {
|
|
210
635
|
const segmentedControlProps = typeof props.segmented === "object" ? props.segmented : {};
|
|
211
|
-
const data = segmentedControlProps.data ?? (props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum?.map((value) => ({
|
|
212
|
-
value,
|
|
213
|
-
label: value
|
|
214
|
-
})) : []);
|
|
215
636
|
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
216
637
|
...inputProps,
|
|
217
|
-
children: /* @__PURE__ */ jsx(Flex, {
|
|
638
|
+
children: /* @__PURE__ */ jsx(Flex$1, {
|
|
218
639
|
mt: "calc(var(--mantine-spacing-xs) / 2)",
|
|
219
640
|
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
220
|
-
disabled,
|
|
641
|
+
disabled: inputProps.disabled,
|
|
221
642
|
defaultValue: String(props.input.props.defaultValue),
|
|
222
643
|
...segmentedControlProps,
|
|
223
644
|
onChange: (value) => {
|
|
224
645
|
props.input.set(value);
|
|
225
646
|
},
|
|
226
|
-
data
|
|
647
|
+
data: data.slice(0, 10)
|
|
227
648
|
})
|
|
228
649
|
})
|
|
229
650
|
});
|
|
@@ -234,23 +655,143 @@ const Control = (props) => {
|
|
|
234
655
|
...inputProps,
|
|
235
656
|
id,
|
|
236
657
|
leftSection: icon,
|
|
658
|
+
data,
|
|
237
659
|
...props.input.props,
|
|
238
660
|
...autocompleteProps
|
|
239
661
|
});
|
|
240
662
|
}
|
|
241
|
-
if (
|
|
242
|
-
const
|
|
663
|
+
if (isArray && !itemsEnum || props.tags) {
|
|
664
|
+
const tagsInputProps = typeof props.tags === "object" ? props.tags : {};
|
|
665
|
+
return /* @__PURE__ */ jsx(TagsInput, {
|
|
666
|
+
...inputProps,
|
|
667
|
+
id,
|
|
668
|
+
leftSection: icon,
|
|
669
|
+
defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
|
|
670
|
+
onChange: (value) => {
|
|
671
|
+
props.input.set(value);
|
|
672
|
+
},
|
|
673
|
+
...tagsInputProps
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
if (isArray && itemsEnum || props.multi) {
|
|
677
|
+
const data$1 = itemsEnum?.map((value) => ({
|
|
243
678
|
value,
|
|
244
679
|
label: value
|
|
245
|
-
}))
|
|
246
|
-
const
|
|
247
|
-
return /* @__PURE__ */ jsx(
|
|
680
|
+
})) || [];
|
|
681
|
+
const multiSelectProps = typeof props.multi === "object" ? props.multi : {};
|
|
682
|
+
return /* @__PURE__ */ jsx(MultiSelect, {
|
|
683
|
+
...inputProps,
|
|
684
|
+
id,
|
|
685
|
+
leftSection: icon,
|
|
686
|
+
data: data$1,
|
|
687
|
+
defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
|
|
688
|
+
onChange: (value) => {
|
|
689
|
+
props.input.set(value);
|
|
690
|
+
},
|
|
691
|
+
...multiSelectProps
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
const selectProps = typeof props.select === "object" ? props.select : {};
|
|
695
|
+
return /* @__PURE__ */ jsx(Select, {
|
|
696
|
+
...inputProps,
|
|
697
|
+
id,
|
|
698
|
+
leftSection: icon,
|
|
699
|
+
data,
|
|
700
|
+
...props.input.props,
|
|
701
|
+
...selectProps
|
|
702
|
+
});
|
|
703
|
+
};
|
|
704
|
+
var ControlSelect_default = ControlSelect;
|
|
705
|
+
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region src/components/Control.tsx
|
|
708
|
+
/**
|
|
709
|
+
* Generic form control that renders the appropriate input based on the schema and props.
|
|
710
|
+
*
|
|
711
|
+
* Supports:
|
|
712
|
+
* - TextInput (with format detection: email, url, tel)
|
|
713
|
+
* - Textarea
|
|
714
|
+
* - NumberInput (for number/integer types)
|
|
715
|
+
* - FileInput
|
|
716
|
+
* - ColorInput (for color format)
|
|
717
|
+
* - Select (for enum types)
|
|
718
|
+
* - Autocomplete
|
|
719
|
+
* - PasswordInput
|
|
720
|
+
* - Switch (for boolean types)
|
|
721
|
+
* - SegmentedControl (for enum types)
|
|
722
|
+
* - DateInput (for date format)
|
|
723
|
+
* - DateTimePicker (for date-time format)
|
|
724
|
+
* - TimeInput (for time format)
|
|
725
|
+
* - Custom component
|
|
726
|
+
*
|
|
727
|
+
* Automatically handles labels, descriptions, error messages, required state, and default icons.
|
|
728
|
+
*/
|
|
729
|
+
const Control = (_props) => {
|
|
730
|
+
const { inputProps, id, icon, format, schema } = parseInput(_props, useFormState(_props.input, ["error"]));
|
|
731
|
+
if (!_props.input?.props) return null;
|
|
732
|
+
const props = {
|
|
733
|
+
..._props,
|
|
734
|
+
...schema.$control
|
|
735
|
+
};
|
|
736
|
+
if (props.custom) {
|
|
737
|
+
const Custom = props.custom;
|
|
738
|
+
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
739
|
+
...inputProps,
|
|
740
|
+
children: /* @__PURE__ */ jsx(Flex$1, {
|
|
741
|
+
flex: 1,
|
|
742
|
+
mt: "calc(var(--mantine-spacing-xs) / 2)",
|
|
743
|
+
children: /* @__PURE__ */ jsx(Custom, {
|
|
744
|
+
defaultValue: props.input.props.defaultValue,
|
|
745
|
+
onChange: (value) => {
|
|
746
|
+
props.input.set(value);
|
|
747
|
+
}
|
|
748
|
+
})
|
|
749
|
+
})
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
if (props.number || props.input.schema && "type" in props.input.schema && (props.input.schema.type === "number" || props.input.schema.type === "integer")) {
|
|
753
|
+
const numberInputProps = typeof props.number === "object" ? props.number : {};
|
|
754
|
+
const { type,...inputPropsWithoutType } = props.input.props;
|
|
755
|
+
return /* @__PURE__ */ jsx(NumberInput, {
|
|
756
|
+
...inputProps,
|
|
757
|
+
id,
|
|
758
|
+
leftSection: icon,
|
|
759
|
+
...inputPropsWithoutType,
|
|
760
|
+
...numberInputProps
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
if (props.file) {
|
|
764
|
+
const fileInputProps = typeof props.file === "object" ? props.file : {};
|
|
765
|
+
return /* @__PURE__ */ jsx(FileInput, {
|
|
766
|
+
...inputProps,
|
|
767
|
+
id,
|
|
768
|
+
leftSection: icon,
|
|
769
|
+
onChange: (file) => {
|
|
770
|
+
props.input.set(file);
|
|
771
|
+
},
|
|
772
|
+
...fileInputProps
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
if (props.color || format === "color") {
|
|
776
|
+
const colorInputProps = typeof props.color === "object" ? props.color : {};
|
|
777
|
+
return /* @__PURE__ */ jsx(ColorInput, {
|
|
248
778
|
...inputProps,
|
|
249
779
|
id,
|
|
250
780
|
leftSection: icon,
|
|
251
|
-
data,
|
|
252
781
|
...props.input.props,
|
|
253
|
-
...
|
|
782
|
+
...colorInputProps
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const isEnum = props.input.schema && "enum" in props.input.schema && props.input.schema.enum;
|
|
786
|
+
const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
|
|
787
|
+
if (isEnum || isArray || props.select) {
|
|
788
|
+
const opts = typeof props.select === "object" ? props.select : {};
|
|
789
|
+
return /* @__PURE__ */ jsx(ControlSelect_default, {
|
|
790
|
+
input: props.input,
|
|
791
|
+
title: props.title,
|
|
792
|
+
description: props.description,
|
|
793
|
+
icon,
|
|
794
|
+
...opts
|
|
254
795
|
});
|
|
255
796
|
}
|
|
256
797
|
if (props.input.schema && "type" in props.input.schema && props.input.schema.type === "boolean" || props.switch) {
|
|
@@ -264,7 +805,7 @@ const Control = (props) => {
|
|
|
264
805
|
...switchProps
|
|
265
806
|
});
|
|
266
807
|
}
|
|
267
|
-
if (props.password) {
|
|
808
|
+
if (props.password || props.input.props.name?.includes("password")) {
|
|
268
809
|
const passwordInputProps = typeof props.password === "object" ? props.password : {};
|
|
269
810
|
return /* @__PURE__ */ jsx(PasswordInput, {
|
|
270
811
|
...inputProps,
|
|
@@ -284,34 +825,725 @@ const Control = (props) => {
|
|
|
284
825
|
...textAreaProps
|
|
285
826
|
});
|
|
286
827
|
}
|
|
828
|
+
if (props.date || props.datetime || props.time || format === "date" || format === "date-time" || format === "time") return /* @__PURE__ */ jsx(ControlDate_default, {
|
|
829
|
+
input: props.input,
|
|
830
|
+
title: props.title,
|
|
831
|
+
description: props.description,
|
|
832
|
+
icon,
|
|
833
|
+
date: props.date,
|
|
834
|
+
datetime: props.datetime,
|
|
835
|
+
time: props.time
|
|
836
|
+
});
|
|
287
837
|
const textInputProps = typeof props.text === "object" ? props.text : {};
|
|
838
|
+
const getInputType = () => {
|
|
839
|
+
switch (format) {
|
|
840
|
+
case "email": return "email";
|
|
841
|
+
case "url":
|
|
842
|
+
case "uri": return "url";
|
|
843
|
+
case "tel":
|
|
844
|
+
case "phone": return "tel";
|
|
845
|
+
default: return;
|
|
846
|
+
}
|
|
847
|
+
};
|
|
288
848
|
return /* @__PURE__ */ jsx(TextInput, {
|
|
289
849
|
...inputProps,
|
|
290
850
|
id,
|
|
291
851
|
leftSection: icon,
|
|
852
|
+
type: getInputType(),
|
|
292
853
|
...props.input.props,
|
|
293
854
|
...textInputProps
|
|
294
855
|
});
|
|
295
856
|
};
|
|
296
|
-
|
|
297
|
-
|
|
857
|
+
var Control_default = Control;
|
|
858
|
+
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/components/DarkModeButton.tsx
|
|
861
|
+
const DarkModeButton = (props) => {
|
|
862
|
+
const { setColorScheme } = useMantineColorScheme();
|
|
863
|
+
const computedColorScheme = useComputedColorScheme("light");
|
|
864
|
+
const [colorScheme, setColorScheme2] = useState("default");
|
|
865
|
+
const mode = props.mode ?? "minimal";
|
|
866
|
+
useEffect(() => {
|
|
867
|
+
setColorScheme2(computedColorScheme);
|
|
868
|
+
}, [computedColorScheme]);
|
|
869
|
+
const toggleColorScheme = () => {
|
|
870
|
+
setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
|
|
871
|
+
};
|
|
872
|
+
if (mode === "segmented") return /* @__PURE__ */ jsx(SegmentedControl, {
|
|
873
|
+
value: colorScheme,
|
|
874
|
+
onChange: (value) => setColorScheme(value),
|
|
875
|
+
data: [{
|
|
876
|
+
value: "light",
|
|
877
|
+
label: /* @__PURE__ */ jsx(Flex$1, {
|
|
878
|
+
h: 20,
|
|
879
|
+
align: "center",
|
|
880
|
+
justify: "center",
|
|
881
|
+
children: /* @__PURE__ */ jsx(IconSun, { size: 16 })
|
|
882
|
+
})
|
|
883
|
+
}, {
|
|
884
|
+
value: "dark",
|
|
885
|
+
label: /* @__PURE__ */ jsx(Flex$1, {
|
|
886
|
+
h: 20,
|
|
887
|
+
align: "center",
|
|
888
|
+
justify: "center",
|
|
889
|
+
children: /* @__PURE__ */ jsx(IconMoon, { size: 16 })
|
|
890
|
+
})
|
|
891
|
+
}]
|
|
892
|
+
});
|
|
893
|
+
return /* @__PURE__ */ jsx(ActionIcon, {
|
|
894
|
+
onClick: toggleColorScheme,
|
|
895
|
+
variant: props.variant ?? "default",
|
|
896
|
+
size: props.size ?? "lg",
|
|
897
|
+
"aria-label": "Toggle color scheme",
|
|
898
|
+
children: colorScheme === "dark" ? /* @__PURE__ */ jsx(IconSun, { size: 20 }) : colorScheme === "light" ? /* @__PURE__ */ jsx(IconMoon, { size: 20 }) : /* @__PURE__ */ jsx(Flex$1, { h: 20 })
|
|
899
|
+
});
|
|
298
900
|
};
|
|
299
|
-
|
|
300
|
-
|
|
901
|
+
var DarkModeButton_default = DarkModeButton;
|
|
902
|
+
|
|
903
|
+
//#endregion
|
|
904
|
+
//#region src/components/DataTable.tsx
|
|
905
|
+
function getNestedValue(obj, path) {
|
|
906
|
+
return path.split(".").reduce((acc, part) => acc?.[part], obj);
|
|
907
|
+
}
|
|
908
|
+
function DataTable({ data = [], columns: initialColumns = [], loading = false, emptyMessage = "No data available", selectable = false, selectedRows = [], onRowSelect, sortable = false, sort, onSortChange, filterable = false, filters = [], onFiltersChange, filterPlaceholder = "Search...", paginate = false, page = 1, pageSize = 10, totalRecords, pageSizeOptions = [
|
|
909
|
+
10,
|
|
910
|
+
25,
|
|
911
|
+
50,
|
|
912
|
+
100
|
|
913
|
+
], onPageChange, onPageSizeChange, rowActions, onRowClick, rowClassName, showHeader = true, showFooter = true, stickyHeader = false, striped = false, highlightOnHover = true, showToolbar = true, title, actions, showColumnToggle = true, showRefresh = false, onRefresh, showExport = false, onExport, height, minHeight, maxHeight,...tableProps }) {
|
|
914
|
+
const [hiddenColumns, setHiddenColumns] = useState(/* @__PURE__ */ new Set());
|
|
915
|
+
const [globalFilter, setGlobalFilter] = useState("");
|
|
916
|
+
const [internalPage, setInternalPage] = useState(page);
|
|
917
|
+
const [internalPageSize, setInternalPageSize] = useState(pageSize);
|
|
918
|
+
const [internalSort, setInternalSort] = useState(sort);
|
|
919
|
+
const [internalFilters, setInternalFilters] = useState(filters);
|
|
920
|
+
const [internalSelectedRows, setInternalSelectedRows] = useState(selectedRows);
|
|
921
|
+
const currentPage = onPageChange ? page : internalPage;
|
|
922
|
+
const currentPageSize = onPageSizeChange ? pageSize : internalPageSize;
|
|
923
|
+
const currentSort = onSortChange ? sort : internalSort;
|
|
924
|
+
const currentFilters = onFiltersChange ? filters : internalFilters;
|
|
925
|
+
const currentSelectedRows = onRowSelect ? selectedRows : internalSelectedRows;
|
|
926
|
+
const visibleColumns = useMemo(() => initialColumns.filter((col) => !col.hidden && !hiddenColumns.has(String(col.accessor))), [initialColumns, hiddenColumns]);
|
|
927
|
+
const processedData = useMemo(() => {
|
|
928
|
+
let result = [...data];
|
|
929
|
+
if (filterable && globalFilter) result = result.filter((row) => {
|
|
930
|
+
return visibleColumns.some((col) => {
|
|
931
|
+
const value = getNestedValue(row, String(col.accessor));
|
|
932
|
+
return String(value).toLowerCase().includes(globalFilter.toLowerCase());
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
if (filterable && currentFilters.length > 0) result = result.filter((row) => {
|
|
936
|
+
return currentFilters.every((filter) => {
|
|
937
|
+
const value = String(getNestedValue(row, filter.column)).toLowerCase();
|
|
938
|
+
const filterValue = filter.value.toLowerCase();
|
|
939
|
+
switch (filter.operator) {
|
|
940
|
+
case "equals": return value === filterValue;
|
|
941
|
+
case "startsWith": return value.startsWith(filterValue);
|
|
942
|
+
case "endsWith": return value.endsWith(filterValue);
|
|
943
|
+
default: return value.includes(filterValue);
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
if (sortable && currentSort?.direction) result.sort((a, b) => {
|
|
948
|
+
const aVal = getNestedValue(a, currentSort.column);
|
|
949
|
+
const bVal = getNestedValue(b, currentSort.column);
|
|
950
|
+
if (aVal === bVal) return 0;
|
|
951
|
+
if (aVal === null || aVal === void 0) return 1;
|
|
952
|
+
if (bVal === null || bVal === void 0) return -1;
|
|
953
|
+
const comparison = aVal < bVal ? -1 : 1;
|
|
954
|
+
return currentSort.direction === "asc" ? comparison : -comparison;
|
|
955
|
+
});
|
|
956
|
+
return result;
|
|
957
|
+
}, [
|
|
958
|
+
data,
|
|
959
|
+
visibleColumns,
|
|
960
|
+
filterable,
|
|
961
|
+
globalFilter,
|
|
962
|
+
currentFilters,
|
|
963
|
+
sortable,
|
|
964
|
+
currentSort
|
|
965
|
+
]);
|
|
966
|
+
const paginatedData = useMemo(() => {
|
|
967
|
+
if (!paginate) return processedData;
|
|
968
|
+
const startIndex = (currentPage - 1) * currentPageSize;
|
|
969
|
+
return processedData.slice(startIndex, startIndex + currentPageSize);
|
|
970
|
+
}, [
|
|
971
|
+
processedData,
|
|
972
|
+
paginate,
|
|
973
|
+
currentPage,
|
|
974
|
+
currentPageSize
|
|
975
|
+
]);
|
|
976
|
+
const totalPages = useMemo(() => {
|
|
977
|
+
const total = totalRecords ?? processedData.length;
|
|
978
|
+
return Math.ceil(total / currentPageSize);
|
|
979
|
+
}, [
|
|
980
|
+
totalRecords,
|
|
981
|
+
processedData.length,
|
|
982
|
+
currentPageSize
|
|
983
|
+
]);
|
|
984
|
+
const handleSort = useCallback((column) => {
|
|
985
|
+
if (!sortable) return;
|
|
986
|
+
const newSort = {
|
|
987
|
+
column,
|
|
988
|
+
direction: currentSort?.column === column ? currentSort.direction === "asc" ? "desc" : currentSort.direction === "desc" ? null : "asc" : "asc"
|
|
989
|
+
};
|
|
990
|
+
if (onSortChange) onSortChange(newSort);
|
|
991
|
+
else setInternalSort(newSort);
|
|
992
|
+
}, [
|
|
993
|
+
sortable,
|
|
994
|
+
currentSort,
|
|
995
|
+
onSortChange
|
|
996
|
+
]);
|
|
997
|
+
const handlePageChange = useCallback((newPage) => {
|
|
998
|
+
if (onPageChange) onPageChange(newPage);
|
|
999
|
+
else setInternalPage(newPage);
|
|
1000
|
+
}, [onPageChange]);
|
|
1001
|
+
const handlePageSizeChange = useCallback((size) => {
|
|
1002
|
+
const newSize = Number(size) || currentPageSize;
|
|
1003
|
+
if (onPageSizeChange) {
|
|
1004
|
+
onPageSizeChange(newSize);
|
|
1005
|
+
onPageChange?.(1);
|
|
1006
|
+
} else {
|
|
1007
|
+
setInternalPageSize(newSize);
|
|
1008
|
+
setInternalPage(1);
|
|
1009
|
+
}
|
|
1010
|
+
}, [
|
|
1011
|
+
currentPageSize,
|
|
1012
|
+
onPageSizeChange,
|
|
1013
|
+
onPageChange
|
|
1014
|
+
]);
|
|
1015
|
+
const handleSelectAll = useCallback((checked) => {
|
|
1016
|
+
const newSelection = checked ? paginatedData : [];
|
|
1017
|
+
if (onRowSelect) onRowSelect(newSelection);
|
|
1018
|
+
else setInternalSelectedRows(newSelection);
|
|
1019
|
+
}, [paginatedData, onRowSelect]);
|
|
1020
|
+
const handleSelectRow = useCallback((row, checked) => {
|
|
1021
|
+
const newSelection = checked ? [...currentSelectedRows, row] : currentSelectedRows.filter((r) => r !== row);
|
|
1022
|
+
if (onRowSelect) onRowSelect(newSelection);
|
|
1023
|
+
else setInternalSelectedRows(newSelection);
|
|
1024
|
+
}, [currentSelectedRows, onRowSelect]);
|
|
1025
|
+
const handleColumnToggle = useCallback((column) => {
|
|
1026
|
+
const newHidden = new Set(hiddenColumns);
|
|
1027
|
+
if (newHidden.has(column)) newHidden.delete(column);
|
|
1028
|
+
else newHidden.add(column);
|
|
1029
|
+
setHiddenColumns(newHidden);
|
|
1030
|
+
}, [hiddenColumns]);
|
|
1031
|
+
const isAllSelected = currentSelectedRows.length > 0 && currentSelectedRows.length === paginatedData.length;
|
|
1032
|
+
const isIndeterminate = currentSelectedRows.length > 0 && currentSelectedRows.length < paginatedData.length;
|
|
1033
|
+
const renderSortIcon = (column) => {
|
|
1034
|
+
if (!sortable) return null;
|
|
1035
|
+
if (currentSort?.column === column) {
|
|
1036
|
+
if (currentSort.direction === "asc") return /* @__PURE__ */ jsx(IconChevronUp, {
|
|
1037
|
+
className: "alepha-datatable-sort-icon",
|
|
1038
|
+
size: 16
|
|
1039
|
+
});
|
|
1040
|
+
if (currentSort.direction === "desc") return /* @__PURE__ */ jsx(IconChevronDown, {
|
|
1041
|
+
className: "alepha-datatable-sort-icon",
|
|
1042
|
+
size: 16
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
return /* @__PURE__ */ jsx(IconChevronUp, {
|
|
1046
|
+
className: "alepha-datatable-sort-icon-inactive",
|
|
1047
|
+
size: 16
|
|
1048
|
+
});
|
|
1049
|
+
};
|
|
1050
|
+
const toolbar = showToolbar && (title || actions || filterable || showColumnToggle || showRefresh || showExport) && /* @__PURE__ */ jsx(Paper, {
|
|
1051
|
+
className: "alepha-datatable-toolbar",
|
|
1052
|
+
p: "md",
|
|
1053
|
+
mb: "sm",
|
|
1054
|
+
children: /* @__PURE__ */ jsxs(Flex$1, {
|
|
1055
|
+
justify: "space-between",
|
|
1056
|
+
align: "center",
|
|
1057
|
+
gap: "md",
|
|
1058
|
+
children: [/* @__PURE__ */ jsxs(Group, { children: [title && /* @__PURE__ */ jsx(Text, {
|
|
1059
|
+
size: "lg",
|
|
1060
|
+
fw: 600,
|
|
1061
|
+
children: title
|
|
1062
|
+
}), currentSelectedRows.length > 0 && /* @__PURE__ */ jsxs(Badge, {
|
|
1063
|
+
color: "blue",
|
|
1064
|
+
variant: "light",
|
|
1065
|
+
children: [currentSelectedRows.length, " selected"]
|
|
1066
|
+
})] }), /* @__PURE__ */ jsxs(Group, { children: [
|
|
1067
|
+
filterable && /* @__PURE__ */ jsx(TextInput, {
|
|
1068
|
+
placeholder: filterPlaceholder,
|
|
1069
|
+
value: globalFilter,
|
|
1070
|
+
onChange: (e) => setGlobalFilter(e.target.value),
|
|
1071
|
+
leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
|
|
1072
|
+
rightSection: globalFilter && /* @__PURE__ */ jsx(ActionIcon, {
|
|
1073
|
+
size: "xs",
|
|
1074
|
+
variant: "subtle",
|
|
1075
|
+
onClick: () => setGlobalFilter(""),
|
|
1076
|
+
children: /* @__PURE__ */ jsx(IconX, { size: 14 })
|
|
1077
|
+
}),
|
|
1078
|
+
className: "alepha-datatable-search-input"
|
|
1079
|
+
}),
|
|
1080
|
+
showColumnToggle && /* @__PURE__ */ jsxs(Menu, {
|
|
1081
|
+
position: "bottom-end",
|
|
1082
|
+
children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Tooltip, {
|
|
1083
|
+
label: "Toggle columns",
|
|
1084
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
1085
|
+
variant: "subtle",
|
|
1086
|
+
children: /* @__PURE__ */ jsx(IconColumns, { size: 20 })
|
|
1087
|
+
})
|
|
1088
|
+
}) }), /* @__PURE__ */ jsxs(Menu.Dropdown, { children: [/* @__PURE__ */ jsx(Menu.Label, { children: "Visible columns" }), initialColumns.map((col) => /* @__PURE__ */ jsx(Menu.Item, {
|
|
1089
|
+
onClick: () => handleColumnToggle(String(col.accessor)),
|
|
1090
|
+
leftSection: /* @__PURE__ */ jsx(Checkbox, {
|
|
1091
|
+
checked: !hiddenColumns.has(String(col.accessor)) && !col.hidden,
|
|
1092
|
+
readOnly: true,
|
|
1093
|
+
size: "xs"
|
|
1094
|
+
}),
|
|
1095
|
+
children: col.title || String(col.accessor)
|
|
1096
|
+
}, String(col.accessor)))] })]
|
|
1097
|
+
}),
|
|
1098
|
+
showRefresh && /* @__PURE__ */ jsx(Tooltip, {
|
|
1099
|
+
label: "Refresh",
|
|
1100
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
1101
|
+
variant: "subtle",
|
|
1102
|
+
onClick: onRefresh,
|
|
1103
|
+
loading,
|
|
1104
|
+
children: /* @__PURE__ */ jsx(IconRefresh, { size: 20 })
|
|
1105
|
+
})
|
|
1106
|
+
}),
|
|
1107
|
+
showExport && /* @__PURE__ */ jsx(Tooltip, {
|
|
1108
|
+
label: "Export",
|
|
1109
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
1110
|
+
variant: "subtle",
|
|
1111
|
+
onClick: onExport,
|
|
1112
|
+
children: /* @__PURE__ */ jsx(IconDownload, { size: 20 })
|
|
1113
|
+
})
|
|
1114
|
+
}),
|
|
1115
|
+
actions
|
|
1116
|
+
] })]
|
|
1117
|
+
})
|
|
1118
|
+
});
|
|
1119
|
+
const tableContent = /* @__PURE__ */ jsxs(Table, {
|
|
1120
|
+
striped,
|
|
1121
|
+
highlightOnHover,
|
|
1122
|
+
stickyHeader,
|
|
1123
|
+
className: "alepha-datatable-table",
|
|
1124
|
+
...tableProps,
|
|
1125
|
+
children: [
|
|
1126
|
+
showHeader && /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
1127
|
+
selectable && /* @__PURE__ */ jsx(Table.Th, {
|
|
1128
|
+
className: "alepha-datatable-checkbox-column",
|
|
1129
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
1130
|
+
checked: isAllSelected,
|
|
1131
|
+
indeterminate: isIndeterminate,
|
|
1132
|
+
onChange: (e) => handleSelectAll(e.currentTarget.checked)
|
|
1133
|
+
})
|
|
1134
|
+
}),
|
|
1135
|
+
visibleColumns.map((column) => /* @__PURE__ */ jsx(Table.Th, {
|
|
1136
|
+
className: `alepha-datatable-th ${column.headerClassName || ""}`,
|
|
1137
|
+
style: {
|
|
1138
|
+
width: column.width,
|
|
1139
|
+
textAlign: column.align,
|
|
1140
|
+
cursor: column.sortable && sortable ? "pointer" : "default"
|
|
1141
|
+
},
|
|
1142
|
+
onClick: () => column.sortable && handleSort(String(column.accessor)),
|
|
1143
|
+
children: /* @__PURE__ */ jsxs(Group, {
|
|
1144
|
+
gap: "xs",
|
|
1145
|
+
justify: column.align === "center" ? "center" : column.align === "right" ? "flex-end" : "flex-start",
|
|
1146
|
+
children: [column.renderHeader ? column.renderHeader() : column.title || String(column.accessor), column.sortable && renderSortIcon(String(column.accessor))]
|
|
1147
|
+
})
|
|
1148
|
+
}, String(column.accessor))),
|
|
1149
|
+
rowActions && /* @__PURE__ */ jsx(Table.Th, {
|
|
1150
|
+
className: "alepha-datatable-actions-column",
|
|
1151
|
+
children: "Actions"
|
|
1152
|
+
})
|
|
1153
|
+
] }) }),
|
|
1154
|
+
/* @__PURE__ */ jsx(Table.Tbody, { children: loading ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
|
|
1155
|
+
colSpan: visibleColumns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0),
|
|
1156
|
+
children: /* @__PURE__ */ jsx(Center, {
|
|
1157
|
+
py: "xl",
|
|
1158
|
+
children: /* @__PURE__ */ jsx(Loader, { size: "sm" })
|
|
1159
|
+
})
|
|
1160
|
+
}) }) : paginatedData.length === 0 ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
|
|
1161
|
+
colSpan: visibleColumns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0),
|
|
1162
|
+
children: /* @__PURE__ */ jsx(Center, {
|
|
1163
|
+
py: "xl",
|
|
1164
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1165
|
+
c: "dimmed",
|
|
1166
|
+
children: emptyMessage
|
|
1167
|
+
})
|
|
1168
|
+
})
|
|
1169
|
+
}) }) : paginatedData.map((row, index) => {
|
|
1170
|
+
const isSelected = currentSelectedRows.includes(row);
|
|
1171
|
+
return /* @__PURE__ */ jsxs(Table.Tr, {
|
|
1172
|
+
className: `alepha-datatable-tr ${isSelected ? "alepha-datatable-selected" : ""} ${rowClassName?.(row, index) || ""}`,
|
|
1173
|
+
onClick: () => onRowClick?.(row, index),
|
|
1174
|
+
style: { cursor: onRowClick ? "pointer" : "default" },
|
|
1175
|
+
children: [
|
|
1176
|
+
selectable && /* @__PURE__ */ jsx(Table.Td, {
|
|
1177
|
+
className: "alepha-datatable-checkbox-column",
|
|
1178
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
1179
|
+
checked: isSelected,
|
|
1180
|
+
onChange: (e) => handleSelectRow(row, e.currentTarget.checked),
|
|
1181
|
+
onClick: (e) => e.stopPropagation()
|
|
1182
|
+
})
|
|
1183
|
+
}),
|
|
1184
|
+
visibleColumns.map((column) => {
|
|
1185
|
+
const value = getNestedValue(row, String(column.accessor));
|
|
1186
|
+
return /* @__PURE__ */ jsx(Table.Td, {
|
|
1187
|
+
className: column.className,
|
|
1188
|
+
style: {
|
|
1189
|
+
textAlign: column.align,
|
|
1190
|
+
...column.ellipsis && {
|
|
1191
|
+
maxWidth: column.width,
|
|
1192
|
+
overflow: "hidden",
|
|
1193
|
+
textOverflow: "ellipsis",
|
|
1194
|
+
whiteSpace: "nowrap"
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
children: column.render ? column.render(value, row, index) : value
|
|
1198
|
+
}, String(column.accessor));
|
|
1199
|
+
}),
|
|
1200
|
+
rowActions && /* @__PURE__ */ jsx(Table.Td, {
|
|
1201
|
+
className: "alepha-datatable-actions-column",
|
|
1202
|
+
children: rowActions(row, index)
|
|
1203
|
+
})
|
|
1204
|
+
]
|
|
1205
|
+
}, index);
|
|
1206
|
+
}) }),
|
|
1207
|
+
showFooter && paginate && /* @__PURE__ */ jsx(Table.Tfoot, { children: /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
|
|
1208
|
+
colSpan: visibleColumns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0),
|
|
1209
|
+
children: /* @__PURE__ */ jsxs(Flex$1, {
|
|
1210
|
+
justify: "space-between",
|
|
1211
|
+
align: "center",
|
|
1212
|
+
py: "xs",
|
|
1213
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
1214
|
+
gap: "xs",
|
|
1215
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1216
|
+
size: "sm",
|
|
1217
|
+
c: "dimmed",
|
|
1218
|
+
children: [
|
|
1219
|
+
"Showing ",
|
|
1220
|
+
(currentPage - 1) * currentPageSize + 1,
|
|
1221
|
+
" to",
|
|
1222
|
+
" ",
|
|
1223
|
+
Math.min(currentPage * currentPageSize, totalRecords ?? processedData.length),
|
|
1224
|
+
" ",
|
|
1225
|
+
"of ",
|
|
1226
|
+
totalRecords ?? processedData.length,
|
|
1227
|
+
" records"
|
|
1228
|
+
]
|
|
1229
|
+
}), /* @__PURE__ */ jsx(Select, {
|
|
1230
|
+
size: "xs",
|
|
1231
|
+
value: String(currentPageSize),
|
|
1232
|
+
onChange: handlePageSizeChange,
|
|
1233
|
+
data: pageSizeOptions.map((size) => ({
|
|
1234
|
+
value: String(size),
|
|
1235
|
+
label: `${size} / page`
|
|
1236
|
+
})),
|
|
1237
|
+
className: "alepha-datatable-page-size-select"
|
|
1238
|
+
})]
|
|
1239
|
+
}), /* @__PURE__ */ jsx(Pagination, {
|
|
1240
|
+
size: "sm",
|
|
1241
|
+
value: currentPage,
|
|
1242
|
+
onChange: handlePageChange,
|
|
1243
|
+
total: totalPages,
|
|
1244
|
+
siblings: 1,
|
|
1245
|
+
boundaries: 1
|
|
1246
|
+
})]
|
|
1247
|
+
})
|
|
1248
|
+
}) }) })
|
|
1249
|
+
]
|
|
1250
|
+
});
|
|
1251
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1252
|
+
className: "alepha-datatable-container",
|
|
1253
|
+
children: [toolbar, height || maxHeight ? /* @__PURE__ */ jsx(ScrollArea.Autosize, {
|
|
1254
|
+
mah: maxHeight,
|
|
1255
|
+
h: height,
|
|
1256
|
+
mih: minHeight,
|
|
1257
|
+
children: tableContent
|
|
1258
|
+
}) : tableContent]
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
//#endregion
|
|
1263
|
+
//#region src/components/Sidebar.tsx
|
|
1264
|
+
const Sidebar = ({ menu, defaultOpenIds = [], onItemClick, showSearchButton = false, onSearchClick }) => {
|
|
1265
|
+
const [openIds, setOpenIds] = useState(new Set(defaultOpenIds));
|
|
1266
|
+
const toggleOpen = (id) => {
|
|
1267
|
+
setOpenIds((prev) => {
|
|
1268
|
+
const newSet = new Set(prev);
|
|
1269
|
+
if (newSet.has(id)) newSet.delete(id);
|
|
1270
|
+
else newSet.add(id);
|
|
1271
|
+
return newSet;
|
|
1272
|
+
});
|
|
1273
|
+
};
|
|
1274
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1275
|
+
component: "nav",
|
|
1276
|
+
className: "alepha-sidebar",
|
|
1277
|
+
children: [showSearchButton && /* @__PURE__ */ jsx(UnstyledButton, {
|
|
1278
|
+
className: "alepha-sidebar-search-button",
|
|
1279
|
+
onClick: onSearchClick,
|
|
1280
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
1281
|
+
className: "alepha-sidebar-search-button-content",
|
|
1282
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1283
|
+
className: "alepha-sidebar-search-button-left",
|
|
1284
|
+
children: [/* @__PURE__ */ jsx(IconSearch, { size: 16 }), /* @__PURE__ */ jsx("span", { children: "Search..." })]
|
|
1285
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1286
|
+
className: "alepha-sidebar-search-button-shortcut",
|
|
1287
|
+
children: /* @__PURE__ */ jsx("kbd", { children: "⌘+K" })
|
|
1288
|
+
})]
|
|
1289
|
+
})
|
|
1290
|
+
}), menu.map((item) => /* @__PURE__ */ jsx(SidebarItem, {
|
|
1291
|
+
item,
|
|
1292
|
+
level: 0,
|
|
1293
|
+
openIds,
|
|
1294
|
+
onToggle: toggleOpen,
|
|
1295
|
+
onItemClick
|
|
1296
|
+
}, item.id))]
|
|
1297
|
+
});
|
|
1298
|
+
};
|
|
1299
|
+
const SidebarItem = (props) => {
|
|
1300
|
+
const { item, level } = props;
|
|
1301
|
+
if (level > 2) return null;
|
|
1302
|
+
if (item.href) return /* @__PURE__ */ jsx(SidebarItemHref, { ...props });
|
|
1303
|
+
return /* @__PURE__ */ jsx(SidebarItemButton, { ...props });
|
|
1304
|
+
};
|
|
1305
|
+
const SidebarItemHref = ({ item, level, openIds, onToggle, onItemClick }) => {
|
|
1306
|
+
const hasChildren = item.children && item.children.length > 0;
|
|
1307
|
+
const isOpen = openIds.has(item.id);
|
|
1308
|
+
const { isActive, anchorProps } = useActive$1({
|
|
1309
|
+
href: item.href,
|
|
1310
|
+
startWith: item.activeStartsWith
|
|
1311
|
+
});
|
|
1312
|
+
const handleItemClick = (e) => {
|
|
1313
|
+
if (hasChildren) {
|
|
1314
|
+
e.preventDefault();
|
|
1315
|
+
onToggle(item.id);
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1319
|
+
className: "alepha-sidebar-item-wrapper",
|
|
1320
|
+
children: [/* @__PURE__ */ jsx(UnstyledButton, {
|
|
1321
|
+
component: "a",
|
|
1322
|
+
...anchorProps,
|
|
1323
|
+
className: `alepha-sidebar-item alepha-sidebar-level-${level} ${isActive ? "alepha-sidebar-item-active" : ""}`,
|
|
1324
|
+
onClick: hasChildren ? handleItemClick : anchorProps.onClick,
|
|
1325
|
+
children: /* @__PURE__ */ jsxs(Flex$1, {
|
|
1326
|
+
justify: "space-between",
|
|
1327
|
+
align: "center",
|
|
1328
|
+
w: "100%",
|
|
1329
|
+
children: [/* @__PURE__ */ jsxs(Flex$1, {
|
|
1330
|
+
className: "alepha-sidebar-item-content",
|
|
1331
|
+
align: "center",
|
|
1332
|
+
gap: 10,
|
|
1333
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
1334
|
+
className: "alepha-sidebar-item-icon",
|
|
1335
|
+
children: item.icon || /* @__PURE__ */ jsx(IconCircle, { size: 16 })
|
|
1336
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1337
|
+
className: "alepha-sidebar-item-label",
|
|
1338
|
+
children: item.label
|
|
1339
|
+
})]
|
|
1340
|
+
}), hasChildren && /* @__PURE__ */ jsx(Box, {
|
|
1341
|
+
className: "alepha-sidebar-item-caret",
|
|
1342
|
+
children: isOpen ? /* @__PURE__ */ jsx(IconChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(IconChevronRight, { size: 14 })
|
|
1343
|
+
})]
|
|
1344
|
+
})
|
|
1345
|
+
}), hasChildren && isOpen && /* @__PURE__ */ jsxs(Box, {
|
|
1346
|
+
className: "alepha-sidebar-children",
|
|
1347
|
+
"data-parent-level": level,
|
|
1348
|
+
children: [(level === 0 || level === 1) && /* @__PURE__ */ jsx(Box, { className: "alepha-sidebar-vertical-bar" }), /* @__PURE__ */ jsx(Box, {
|
|
1349
|
+
className: "alepha-sidebar-children-items",
|
|
1350
|
+
children: item.children.map((child) => /* @__PURE__ */ jsx(SidebarItem, {
|
|
1351
|
+
item: child,
|
|
1352
|
+
level: level + 1,
|
|
1353
|
+
openIds,
|
|
1354
|
+
onToggle,
|
|
1355
|
+
onItemClick
|
|
1356
|
+
}, child.id))
|
|
1357
|
+
})]
|
|
1358
|
+
})]
|
|
1359
|
+
});
|
|
1360
|
+
};
|
|
1361
|
+
const SidebarItemButton = ({ item, level, openIds, onToggle, onItemClick }) => {
|
|
1362
|
+
const hasChildren = item.children && item.children.length > 0;
|
|
1363
|
+
const isOpen = openIds.has(item.id);
|
|
1364
|
+
const handleItemClick = (e) => {
|
|
1365
|
+
e.preventDefault();
|
|
1366
|
+
if (hasChildren) onToggle(item.id);
|
|
1367
|
+
else {
|
|
1368
|
+
onItemClick?.(item);
|
|
1369
|
+
item.onClick?.();
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1373
|
+
className: "alepha-sidebar-item-wrapper",
|
|
1374
|
+
children: [/* @__PURE__ */ jsx(UnstyledButton, {
|
|
1375
|
+
component: "button",
|
|
1376
|
+
className: `alepha-sidebar-item alepha-sidebar-level-${level}`,
|
|
1377
|
+
onClick: handleItemClick,
|
|
1378
|
+
children: /* @__PURE__ */ jsxs(Flex$1, {
|
|
1379
|
+
justify: "space-between",
|
|
1380
|
+
align: "center",
|
|
1381
|
+
w: "100%",
|
|
1382
|
+
children: [/* @__PURE__ */ jsxs(Flex$1, {
|
|
1383
|
+
className: "alepha-sidebar-item-content",
|
|
1384
|
+
align: "center",
|
|
1385
|
+
gap: 10,
|
|
1386
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
1387
|
+
className: "alepha-sidebar-item-icon",
|
|
1388
|
+
children: item.icon || /* @__PURE__ */ jsx(IconCircle, { size: 16 })
|
|
1389
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1390
|
+
className: "alepha-sidebar-item-label",
|
|
1391
|
+
children: item.label
|
|
1392
|
+
})]
|
|
1393
|
+
}), hasChildren && /* @__PURE__ */ jsx(Box, {
|
|
1394
|
+
className: "alepha-sidebar-item-caret",
|
|
1395
|
+
children: isOpen ? /* @__PURE__ */ jsx(IconChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(IconChevronRight, { size: 14 })
|
|
1396
|
+
})]
|
|
1397
|
+
})
|
|
1398
|
+
}), hasChildren && isOpen && /* @__PURE__ */ jsxs(Box, {
|
|
1399
|
+
className: "alepha-sidebar-children",
|
|
1400
|
+
"data-parent-level": level,
|
|
1401
|
+
children: [(level === 0 || level === 1) && /* @__PURE__ */ jsx(Box, { className: "alepha-sidebar-vertical-bar" }), /* @__PURE__ */ jsx(Box, {
|
|
1402
|
+
className: "alepha-sidebar-children-items",
|
|
1403
|
+
children: item.children.map((child) => /* @__PURE__ */ jsx(SidebarItem, {
|
|
1404
|
+
item: child,
|
|
1405
|
+
level: level + 1,
|
|
1406
|
+
openIds,
|
|
1407
|
+
onToggle,
|
|
1408
|
+
onItemClick
|
|
1409
|
+
}, child.id))
|
|
1410
|
+
})]
|
|
1411
|
+
})]
|
|
1412
|
+
});
|
|
301
1413
|
};
|
|
302
1414
|
|
|
303
1415
|
//#endregion
|
|
304
|
-
//#region src/
|
|
1416
|
+
//#region src/components/TypeForm.tsx
|
|
305
1417
|
/**
|
|
1418
|
+
* TypeForm component that automatically renders all form inputs based on schema.
|
|
1419
|
+
* Uses the Control component to render individual fields and Mantine Grid for responsive layout.
|
|
1420
|
+
*
|
|
1421
|
+
* @example
|
|
1422
|
+
* ```tsx
|
|
1423
|
+
* import { t } from "alepha";
|
|
1424
|
+
* import { useForm } from "@alepha/react-form";
|
|
1425
|
+
* import { TypeForm } from "@alepha/ui";
|
|
306
1426
|
*
|
|
1427
|
+
* const form = useForm({
|
|
1428
|
+
* schema: t.object({
|
|
1429
|
+
* username: t.text(),
|
|
1430
|
+
* email: t.text(),
|
|
1431
|
+
* age: t.integer(),
|
|
1432
|
+
* subscribe: t.boolean(),
|
|
1433
|
+
* }),
|
|
1434
|
+
* handler: (values) => {
|
|
1435
|
+
* console.log(values);
|
|
1436
|
+
* },
|
|
1437
|
+
* });
|
|
1438
|
+
*
|
|
1439
|
+
* return <TypeForm form={form} columns={2} />;
|
|
1440
|
+
* ```
|
|
1441
|
+
*/
|
|
1442
|
+
const TypeForm = (props) => {
|
|
1443
|
+
const { form, columns = 3, children, controlProps, skipFormElement = false, skipSubmitButton = false, submitButtonProps } = props;
|
|
1444
|
+
if (!form.options?.schema?.properties) return null;
|
|
1445
|
+
const supportedFields = Object.keys(form.options.schema.properties).filter((fieldName) => {
|
|
1446
|
+
const field = form.input[fieldName];
|
|
1447
|
+
if (!field || typeof field !== "object" || !("schema" in field)) return false;
|
|
1448
|
+
const schema = field.schema;
|
|
1449
|
+
if ("type" in schema) {
|
|
1450
|
+
if (schema.type === "object") return false;
|
|
1451
|
+
}
|
|
1452
|
+
if ("properties" in schema && schema.properties) return false;
|
|
1453
|
+
return true;
|
|
1454
|
+
});
|
|
1455
|
+
const colSpan = typeof columns === "number" ? {
|
|
1456
|
+
xs: 12,
|
|
1457
|
+
sm: 6,
|
|
1458
|
+
lg: 12 / columns
|
|
1459
|
+
} : {
|
|
1460
|
+
base: columns.base ? 12 / columns.base : void 0,
|
|
1461
|
+
xs: columns.xs ? 12 / columns.xs : 12,
|
|
1462
|
+
sm: columns.sm ? 12 / columns.sm : 6,
|
|
1463
|
+
md: columns.md ? 12 / columns.md : void 0,
|
|
1464
|
+
lg: columns.lg ? 12 / columns.lg : 4,
|
|
1465
|
+
xl: columns.xl ? 12 / columns.xl : void 0
|
|
1466
|
+
};
|
|
1467
|
+
const renderFields = () => {
|
|
1468
|
+
if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(form.input) });
|
|
1469
|
+
return /* @__PURE__ */ jsx(Grid, { children: supportedFields.map((fieldName) => {
|
|
1470
|
+
const field = form.input[fieldName];
|
|
1471
|
+
if (!field || typeof field !== "object" || !("schema" in field)) return null;
|
|
1472
|
+
return /* @__PURE__ */ jsx(Grid.Col, {
|
|
1473
|
+
span: colSpan,
|
|
1474
|
+
children: /* @__PURE__ */ jsx(Control_default, {
|
|
1475
|
+
input: field,
|
|
1476
|
+
...controlProps
|
|
1477
|
+
})
|
|
1478
|
+
}, fieldName);
|
|
1479
|
+
}) });
|
|
1480
|
+
};
|
|
1481
|
+
const content = /* @__PURE__ */ jsxs(Flex$1, {
|
|
1482
|
+
direction: "column",
|
|
1483
|
+
gap: "sm",
|
|
1484
|
+
children: [renderFields(), !skipSubmitButton && /* @__PURE__ */ jsxs(Flex$1, { children: [/* @__PURE__ */ jsx(Action_default, {
|
|
1485
|
+
form,
|
|
1486
|
+
...submitButtonProps,
|
|
1487
|
+
children: submitButtonProps?.children ?? "Submit"
|
|
1488
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1489
|
+
type: "reset",
|
|
1490
|
+
children: "Reset"
|
|
1491
|
+
})] })]
|
|
1492
|
+
});
|
|
1493
|
+
if (skipFormElement) return content;
|
|
1494
|
+
return /* @__PURE__ */ jsx("form", {
|
|
1495
|
+
...form.props,
|
|
1496
|
+
children: content
|
|
1497
|
+
});
|
|
1498
|
+
};
|
|
1499
|
+
var TypeForm_default = TypeForm;
|
|
1500
|
+
|
|
1501
|
+
//#endregion
|
|
1502
|
+
//#region src/hooks/useDialog.ts
|
|
1503
|
+
/**
|
|
1504
|
+
* Use this hook to access the Dialog Service for showing various dialog types.
|
|
1505
|
+
*
|
|
1506
|
+
* @example
|
|
1507
|
+
* const dialog = useDialog();
|
|
1508
|
+
* await dialog.alert({ title: "Alert", message: "This is an alert message" });
|
|
1509
|
+
* const confirmed = await dialog.confirm({ title: "Confirm", message: "Are you sure?" });
|
|
1510
|
+
* const input = await dialog.prompt({ title: "Input", message: "Enter your name:" });
|
|
1511
|
+
*/
|
|
1512
|
+
const useDialog = () => {
|
|
1513
|
+
return useInject(DialogService);
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
//#endregion
|
|
1517
|
+
//#region src/hooks/useToast.ts
|
|
1518
|
+
/**
|
|
1519
|
+
* Use this hook to access the Toast Service for showing notifications.
|
|
1520
|
+
*
|
|
1521
|
+
* @example
|
|
1522
|
+
* const toast = useToast();
|
|
1523
|
+
* toast.success({ message: "Operation completed successfully!" });
|
|
1524
|
+
* toast.error({ title: "Error", message: "Something went wrong" });
|
|
1525
|
+
*/
|
|
1526
|
+
const useToast = () => {
|
|
1527
|
+
return useInject(ToastService);
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region src/index.ts
|
|
1532
|
+
/**
|
|
1533
|
+
* Mantine
|
|
307
1534
|
*
|
|
308
1535
|
* @module alepha.ui
|
|
309
1536
|
*/
|
|
310
1537
|
const AlephaUI = $module({
|
|
311
1538
|
name: "alepha.ui",
|
|
312
|
-
services: [
|
|
1539
|
+
services: [
|
|
1540
|
+
AlephaReact,
|
|
1541
|
+
DialogService,
|
|
1542
|
+
ToastService,
|
|
1543
|
+
RootRouter
|
|
1544
|
+
]
|
|
313
1545
|
});
|
|
314
1546
|
|
|
315
1547
|
//#endregion
|
|
316
|
-
export { Action, AlephaMantineProvider, AlephaUI, Control };
|
|
1548
|
+
export { Action_default as Action, AlephaMantineProvider_default as AlephaMantineProvider, AlephaUI, AlertDialog, ConfirmDialog, Control_default as Control, ControlDate_default as ControlDate, ControlSelect_default as ControlSelect, DarkModeButton_default as DarkModeButton, DataTable, DialogService, Flex, ICON_SIZES, Omnibar_default as Omnibar, PromptDialog, RootRouter, Sidebar, SidebarItem, ToastService, TypeForm_default as TypeForm, capitalize, getDefaultIcon, prettyName, useDialog, useToast };
|
|
317
1549
|
//# sourceMappingURL=index.js.map
|