@alepha/ui 0.11.3 → 0.11.5
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-Ba88lMeq.js +3 -0
- package/dist/AlephaMantineProvider-Be0DAazb.js +150 -0
- package/dist/AlephaMantineProvider-Be0DAazb.js.map +1 -0
- package/dist/index.d.ts +289 -225
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +650 -729
- package/dist/index.js.map +1 -1
- package/package.json +14 -12
- package/src/RootRouter.ts +1 -1
- package/src/components/buttons/ActionButton.tsx +542 -0
- package/src/components/buttons/BurgerButton.tsx +20 -0
- package/src/components/{DarkModeButton.tsx → buttons/DarkModeButton.tsx} +27 -14
- package/src/components/buttons/LanguageButton.tsx +28 -0
- package/src/components/buttons/OmnibarButton.tsx +32 -0
- package/src/components/buttons/ToggleSidebarButton.tsx +28 -0
- package/src/components/dialogs/AlertDialog.tsx +10 -10
- package/src/components/dialogs/ConfirmDialog.tsx +18 -18
- package/src/components/dialogs/PromptDialog.tsx +5 -3
- package/src/components/{Control.tsx → form/Control.tsx} +6 -3
- package/src/components/{ControlDate.tsx → form/ControlDate.tsx} +4 -1
- package/src/components/{ControlSelect.tsx → form/ControlSelect.tsx} +4 -1
- package/src/components/{TypeForm.tsx → form/TypeForm.tsx} +8 -6
- package/src/components/layout/AdminShell.tsx +97 -0
- package/src/components/{AlephaMantineProvider.tsx → layout/AlephaMantineProvider.tsx} +30 -10
- package/src/components/layout/AppBar.tsx +133 -0
- package/src/components/layout/Omnibar.tsx +43 -0
- package/src/components/layout/Sidebar.tsx +410 -0
- package/src/components/table/DataTable.tsx +63 -0
- package/src/constants/ui.ts +8 -0
- package/src/index.ts +89 -24
- package/src/services/DialogService.tsx +13 -32
- package/src/services/ToastService.tsx +16 -4
- package/src/utils/parseInput.ts +1 -1
- package/dist/AlephaMantineProvider-DDbIijPF.js +0 -96
- package/dist/AlephaMantineProvider-DDbIijPF.js.map +0 -1
- package/dist/AlephaMantineProvider-pOu8hOzK.js +0 -3
- package/src/components/Action.tsx +0 -345
- package/src/components/DataTable.css +0 -199
- package/src/components/DataTable.tsx +0 -724
- package/src/components/Omnibar.tsx +0 -77
- package/src/components/Sidebar.css +0 -217
- package/src/components/Sidebar.tsx +0 -255
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { useEvents, useRouter } from "@alepha/react";
|
|
2
|
+
import {
|
|
3
|
+
Flex,
|
|
4
|
+
type FlexProps,
|
|
5
|
+
type MantineBreakpoint,
|
|
6
|
+
Text,
|
|
7
|
+
ThemeIcon,
|
|
8
|
+
} from "@mantine/core";
|
|
9
|
+
import {
|
|
10
|
+
IconChevronDown,
|
|
11
|
+
IconChevronRight,
|
|
12
|
+
IconSquareRounded,
|
|
13
|
+
} from "@tabler/icons-react";
|
|
14
|
+
import { type ReactNode, useCallback, useState } from "react";
|
|
15
|
+
import ActionButton, { type ActionProps } from "../buttons/ActionButton.tsx";
|
|
16
|
+
import OmnibarButton from "../buttons/OmnibarButton.tsx";
|
|
17
|
+
|
|
18
|
+
export interface SidebarProps {
|
|
19
|
+
menu?: SidebarNode[];
|
|
20
|
+
top?: SidebarNode[];
|
|
21
|
+
bottom?: SidebarNode[];
|
|
22
|
+
onItemClick?: (item: SidebarMenuItem) => void;
|
|
23
|
+
onSearchClick?: () => void;
|
|
24
|
+
theme?: SidebarTheme;
|
|
25
|
+
flexProps?: Partial<FlexProps>;
|
|
26
|
+
collapsed?: boolean;
|
|
27
|
+
gap?: MantineBreakpoint;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Sidebar = (props: SidebarProps) => {
|
|
31
|
+
const router = useRouter();
|
|
32
|
+
const { top = [], bottom = [], onItemClick } = props;
|
|
33
|
+
|
|
34
|
+
const renderNode = (item: SidebarNode, key: number) => {
|
|
35
|
+
if ("type" in item) {
|
|
36
|
+
if (item.type === "spacer") {
|
|
37
|
+
return <Flex key={key} h={16} />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (item.type === "divider") {
|
|
41
|
+
return (
|
|
42
|
+
<Flex
|
|
43
|
+
key={key}
|
|
44
|
+
h={1}
|
|
45
|
+
bg={"var(--alepha-border)"}
|
|
46
|
+
my={"md"}
|
|
47
|
+
mx={"sm"}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (item.type === "search") {
|
|
53
|
+
return <OmnibarButton collapsed={props.collapsed} key={key} />;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (item.type === "section") {
|
|
57
|
+
if (props.collapsed) return;
|
|
58
|
+
return (
|
|
59
|
+
<Text
|
|
60
|
+
key={key}
|
|
61
|
+
size={"xs"}
|
|
62
|
+
c={"dimmed"}
|
|
63
|
+
mt={"md"}
|
|
64
|
+
mb={"xs"}
|
|
65
|
+
mx={"sm"}
|
|
66
|
+
tt={"uppercase"}
|
|
67
|
+
fw={"bold"}
|
|
68
|
+
>
|
|
69
|
+
{item.label}
|
|
70
|
+
</Text>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if ("element" in item) {
|
|
76
|
+
return <Flex key={key}>{item.element}</Flex>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (props.collapsed) {
|
|
80
|
+
return (
|
|
81
|
+
<SidebarCollapsedItem
|
|
82
|
+
key={key}
|
|
83
|
+
item={item}
|
|
84
|
+
level={0}
|
|
85
|
+
onItemClick={onItemClick}
|
|
86
|
+
theme={props.theme ?? {}}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<SidebarItem
|
|
93
|
+
key={key}
|
|
94
|
+
item={item}
|
|
95
|
+
level={0}
|
|
96
|
+
onItemClick={onItemClick}
|
|
97
|
+
theme={props.theme ?? {}}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const padding = "md";
|
|
103
|
+
const gap = props.gap;
|
|
104
|
+
const menu =
|
|
105
|
+
props.menu ??
|
|
106
|
+
(router.concretePages.map((page) => ({
|
|
107
|
+
label: page.label ?? page.name,
|
|
108
|
+
description: page.description,
|
|
109
|
+
icon: page.icon,
|
|
110
|
+
href: page.path,
|
|
111
|
+
})) as SidebarMenuItem[]);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Flex
|
|
115
|
+
flex={1}
|
|
116
|
+
py={padding}
|
|
117
|
+
direction={"column"}
|
|
118
|
+
className={"overflow-auto"}
|
|
119
|
+
{...props.flexProps}
|
|
120
|
+
>
|
|
121
|
+
<Flex gap={gap} px={padding} direction={"column"}>
|
|
122
|
+
{top.map((item, index) => renderNode(item, index))}
|
|
123
|
+
{menu
|
|
124
|
+
.filter((it) => it.position === "top")
|
|
125
|
+
.map((item, index) => renderNode(item, index + top.length))}
|
|
126
|
+
</Flex>
|
|
127
|
+
<Flex
|
|
128
|
+
gap={gap}
|
|
129
|
+
px={padding}
|
|
130
|
+
direction={"column"}
|
|
131
|
+
flex={1}
|
|
132
|
+
className={"overflow-auto"}
|
|
133
|
+
>
|
|
134
|
+
{menu
|
|
135
|
+
.filter((it) => !it.position)
|
|
136
|
+
.map((item, index) => renderNode(item, index))}
|
|
137
|
+
</Flex>
|
|
138
|
+
<Flex gap={gap} px={padding} direction={"column"}>
|
|
139
|
+
{bottom.map((item, index) => renderNode(item, index))}
|
|
140
|
+
{menu
|
|
141
|
+
.filter((it) => it.position === "bottom")
|
|
142
|
+
.map((item, index) => renderNode(item, index + bottom.length))}
|
|
143
|
+
</Flex>
|
|
144
|
+
</Flex>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
export interface SidebarItemProps {
|
|
151
|
+
item: SidebarMenuItem;
|
|
152
|
+
level: number;
|
|
153
|
+
onItemClick?: (item: SidebarMenuItem) => void;
|
|
154
|
+
theme: SidebarTheme;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const SidebarItem = (props: SidebarItemProps) => {
|
|
158
|
+
const { item, level } = props;
|
|
159
|
+
const maxLevel = 2; // 0, 1, 2 = 3 levels total
|
|
160
|
+
|
|
161
|
+
const router = useRouter();
|
|
162
|
+
const isActive = useCallback((item: SidebarMenuItem): boolean => {
|
|
163
|
+
if (!item.children) return false;
|
|
164
|
+
for (const child of item.children) {
|
|
165
|
+
if (child.href) {
|
|
166
|
+
if (router.isActive(child.href)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (isActive(child)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
const [isOpen, setIsOpen] = useState<boolean>(isActive(item));
|
|
178
|
+
|
|
179
|
+
useEvents(
|
|
180
|
+
{
|
|
181
|
+
"react:transition:end": () => {
|
|
182
|
+
// recalculate open state on transition end to ensure correct state after navigation
|
|
183
|
+
if (isActive(item)) {
|
|
184
|
+
setIsOpen(true);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
[],
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (level > maxLevel) return null;
|
|
192
|
+
|
|
193
|
+
const handleItemClick = (e: MouseEvent) => {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
if (item.children && item.children.length > 0) {
|
|
196
|
+
setIsOpen(!isOpen);
|
|
197
|
+
} else {
|
|
198
|
+
props.onItemClick?.(item);
|
|
199
|
+
item.onClick?.();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<Flex direction={"column"} ps={level === 0 ? 0 : 32} pos={"relative"}>
|
|
205
|
+
<ActionButton
|
|
206
|
+
w={"100%"}
|
|
207
|
+
justify="space-between"
|
|
208
|
+
href={props.item.href}
|
|
209
|
+
variant={"subtle"}
|
|
210
|
+
size={
|
|
211
|
+
props.item.theme?.size ??
|
|
212
|
+
props.theme.button?.size ??
|
|
213
|
+
(level === 0 ? "sm" : "xs")
|
|
214
|
+
}
|
|
215
|
+
variantActive={"default"}
|
|
216
|
+
radius={props.item.theme?.radius ?? props.theme.button?.radius ?? "md"}
|
|
217
|
+
onClick={handleItemClick}
|
|
218
|
+
leftSection={
|
|
219
|
+
<Flex w={"100%"} align="center" gap={"sm"}>
|
|
220
|
+
{item.icon && (
|
|
221
|
+
<ThemeIcon
|
|
222
|
+
size={level === 0 ? "sm" : "xs"}
|
|
223
|
+
variant={"transparent"}
|
|
224
|
+
>
|
|
225
|
+
{item.icon}
|
|
226
|
+
</ThemeIcon>
|
|
227
|
+
)}
|
|
228
|
+
<Flex direction={"column"}>
|
|
229
|
+
<Flex>{item.label}</Flex>
|
|
230
|
+
{item.description && (
|
|
231
|
+
<Text size={"xs"} c={"dimmed"}>
|
|
232
|
+
{item.description}
|
|
233
|
+
</Text>
|
|
234
|
+
)}
|
|
235
|
+
</Flex>
|
|
236
|
+
</Flex>
|
|
237
|
+
}
|
|
238
|
+
rightSection={
|
|
239
|
+
item.children ? (
|
|
240
|
+
<Flex>
|
|
241
|
+
{isOpen ? (
|
|
242
|
+
<IconChevronDown size={14} />
|
|
243
|
+
) : (
|
|
244
|
+
<IconChevronRight size={14} />
|
|
245
|
+
)}
|
|
246
|
+
</Flex>
|
|
247
|
+
) : (
|
|
248
|
+
props.item.rightSection
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
{...props.item.actionProps}
|
|
252
|
+
/>
|
|
253
|
+
|
|
254
|
+
{item.children && isOpen && (
|
|
255
|
+
<Flex direction={"column"} data-parent-level={level}>
|
|
256
|
+
<Flex
|
|
257
|
+
style={{
|
|
258
|
+
position: "absolute",
|
|
259
|
+
width: 1,
|
|
260
|
+
background:
|
|
261
|
+
"linear-gradient(to bottom, transparent, var(--alepha-border), transparent)",
|
|
262
|
+
top: 48,
|
|
263
|
+
left: 20 + 32 * level,
|
|
264
|
+
bottom: 16,
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
{item.children.map((child, index) => (
|
|
268
|
+
<SidebarItem
|
|
269
|
+
key={index}
|
|
270
|
+
item={child}
|
|
271
|
+
level={level + 1}
|
|
272
|
+
onItemClick={props.onItemClick}
|
|
273
|
+
theme={props.theme}
|
|
274
|
+
/>
|
|
275
|
+
))}
|
|
276
|
+
</Flex>
|
|
277
|
+
)}
|
|
278
|
+
</Flex>
|
|
279
|
+
);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
export interface SidebarItemProps {
|
|
285
|
+
item: SidebarMenuItem;
|
|
286
|
+
level: number;
|
|
287
|
+
onItemClick?: (item: SidebarMenuItem) => void;
|
|
288
|
+
theme: SidebarTheme;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const SidebarCollapsedItem = (props: SidebarItemProps) => {
|
|
292
|
+
const { item, level } = props;
|
|
293
|
+
|
|
294
|
+
const router = useRouter();
|
|
295
|
+
const isActive = useCallback((item: SidebarMenuItem): boolean => {
|
|
296
|
+
if (!item.children) return false;
|
|
297
|
+
for (const child of item.children) {
|
|
298
|
+
if (child.href) {
|
|
299
|
+
if (router.isActive(child.href)) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (isActive(child)) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}, []);
|
|
309
|
+
|
|
310
|
+
const [isOpen, setIsOpen] = useState<boolean>(isActive(item));
|
|
311
|
+
|
|
312
|
+
const handleItemClick = (e: MouseEvent) => {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
if (item.children && item.children.length > 0) {
|
|
315
|
+
setIsOpen(!isOpen);
|
|
316
|
+
} else {
|
|
317
|
+
props.onItemClick?.(item);
|
|
318
|
+
item.onClick?.();
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
<ActionButton
|
|
324
|
+
variant={"subtle"}
|
|
325
|
+
size={
|
|
326
|
+
props.item.theme?.size ??
|
|
327
|
+
props.theme.button?.size ??
|
|
328
|
+
(level === 0 ? "sm" : "xs")
|
|
329
|
+
}
|
|
330
|
+
variantActive={"default"}
|
|
331
|
+
radius={props.item.theme?.radius ?? props.theme.button?.radius ?? "md"}
|
|
332
|
+
onClick={handleItemClick}
|
|
333
|
+
icon={item.icon ?? <IconSquareRounded />}
|
|
334
|
+
href={props.item.href}
|
|
335
|
+
menu={
|
|
336
|
+
item.children
|
|
337
|
+
? {
|
|
338
|
+
position: "right",
|
|
339
|
+
on: "hover",
|
|
340
|
+
items: item.children.map((child) => ({
|
|
341
|
+
label: child.label,
|
|
342
|
+
href: child.href,
|
|
343
|
+
icon: child.icon,
|
|
344
|
+
children: child.children,
|
|
345
|
+
})),
|
|
346
|
+
}
|
|
347
|
+
: undefined
|
|
348
|
+
}
|
|
349
|
+
{...props.item.actionProps}
|
|
350
|
+
/>
|
|
351
|
+
);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
export type SidebarNode =
|
|
357
|
+
| SidebarMenuItem
|
|
358
|
+
| SidebarSpacer
|
|
359
|
+
| SidebarDivider
|
|
360
|
+
| SidebarSearch
|
|
361
|
+
| SidebarElement
|
|
362
|
+
| SidebarSection;
|
|
363
|
+
|
|
364
|
+
export interface SidebarAbstractItem {
|
|
365
|
+
position?: "top" | "bottom";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface SidebarElement extends SidebarAbstractItem {
|
|
369
|
+
element: ReactNode;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export interface SidebarSpacer extends SidebarAbstractItem {
|
|
373
|
+
type: "spacer";
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export interface SidebarDivider extends SidebarAbstractItem {
|
|
377
|
+
type: "divider";
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface SidebarSearch extends SidebarAbstractItem {
|
|
381
|
+
type: "search";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export interface SidebarSection extends SidebarAbstractItem {
|
|
385
|
+
type: "section";
|
|
386
|
+
label: string;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export interface SidebarMenuItem extends SidebarAbstractItem {
|
|
390
|
+
label: string | ReactNode;
|
|
391
|
+
description?: string;
|
|
392
|
+
icon?: ReactNode;
|
|
393
|
+
href?: string;
|
|
394
|
+
activeStartsWith?: boolean; // Use startWith matching for active state
|
|
395
|
+
onClick?: () => void;
|
|
396
|
+
children?: SidebarMenuItem[];
|
|
397
|
+
rightSection?: ReactNode;
|
|
398
|
+
theme?: SidebarButtonTheme;
|
|
399
|
+
actionProps?: ActionProps;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export interface SidebarButtonTheme {
|
|
403
|
+
radius?: MantineBreakpoint;
|
|
404
|
+
size?: MantineBreakpoint;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface SidebarTheme {
|
|
408
|
+
button?: SidebarButtonTheme;
|
|
409
|
+
search?: SidebarButtonTheme;
|
|
410
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Async } from "@alepha/core";
|
|
2
|
+
import type { TableTrProps } from "@mantine/core";
|
|
3
|
+
import { Table, type TableProps } from "@mantine/core";
|
|
4
|
+
import { type ReactNode, useEffect, useState } from "react";
|
|
5
|
+
import ActionButton from "../buttons/ActionButton.tsx";
|
|
6
|
+
|
|
7
|
+
export interface DataTableColumn<T extends object> {
|
|
8
|
+
label: string;
|
|
9
|
+
value: (item: T) => ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DataTableProps<T extends object> {
|
|
13
|
+
items: T[] | (() => Async<T[]>);
|
|
14
|
+
columns: {
|
|
15
|
+
[key: string]: DataTableColumn<T>;
|
|
16
|
+
};
|
|
17
|
+
tableProps?: TableProps;
|
|
18
|
+
tableTrProps?: (item: T) => TableTrProps;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DataTable = <T extends object>(props: DataTableProps<T>) => {
|
|
22
|
+
const [items, setItems] = useState<object[]>(
|
|
23
|
+
typeof props.items === "function" ? [] : props.items,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (typeof props.items !== "function") {
|
|
28
|
+
setItems(props.items);
|
|
29
|
+
}
|
|
30
|
+
}, [props.items]);
|
|
31
|
+
|
|
32
|
+
const head = Object.entries(props.columns).map(([key, col]) => (
|
|
33
|
+
<Table.Th key={key}>
|
|
34
|
+
<ActionButton justify={"space-between"} radius={0} fullWidth size={"xs"}>
|
|
35
|
+
{col.label}
|
|
36
|
+
</ActionButton>
|
|
37
|
+
</Table.Th>
|
|
38
|
+
));
|
|
39
|
+
|
|
40
|
+
const rows = items.map((item, index) => {
|
|
41
|
+
const trProps = props.tableTrProps
|
|
42
|
+
? props.tableTrProps(item as T)
|
|
43
|
+
: ({} as TableTrProps);
|
|
44
|
+
return (
|
|
45
|
+
<Table.Tr key={JSON.stringify(item)} {...trProps}>
|
|
46
|
+
{Object.entries(props.columns).map(([key, col]) => (
|
|
47
|
+
<Table.Td key={key}>{col.value(item as T)}</Table.Td>
|
|
48
|
+
))}
|
|
49
|
+
</Table.Tr>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Table {...props.tableProps}>
|
|
55
|
+
<Table.Thead>
|
|
56
|
+
<Table.Tr>{head}</Table.Tr>
|
|
57
|
+
</Table.Thead>
|
|
58
|
+
<Table.Tbody>{rows}</Table.Tbody>
|
|
59
|
+
</Table>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default DataTable;
|
package/src/index.ts
CHANGED
|
@@ -1,37 +1,71 @@
|
|
|
1
1
|
import { $module } from "@alepha/core";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { AlephaReactForm } from "@alepha/react-form";
|
|
3
|
+
import { AlephaReactHead } from "@alepha/react-head";
|
|
4
|
+
import { AlephaReactI18n } from "@alepha/react-i18n";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import type { ControlProps } from "./components/form/Control.tsx";
|
|
4
7
|
import { RootRouter } from "./RootRouter.ts";
|
|
5
8
|
import { DialogService } from "./services/DialogService.tsx";
|
|
6
9
|
import { ToastService } from "./services/ToastService.tsx";
|
|
7
10
|
|
|
8
11
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
9
12
|
|
|
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";
|
|
13
|
+
export { Flex, Text } from "@mantine/core";
|
|
17
14
|
export type {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export {
|
|
27
|
-
export { default as
|
|
15
|
+
ActionClickButtonProps,
|
|
16
|
+
ActionCommonProps,
|
|
17
|
+
ActionMenuConfig,
|
|
18
|
+
ActionMenuItem,
|
|
19
|
+
ActionNavigationButtonProps,
|
|
20
|
+
ActionProps,
|
|
21
|
+
ActionSubmitButtonProps,
|
|
22
|
+
} from "./components/buttons/ActionButton.tsx";
|
|
23
|
+
export { default as ActionButton } from "./components/buttons/ActionButton.tsx";
|
|
24
|
+
export { default as DarkModeButton } from "./components/buttons/DarkModeButton.tsx";
|
|
25
|
+
export { default as OmnibarButton } from "./components/buttons/OmnibarButton.tsx";
|
|
26
|
+
export { default as AlertDialog } from "./components/dialogs/AlertDialog.tsx";
|
|
27
|
+
export { default as ConfirmDialog } from "./components/dialogs/ConfirmDialog.tsx";
|
|
28
|
+
export { default as PromptDialog } from "./components/dialogs/PromptDialog.tsx";
|
|
29
|
+
export { default as Control } from "./components/form/Control.tsx";
|
|
30
|
+
export { default as ControlDate } from "./components/form/ControlDate.tsx";
|
|
31
|
+
export { default as ControlSelect } from "./components/form/ControlSelect.tsx";
|
|
32
|
+
export { default as TypeForm } from "./components/form/TypeForm.tsx";
|
|
33
|
+
export { default as AdminShell } from "./components/layout/AdminShell.tsx";
|
|
34
|
+
export { default as AlephaMantineProvider } from "./components/layout/AlephaMantineProvider.tsx";
|
|
35
|
+
export type {
|
|
36
|
+
AppBarBurger,
|
|
37
|
+
AppBarDark,
|
|
38
|
+
AppBarDivider,
|
|
39
|
+
AppBarElement,
|
|
40
|
+
AppBarItem,
|
|
41
|
+
AppBarLang,
|
|
42
|
+
AppBarProps,
|
|
43
|
+
AppBarSearch,
|
|
44
|
+
AppBarSpacer,
|
|
45
|
+
} from "./components/layout/AppBar.tsx";
|
|
46
|
+
export { default as AppBar } from "./components/layout/AppBar.tsx";
|
|
47
|
+
export { default as Omnibar } from "./components/layout/Omnibar.tsx";
|
|
28
48
|
export type {
|
|
29
|
-
|
|
49
|
+
SidebarAbstractItem,
|
|
50
|
+
SidebarButtonTheme,
|
|
51
|
+
SidebarDivider,
|
|
52
|
+
SidebarElement,
|
|
30
53
|
SidebarItemProps,
|
|
54
|
+
SidebarMenuItem,
|
|
55
|
+
SidebarNode,
|
|
31
56
|
SidebarProps,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
SidebarSearch,
|
|
58
|
+
SidebarSection,
|
|
59
|
+
SidebarSpacer,
|
|
60
|
+
SidebarTheme,
|
|
61
|
+
} from "./components/layout/Sidebar.tsx";
|
|
62
|
+
export { Sidebar } from "./components/layout/Sidebar.tsx";
|
|
63
|
+
export type {
|
|
64
|
+
DataTableColumn,
|
|
65
|
+
DataTableProps,
|
|
66
|
+
} from "./components/table/DataTable.tsx";
|
|
67
|
+
export { default as DataTable } from "./components/table/DataTable.tsx";
|
|
68
|
+
export * from "./constants/ui.ts";
|
|
35
69
|
export { useDialog } from "./hooks/useDialog.ts";
|
|
36
70
|
export { useToast } from "./hooks/useToast.ts";
|
|
37
71
|
export * from "./RootRouter.ts";
|
|
@@ -57,6 +91,30 @@ declare module "typebox" {
|
|
|
57
91
|
}
|
|
58
92
|
}
|
|
59
93
|
|
|
94
|
+
declare module "@alepha/react" {
|
|
95
|
+
interface PageDescriptorOptions {
|
|
96
|
+
/**
|
|
97
|
+
* Human-readable title for the page.
|
|
98
|
+
* - for Sidebar navigation
|
|
99
|
+
* - for Omnibar navigation
|
|
100
|
+
* (soon)
|
|
101
|
+
* - for Breadcrumbs
|
|
102
|
+
* - for document title (with AlephaReactHead)
|
|
103
|
+
*/
|
|
104
|
+
label?: string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Optional description of the page.
|
|
108
|
+
*/
|
|
109
|
+
description?: string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Optional icon for the page.
|
|
113
|
+
*/
|
|
114
|
+
icon?: ReactNode;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
60
118
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
61
119
|
|
|
62
120
|
/**
|
|
@@ -66,5 +124,12 @@ declare module "typebox" {
|
|
|
66
124
|
*/
|
|
67
125
|
export const AlephaUI = $module({
|
|
68
126
|
name: "alepha.ui",
|
|
69
|
-
services: [
|
|
127
|
+
services: [DialogService, ToastService, RootRouter],
|
|
128
|
+
register: (alepha) => {
|
|
129
|
+
alepha.with(AlephaReactI18n);
|
|
130
|
+
alepha.with(AlephaReactHead);
|
|
131
|
+
alepha.with(AlephaReactForm);
|
|
132
|
+
alepha.with(DialogService);
|
|
133
|
+
alepha.with(ToastService);
|
|
134
|
+
},
|
|
70
135
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ModalProps } from "@mantine/core";
|
|
2
2
|
import { modals } from "@mantine/modals";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import AlertDialog from "../components/dialogs/AlertDialog";
|
|
5
|
+
import ConfirmDialog from "../components/dialogs/ConfirmDialog";
|
|
6
|
+
import PromptDialog from "../components/dialogs/PromptDialog";
|
|
7
7
|
|
|
8
8
|
// Base interfaces
|
|
9
9
|
export interface BaseDialogOptions extends Partial<ModalProps> {
|
|
@@ -139,27 +139,11 @@ export class DialogService {
|
|
|
139
139
|
* Open a custom dialog with provided content
|
|
140
140
|
*/
|
|
141
141
|
public open(options?: BaseDialogOptions): string {
|
|
142
|
-
|
|
142
|
+
return modals.open({
|
|
143
143
|
...this.options.default,
|
|
144
144
|
...options,
|
|
145
145
|
children: options?.content || options?.message,
|
|
146
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
147
|
}
|
|
164
148
|
|
|
165
149
|
/**
|
|
@@ -174,34 +158,31 @@ export class DialogService {
|
|
|
174
158
|
}
|
|
175
159
|
|
|
176
160
|
/**
|
|
177
|
-
* Show a
|
|
161
|
+
* Show a JSON editor/viewer dialog
|
|
178
162
|
*/
|
|
179
|
-
public
|
|
163
|
+
public json(data?: any, options?: BaseDialogOptions): void {
|
|
180
164
|
// Implementation to be added
|
|
181
165
|
}
|
|
182
166
|
|
|
183
167
|
/**
|
|
184
|
-
* Show
|
|
168
|
+
* Show a form dialog for structured input
|
|
185
169
|
*/
|
|
186
|
-
public
|
|
170
|
+
public form(options?: BaseDialogOptions): Promise<any> {
|
|
187
171
|
// Implementation to be added
|
|
172
|
+
return Promise.resolve(null);
|
|
188
173
|
}
|
|
189
174
|
|
|
190
175
|
/**
|
|
191
|
-
* Show a
|
|
176
|
+
* Show a loading/progress dialog with optional progress percentage
|
|
192
177
|
*/
|
|
193
|
-
public
|
|
194
|
-
data: any[],
|
|
195
|
-
options?: BaseDialogOptions & { columns?: any[] },
|
|
196
|
-
): void {
|
|
178
|
+
public loading(options?: BaseDialogOptions & { progress?: number }): void {
|
|
197
179
|
// Implementation to be added
|
|
198
180
|
}
|
|
199
181
|
|
|
200
182
|
/**
|
|
201
|
-
* Show
|
|
183
|
+
* Show an image viewer/gallery dialog
|
|
202
184
|
*/
|
|
203
|
-
public
|
|
185
|
+
public image(src: string | string[], options?: BaseDialogOptions): void {
|
|
204
186
|
// Implementation to be added
|
|
205
|
-
return Promise.resolve(null);
|
|
206
187
|
}
|
|
207
188
|
}
|