@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.
@@ -0,0 +1,13 @@
1
+ import { Button, Group, Text } from "@mantine/core";
2
+ import type { AlertDialogProps } from "../../services/DialogService";
3
+
4
+ export function AlertDialog({ options, onClose }: AlertDialogProps) {
5
+ return (
6
+ <>
7
+ {options?.message && <Text mb="md">{options.message}</Text>}
8
+ <Group justify="flex-end">
9
+ <Button onClick={onClose}>{options?.okLabel || "OK"}</Button>
10
+ </Group>
11
+ </>
12
+ );
13
+ }
@@ -0,0 +1,21 @@
1
+ import { Button, Group, Text } from "@mantine/core";
2
+ import type { ConfirmDialogProps } from "../../services/DialogService";
3
+
4
+ export function ConfirmDialog({ options, onConfirm }: ConfirmDialogProps) {
5
+ return (
6
+ <>
7
+ {options?.message && <Text mb="md">{options.message}</Text>}
8
+ <Group justify="flex-end">
9
+ <Button variant="subtle" onClick={() => onConfirm(false)}>
10
+ {options?.cancelLabel || "Cancel"}
11
+ </Button>
12
+ <Button
13
+ color={options?.confirmColor || "blue"}
14
+ onClick={() => onConfirm(true)}
15
+ >
16
+ {options?.confirmLabel || "Confirm"}
17
+ </Button>
18
+ </Group>
19
+ </>
20
+ );
21
+ }
@@ -0,0 +1,52 @@
1
+ import { Button, Group, Text, TextInput } from "@mantine/core";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import type { PromptDialogProps } from "../../services/DialogService";
4
+
5
+ export function PromptDialog({ options, onSubmit }: PromptDialogProps) {
6
+ const [value, setValue] = useState(options?.defaultValue || "");
7
+ const inputRef = useRef<HTMLInputElement>(null);
8
+
9
+ useEffect(() => {
10
+ // Auto-focus the input when the dialog opens
11
+ inputRef.current?.focus();
12
+ }, []);
13
+
14
+ const handleSubmit = () => {
15
+ if (!options?.required || value.trim()) {
16
+ onSubmit(value);
17
+ }
18
+ };
19
+
20
+ const handleKeyDown = (event: React.KeyboardEvent) => {
21
+ if (event.key === "Enter") {
22
+ handleSubmit();
23
+ }
24
+ };
25
+
26
+ return (
27
+ <>
28
+ {options?.message && <Text mb="md">{options.message}</Text>}
29
+ <TextInput
30
+ ref={inputRef}
31
+ label={options?.label}
32
+ placeholder={options?.placeholder}
33
+ value={value}
34
+ onChange={(event) => setValue(event.currentTarget.value)}
35
+ onKeyDown={handleKeyDown}
36
+ required={options?.required}
37
+ mb="md"
38
+ />
39
+ <Group justify="flex-end">
40
+ <Button variant="subtle" onClick={() => onSubmit(null)}>
41
+ {options?.cancelLabel || "Cancel"}
42
+ </Button>
43
+ <Button
44
+ onClick={handleSubmit}
45
+ disabled={options?.required && !value.trim()}
46
+ >
47
+ {options?.submitLabel || "OK"}
48
+ </Button>
49
+ </Group>
50
+ </>
51
+ );
52
+ }
@@ -0,0 +1,15 @@
1
+ import { useInject } from "@alepha/react";
2
+ import { DialogService } from "../services/DialogService.tsx";
3
+
4
+ /**
5
+ * Use this hook to access the Dialog Service for showing various dialog types.
6
+ *
7
+ * @example
8
+ * const dialog = useDialog();
9
+ * await dialog.alert({ title: "Alert", message: "This is an alert message" });
10
+ * const confirmed = await dialog.confirm({ title: "Confirm", message: "Are you sure?" });
11
+ * const input = await dialog.prompt({ title: "Input", message: "Enter your name:" });
12
+ */
13
+ export const useDialog = (): DialogService => {
14
+ return useInject(DialogService);
15
+ };
@@ -0,0 +1,14 @@
1
+ import { useInject } from "@alepha/react";
2
+ import { ToastService } from "../services/ToastService.tsx";
3
+
4
+ /**
5
+ * Use this hook to access the Toast Service for showing notifications.
6
+ *
7
+ * @example
8
+ * const toast = useToast();
9
+ * toast.success({ message: "Operation completed successfully!" });
10
+ * toast.error({ title: "Error", message: "Something went wrong" });
11
+ */
12
+ export const useToast = (): ToastService => {
13
+ return useInject(ToastService);
14
+ };
package/src/index.ts CHANGED
@@ -1,26 +1,70 @@
1
1
  import { $module } from "@alepha/core";
2
2
  import { AlephaReact } from "@alepha/react";
3
+ import type { ControlProps } from "./components/Control.tsx";
4
+ import { RootRouter } from "./RootRouter.ts";
5
+ import { DialogService } from "./services/DialogService.tsx";
6
+ import { ToastService } from "./services/ToastService.tsx";
3
7
 
4
8
  // ---------------------------------------------------------------------------------------------------------------------
5
- import "@mantine/core/styles.css";
6
- import "@mantine/nprogress/styles.css";
7
- import "@mantine/spotlight/styles.css";
8
- import "@mantine/notifications/styles.css";
9
+
10
+ export { Flex } from "@mantine/core";
11
+ export { default as Action } from "./components/Action.tsx";
12
+ export { default as AlephaMantineProvider } from "./components/AlephaMantineProvider.tsx";
13
+ export { default as Control } from "./components/Control.tsx";
14
+ export { default as ControlDate } from "./components/ControlDate.tsx";
15
+ export { default as ControlSelect } from "./components/ControlSelect.tsx";
16
+ export { default as DarkModeButton } from "./components/DarkModeButton.tsx";
17
+ export type {
18
+ DataTableColumn,
19
+ DataTableFilter,
20
+ DataTableProps,
21
+ DataTableSort,
22
+ } from "./components/DataTable.tsx";
23
+ export { default as DataTable } from "./components/DataTable.tsx";
24
+ export { AlertDialog } from "./components/dialogs/AlertDialog.tsx";
25
+ export { ConfirmDialog } from "./components/dialogs/ConfirmDialog.tsx";
26
+ export { PromptDialog } from "./components/dialogs/PromptDialog.tsx";
27
+ export { default as Omnibar } from "./components/Omnibar.tsx";
28
+ export type {
29
+ MenuItem,
30
+ SidebarItemProps,
31
+ SidebarProps,
32
+ } from "./components/Sidebar.tsx";
33
+ export { Sidebar, SidebarItem } from "./components/Sidebar.tsx";
34
+ export { default as TypeForm } from "./components/TypeForm.tsx";
35
+ export { useDialog } from "./hooks/useDialog.ts";
36
+ export { useToast } from "./hooks/useToast.ts";
37
+ export * from "./RootRouter.ts";
38
+ export type {
39
+ AlertDialogOptions,
40
+ AlertDialogProps,
41
+ BaseDialogOptions,
42
+ ConfirmDialogOptions,
43
+ ConfirmDialogProps,
44
+ PromptDialogOptions,
45
+ PromptDialogProps,
46
+ } from "./services/DialogService.tsx";
47
+ export { DialogService } from "./services/DialogService.tsx";
48
+ export { ToastService } from "./services/ToastService.tsx";
49
+ export * from "./utils/icons.tsx";
50
+ export * from "./utils/string.ts";
9
51
 
10
52
  // ---------------------------------------------------------------------------------------------------------------------
11
53
 
12
- export { default as Action } from "./components/Action";
13
- export { default as AlephaMantineProvider } from "./components/AlephaMantineProvider.tsx";
14
- export { default as Control } from "./components/Control";
54
+ declare module "typebox" {
55
+ interface TSchemaOptions {
56
+ $control?: Omit<ControlProps, "input">;
57
+ }
58
+ }
15
59
 
16
60
  // ---------------------------------------------------------------------------------------------------------------------
17
61
 
18
62
  /**
19
- *
63
+ * Mantine
20
64
  *
21
65
  * @module alepha.ui
22
66
  */
23
67
  export const AlephaUI = $module({
24
- name: "alepha.ui",
25
- services: [AlephaReact],
68
+ name: "alepha.ui",
69
+ services: [AlephaReact, DialogService, ToastService, RootRouter],
26
70
  });
@@ -0,0 +1,207 @@
1
+ import type { ModalProps } from "@mantine/core";
2
+ import { modals } from "@mantine/modals";
3
+ import type { ReactNode } from "react";
4
+ import { AlertDialog } from "../components/dialogs/AlertDialog";
5
+ import { ConfirmDialog } from "../components/dialogs/ConfirmDialog";
6
+ import { PromptDialog } from "../components/dialogs/PromptDialog";
7
+
8
+ // Base interfaces
9
+ export interface BaseDialogOptions extends Partial<ModalProps> {
10
+ title?: ReactNode;
11
+ message?: ReactNode;
12
+ content?: any; // weird typing for mantine modals content
13
+ }
14
+
15
+ export interface AlertDialogOptions extends BaseDialogOptions {
16
+ okLabel?: string;
17
+ }
18
+
19
+ export interface ConfirmDialogOptions extends BaseDialogOptions {
20
+ confirmLabel?: string;
21
+ cancelLabel?: string;
22
+ confirmColor?: string;
23
+ }
24
+
25
+ export interface PromptDialogOptions extends BaseDialogOptions {
26
+ placeholder?: string;
27
+ defaultValue?: string;
28
+ label?: string;
29
+ required?: boolean;
30
+ submitLabel?: string;
31
+ cancelLabel?: string;
32
+ }
33
+
34
+ // Component prop interfaces
35
+ export interface AlertDialogProps {
36
+ options?: AlertDialogOptions;
37
+ onClose: () => void;
38
+ }
39
+
40
+ export interface ConfirmDialogProps {
41
+ options?: ConfirmDialogOptions;
42
+ onConfirm: (confirmed: boolean) => void;
43
+ }
44
+
45
+ export interface PromptDialogProps {
46
+ options?: PromptDialogOptions;
47
+ onSubmit: (value: string | null) => void;
48
+ }
49
+
50
+ export interface DialogServiceOptions {
51
+ default?: Partial<BaseDialogOptions>;
52
+ }
53
+
54
+ export class DialogService {
55
+ public readonly options: DialogServiceOptions = {
56
+ default: {
57
+ centered: true,
58
+ withCloseButton: true,
59
+ size: "md",
60
+ overlayProps: {
61
+ backgroundOpacity: 0.55,
62
+ blur: 3,
63
+ },
64
+ transitionProps: {
65
+ transition: "pop",
66
+ duration: 200,
67
+ },
68
+ },
69
+ };
70
+
71
+ /**
72
+ * Show an alert dialog with a message
73
+ */
74
+ public alert(options?: AlertDialogOptions): Promise<void> {
75
+ return new Promise((resolve) => {
76
+ const modalId = this.open({
77
+ ...options,
78
+ title: options?.title || "Alert",
79
+ content: (
80
+ <AlertDialog
81
+ options={options}
82
+ onClose={() => {
83
+ this.close(modalId);
84
+ resolve();
85
+ }}
86
+ />
87
+ ),
88
+ });
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Show a confirmation dialog that returns a promise
94
+ */
95
+ public confirm(options?: ConfirmDialogOptions): Promise<boolean> {
96
+ return new Promise((resolve) => {
97
+ const modalId = this.open({
98
+ ...options,
99
+ title: options?.title || "Confirm",
100
+ closeOnClickOutside: false,
101
+ closeOnEscape: false,
102
+ content: (
103
+ <ConfirmDialog
104
+ options={options}
105
+ onConfirm={(confirmed) => {
106
+ this.close(modalId);
107
+ resolve(confirmed);
108
+ }}
109
+ />
110
+ ),
111
+ });
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Show a prompt dialog to get user input
117
+ */
118
+ public prompt(options?: PromptDialogOptions): Promise<string | null> {
119
+ return new Promise((resolve) => {
120
+ const modalId = this.open({
121
+ ...options,
122
+ title: options?.title || "Input",
123
+ closeOnClickOutside: false,
124
+ closeOnEscape: false,
125
+ content: (
126
+ <PromptDialog
127
+ options={options}
128
+ onSubmit={(value) => {
129
+ this.close(modalId);
130
+ resolve(value);
131
+ }}
132
+ />
133
+ ),
134
+ });
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Open a custom dialog with provided content
140
+ */
141
+ public open(options?: BaseDialogOptions): string {
142
+ const modalId = modals.open({
143
+ ...this.options.default,
144
+ ...options,
145
+ children: options?.content || options?.message,
146
+ });
147
+ return modalId;
148
+ }
149
+
150
+ /**
151
+ * Show a JSON editor/viewer dialog
152
+ */
153
+ public json(data?: any, options?: BaseDialogOptions): void {
154
+ // Implementation to be added
155
+ }
156
+
157
+ /**
158
+ * Show a form dialog for structured input
159
+ */
160
+ public form(options?: BaseDialogOptions): Promise<any> {
161
+ // Implementation to be added
162
+ return Promise.resolve(null);
163
+ }
164
+
165
+ /**
166
+ * Close the currently open dialog or a specific dialog by ID
167
+ */
168
+ public close(modalId?: string): void {
169
+ if (modalId) {
170
+ modals.close(modalId);
171
+ } else {
172
+ modals.closeAll();
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Show a loading/progress dialog with optional progress percentage
178
+ */
179
+ public loading(options?: BaseDialogOptions & { progress?: number }): void {
180
+ // Implementation to be added
181
+ }
182
+
183
+ /**
184
+ * Show an image viewer/gallery dialog
185
+ */
186
+ public image(src: string | string[], options?: BaseDialogOptions): void {
187
+ // Implementation to be added
188
+ }
189
+
190
+ /**
191
+ * Show a table/data grid dialog for displaying tabular data
192
+ */
193
+ public table(
194
+ data: any[],
195
+ options?: BaseDialogOptions & { columns?: any[] },
196
+ ): void {
197
+ // Implementation to be added
198
+ }
199
+
200
+ /**
201
+ * Show a multi-step wizard dialog
202
+ */
203
+ public wizard(steps: any[], options?: BaseDialogOptions): Promise<any> {
204
+ // Implementation to be added
205
+ return Promise.resolve(null);
206
+ }
207
+ }
@@ -0,0 +1,71 @@
1
+ import type { NotificationData } from "@mantine/notifications";
2
+ import { notifications } from "@mantine/notifications";
3
+ import {
4
+ IconAlertTriangle,
5
+ IconCheck,
6
+ IconInfoCircle,
7
+ IconX,
8
+ } from "@tabler/icons-react";
9
+
10
+ export interface ToastServiceOptions {
11
+ default?: Partial<NotificationData>;
12
+ }
13
+
14
+ export class ToastService {
15
+ protected readonly raw = notifications;
16
+
17
+ public readonly options: ToastServiceOptions = {
18
+ default: {
19
+ autoClose: 5000,
20
+ withCloseButton: true,
21
+ position: "top-center",
22
+ },
23
+ };
24
+
25
+ public show(options: NotificationData) {
26
+ notifications.show({
27
+ ...this.options.default,
28
+ ...options,
29
+ });
30
+ }
31
+
32
+ public info(options: Partial<NotificationData>) {
33
+ this.show({
34
+ color: "blue",
35
+ icon: <IconInfoCircle size={20} />,
36
+ title: "Info",
37
+ message: "Information notification",
38
+ ...options,
39
+ });
40
+ }
41
+
42
+ public success(options: Partial<NotificationData>) {
43
+ this.show({
44
+ color: "green",
45
+ icon: <IconCheck size={16} />,
46
+ title: "Success",
47
+ message: "Operation completed successfully",
48
+ ...options,
49
+ });
50
+ }
51
+
52
+ public warning(options: Partial<NotificationData>) {
53
+ this.show({
54
+ color: "yellow",
55
+ icon: <IconAlertTriangle size={20} />,
56
+ title: "Warning",
57
+ message: "Please review this warning",
58
+ ...options,
59
+ });
60
+ }
61
+
62
+ public danger(options: Partial<NotificationData>) {
63
+ this.show({
64
+ color: "red",
65
+ icon: <IconX size={20} />,
66
+ title: "Error",
67
+ message: "An error occurred",
68
+ ...options,
69
+ });
70
+ }
71
+ }
@@ -0,0 +1,121 @@
1
+ import {
2
+ IconAt,
3
+ IconCalendar,
4
+ IconClock,
5
+ IconColorPicker,
6
+ IconFile,
7
+ IconHash,
8
+ IconKey,
9
+ IconLetterCase,
10
+ IconLink,
11
+ IconList,
12
+ IconMail,
13
+ IconPalette,
14
+ IconPhone,
15
+ IconSelector,
16
+ IconToggleLeft,
17
+ } from "@tabler/icons-react";
18
+ import type { ReactNode } from "react";
19
+
20
+ /**
21
+ * Icon size presets following Mantine's size conventions
22
+ */
23
+ export const ICON_SIZES = {
24
+ xs: 12,
25
+ sm: 16,
26
+ md: 20,
27
+ lg: 24,
28
+ xl: 28,
29
+ } as const;
30
+
31
+ export type IconSize = keyof typeof ICON_SIZES;
32
+
33
+ /**
34
+ * Get the default icon for an input based on its type, format, or name.
35
+ */
36
+ export const getDefaultIcon = (params: {
37
+ type?: string;
38
+ format?: string;
39
+ name?: string;
40
+ isEnum?: boolean;
41
+ isArray?: boolean;
42
+ size?: IconSize;
43
+ }): ReactNode => {
44
+ const { type, format, name, isEnum, isArray, size = "sm" } = params;
45
+ const iconSize = ICON_SIZES[size];
46
+
47
+ // Format-based icons (highest priority)
48
+ if (format) {
49
+ switch (format) {
50
+ case "email":
51
+ return <IconMail size={iconSize} />;
52
+ case "url":
53
+ case "uri":
54
+ return <IconLink size={iconSize} />;
55
+ case "tel":
56
+ case "phone":
57
+ return <IconPhone size={iconSize} />;
58
+ case "date":
59
+ return <IconCalendar size={iconSize} />;
60
+ case "date-time":
61
+ return <IconCalendar size={iconSize} />;
62
+ case "time":
63
+ return <IconClock size={iconSize} />;
64
+ case "color":
65
+ return <IconColorPicker size={iconSize} />;
66
+ case "uuid":
67
+ return <IconKey size={iconSize} />;
68
+ }
69
+ }
70
+
71
+ // Name-based icons (medium priority)
72
+ if (name) {
73
+ const nameLower = name.toLowerCase();
74
+ if (nameLower.includes("password") || nameLower.includes("secret")) {
75
+ return <IconKey size={iconSize} />;
76
+ }
77
+ if (nameLower.includes("email") || nameLower.includes("mail")) {
78
+ return <IconMail size={iconSize} />;
79
+ }
80
+ if (nameLower.includes("url") || nameLower.includes("link")) {
81
+ return <IconLink size={iconSize} />;
82
+ }
83
+ if (nameLower.includes("phone") || nameLower.includes("tel")) {
84
+ return <IconPhone size={iconSize} />;
85
+ }
86
+ if (nameLower.includes("color")) {
87
+ return <IconPalette size={iconSize} />;
88
+ }
89
+ if (nameLower.includes("file") || nameLower.includes("upload")) {
90
+ return <IconFile size={iconSize} />;
91
+ }
92
+ if (nameLower.includes("date")) {
93
+ return <IconCalendar size={iconSize} />;
94
+ }
95
+ if (nameLower.includes("time")) {
96
+ return <IconClock size={iconSize} />;
97
+ }
98
+ }
99
+
100
+ // Type-based icons (lowest priority)
101
+ if (isEnum || isArray) {
102
+ return <IconSelector size={iconSize} />;
103
+ }
104
+
105
+ if (type) {
106
+ switch (type) {
107
+ case "boolean":
108
+ return <IconToggleLeft size={iconSize} />;
109
+ case "number":
110
+ case "integer":
111
+ return <IconHash size={iconSize} />;
112
+ case "array":
113
+ return <IconList size={iconSize} />;
114
+ case "string":
115
+ return <IconLetterCase size={iconSize} />;
116
+ }
117
+ }
118
+
119
+ // Default icon
120
+ return <IconAt size={iconSize} />;
121
+ };