@alepha/ui 0.10.5 → 0.10.7

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,17 +1,76 @@
1
+ import { n as Omnibar_default, t as AlephaMantineProvider_default } from "./AlephaMantineProvider-WfiC2EH6.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 { notifications } from "@mantine/notifications";
5
+ import { IconAlertTriangle, IconAt, IconCalendar, IconCheck, IconClock, IconColorPicker, IconFile, IconHash, IconInfoCircle, IconKey, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconSelector, IconSun, IconToggleLeft, IconX } from "@tabler/icons-react";
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 { ActionIcon, Autocomplete, Button, ColorInput, FileInput, Flex, Flex as Flex$1, Grid, Input, MultiSelect, NumberInput, PasswordInput, SegmentedControl, Select, Stack, Switch, TagsInput, TextInput, Textarea, useComputedColorScheme, useMantineColorScheme } from "@mantine/core";
8
+ import { useFormState } from "@alepha/react-form";
9
+ import { useEffect, useState } from "react";
10
+ import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
11
+
12
+ //#region src/RootRouter.ts
13
+ var RootRouter = class {
14
+ root = $page({
15
+ path: "/",
16
+ lazy: () => import("./AlephaMantineProvider-EemOtraW.js")
17
+ });
18
+ };
14
19
 
20
+ //#endregion
21
+ //#region src/services/ToastService.tsx
22
+ var ToastService = class {
23
+ raw = notifications;
24
+ options = { default: {
25
+ autoClose: 5e3,
26
+ withCloseButton: true,
27
+ position: "top-center"
28
+ } };
29
+ show(options) {
30
+ notifications.show({
31
+ ...this.options.default,
32
+ ...options
33
+ });
34
+ }
35
+ info(options) {
36
+ this.show({
37
+ color: "blue",
38
+ icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 20 }),
39
+ title: "Info",
40
+ message: "Information notification",
41
+ ...options
42
+ });
43
+ }
44
+ success(options) {
45
+ this.show({
46
+ color: "green",
47
+ icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }),
48
+ title: "Success",
49
+ message: "Operation completed successfully",
50
+ ...options
51
+ });
52
+ }
53
+ warning(options) {
54
+ this.show({
55
+ color: "yellow",
56
+ icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20 }),
57
+ title: "Warning",
58
+ message: "Please review this warning",
59
+ ...options
60
+ });
61
+ }
62
+ danger(options) {
63
+ this.show({
64
+ color: "red",
65
+ icon: /* @__PURE__ */ jsx(IconX, { size: 20 }),
66
+ title: "Error",
67
+ message: "An error occurred",
68
+ ...options
69
+ });
70
+ }
71
+ };
72
+
73
+ //#endregion
15
74
  //#region src/components/Action.tsx
16
75
  const Action = (_props) => {
17
76
  const props = {
@@ -24,7 +83,7 @@ const Action = (_props) => {
24
83
  }
25
84
  if (props.textVisibleFrom) {
26
85
  const { children, textVisibleFrom, leftSection,...rest } = props;
27
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex, {
86
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex$1, {
28
87
  w: "100%",
29
88
  visibleFrom: textVisibleFrom,
30
89
  children: /* @__PURE__ */ jsx(Action, {
@@ -33,7 +92,7 @@ const Action = (_props) => {
33
92
  leftSection,
34
93
  children
35
94
  })
36
- }), /* @__PURE__ */ jsx(Flex, {
95
+ }), /* @__PURE__ */ jsx(Flex$1, {
37
96
  w: "100%",
38
97
  hiddenFrom: textVisibleFrom,
39
98
  children: /* @__PURE__ */ jsx(Action, {
@@ -66,6 +125,7 @@ const Action = (_props) => {
66
125
  };
67
126
  return renderAction();
68
127
  };
128
+ var Action_default = Action;
69
129
  /**
70
130
  * Action button that submits a form with loading and disabled state handling.
71
131
  */
@@ -75,7 +135,7 @@ const ActionSubmit = (props) => {
75
135
  return /* @__PURE__ */ jsx(Button, {
76
136
  ...buttonProps,
77
137
  loading: state.loading,
78
- disabled: state.loading || !state.dirty,
138
+ disabled: state.loading,
79
139
  type: "submit",
80
140
  children: props.children
81
141
  });
@@ -118,11 +178,10 @@ const ActionHref = (props) => {
118
178
  href: props.href,
119
179
  ...options
120
180
  } : { href: props.href });
121
- const anchorProps = router.anchor(props.href, routerGoOptions);
122
181
  return /* @__PURE__ */ jsx(Button, {
123
182
  component: "a",
124
183
  loading: isPending,
125
- ...anchorProps,
184
+ ...router.anchor(props.href, routerGoOptions),
126
185
  ...buttonProps,
127
186
  variant: isActive && options !== false ? "filled" : "subtle",
128
187
  children: props.children
@@ -130,31 +189,210 @@ const ActionHref = (props) => {
130
189
  };
131
190
 
132
191
  //#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
- }
192
+ //#region src/utils/icons.tsx
193
+ /**
194
+ * Icon size presets following Mantine's size conventions
195
+ */
196
+ const ICON_SIZES = {
197
+ xs: 12,
198
+ sm: 16,
199
+ md: 20,
200
+ lg: 24,
201
+ xl: 28
202
+ };
203
+ /**
204
+ * Get the default icon for an input based on its type, format, or name.
205
+ */
206
+ const getDefaultIcon = (params) => {
207
+ const { type, format, name, isEnum, isArray, size = "sm" } = params;
208
+ const iconSize = ICON_SIZES[size];
209
+ if (format) switch (format) {
210
+ case "email": return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
211
+ case "url":
212
+ case "uri": return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
213
+ case "tel":
214
+ case "phone": return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
215
+ case "date": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
216
+ case "date-time": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
217
+ case "time": return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
218
+ case "color": return /* @__PURE__ */ jsx(IconColorPicker, { size: iconSize });
219
+ case "uuid": return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
220
+ }
221
+ if (name) {
222
+ const nameLower = name.toLowerCase();
223
+ if (nameLower.includes("password") || nameLower.includes("secret")) return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
224
+ if (nameLower.includes("email") || nameLower.includes("mail")) return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
225
+ if (nameLower.includes("url") || nameLower.includes("link")) return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
226
+ if (nameLower.includes("phone") || nameLower.includes("tel")) return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
227
+ if (nameLower.includes("color")) return /* @__PURE__ */ jsx(IconPalette, { size: iconSize });
228
+ if (nameLower.includes("file") || nameLower.includes("upload")) return /* @__PURE__ */ jsx(IconFile, { size: iconSize });
229
+ if (nameLower.includes("date")) return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
230
+ if (nameLower.includes("time")) return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
231
+ }
232
+ if (isEnum || isArray) return /* @__PURE__ */ jsx(IconSelector, { size: iconSize });
233
+ if (type) switch (type) {
234
+ case "boolean": return /* @__PURE__ */ jsx(IconToggleLeft, { size: iconSize });
235
+ case "number":
236
+ case "integer": return /* @__PURE__ */ jsx(IconHash, { size: iconSize });
237
+ case "array": return /* @__PURE__ */ jsx(IconList, { size: iconSize });
238
+ case "string": return /* @__PURE__ */ jsx(IconLetterCase, { size: iconSize });
239
+ }
240
+ return /* @__PURE__ */ jsx(IconAt, { size: iconSize });
241
+ };
242
+
243
+ //#endregion
244
+ //#region src/utils/string.ts
245
+ /**
246
+ * Capitalizes the first letter of a string.
247
+ *
248
+ * @example
249
+ * capitalize("hello") // "Hello"
250
+ */
251
+ const capitalize = (str) => {
252
+ return str.charAt(0).toUpperCase() + str.slice(1);
253
+ };
254
+ /**
255
+ * Converts a path or identifier string into a pretty display name.
256
+ * Removes slashes and capitalizes the first letter.
257
+ *
258
+ * @example
259
+ * prettyName("/userName") // "UserName"
260
+ * prettyName("email") // "Email"
261
+ */
262
+ const prettyName = (name) => {
263
+ return capitalize(name.replaceAll("/", ""));
264
+ };
265
+
266
+ //#endregion
267
+ //#region src/components/ControlDate.tsx
268
+ /**
269
+ * ControlDate component for handling date, datetime, and time inputs.
270
+ *
271
+ * Features:
272
+ * - DateInput for date format
273
+ * - DateTimePicker for date-time format
274
+ * - TimeInput for time format
275
+ *
276
+ * Automatically detects date formats from schema and renders appropriate picker.
277
+ */
278
+ const ControlDate = (props) => {
279
+ const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
280
+ if (!props.input?.props) return null;
281
+ const format = props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0;
282
+ if (props.datetime || format === "date-time") {
283
+ const dateTimePickerProps = typeof props.datetime === "object" ? props.datetime : {};
284
+ return /* @__PURE__ */ jsx(DateTimePicker, {
285
+ ...inputProps,
286
+ id,
287
+ leftSection: icon,
288
+ defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
289
+ onChange: (value) => {
290
+ props.input.set(value ? new Date(value).toISOString() : void 0);
291
+ },
292
+ ...dateTimePickerProps
293
+ });
294
+ }
295
+ if (props.date || format === "date") {
296
+ const dateInputProps = typeof props.date === "object" ? props.date : {};
297
+ return /* @__PURE__ */ jsx(DateInput, {
298
+ ...inputProps,
299
+ id,
300
+ leftSection: icon,
301
+ defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
302
+ onChange: (value) => {
303
+ props.input.set(value ? new Date(value).toISOString().slice(0, 10) : void 0);
304
+ },
305
+ ...dateInputProps
306
+ });
307
+ }
308
+ if (props.time || format === "time") {
309
+ const timeInputProps = typeof props.time === "object" ? props.time : {};
310
+ return /* @__PURE__ */ jsx(TimeInput, {
311
+ ...inputProps,
312
+ id,
313
+ leftSection: icon,
314
+ defaultValue: props.input.props.defaultValue,
315
+ onChange: (event) => {
316
+ props.input.set(event.currentTarget.value);
317
+ },
318
+ ...timeInputProps
319
+ });
320
+ }
321
+ return null;
322
+ };
323
+ var ControlDate_default = ControlDate;
324
+
325
+ //#endregion
326
+ //#region src/components/ControlSelect.tsx
327
+ /**
328
+ * ControlSelect component for handling Select, MultiSelect, and TagsInput.
329
+ *
330
+ * Features:
331
+ * - Basic Select with enum support
332
+ * - MultiSelect for array of enums
333
+ * - TagsInput for array of strings (no enum)
334
+ * - Future: Lazy loading
335
+ * - Future: Searchable/filterable options
336
+ * - Future: Custom option rendering
337
+ *
338
+ * Automatically detects enum values and array types from schema.
339
+ */
340
+ const ControlSelect = (props) => {
341
+ const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
342
+ if (!props.input?.props) return null;
343
+ const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
344
+ let itemsEnum;
345
+ if (isArray && "items" in props.input.schema && props.input.schema.items) {
346
+ const items = props.input.schema.items;
347
+ if ("enum" in items && Array.isArray(items.enum)) itemsEnum = items.enum;
348
+ }
349
+ const enumValues = props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum : [];
350
+ if (isArray && !itemsEnum || props.tags) {
351
+ const tagsInputProps = typeof props.tags === "object" ? props.tags : {};
352
+ return /* @__PURE__ */ jsx(TagsInput, {
353
+ ...inputProps,
354
+ id,
355
+ leftSection: icon,
356
+ defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
357
+ onChange: (value) => {
358
+ props.input.set(value);
359
+ },
360
+ ...tagsInputProps
361
+ });
362
+ }
363
+ if (isArray && itemsEnum || props.multi) {
364
+ const data$1 = itemsEnum?.map((value) => ({
365
+ value,
366
+ label: value
367
+ })) || [];
368
+ const multiSelectProps = typeof props.multi === "object" ? props.multi : {};
369
+ return /* @__PURE__ */ jsx(MultiSelect, {
370
+ ...inputProps,
371
+ id,
372
+ leftSection: icon,
373
+ data: data$1,
374
+ defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
375
+ onChange: (value) => {
376
+ props.input.set(value);
377
+ },
378
+ ...multiSelectProps
379
+ });
380
+ }
381
+ const data = enumValues.map((value) => ({
382
+ value,
383
+ label: value
384
+ }));
385
+ const selectProps = typeof props.select === "object" ? props.select : {};
386
+ return /* @__PURE__ */ jsx(Select, {
387
+ ...inputProps,
388
+ id,
389
+ leftSection: icon,
390
+ data,
391
+ ...props.input.props,
392
+ ...selectProps
142
393
  });
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
- })] });
157
394
  };
395
+ var ControlSelect_default = ControlSelect;
158
396
 
159
397
  //#endregion
160
398
  //#region src/components/Control.tsx
@@ -162,39 +400,32 @@ const AlephaMantineProvider = (props) => {
162
400
  * Generic form control that renders the appropriate input based on the schema and props.
163
401
  *
164
402
  * Supports:
165
- * - TextInput
403
+ * - TextInput (with format detection: email, url, tel)
166
404
  * - Textarea
405
+ * - NumberInput (for number/integer types)
406
+ * - FileInput
407
+ * - ColorInput (for color format)
167
408
  * - Select (for enum types)
168
409
  * - Autocomplete
169
410
  * - PasswordInput
170
411
  * - Switch (for boolean types)
171
412
  * - SegmentedControl (for enum types)
413
+ * - DateInput (for date format)
414
+ * - DateTimePicker (for date-time format)
415
+ * - TimeInput (for time format)
172
416
  * - Custom component
173
417
  *
174
- * Automatically handles labels, descriptions, error messages, and required state.
418
+ * Automatically handles labels, descriptions, error messages, required state, and default icons.
175
419
  */
176
420
  const Control = (props) => {
177
- const form = useFormState(props.input);
421
+ const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
178
422
  if (!props.input?.props) return null;
179
- const disabled = false;
180
- const id = props.input.props.id;
181
- 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
- const description = props.description ?? ("description" in props.input.schema && typeof props.input.schema.description === "string" ? props.input.schema.description : void 0);
183
- const error = form.error && form.error instanceof TypeBoxError ? form.error.value.message : void 0;
184
- const icon = props.icon;
185
- const required = props.input.required;
186
- const inputProps = {
187
- label,
188
- description,
189
- error,
190
- required,
191
- disabled
192
- };
423
+ const format = props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0;
193
424
  if (props.custom) {
194
425
  const Custom = props.custom;
195
426
  return /* @__PURE__ */ jsx(Input.Wrapper, {
196
427
  ...inputProps,
197
- children: /* @__PURE__ */ jsx(Flex, {
428
+ children: /* @__PURE__ */ jsx(Flex$1, {
198
429
  flex: 1,
199
430
  mt: "calc(var(--mantine-spacing-xs) / 2)",
200
431
  children: /* @__PURE__ */ jsx(Custom, {
@@ -206,6 +437,39 @@ const Control = (props) => {
206
437
  })
207
438
  });
208
439
  }
440
+ if (props.number || props.input.schema && "type" in props.input.schema && (props.input.schema.type === "number" || props.input.schema.type === "integer")) {
441
+ const numberInputProps = typeof props.number === "object" ? props.number : {};
442
+ const { type,...inputPropsWithoutType } = props.input.props;
443
+ return /* @__PURE__ */ jsx(NumberInput, {
444
+ ...inputProps,
445
+ id,
446
+ leftSection: icon,
447
+ ...inputPropsWithoutType,
448
+ ...numberInputProps
449
+ });
450
+ }
451
+ if (props.file) {
452
+ const fileInputProps = typeof props.file === "object" ? props.file : {};
453
+ return /* @__PURE__ */ jsx(FileInput, {
454
+ ...inputProps,
455
+ id,
456
+ leftSection: icon,
457
+ onChange: (file) => {
458
+ props.input.set(file);
459
+ },
460
+ ...fileInputProps
461
+ });
462
+ }
463
+ if (props.color || format === "color") {
464
+ const colorInputProps = typeof props.color === "object" ? props.color : {};
465
+ return /* @__PURE__ */ jsx(ColorInput, {
466
+ ...inputProps,
467
+ id,
468
+ leftSection: icon,
469
+ ...props.input.props,
470
+ ...colorInputProps
471
+ });
472
+ }
209
473
  if (props.segmented) {
210
474
  const segmentedControlProps = typeof props.segmented === "object" ? props.segmented : {};
211
475
  const data = segmentedControlProps.data ?? (props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum?.map((value) => ({
@@ -214,10 +478,10 @@ const Control = (props) => {
214
478
  })) : []);
215
479
  return /* @__PURE__ */ jsx(Input.Wrapper, {
216
480
  ...inputProps,
217
- children: /* @__PURE__ */ jsx(Flex, {
481
+ children: /* @__PURE__ */ jsx(Flex$1, {
218
482
  mt: "calc(var(--mantine-spacing-xs) / 2)",
219
483
  children: /* @__PURE__ */ jsx(SegmentedControl, {
220
- disabled,
484
+ disabled: inputProps.disabled,
221
485
  defaultValue: String(props.input.props.defaultValue),
222
486
  ...segmentedControlProps,
223
487
  onChange: (value) => {
@@ -238,21 +502,15 @@ const Control = (props) => {
238
502
  ...autocompleteProps
239
503
  });
240
504
  }
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) => ({
243
- value,
244
- label: value
245
- })) : [];
246
- const selectProps = typeof props.select === "object" ? props.select : {};
247
- return /* @__PURE__ */ jsx(Select, {
248
- ...inputProps,
249
- id,
250
- leftSection: icon,
251
- data,
252
- ...props.input.props,
253
- ...selectProps
254
- });
255
- }
505
+ const isEnum = props.input.schema && "enum" in props.input.schema && props.input.schema.enum;
506
+ const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
507
+ if (isEnum || isArray || props.select) return /* @__PURE__ */ jsx(ControlSelect_default, {
508
+ input: props.input,
509
+ title: props.title,
510
+ description: props.description,
511
+ icon,
512
+ select: props.select
513
+ });
256
514
  if (props.input.schema && "type" in props.input.schema && props.input.schema.type === "boolean" || props.switch) {
257
515
  const switchProps = typeof props.switch === "object" ? props.switch : {};
258
516
  return /* @__PURE__ */ jsx(Switch, {
@@ -264,7 +522,7 @@ const Control = (props) => {
264
522
  ...switchProps
265
523
  });
266
524
  }
267
- if (props.password) {
525
+ if (props.password || props.input.props.name?.includes("password")) {
268
526
  const passwordInputProps = typeof props.password === "object" ? props.password : {};
269
527
  return /* @__PURE__ */ jsx(PasswordInput, {
270
528
  ...inputProps,
@@ -284,20 +542,197 @@ const Control = (props) => {
284
542
  ...textAreaProps
285
543
  });
286
544
  }
545
+ if (props.date || props.datetime || props.time || format === "date" || format === "date-time" || format === "time") return /* @__PURE__ */ jsx(ControlDate_default, {
546
+ input: props.input,
547
+ title: props.title,
548
+ description: props.description,
549
+ icon,
550
+ date: props.date,
551
+ datetime: props.datetime,
552
+ time: props.time
553
+ });
287
554
  const textInputProps = typeof props.text === "object" ? props.text : {};
555
+ const getInputType = () => {
556
+ switch (format) {
557
+ case "email": return "email";
558
+ case "url":
559
+ case "uri": return "url";
560
+ case "tel":
561
+ case "phone": return "tel";
562
+ default: return;
563
+ }
564
+ };
288
565
  return /* @__PURE__ */ jsx(TextInput, {
289
566
  ...inputProps,
290
567
  id,
291
568
  leftSection: icon,
569
+ type: getInputType(),
292
570
  ...props.input.props,
293
571
  ...textInputProps
294
572
  });
295
573
  };
296
- const prettyName = (name) => {
297
- return capitalize(name.replaceAll("/", ""));
574
+ var Control_default = Control;
575
+ const parseInput = (props, form) => {
576
+ const disabled = false;
577
+ const id = props.input.props.id;
578
+ 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);
579
+ const description = props.description ?? ("description" in props.input.schema && typeof props.input.schema.description === "string" ? props.input.schema.description : void 0);
580
+ const error = form.error && form.error instanceof TypeBoxError ? form.error.value.message : void 0;
581
+ return {
582
+ id,
583
+ icon: props.icon ?? getDefaultIcon({
584
+ type: props.input.schema && "type" in props.input.schema ? String(props.input.schema.type) : void 0,
585
+ format: props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0,
586
+ name: props.input.props.name,
587
+ isEnum: props.input.schema && "enum" in props.input.schema && Boolean(props.input.schema.enum),
588
+ isArray: props.input.schema && "type" in props.input.schema && props.input.schema.type === "array"
589
+ }),
590
+ inputProps: {
591
+ label,
592
+ description,
593
+ error,
594
+ required: props.input.required,
595
+ disabled
596
+ }
597
+ };
298
598
  };
299
- const capitalize = (str) => {
300
- return str.charAt(0).toUpperCase() + str.slice(1);
599
+
600
+ //#endregion
601
+ //#region src/components/DarkModeButton.tsx
602
+ const DarkModeButton = (props) => {
603
+ const { setColorScheme } = useMantineColorScheme();
604
+ const computedColorScheme = useComputedColorScheme("light");
605
+ const [colorScheme, setColorScheme2] = useState("default");
606
+ useEffect(() => {
607
+ setColorScheme2(computedColorScheme);
608
+ }, [computedColorScheme]);
609
+ const mode = props.mode ?? "minimal";
610
+ const toggleColorScheme = () => {
611
+ setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
612
+ };
613
+ if (mode === "segmented") return /* @__PURE__ */ jsx(SegmentedControl, {
614
+ value: colorScheme,
615
+ onChange: (value) => setColorScheme(value),
616
+ data: [{
617
+ value: "light",
618
+ label: /* @__PURE__ */ jsx(Flex$1, {
619
+ h: 20,
620
+ align: "center",
621
+ justify: "center",
622
+ children: /* @__PURE__ */ jsx(IconSun, { size: 16 })
623
+ })
624
+ }, {
625
+ value: "dark",
626
+ label: /* @__PURE__ */ jsx(Flex$1, {
627
+ h: 20,
628
+ align: "center",
629
+ justify: "center",
630
+ children: /* @__PURE__ */ jsx(IconMoon, { size: 16 })
631
+ })
632
+ }]
633
+ });
634
+ return /* @__PURE__ */ jsx(ActionIcon, {
635
+ onClick: toggleColorScheme,
636
+ variant: props.variant ?? "default",
637
+ size: props.size ?? "lg",
638
+ "aria-label": "Toggle color scheme",
639
+ children: colorScheme === "dark" ? /* @__PURE__ */ jsx(IconSun, { size: 20 }) : /* @__PURE__ */ jsx(IconMoon, { size: 20 })
640
+ });
641
+ };
642
+ var DarkModeButton_default = DarkModeButton;
643
+
644
+ //#endregion
645
+ //#region src/components/TypeForm.tsx
646
+ /**
647
+ * TypeForm component that automatically renders all form inputs based on schema.
648
+ * Uses the Control component to render individual fields and Mantine Grid for responsive layout.
649
+ *
650
+ * @example
651
+ * ```tsx
652
+ * import { t } from "alepha";
653
+ * import { useForm } from "@alepha/react-form";
654
+ * import { TypeForm } from "@alepha/ui";
655
+ *
656
+ * const form = useForm({
657
+ * schema: t.object({
658
+ * username: t.text(),
659
+ * email: t.text(),
660
+ * age: t.integer(),
661
+ * subscribe: t.boolean(),
662
+ * }),
663
+ * handler: (values) => {
664
+ * console.log(values);
665
+ * },
666
+ * });
667
+ *
668
+ * return <TypeForm form={form} columns={2} />;
669
+ * ```
670
+ */
671
+ const TypeForm = (props) => {
672
+ const { form, columns = 1, children, controlProps, skipFormElement = false, skipSubmitButton = false, submitButtonProps } = props;
673
+ if (!form.options?.schema?.properties) return null;
674
+ const supportedFields = Object.keys(form.options.schema.properties).filter((fieldName) => {
675
+ const field = form.input[fieldName];
676
+ if (!field || typeof field !== "object" || !("schema" in field)) return false;
677
+ const schema = field.schema;
678
+ if ("type" in schema) {
679
+ if (schema.type === "object") return false;
680
+ }
681
+ if ("properties" in schema && schema.properties) return false;
682
+ return true;
683
+ });
684
+ const colSpan = typeof columns === "number" ? {
685
+ xs: 12,
686
+ sm: 6,
687
+ lg: 12 / 3
688
+ } : {
689
+ base: columns.base ? 12 / columns.base : void 0,
690
+ xs: columns.xs ? 12 / columns.xs : 12,
691
+ sm: columns.sm ? 12 / columns.sm : 6,
692
+ md: columns.md ? 12 / columns.md : void 0,
693
+ lg: columns.lg ? 12 / columns.lg : 4,
694
+ xl: columns.xl ? 12 / columns.xl : void 0
695
+ };
696
+ const renderFields = () => {
697
+ if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(form.input) });
698
+ return /* @__PURE__ */ jsx(Grid, { children: supportedFields.map((fieldName) => {
699
+ const field = form.input[fieldName];
700
+ if (!field || typeof field !== "object" || !("schema" in field)) return null;
701
+ return /* @__PURE__ */ jsx(Grid.Col, {
702
+ span: colSpan,
703
+ children: /* @__PURE__ */ jsx(Control_default, {
704
+ input: field,
705
+ ...controlProps
706
+ })
707
+ }, fieldName);
708
+ }) });
709
+ };
710
+ const content = /* @__PURE__ */ jsxs(Stack, { children: [renderFields(), !skipSubmitButton && /* @__PURE__ */ jsx(Action_default, {
711
+ form,
712
+ ...submitButtonProps,
713
+ children: submitButtonProps?.children ?? "Submit"
714
+ })] });
715
+ if (skipFormElement) return content;
716
+ return /* @__PURE__ */ jsx("form", {
717
+ onSubmit: form.onSubmit,
718
+ noValidate: true,
719
+ children: content
720
+ });
721
+ };
722
+ var TypeForm_default = TypeForm;
723
+
724
+ //#endregion
725
+ //#region src/hooks/useToast.ts
726
+ /**
727
+ * Use this hook to access the Toast Service for showing notifications.
728
+ *
729
+ * @example
730
+ * const toast = useToast();
731
+ * toast.success({ message: "Operation completed successfully!" });
732
+ * toast.error({ title: "Error", message: "Something went wrong" });
733
+ */
734
+ const useToast = () => {
735
+ return useInject(ToastService);
301
736
  };
302
737
 
303
738
  //#endregion
@@ -309,9 +744,13 @@ const capitalize = (str) => {
309
744
  */
310
745
  const AlephaUI = $module({
311
746
  name: "alepha.ui",
312
- services: [AlephaReact]
747
+ services: [
748
+ AlephaReact,
749
+ ToastService,
750
+ RootRouter
751
+ ]
313
752
  });
314
753
 
315
754
  //#endregion
316
- export { Action, AlephaMantineProvider, AlephaUI, Control };
755
+ export { Action_default as Action, AlephaMantineProvider_default as AlephaMantineProvider, AlephaUI, Control_default as Control, ControlDate_default as ControlDate, ControlSelect_default as ControlSelect, DarkModeButton_default as DarkModeButton, Flex, ICON_SIZES, Omnibar_default as Omnibar, RootRouter, ToastService, TypeForm_default as TypeForm, capitalize, getDefaultIcon, prettyName, useToast };
317
756
  //# sourceMappingURL=index.js.map