@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,28 @@
|
|
|
1
|
+
import { useStore } from "@alepha/react";
|
|
2
|
+
import {
|
|
3
|
+
IconLayoutSidebarLeftCollapse,
|
|
4
|
+
IconLayoutSidebarRightCollapse,
|
|
5
|
+
} from "@tabler/icons-react";
|
|
6
|
+
import ActionButton from "./ActionButton.tsx";
|
|
7
|
+
|
|
8
|
+
const ToggleSidebarButton = () => {
|
|
9
|
+
const [collapsed, setCollapsed] = useStore("alepha.ui.sidebar.collapsed");
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<ActionButton
|
|
13
|
+
icon={
|
|
14
|
+
collapsed ? (
|
|
15
|
+
<IconLayoutSidebarRightCollapse />
|
|
16
|
+
) : (
|
|
17
|
+
<IconLayoutSidebarLeftCollapse />
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
variant={"subtle"}
|
|
21
|
+
size={"md"}
|
|
22
|
+
onClick={() => setCollapsed(!collapsed)}
|
|
23
|
+
tooltip={collapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default ToggleSidebarButton;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Button, Group, Text } from "@mantine/core";
|
|
2
2
|
import type { AlertDialogProps } from "../../services/DialogService";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
const AlertDialog = ({ options, onClose }: AlertDialogProps) => (
|
|
5
|
+
<>
|
|
6
|
+
{options?.message && <Text mb="md">{options.message}</Text>}
|
|
7
|
+
<Group justify="flex-end">
|
|
8
|
+
<Button onClick={onClose}>{options?.okLabel || "OK"}</Button>
|
|
9
|
+
</Group>
|
|
10
|
+
</>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export default AlertDialog;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { Button, Group, Text } from "@mantine/core";
|
|
2
2
|
import type { ConfirmDialogProps } from "../../services/DialogService";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
4
|
+
const ConfirmDialog = ({ options, onConfirm }: ConfirmDialogProps) => (
|
|
5
|
+
<>
|
|
6
|
+
{options?.message && <Text mb="md">{options.message}</Text>}
|
|
7
|
+
<Group justify="flex-end">
|
|
8
|
+
<Button variant="subtle" onClick={() => onConfirm(false)}>
|
|
9
|
+
{options?.cancelLabel || "Cancel"}
|
|
10
|
+
</Button>
|
|
11
|
+
<Button
|
|
12
|
+
color={options?.confirmColor || "blue"}
|
|
13
|
+
onClick={() => onConfirm(true)}
|
|
14
|
+
>
|
|
15
|
+
{options?.confirmLabel || "Confirm"}
|
|
16
|
+
</Button>
|
|
17
|
+
</Group>
|
|
18
|
+
</>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export default ConfirmDialog;
|
|
@@ -2,12 +2,12 @@ import { Button, Group, Text, TextInput } from "@mantine/core";
|
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import type { PromptDialogProps } from "../../services/DialogService";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const PromptDialog = ({ options, onSubmit }: PromptDialogProps) => {
|
|
6
6
|
const [value, setValue] = useState(options?.defaultValue || "");
|
|
7
7
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
8
8
|
|
|
9
9
|
useEffect(() => {
|
|
10
|
-
//
|
|
10
|
+
// autofocus the input when the dialog opens
|
|
11
11
|
inputRef.current?.focus();
|
|
12
12
|
}, []);
|
|
13
13
|
|
|
@@ -49,4 +49,6 @@ export function PromptDialog({ options, onSubmit }: PromptDialogProps) {
|
|
|
49
49
|
</Group>
|
|
50
50
|
</>
|
|
51
51
|
);
|
|
52
|
-
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default PromptDialog;
|
|
@@ -23,9 +23,12 @@ import type {
|
|
|
23
23
|
TimeInputProps,
|
|
24
24
|
} from "@mantine/dates";
|
|
25
25
|
import type { ComponentType } from "react";
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
import {
|
|
27
|
+
type GenericControlProps,
|
|
28
|
+
parseInput,
|
|
29
|
+
} from "../../utils/parseInput.ts";
|
|
30
|
+
import ControlDate from "./ControlDate.tsx";
|
|
31
|
+
import ControlSelect, { type ControlSelectProps } from "./ControlSelect.tsx";
|
|
29
32
|
|
|
30
33
|
export interface ControlProps extends GenericControlProps {
|
|
31
34
|
text?: TextInputProps;
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
TimeInput,
|
|
8
8
|
type TimeInputProps,
|
|
9
9
|
} from "@mantine/dates";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type GenericControlProps,
|
|
12
|
+
parseInput,
|
|
13
|
+
} from "../../utils/parseInput.ts";
|
|
11
14
|
|
|
12
15
|
export interface ControlDateProps extends GenericControlProps {
|
|
13
16
|
date?: boolean | DateInputProps;
|
|
@@ -14,7 +14,10 @@ import {
|
|
|
14
14
|
type TagsInputProps,
|
|
15
15
|
} from "@mantine/core";
|
|
16
16
|
import { useEffect, useState } from "react";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type GenericControlProps,
|
|
19
|
+
parseInput,
|
|
20
|
+
} from "../../utils/parseInput.ts";
|
|
18
21
|
|
|
19
22
|
export type SelectValueLabel =
|
|
20
23
|
| string
|
|
@@ -2,8 +2,10 @@ import type { TObject } from "@alepha/core";
|
|
|
2
2
|
import type { FormModel } from "@alepha/react-form";
|
|
3
3
|
import { Flex, Grid } from "@mantine/core";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import ActionButton, {
|
|
6
|
+
type ActionSubmitButtonProps,
|
|
7
|
+
} from "../buttons/ActionButton.tsx";
|
|
8
|
+
import Control, { type ControlProps } from "./Control.tsx";
|
|
7
9
|
|
|
8
10
|
export interface TypeFormProps<T extends TObject> {
|
|
9
11
|
form: FormModel<T>;
|
|
@@ -21,8 +23,8 @@ export interface TypeFormProps<T extends TObject> {
|
|
|
21
23
|
controlProps?: Partial<Omit<ControlProps, "input">>;
|
|
22
24
|
skipFormElement?: boolean;
|
|
23
25
|
skipSubmitButton?: boolean;
|
|
24
|
-
submitButtonProps?: Partial<Omit<
|
|
25
|
-
resetButtonProps?: Partial<Omit<
|
|
26
|
+
submitButtonProps?: Partial<Omit<ActionSubmitButtonProps, "form">>;
|
|
27
|
+
resetButtonProps?: Partial<Omit<ActionSubmitButtonProps, "form">>;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -139,9 +141,9 @@ const TypeForm = <T extends TObject>(props: TypeFormProps<T>) => {
|
|
|
139
141
|
{renderFields()}
|
|
140
142
|
{!skipSubmitButton && (
|
|
141
143
|
<Flex>
|
|
142
|
-
<
|
|
144
|
+
<ActionButton form={form} {...submitButtonProps}>
|
|
143
145
|
{submitButtonProps?.children ?? "Submit"}
|
|
144
|
-
</
|
|
146
|
+
</ActionButton>
|
|
145
147
|
<button type={"reset"}>Reset</button>
|
|
146
148
|
</Flex>
|
|
147
149
|
)}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { NestedView, useEvents, useStore } from "@alepha/react";
|
|
2
|
+
import {
|
|
3
|
+
AppShell,
|
|
4
|
+
type AppShellFooterProps,
|
|
5
|
+
type AppShellHeaderProps,
|
|
6
|
+
type AppShellMainProps,
|
|
7
|
+
type AppShellNavbarProps,
|
|
8
|
+
type AppShellProps,
|
|
9
|
+
} from "@mantine/core";
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import { ui } from "../../constants/ui.ts";
|
|
12
|
+
import AppBar, { type AppBarProps } from "./AppBar.tsx";
|
|
13
|
+
import { Sidebar, type SidebarProps } from "./Sidebar.tsx";
|
|
14
|
+
|
|
15
|
+
export interface AdminShellProps {
|
|
16
|
+
appShellProps?: Partial<AppShellProps>;
|
|
17
|
+
appShellMainProps?: Partial<AppShellMainProps>;
|
|
18
|
+
appShellHeaderProps?: Partial<AppShellHeaderProps>;
|
|
19
|
+
appShellNavbarProps?: Partial<AppShellNavbarProps>;
|
|
20
|
+
appShellFooterProps?: Partial<AppShellFooterProps>;
|
|
21
|
+
sidebarProps?: Partial<SidebarProps>;
|
|
22
|
+
appBarProps?: Partial<AppBarProps>;
|
|
23
|
+
header?: ReactNode;
|
|
24
|
+
footer?: ReactNode;
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module "@alepha/core" {
|
|
29
|
+
interface State {
|
|
30
|
+
"alepha.ui.sidebar.opened"?: boolean;
|
|
31
|
+
"alepha.ui.sidebar.collapsed"?: boolean;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const AdminShell = (props: AdminShellProps) => {
|
|
36
|
+
const [opened, setOpened] = useStore("alepha.ui.sidebar.opened");
|
|
37
|
+
const [collapsed] = useStore(
|
|
38
|
+
"alepha.ui.sidebar.collapsed",
|
|
39
|
+
props.sidebarProps?.collapsed,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
useEvents(
|
|
43
|
+
{
|
|
44
|
+
"react:transition:begin": () => {
|
|
45
|
+
setOpened(false);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
[],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Default AppBar items with burger button on the left
|
|
52
|
+
const defaultAppBarItems = [
|
|
53
|
+
{ position: "left" as const, type: "burger" as const },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<AppShell
|
|
58
|
+
padding="md"
|
|
59
|
+
header={{ height: 60 }}
|
|
60
|
+
navbar={
|
|
61
|
+
props.sidebarProps !== undefined
|
|
62
|
+
? {
|
|
63
|
+
width: collapsed ? { base: 72 } : { base: 300 },
|
|
64
|
+
breakpoint: "sm",
|
|
65
|
+
collapsed: { mobile: !opened },
|
|
66
|
+
}
|
|
67
|
+
: undefined
|
|
68
|
+
}
|
|
69
|
+
footer={props.footer ? { height: 60 } : undefined}
|
|
70
|
+
{...props.appShellProps}
|
|
71
|
+
>
|
|
72
|
+
<AppShell.Header bg={ui.colors.surface} {...props.appShellHeaderProps}>
|
|
73
|
+
{props.header ?? (
|
|
74
|
+
<AppBar items={defaultAppBarItems} {...props.appBarProps} />
|
|
75
|
+
)}
|
|
76
|
+
</AppShell.Header>
|
|
77
|
+
|
|
78
|
+
{props.sidebarProps !== undefined && (
|
|
79
|
+
<AppShell.Navbar bg={ui.colors.surface} {...props.appShellNavbarProps}>
|
|
80
|
+
<Sidebar collapsed={collapsed} {...props.sidebarProps} />
|
|
81
|
+
</AppShell.Navbar>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<AppShell.Main {...props.appShellMainProps}>
|
|
85
|
+
{props.children ?? <NestedView />}
|
|
86
|
+
</AppShell.Main>
|
|
87
|
+
|
|
88
|
+
{props.footer && (
|
|
89
|
+
<AppShell.Footer bg={ui.colors.surface} {...props.appShellFooterProps}>
|
|
90
|
+
{props.footer}
|
|
91
|
+
</AppShell.Footer>
|
|
92
|
+
)}
|
|
93
|
+
</AppShell>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default AdminShell;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NestedView,
|
|
1
|
+
import { NestedView, useEvents } from "@alepha/react";
|
|
2
2
|
import type {
|
|
3
3
|
ColorSchemeScriptProps,
|
|
4
4
|
MantineProviderProps,
|
|
@@ -9,7 +9,8 @@ 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
|
|
12
|
+
import { useToast } from "../../hooks/useToast.ts";
|
|
13
|
+
import Omnibar, { type OmnibarProps } from "./Omnibar.tsx";
|
|
13
14
|
|
|
14
15
|
export interface AlephaMantineProviderProps {
|
|
15
16
|
children?: ReactNode;
|
|
@@ -22,14 +23,22 @@ export interface AlephaMantineProviderProps {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
const toast = useToast();
|
|
27
|
+
|
|
28
|
+
useEvents(
|
|
29
|
+
{
|
|
30
|
+
"react:transition:begin": () => {
|
|
31
|
+
nprogress.start();
|
|
32
|
+
},
|
|
33
|
+
"react:transition:end": () => {
|
|
34
|
+
nprogress.complete();
|
|
35
|
+
},
|
|
36
|
+
"react:action:error": () => {
|
|
37
|
+
toast.danger("An error occurred while processing your action.");
|
|
38
|
+
},
|
|
31
39
|
},
|
|
32
|
-
|
|
40
|
+
[],
|
|
41
|
+
);
|
|
33
42
|
|
|
34
43
|
return (
|
|
35
44
|
<>
|
|
@@ -37,7 +46,18 @@ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
|
|
|
37
46
|
defaultColorScheme={props.mantine?.defaultColorScheme}
|
|
38
47
|
{...props.colorSchemeScript}
|
|
39
48
|
/>
|
|
40
|
-
<MantineProvider
|
|
49
|
+
<MantineProvider
|
|
50
|
+
{...props.mantine}
|
|
51
|
+
theme={{
|
|
52
|
+
primaryColor: "gray",
|
|
53
|
+
primaryShade: {
|
|
54
|
+
light: 9,
|
|
55
|
+
dark: 8,
|
|
56
|
+
},
|
|
57
|
+
cursorType: "pointer",
|
|
58
|
+
...props.mantine?.theme,
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
41
61
|
<Notifications {...props.notifications} />
|
|
42
62
|
<NavigationProgress {...props.navigationProgress} />
|
|
43
63
|
<ModalsProvider {...props.modals}>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Divider, Flex, type FlexProps } from "@mantine/core";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import BurgerButton from "../buttons/BurgerButton.tsx";
|
|
4
|
+
import DarkModeButton, {
|
|
5
|
+
type DarkModeButtonProps,
|
|
6
|
+
} from "../buttons/DarkModeButton.tsx";
|
|
7
|
+
import LanguageButton, {
|
|
8
|
+
type LanguageButtonProps,
|
|
9
|
+
} from "../buttons/LanguageButton.tsx";
|
|
10
|
+
import OmnibarButton, {
|
|
11
|
+
type OmnibarButtonProps,
|
|
12
|
+
} from "../buttons/OmnibarButton.tsx";
|
|
13
|
+
|
|
14
|
+
export type AppBarItem =
|
|
15
|
+
| AppBarElement
|
|
16
|
+
| AppBarBurger
|
|
17
|
+
| AppBarDark
|
|
18
|
+
| AppBarSearch
|
|
19
|
+
| AppBarLang
|
|
20
|
+
| AppBarSpacer
|
|
21
|
+
| AppBarDivider;
|
|
22
|
+
|
|
23
|
+
export interface AppBarElement {
|
|
24
|
+
position: "left" | "center" | "right";
|
|
25
|
+
element: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AppBarBurger {
|
|
29
|
+
position: "left" | "center" | "right";
|
|
30
|
+
type: "burger";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AppBarDark {
|
|
34
|
+
position: "left" | "center" | "right";
|
|
35
|
+
type: "dark";
|
|
36
|
+
props?: DarkModeButtonProps;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AppBarSearch {
|
|
40
|
+
position: "left" | "center" | "right";
|
|
41
|
+
type: "search";
|
|
42
|
+
props?: OmnibarButtonProps;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AppBarLang {
|
|
46
|
+
position: "left" | "center" | "right";
|
|
47
|
+
type: "lang";
|
|
48
|
+
props?: LanguageButtonProps;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AppBarSpacer {
|
|
52
|
+
position: "left" | "center" | "right";
|
|
53
|
+
type: "spacer";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface AppBarDivider {
|
|
57
|
+
position: "left" | "center" | "right";
|
|
58
|
+
type: "divider";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AppBarProps {
|
|
62
|
+
flexProps?: FlexProps;
|
|
63
|
+
items?: AppBarItem[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const AppBar = (props: AppBarProps) => {
|
|
67
|
+
const { items = [] } = props;
|
|
68
|
+
|
|
69
|
+
const renderItem = (item: AppBarItem, index: number) => {
|
|
70
|
+
if ("type" in item) {
|
|
71
|
+
if (item.type === "burger") {
|
|
72
|
+
return <BurgerButton key={index} />;
|
|
73
|
+
}
|
|
74
|
+
if (item.type === "dark") {
|
|
75
|
+
return <DarkModeButton key={index} {...item.props} />;
|
|
76
|
+
}
|
|
77
|
+
if (item.type === "search") {
|
|
78
|
+
return <OmnibarButton key={index} {...item.props} />;
|
|
79
|
+
}
|
|
80
|
+
if (item.type === "lang") {
|
|
81
|
+
return <LanguageButton key={index} {...item.props} />;
|
|
82
|
+
}
|
|
83
|
+
if (item.type === "spacer") {
|
|
84
|
+
return <Flex key={index} w={16} />;
|
|
85
|
+
}
|
|
86
|
+
if (item.type === "divider") {
|
|
87
|
+
return <Divider key={index} orientation="vertical" />;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if ("element" in item) {
|
|
91
|
+
return item.element;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const leftItems = items.filter((item) => item.position === "left");
|
|
97
|
+
const centerItems = items.filter((item) => item.position === "center");
|
|
98
|
+
const rightItems = items.filter((item) => item.position === "right");
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<Flex
|
|
102
|
+
h="100%"
|
|
103
|
+
align="center"
|
|
104
|
+
px="md"
|
|
105
|
+
justify="space-between"
|
|
106
|
+
{...props.flexProps}
|
|
107
|
+
>
|
|
108
|
+
<Flex flex={1}>
|
|
109
|
+
{leftItems.map((item, index) => (
|
|
110
|
+
<Flex key={index} ml={index === 0 ? 0 : "md"} align="center">
|
|
111
|
+
{renderItem(item, index)}
|
|
112
|
+
</Flex>
|
|
113
|
+
))}
|
|
114
|
+
</Flex>
|
|
115
|
+
<Flex>
|
|
116
|
+
{centerItems.map((item, index) => (
|
|
117
|
+
<Flex key={index} mx="md" align="center">
|
|
118
|
+
{renderItem(item, index)}
|
|
119
|
+
</Flex>
|
|
120
|
+
))}
|
|
121
|
+
</Flex>
|
|
122
|
+
<Flex flex={1} gap="md" align={"center"} justify={"end"}>
|
|
123
|
+
{rightItems.map((item, index) => (
|
|
124
|
+
<Flex key={index} ml={index === 0 ? 0 : "md"} align="center">
|
|
125
|
+
{renderItem(item, index)}
|
|
126
|
+
</Flex>
|
|
127
|
+
))}
|
|
128
|
+
</Flex>
|
|
129
|
+
</Flex>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default AppBar;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useRouter } from "@alepha/react";
|
|
2
|
+
import { Spotlight, type SpotlightActionData } from "@mantine/spotlight";
|
|
3
|
+
import { IconSearch } from "@tabler/icons-react";
|
|
4
|
+
import { type ReactNode, useMemo } from "react";
|
|
5
|
+
|
|
6
|
+
export interface OmnibarProps {
|
|
7
|
+
shortcut?: string | string[];
|
|
8
|
+
searchPlaceholder?: string;
|
|
9
|
+
nothingFound?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Omnibar = (props: OmnibarProps) => {
|
|
13
|
+
const shortcut = props.shortcut ?? "mod+K";
|
|
14
|
+
const searchPlaceholder = props.searchPlaceholder ?? "Search...";
|
|
15
|
+
const nothingFound = props.nothingFound ?? "Nothing found...";
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const actions: SpotlightActionData[] = useMemo(
|
|
18
|
+
() =>
|
|
19
|
+
router.concretePages.map((page) => ({
|
|
20
|
+
id: page.name,
|
|
21
|
+
label: page.label ?? page.name,
|
|
22
|
+
description: page.description,
|
|
23
|
+
onClick: () => router.go(page.path ?? page.name),
|
|
24
|
+
leftSection: page.icon,
|
|
25
|
+
})),
|
|
26
|
+
[],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Spotlight
|
|
31
|
+
actions={actions}
|
|
32
|
+
shortcut={shortcut}
|
|
33
|
+
limit={10}
|
|
34
|
+
searchProps={{
|
|
35
|
+
leftSection: <IconSearch size={20} />,
|
|
36
|
+
placeholder: searchPlaceholder,
|
|
37
|
+
}}
|
|
38
|
+
nothingFound={nothingFound}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default Omnibar;
|