@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/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 { AlephaReact, NestedView, useActive, useAlepha, useRouter, useRouterEvents } from "@alepha/react";
3
- import "@mantine/core/styles.css";
4
- import "@mantine/nprogress/styles.css";
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 { ModalsProvider } from "@mantine/modals";
12
- import { Notifications } from "@mantine/notifications";
13
- import { NavigationProgress, nprogress } from "@mantine/nprogress";
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
- props.className ??= "mantine-Action-iconOnly";
23
- props.p ??= "xs";
304
+ restProps.className ??= "mantine-Action-iconOnly";
305
+ restProps.p ??= "xs";
24
306
  }
25
307
  if (props.textVisibleFrom) {
26
- const { children, textVisibleFrom, leftSection,...rest } = props;
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 props && props.href) return /* @__PURE__ */ jsx(ActionHref, {
48
- ...props,
49
- href: props.href,
50
- children: props.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 props && props.onClick) return /* @__PURE__ */ jsx(ActionClick, {
53
- ...props,
54
- onClick: props.onClick,
55
- children: props.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 props && props.form) return /* @__PURE__ */ jsx(ActionSubmit, {
58
- ...props,
59
- form: props.form,
60
- children: props.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
- ...props,
64
- children: props.children
349
+ ...restProps,
350
+ children: restProps.children
65
351
  });
66
352
  };
67
- return renderAction();
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 || !state.dirty,
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
- ...anchorProps,
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/components/AlephaMantineProvider.tsx
134
- const AlephaMantineProvider = (props) => {
135
- useRouterEvents({
136
- onBegin: () => {
137
- nprogress.start();
138
- },
139
- onEnd: () => {
140
- nprogress.complete();
141
- }
142
- });
143
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ColorSchemeScript, {
144
- defaultColorScheme: props.mantine?.defaultColorScheme,
145
- ...props.colorSchemeScript
146
- }), /* @__PURE__ */ jsxs(MantineProvider, {
147
- ...props.mantine,
148
- children: [
149
- /* @__PURE__ */ jsx(Notifications, { ...props.notifications }),
150
- /* @__PURE__ */ jsx(NavigationProgress, { ...props.navigationProgress }),
151
- /* @__PURE__ */ jsx(ModalsProvider, {
152
- ...props.modals,
153
- children: props.children ?? /* @__PURE__ */ jsx(NestedView, {})
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/components/Control.tsx
485
+ //#region src/utils/string.ts
161
486
  /**
162
- * Generic form control that renders the appropriate input based on the schema and props.
487
+ * Capitalizes the first letter of a string.
163
488
  *
164
- * Supports:
165
- * - TextInput
166
- * - Textarea
167
- * - Select (for enum types)
168
- * - Autocomplete
169
- * - PasswordInput
170
- * - Switch (for boolean types)
171
- * - SegmentedControl (for enum types)
172
- * - Custom component
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
- * Automatically handles labels, descriptions, error messages, and required state.
499
+ * @example
500
+ * prettyName("/userName") // "UserName"
501
+ * prettyName("email") // "Email"
175
502
  */
176
- const Control = (props) => {
177
- const form = useFormState(props.input);
178
- if (!props.input?.props) return null;
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 (props.custom) {
194
- const Custom = props.custom;
195
- return /* @__PURE__ */ jsx(Input.Wrapper, {
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
- children: /* @__PURE__ */ jsx(Flex, {
198
- flex: 1,
199
- mt: "calc(var(--mantine-spacing-xs) / 2)",
200
- children: /* @__PURE__ */ jsx(Custom, {
201
- defaultValue: props.input.props.defaultValue,
202
- onChange: (value) => {
203
- props.input.set(value);
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 (props.input.schema && "enum" in props.input.schema && props.input.schema.enum || props.select) {
242
- const data = props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum?.map((value) => ({
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 selectProps = typeof props.select === "object" ? props.select : {};
247
- return /* @__PURE__ */ jsx(Select, {
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
- ...selectProps
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
- const prettyName = (name) => {
297
- return capitalize(name.replaceAll("/", ""));
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
- const capitalize = (str) => {
300
- return str.charAt(0).toUpperCase() + str.slice(1);
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/index.ts
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: [AlephaReact]
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