@alepha/ui 0.10.6 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AlephaMantineProvider-DDbIijPF.js +96 -0
- package/dist/AlephaMantineProvider-DDbIijPF.js.map +1 -0
- package/dist/AlephaMantineProvider-pOu8hOzK.js +3 -0
- package/dist/index.d.ts +560 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1341 -109
- package/dist/index.js.map +1 -1
- package/package.json +21 -12
- package/src/RootRouter.ts +8 -0
- package/src/components/Action.tsx +296 -135
- package/src/components/AlephaMantineProvider.tsx +34 -31
- package/src/components/Control.tsx +251 -236
- package/src/components/ControlDate.tsx +104 -0
- package/src/components/ControlSelect.tsx +196 -0
- package/src/components/DarkModeButton.tsx +82 -0
- package/src/components/DataTable.css +199 -0
- package/src/components/DataTable.tsx +724 -0
- package/src/components/Omnibar.tsx +77 -0
- package/src/components/Sidebar.css +217 -0
- package/src/components/Sidebar.tsx +255 -0
- package/src/components/TypeForm.tsx +158 -0
- package/src/components/dialogs/AlertDialog.tsx +13 -0
- package/src/components/dialogs/ConfirmDialog.tsx +21 -0
- package/src/components/dialogs/PromptDialog.tsx +52 -0
- package/src/hooks/useDialog.ts +15 -0
- package/src/hooks/useToast.ts +14 -0
- package/src/index.ts +54 -10
- package/src/services/DialogService.tsx +207 -0
- package/src/services/ToastService.tsx +71 -0
- package/src/utils/icons.tsx +121 -0
- package/src/utils/parseInput.ts +125 -0
- package/src/utils/string.ts +21 -0
|
@@ -1,88 +1,249 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
type RouterGoOptions,
|
|
3
|
+
type UseActiveOptions,
|
|
4
|
+
useActive,
|
|
5
|
+
useAlepha,
|
|
6
|
+
useRouter,
|
|
7
7
|
} from "@alepha/react";
|
|
8
8
|
import { type FormModel, useFormState } from "@alepha/react-form";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Button,
|
|
11
|
+
type ButtonProps,
|
|
12
|
+
Flex,
|
|
13
|
+
Menu,
|
|
14
|
+
Tooltip,
|
|
15
|
+
type TooltipProps,
|
|
16
|
+
} from "@mantine/core";
|
|
17
|
+
import { IconChevronRight } from "@tabler/icons-react";
|
|
10
18
|
import { type ReactNode, useState } from "react";
|
|
11
19
|
|
|
20
|
+
export interface ActionMenuItem {
|
|
21
|
+
/**
|
|
22
|
+
* Menu item type
|
|
23
|
+
*/
|
|
24
|
+
type?: "item" | "divider" | "label";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Label text for the menu item
|
|
28
|
+
*/
|
|
29
|
+
label?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Icon element to display before the label
|
|
33
|
+
*/
|
|
34
|
+
icon?: ReactNode;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Click handler for menu items
|
|
38
|
+
*/
|
|
39
|
+
onClick?: () => void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Color for the menu item (e.g., "red" for danger actions)
|
|
43
|
+
*/
|
|
44
|
+
color?: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Nested submenu items
|
|
48
|
+
*/
|
|
49
|
+
children?: ActionMenuItem[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ActionMenuConfig {
|
|
53
|
+
/**
|
|
54
|
+
* Array of menu items to display
|
|
55
|
+
*/
|
|
56
|
+
items: ActionMenuItem[];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Menu position relative to the button
|
|
60
|
+
*/
|
|
61
|
+
position?:
|
|
62
|
+
| "bottom"
|
|
63
|
+
| "bottom-start"
|
|
64
|
+
| "bottom-end"
|
|
65
|
+
| "top"
|
|
66
|
+
| "top-start"
|
|
67
|
+
| "top-end"
|
|
68
|
+
| "left"
|
|
69
|
+
| "right";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Menu width
|
|
73
|
+
*/
|
|
74
|
+
width?: number | string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Menu shadow
|
|
78
|
+
*/
|
|
79
|
+
shadow?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
80
|
+
}
|
|
81
|
+
|
|
12
82
|
export interface ActionCommonProps extends ButtonProps {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
83
|
+
children?: ReactNode;
|
|
84
|
+
textVisibleFrom?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Tooltip to display on hover. Can be a string for simple tooltips
|
|
88
|
+
* or a TooltipProps object for advanced configuration.
|
|
89
|
+
*/
|
|
90
|
+
tooltip?: string | TooltipProps;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Menu configuration. When provided, the action will display a dropdown menu.
|
|
94
|
+
*/
|
|
95
|
+
menu?: ActionMenuConfig;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* If set, a confirmation dialog will be shown before performing the action.
|
|
99
|
+
* If `true`, a default title and message will be used.
|
|
100
|
+
* If a string, it will be used as the message with a default title.
|
|
101
|
+
* If an object, it can contain `title` and `message` properties to customize the dialog.
|
|
102
|
+
*/
|
|
103
|
+
confirm?: boolean | string | { title?: string; message: string };
|
|
24
104
|
}
|
|
25
105
|
|
|
26
106
|
export type ActionProps = ActionCommonProps &
|
|
27
|
-
|
|
107
|
+
(ActiveHrefProps | ActionClickProps | ActionSubmitProps | {});
|
|
28
108
|
|
|
29
109
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
30
110
|
|
|
111
|
+
// Helper function to render menu items recursively
|
|
112
|
+
const renderMenuItem = (item: ActionMenuItem, index: number): ReactNode => {
|
|
113
|
+
// Render divider
|
|
114
|
+
if (item.type === "divider") {
|
|
115
|
+
return <Menu.Divider key={index} />;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Render label
|
|
119
|
+
if (item.type === "label") {
|
|
120
|
+
return <Menu.Label key={index}>{item.label}</Menu.Label>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Render submenu if has children
|
|
124
|
+
if (item.children && item.children.length > 0) {
|
|
125
|
+
return (
|
|
126
|
+
<Menu key={index} trigger="hover" position="right-start" offset={2}>
|
|
127
|
+
<Menu.Target>
|
|
128
|
+
<Menu.Item
|
|
129
|
+
leftSection={item.icon}
|
|
130
|
+
rightSection={<IconChevronRight size={14} />}
|
|
131
|
+
>
|
|
132
|
+
{item.label}
|
|
133
|
+
</Menu.Item>
|
|
134
|
+
</Menu.Target>
|
|
135
|
+
<Menu.Dropdown>
|
|
136
|
+
{item.children.map((child, childIndex) =>
|
|
137
|
+
renderMenuItem(child, childIndex),
|
|
138
|
+
)}
|
|
139
|
+
</Menu.Dropdown>
|
|
140
|
+
</Menu>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Render regular menu item
|
|
145
|
+
return (
|
|
146
|
+
<Menu.Item
|
|
147
|
+
key={index}
|
|
148
|
+
leftSection={item.icon}
|
|
149
|
+
onClick={item.onClick}
|
|
150
|
+
color={item.color}
|
|
151
|
+
>
|
|
152
|
+
{item.label}
|
|
153
|
+
</Menu.Item>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
31
157
|
const Action = (_props: ActionProps) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
158
|
+
const props = { variant: "subtle", ..._props };
|
|
159
|
+
const { tooltip, menu, ...restProps } = props;
|
|
160
|
+
|
|
161
|
+
if (props.leftSection && !props.children) {
|
|
162
|
+
restProps.className ??= "mantine-Action-iconOnly";
|
|
163
|
+
restProps.p ??= "xs";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (props.textVisibleFrom) {
|
|
167
|
+
const { children, textVisibleFrom, leftSection, ...rest } = restProps;
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
<Flex w={"100%"} visibleFrom={textVisibleFrom}>
|
|
171
|
+
<Action
|
|
172
|
+
flex={1}
|
|
173
|
+
{...rest}
|
|
174
|
+
leftSection={leftSection}
|
|
175
|
+
tooltip={tooltip}
|
|
176
|
+
menu={menu}
|
|
177
|
+
>
|
|
178
|
+
{children}
|
|
179
|
+
</Action>
|
|
180
|
+
</Flex>
|
|
181
|
+
<Flex w={"100%"} hiddenFrom={textVisibleFrom}>
|
|
182
|
+
<Action px={"xs"} {...rest} tooltip={tooltip} menu={menu}>
|
|
183
|
+
{leftSection}
|
|
184
|
+
</Action>
|
|
185
|
+
</Flex>
|
|
186
|
+
</>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const renderAction = () => {
|
|
191
|
+
if ("href" in restProps && restProps.href) {
|
|
192
|
+
return (
|
|
193
|
+
<ActionHref {...restProps} href={restProps.href}>
|
|
194
|
+
{restProps.children}
|
|
195
|
+
</ActionHref>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if ("onClick" in restProps && restProps.onClick) {
|
|
200
|
+
return (
|
|
201
|
+
<ActionClick {...restProps} onClick={restProps.onClick}>
|
|
202
|
+
{restProps.children}
|
|
203
|
+
</ActionClick>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ("form" in restProps && restProps.form) {
|
|
208
|
+
return (
|
|
209
|
+
<ActionSubmit {...restProps} form={restProps.form}>
|
|
210
|
+
{restProps.children}
|
|
211
|
+
</ActionSubmit>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return <Button {...(restProps as any)}>{restProps.children}</Button>;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let actionElement = renderAction();
|
|
219
|
+
|
|
220
|
+
// Wrap with Menu if provided
|
|
221
|
+
if (menu) {
|
|
222
|
+
actionElement = (
|
|
223
|
+
<Menu
|
|
224
|
+
position={menu.position || "bottom-start"}
|
|
225
|
+
width={menu.width || 200}
|
|
226
|
+
shadow={menu.shadow || "md"}
|
|
227
|
+
>
|
|
228
|
+
<Menu.Target>{actionElement}</Menu.Target>
|
|
229
|
+
<Menu.Dropdown>
|
|
230
|
+
{menu.items.map((item, index) => renderMenuItem(item, index))}
|
|
231
|
+
</Menu.Dropdown>
|
|
232
|
+
</Menu>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Wrap with Tooltip if provided
|
|
237
|
+
if (tooltip) {
|
|
238
|
+
const tooltipProps: TooltipProps =
|
|
239
|
+
typeof tooltip === "string"
|
|
240
|
+
? { label: tooltip, children: actionElement }
|
|
241
|
+
: { ...tooltip, children: actionElement };
|
|
242
|
+
|
|
243
|
+
return <Tooltip {...tooltipProps} />;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return actionElement;
|
|
86
247
|
};
|
|
87
248
|
|
|
88
249
|
export default Action;
|
|
@@ -90,95 +251,95 @@ export default Action;
|
|
|
90
251
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
91
252
|
|
|
92
253
|
export interface ActionSubmitProps extends ButtonProps {
|
|
93
|
-
|
|
254
|
+
form: FormModel<any>;
|
|
94
255
|
}
|
|
95
256
|
|
|
96
257
|
/**
|
|
97
258
|
* Action button that submits a form with loading and disabled state handling.
|
|
98
259
|
*/
|
|
99
260
|
const ActionSubmit = (props: ActionSubmitProps) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
261
|
+
const { form, ...buttonProps } = props;
|
|
262
|
+
const state = useFormState(form);
|
|
263
|
+
return (
|
|
264
|
+
<Button
|
|
265
|
+
{...buttonProps}
|
|
266
|
+
loading={state.loading}
|
|
267
|
+
disabled={state.loading}
|
|
268
|
+
type={"submit"}
|
|
269
|
+
>
|
|
270
|
+
{props.children}
|
|
271
|
+
</Button>
|
|
272
|
+
);
|
|
112
273
|
};
|
|
113
274
|
|
|
114
275
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
115
276
|
|
|
116
277
|
export interface ActionClickProps extends ButtonProps {
|
|
117
|
-
|
|
278
|
+
onClick: (e: any) => any;
|
|
118
279
|
}
|
|
119
280
|
|
|
120
281
|
/**
|
|
121
282
|
* Basic action button that handles click events with loading and error handling.
|
|
122
283
|
*/
|
|
123
284
|
const ActionClick = (props: ActionClickProps) => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
285
|
+
const [pending, setPending] = useState(false);
|
|
286
|
+
const alepha = useAlepha();
|
|
287
|
+
|
|
288
|
+
const onClick = async (e: any) => {
|
|
289
|
+
setPending(true);
|
|
290
|
+
try {
|
|
291
|
+
await props.onClick(e);
|
|
292
|
+
} catch (e) {
|
|
293
|
+
console.error(e);
|
|
294
|
+
await alepha.events.emit("form:submit:error", {
|
|
295
|
+
id: "action",
|
|
296
|
+
error: e as Error,
|
|
297
|
+
});
|
|
298
|
+
} finally {
|
|
299
|
+
setPending(false);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<Button
|
|
305
|
+
{...props}
|
|
306
|
+
disabled={pending || props.disabled}
|
|
307
|
+
loading={pending}
|
|
308
|
+
onClick={onClick}
|
|
309
|
+
>
|
|
310
|
+
{props.children}
|
|
311
|
+
</Button>
|
|
312
|
+
);
|
|
152
313
|
};
|
|
153
314
|
|
|
154
315
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
155
316
|
|
|
156
317
|
export interface ActiveHrefProps extends ButtonProps {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
318
|
+
href: string;
|
|
319
|
+
active?: Partial<UseActiveOptions> | false;
|
|
320
|
+
routerGoOptions?: RouterGoOptions;
|
|
160
321
|
}
|
|
161
322
|
|
|
162
323
|
/**
|
|
163
324
|
* Action for navigation with active state support.
|
|
164
325
|
*/
|
|
165
326
|
const ActionHref = (props: ActiveHrefProps) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
327
|
+
const { active: options, routerGoOptions, ...buttonProps } = props;
|
|
328
|
+
const router = useRouter();
|
|
329
|
+
const { isPending, isActive } = useActive(
|
|
330
|
+
options ? { href: props.href, ...options } : { href: props.href },
|
|
331
|
+
);
|
|
332
|
+
const anchorProps = router.anchor(props.href, routerGoOptions);
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<Button
|
|
336
|
+
component={"a"}
|
|
337
|
+
loading={isPending}
|
|
338
|
+
{...anchorProps}
|
|
339
|
+
{...buttonProps}
|
|
340
|
+
variant={isActive && options !== false ? "filled" : "subtle"}
|
|
341
|
+
>
|
|
342
|
+
{props.children}
|
|
343
|
+
</Button>
|
|
344
|
+
);
|
|
184
345
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NestedView, useRouterEvents } from "@alepha/react";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
ColorSchemeScriptProps,
|
|
4
|
+
MantineProviderProps,
|
|
5
5
|
} from "@mantine/core";
|
|
6
6
|
import { ColorSchemeScript, MantineProvider } from "@mantine/core";
|
|
7
7
|
import { ModalsProvider, type ModalsProviderProps } from "@mantine/modals";
|
|
@@ -9,41 +9,44 @@ import { Notifications, type NotificationsProps } from "@mantine/notifications";
|
|
|
9
9
|
import type { NavigationProgressProps } from "@mantine/nprogress";
|
|
10
10
|
import { NavigationProgress, nprogress } from "@mantine/nprogress";
|
|
11
11
|
import type { ReactNode } from "react";
|
|
12
|
+
import Omnibar, { type OmnibarProps } from "./Omnibar";
|
|
12
13
|
|
|
13
14
|
export interface AlephaMantineProviderProps {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
mantine?: MantineProviderProps;
|
|
17
|
+
colorSchemeScript?: ColorSchemeScriptProps;
|
|
18
|
+
navigationProgress?: NavigationProgressProps;
|
|
19
|
+
notifications?: NotificationsProps;
|
|
20
|
+
modals?: ModalsProviderProps;
|
|
21
|
+
omnibar?: OmnibarProps;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
useRouterEvents({
|
|
26
|
+
onBegin: () => {
|
|
27
|
+
nprogress.start();
|
|
28
|
+
},
|
|
29
|
+
onEnd: () => {
|
|
30
|
+
nprogress.complete();
|
|
31
|
+
},
|
|
32
|
+
});
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<ColorSchemeScript
|
|
37
|
+
defaultColorScheme={props.mantine?.defaultColorScheme}
|
|
38
|
+
{...props.colorSchemeScript}
|
|
39
|
+
/>
|
|
40
|
+
<MantineProvider {...props.mantine}>
|
|
41
|
+
<Notifications {...props.notifications} />
|
|
42
|
+
<NavigationProgress {...props.navigationProgress} />
|
|
43
|
+
<ModalsProvider {...props.modals}>
|
|
44
|
+
<Omnibar {...props.omnibar} />
|
|
45
|
+
{props.children ?? <NestedView />}
|
|
46
|
+
</ModalsProvider>
|
|
47
|
+
</MantineProvider>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
export default AlephaMantineProvider;
|