@alepha/ui 0.13.2 → 0.13.4
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/admin/{AdminFiles-BjofP3OC.js → AdminFiles-8CC9mVsc.js} +3 -3
- package/dist/admin/{AdminFiles-BjofP3OC.js.map → AdminFiles-8CC9mVsc.js.map} +1 -1
- package/dist/admin/AdminFiles-BRLMP_7y.js +3 -0
- package/dist/admin/AdminLayout-Cm-Y4YTQ.js +396 -0
- package/dist/admin/AdminLayout-Cm-Y4YTQ.js.map +1 -0
- package/dist/admin/AdminLayout-D5M9kSiV.js +3 -0
- package/dist/admin/AdminNotifications-DxBKi2RO.js +3 -0
- package/dist/admin/AdminNotifications-d-gw5Uie.js +154 -0
- package/dist/admin/AdminNotifications-d-gw5Uie.js.map +1 -0
- package/dist/admin/{AdminSessions-CmDVneE2.js → AdminSessions-CpVusqmd.js} +37 -10
- package/dist/admin/AdminSessions-CpVusqmd.js.map +1 -0
- package/dist/admin/AdminSessions-DA285-5Q.js +3 -0
- package/dist/admin/AdminUserCreate-CQIrSslj.js +3 -0
- package/dist/admin/AdminUserCreate-DH7u_yJj.js +103 -0
- package/dist/admin/AdminUserCreate-DH7u_yJj.js.map +1 -0
- package/dist/admin/AdminUserDetails-DVmFCDsU.js +221 -0
- package/dist/admin/AdminUserDetails-DVmFCDsU.js.map +1 -0
- package/dist/admin/AdminUserDetails-T3nkXSdz.js +3 -0
- package/dist/admin/AdminUserLayout-DdtZGX8n.js +3 -0
- package/dist/admin/AdminUserLayout-gpOyn0Y7.js +153 -0
- package/dist/admin/AdminUserLayout-gpOyn0Y7.js.map +1 -0
- package/dist/admin/AdminUserSessions-CWYzjB3D.js +3 -0
- package/dist/admin/AdminUserSessions-CdVwoM-h.js +129 -0
- package/dist/admin/AdminUserSessions-CdVwoM-h.js.map +1 -0
- package/dist/admin/AdminUserSettings-S7gZvvjO.js +164 -0
- package/dist/admin/AdminUserSettings-S7gZvvjO.js.map +1 -0
- package/dist/admin/AdminUserSettings-jCzVYw_2.js +3 -0
- package/dist/admin/{AdminUsers-88De5pev.js → AdminUsers-9qEzxqAL.js} +33 -15
- package/dist/admin/AdminUsers-9qEzxqAL.js.map +1 -0
- package/dist/admin/AdminUsers-BcSUxV01.js +3 -0
- package/dist/admin/index.d.ts +5568 -418
- package/dist/admin/index.js +302 -42
- package/dist/admin/index.js.map +1 -1
- package/dist/auth/AuthLayout-BSL8ZHgr.js +19 -0
- package/dist/auth/AuthLayout-BSL8ZHgr.js.map +1 -0
- package/dist/auth/{Login-OCrvjs9U.js → Login-AlVPPqQp.js} +6 -5
- package/dist/auth/Login-AlVPPqQp.js.map +1 -0
- package/dist/auth/Login-otdWVvVU.js +4 -0
- package/dist/auth/{Register-Ei34GSba.js → Register-BxJmOqpF.js} +9 -6
- package/dist/auth/Register-BxJmOqpF.js.map +1 -0
- package/dist/auth/Register-D10MnlQc.js +4 -0
- package/dist/auth/{ResetPassword-tO0oMzfo.js → ResetPassword-BhyZ9ek4.js} +3 -3
- package/dist/auth/ResetPassword-BhyZ9ek4.js.map +1 -0
- package/dist/auth/ResetPassword-llBG-STp.js +3 -0
- package/dist/auth/VerifyEmail-BvOG-IUC.js +3 -0
- package/dist/auth/VerifyEmail-DeLct3oQ.js +131 -0
- package/dist/auth/VerifyEmail-DeLct3oQ.js.map +1 -0
- package/dist/auth/index.d.ts +2412 -2254
- package/dist/auth/index.js +97 -20
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +280 -95
- package/dist/core/index.js +1375 -394
- package/dist/core/index.js.map +1 -1
- package/package.json +7 -6
- package/src/admin/AdminRouter.ts +116 -29
- package/src/admin/AdminSidebar.ts +31 -0
- package/src/admin/MainRouter.ts +23 -0
- package/src/admin/components/AdminLayout.tsx +66 -104
- package/src/admin/components/AdminNotifications.tsx +196 -12
- package/src/admin/components/AdminSessions.tsx +43 -7
- package/src/admin/components/AdminUserCreate.tsx +84 -0
- package/src/admin/components/AdminUserDetails.tsx +180 -0
- package/src/admin/components/AdminUserLayout.tsx +172 -0
- package/src/admin/components/AdminUserSessions.tsx +158 -0
- package/src/admin/components/AdminUserSettings.tsx +165 -0
- package/src/admin/components/AdminUsers.tsx +29 -9
- package/src/admin/index.ts +15 -3
- package/src/auth/AuthI18n.ts +22 -0
- package/src/auth/AuthRouter.ts +82 -8
- package/src/auth/components/AuthLayout.tsx +12 -0
- package/src/auth/components/Login.tsx +14 -12
- package/src/auth/components/Register.tsx +6 -5
- package/src/auth/components/ResetPassword.tsx +1 -1
- package/src/auth/components/VerifyEmail.tsx +102 -0
- package/src/auth/components/buttons/UserButton.tsx +12 -2
- package/src/auth/index.ts +1 -0
- package/src/core/components/buttons/ActionButton.tsx +12 -4
- package/src/core/components/buttons/DarkModeButton.tsx +1 -1
- package/src/core/components/buttons/ThemeButton.tsx +31 -0
- package/src/core/components/layout/AdminShell.tsx +4 -2
- package/src/core/components/layout/AlephaMantineProvider.tsx +10 -4
- package/src/core/components/layout/Omnibar.tsx +27 -15
- package/src/core/components/layout/Sidebar.tsx +33 -17
- package/src/core/components/table/DataTable.tsx +9 -5
- package/src/core/hooks/useTheme.ts +25 -0
- package/src/core/index.ts +8 -3
- package/src/core/providers/ThemeProvider.ts +90 -0
- package/src/core/themes/aurora.ts +107 -0
- package/src/core/themes/crystal.ts +107 -0
- package/src/core/themes/default.ts +7 -0
- package/src/core/themes/ember.ts +107 -0
- package/src/core/themes/index.ts +7 -0
- package/src/core/themes/midnight.ts +98 -0
- package/src/core/themes/remoraid.ts +278 -0
- package/src/core/themes/slate.ts +81 -0
- package/styles.css +84 -0
- package/dist/admin/AdminFiles-DldZB7oo.js +0 -3
- package/dist/admin/AdminJobs-BOq6AZOW.js +0 -3
- package/dist/admin/AdminJobs-CDnVxEv6.js +0 -125
- package/dist/admin/AdminJobs-CDnVxEv6.js.map +0 -1
- package/dist/admin/AdminLayout-Bgx25J8m.js +0 -3
- package/dist/admin/AdminLayout-CervL8LV.js +0 -88
- package/dist/admin/AdminLayout-CervL8LV.js.map +0 -1
- package/dist/admin/AdminNotifications-BDQXt3-e.js +0 -3
- package/dist/admin/AdminNotifications-DvI2989x.js +0 -40
- package/dist/admin/AdminNotifications-DvI2989x.js.map +0 -1
- package/dist/admin/AdminParameters-D_v0GAvI.js +0 -3
- package/dist/admin/AdminParameters-P1LB6ZI1.js +0 -40
- package/dist/admin/AdminParameters-P1LB6ZI1.js.map +0 -1
- package/dist/admin/AdminSessions-CmDVneE2.js.map +0 -1
- package/dist/admin/AdminSessions-Dkk_fzWK.js +0 -3
- package/dist/admin/AdminUsers-88De5pev.js.map +0 -1
- package/dist/admin/AdminUsers-oyAXqZ5l.js +0 -3
- package/dist/admin/AdminVerifications-D93TKymL.js +0 -3
- package/dist/admin/AdminVerifications-DBVEoqJe.js +0 -40
- package/dist/admin/AdminVerifications-DBVEoqJe.js.map +0 -1
- package/dist/auth/Login-BC2jTczq.js +0 -4
- package/dist/auth/Login-OCrvjs9U.js.map +0 -1
- package/dist/auth/Register-Dh0lsQmI.js +0 -4
- package/dist/auth/Register-Ei34GSba.js.map +0 -1
- package/dist/auth/ResetPassword-BnlAQAOE.js +0 -3
- package/dist/auth/ResetPassword-tO0oMzfo.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useRouter } from "@alepha/react";
|
|
1
2
|
import { useAuth } from "@alepha/react/auth";
|
|
2
3
|
import {
|
|
3
4
|
ActionButton,
|
|
@@ -7,8 +8,9 @@ import {
|
|
|
7
8
|
ui,
|
|
8
9
|
} from "@alepha/ui";
|
|
9
10
|
import { Avatar } from "@mantine/core";
|
|
10
|
-
import { IconLogout, IconUser } from "@tabler/icons-react";
|
|
11
|
+
import { IconLogin2, IconLogout, IconUser } from "@tabler/icons-react";
|
|
11
12
|
import type { ReactNode } from "react";
|
|
13
|
+
import type { AuthRouter } from "../../AuthRouter.ts";
|
|
12
14
|
|
|
13
15
|
export interface UserButtonProps
|
|
14
16
|
extends Omit<ActionProps, "menu" | "icon" | "onClick"> {
|
|
@@ -55,8 +57,16 @@ const UserButton = (props: UserButtonProps) => {
|
|
|
55
57
|
picture?: string;
|
|
56
58
|
}>();
|
|
57
59
|
|
|
60
|
+
const authRouter = useRouter<AuthRouter>();
|
|
61
|
+
|
|
58
62
|
if (!auth.user) {
|
|
59
|
-
return
|
|
63
|
+
return (
|
|
64
|
+
<ActionButton
|
|
65
|
+
{...buttonProps}
|
|
66
|
+
icon={IconLogin2}
|
|
67
|
+
href={authRouter.path("login")}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
const userLabel = auth.user.username || auth.user.email;
|
package/src/auth/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { default as UserButton } from "./components/buttons/UserButton.tsx";
|
|
|
13
13
|
export { default as Login } from "./components/Login.tsx";
|
|
14
14
|
export { default as Register } from "./components/Register.tsx";
|
|
15
15
|
export { default as ResetPassword } from "./components/ResetPassword.tsx";
|
|
16
|
+
export { default as VerifyEmail } from "./components/VerifyEmail.tsx";
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
18
19
|
|
|
@@ -144,6 +144,11 @@ export interface ActionCommonProps extends ButtonProps {
|
|
|
144
144
|
* Additional props to pass to the ThemeIcon wrapping the icon.
|
|
145
145
|
*/
|
|
146
146
|
themeIconProps?: ThemeIconProps;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Visual intent of the action button.
|
|
150
|
+
*/
|
|
151
|
+
intent?: "primary" | "success" | "danger" | "warning" | "info";
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
export type ActionProps = ActionCommonProps &
|
|
@@ -234,9 +239,13 @@ const ActionMenuItem = (props: {
|
|
|
234
239
|
};
|
|
235
240
|
|
|
236
241
|
const ActionButton = (_props: ActionProps) => {
|
|
237
|
-
const props = { variant: "
|
|
242
|
+
const props = { variant: "subtle", ..._props };
|
|
238
243
|
const { tooltip, menu, icon, ...restProps } = props;
|
|
239
244
|
|
|
245
|
+
// set default color to gray (not colored)
|
|
246
|
+
restProps.color ??= "gray";
|
|
247
|
+
restProps.c ??= "var(--mantine-color-text)";
|
|
248
|
+
|
|
240
249
|
if (props.icon) {
|
|
241
250
|
const icon = isComponentType(props.icon) ? (
|
|
242
251
|
<props.icon size={ui.sizes.icon.md} />
|
|
@@ -254,15 +263,14 @@ const ActionButton = (_props: ActionProps) => {
|
|
|
254
263
|
|
|
255
264
|
if (!props.children) {
|
|
256
265
|
restProps.children = Children.only(icon);
|
|
257
|
-
restProps.
|
|
266
|
+
restProps.px ??= "xs";
|
|
258
267
|
} else {
|
|
259
268
|
restProps.leftSection = icon;
|
|
260
269
|
}
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
if (props.leftSection && !props.children) {
|
|
264
|
-
restProps.
|
|
265
|
-
restProps.p ??= "xs";
|
|
273
|
+
restProps.px ??= "xs";
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
if (props.textVisibleFrom) {
|
|
@@ -73,7 +73,7 @@ const DarkModeButton = (props: DarkModeButtonProps) => {
|
|
|
73
73
|
return (
|
|
74
74
|
<ActionButton
|
|
75
75
|
onClick={toggleColorScheme}
|
|
76
|
-
variant={props.variant ?? "
|
|
76
|
+
variant={props.variant ?? "subtle"}
|
|
77
77
|
size={props.size ?? "sm"}
|
|
78
78
|
aria-label="Toggle color scheme"
|
|
79
79
|
px={"xs"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useInject } from "@alepha/react";
|
|
2
|
+
import { IconPalette } from "@tabler/icons-react";
|
|
3
|
+
import { useTheme } from "../../hooks/useTheme.ts";
|
|
4
|
+
import { ThemeProvider } from "../../providers/ThemeProvider.ts";
|
|
5
|
+
import ActionButton, { type ActionProps } from "./ActionButton.tsx";
|
|
6
|
+
|
|
7
|
+
export interface ThemeButtonProps {
|
|
8
|
+
actionProps?: Partial<ActionProps>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ThemeButton = (props: ThemeButtonProps) => {
|
|
12
|
+
const [theme, setTheme] = useTheme();
|
|
13
|
+
const themes = useInject(ThemeProvider).themes;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<ActionButton
|
|
17
|
+
variant="subtle"
|
|
18
|
+
icon={IconPalette}
|
|
19
|
+
menu={{
|
|
20
|
+
items: themes.map((it) => ({
|
|
21
|
+
label: it.label,
|
|
22
|
+
onClick: () => setTheme(it),
|
|
23
|
+
active: theme.id === it.id,
|
|
24
|
+
})),
|
|
25
|
+
}}
|
|
26
|
+
{...props.actionProps}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default ThemeButton;
|
|
@@ -93,18 +93,20 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
93
93
|
|
|
94
94
|
return (
|
|
95
95
|
<AppShell
|
|
96
|
+
w={"100%"}
|
|
97
|
+
flex={1}
|
|
96
98
|
padding="md"
|
|
97
99
|
header={hasAppBar ? { height: 60 } : undefined}
|
|
98
100
|
navbar={
|
|
99
101
|
hasSidebar
|
|
100
102
|
? {
|
|
101
|
-
width: collapsed ? { base:
|
|
103
|
+
width: collapsed ? { base: 78 } : { base: 300 },
|
|
102
104
|
breakpoint: "sm",
|
|
103
105
|
collapsed: { mobile: !opened },
|
|
104
106
|
}
|
|
105
107
|
: undefined
|
|
106
108
|
}
|
|
107
|
-
footer={props.footer ? { height:
|
|
109
|
+
footer={props.footer ? { height: 24 } : undefined}
|
|
108
110
|
{...props.appShellProps}
|
|
109
111
|
>
|
|
110
112
|
<AppShell.Header bg={ui.colors.surface} {...props.appShellHeaderProps}>
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { NestedView, useEvents } from "@alepha/react";
|
|
2
2
|
import { FormValidationError } from "@alepha/react/form";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
ColorSchemeScript,
|
|
5
|
+
type ColorSchemeScriptProps,
|
|
6
|
+
MantineProvider,
|
|
7
|
+
type MantineProviderProps,
|
|
6
8
|
} from "@mantine/core";
|
|
7
|
-
import { ColorSchemeScript, MantineProvider } from "@mantine/core";
|
|
8
9
|
import { ModalsProvider, type ModalsProviderProps } from "@mantine/modals";
|
|
9
10
|
import { Notifications, type NotificationsProps } from "@mantine/notifications";
|
|
10
11
|
import type { NavigationProgressProps } from "@mantine/nprogress";
|
|
11
12
|
import { NavigationProgress, nprogress } from "@mantine/nprogress";
|
|
12
13
|
import type { ReactNode } from "react";
|
|
14
|
+
import { useTheme } from "../../hooks/useTheme.ts";
|
|
13
15
|
import { useToast } from "../../hooks/useToast.ts";
|
|
14
16
|
import Omnibar, { type OmnibarProps } from "./Omnibar.tsx";
|
|
15
17
|
|
|
@@ -25,6 +27,7 @@ export interface AlephaMantineProviderProps {
|
|
|
25
27
|
|
|
26
28
|
const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
|
|
27
29
|
const toast = useToast();
|
|
30
|
+
const [theme] = useTheme();
|
|
28
31
|
|
|
29
32
|
useEvents(
|
|
30
33
|
{
|
|
@@ -59,6 +62,9 @@ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
|
|
|
59
62
|
<MantineProvider
|
|
60
63
|
{...props.mantine}
|
|
61
64
|
theme={{
|
|
65
|
+
// Spread all theme properties from the selected theme
|
|
66
|
+
...theme,
|
|
67
|
+
// User overrides take precedence
|
|
62
68
|
...props.mantine?.theme,
|
|
63
69
|
}}
|
|
64
70
|
>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { useRouter } from "@alepha/react";
|
|
1
|
+
import { useRouter, useStore } from "@alepha/react";
|
|
2
2
|
import { Spotlight, type SpotlightActionData } from "@mantine/spotlight";
|
|
3
3
|
import { IconSearch } from "@tabler/icons-react";
|
|
4
4
|
import { type ReactNode, useMemo } from "react";
|
|
5
|
+
import { ui } from "../../constants/ui.ts";
|
|
6
|
+
import { renderIcon } from "../buttons/ActionButton.tsx";
|
|
5
7
|
|
|
6
8
|
export interface OmnibarProps {
|
|
7
9
|
shortcut?: string | string[];
|
|
@@ -14,21 +16,31 @@ const Omnibar = (props: OmnibarProps) => {
|
|
|
14
16
|
const searchPlaceholder = props.searchPlaceholder ?? "Search...";
|
|
15
17
|
const nothingFound = props.nothingFound ?? "Nothing found...";
|
|
16
18
|
const router = useRouter();
|
|
19
|
+
|
|
20
|
+
// watch user to re-render on permission changes
|
|
21
|
+
const [user] = useStore("alepha.server.request.user");
|
|
22
|
+
|
|
17
23
|
const actions: SpotlightActionData[] = useMemo(
|
|
18
24
|
() =>
|
|
19
|
-
router.concretePages
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
router.concretePages
|
|
26
|
+
.filter((page) => {
|
|
27
|
+
if (page.can && !page.can()) return false;
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
})
|
|
31
|
+
.map((page) => ({
|
|
32
|
+
id: page.name,
|
|
33
|
+
label: page.label ?? page.name,
|
|
34
|
+
description: page.description,
|
|
35
|
+
onClick: () => {
|
|
36
|
+
if (page.staticName) {
|
|
37
|
+
return router.go(page.staticName, { params: page.params });
|
|
38
|
+
}
|
|
39
|
+
return router.go(page.name);
|
|
40
|
+
},
|
|
41
|
+
leftSection: renderIcon(page.icon),
|
|
42
|
+
})),
|
|
43
|
+
[user],
|
|
32
44
|
);
|
|
33
45
|
|
|
34
46
|
return (
|
|
@@ -37,7 +49,7 @@ const Omnibar = (props: OmnibarProps) => {
|
|
|
37
49
|
shortcut={shortcut}
|
|
38
50
|
limit={10}
|
|
39
51
|
searchProps={{
|
|
40
|
-
leftSection: <IconSearch size={
|
|
52
|
+
leftSection: <IconSearch size={ui.sizes.icon.md} />,
|
|
41
53
|
placeholder: searchPlaceholder,
|
|
42
54
|
}}
|
|
43
55
|
nothingFound={nothingFound}
|
|
@@ -86,6 +86,11 @@ export const Sidebar = (props: SidebarProps) => {
|
|
|
86
86
|
return <Flex key={key}>{item.element}</Flex>;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// Check visibility control
|
|
90
|
+
if (item.can && !item.can()) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
if (props.collapsed) {
|
|
90
95
|
return (
|
|
91
96
|
<SidebarCollapsedItem
|
|
@@ -224,7 +229,6 @@ export const SidebarItem = (props: SidebarItemProps) => {
|
|
|
224
229
|
props.theme.button?.size ??
|
|
225
230
|
(level === 0 ? "sm" : "xs")
|
|
226
231
|
}
|
|
227
|
-
color={"var(--alepha-text)"}
|
|
228
232
|
variant={"subtle"}
|
|
229
233
|
variantActive={"default"}
|
|
230
234
|
radius={props.item.theme?.radius ?? props.theme.button?.radius ?? "md"}
|
|
@@ -271,15 +275,17 @@ export const SidebarItem = (props: SidebarItemProps) => {
|
|
|
271
275
|
bottom: 16,
|
|
272
276
|
}}
|
|
273
277
|
/>
|
|
274
|
-
{item.children
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
{item.children
|
|
279
|
+
.filter((child) => !child.can || child.can())
|
|
280
|
+
.map((child, index) => (
|
|
281
|
+
<SidebarItem
|
|
282
|
+
key={index}
|
|
283
|
+
item={child}
|
|
284
|
+
level={level + 1}
|
|
285
|
+
onItemClick={props.onItemClick}
|
|
286
|
+
theme={props.theme}
|
|
287
|
+
/>
|
|
288
|
+
))}
|
|
283
289
|
</Flex>
|
|
284
290
|
)}
|
|
285
291
|
</Flex>
|
|
@@ -335,9 +341,16 @@ const SidebarCollapsedItem = (props: SidebarItemProps) => {
|
|
|
335
341
|
props.theme.button?.size ??
|
|
336
342
|
(level === 0 ? "sm" : "xs")
|
|
337
343
|
}
|
|
338
|
-
color={"var(--alepha-text)"}
|
|
339
344
|
variant={"subtle"}
|
|
340
345
|
variantActive={"default"}
|
|
346
|
+
tooltip={
|
|
347
|
+
item.children
|
|
348
|
+
? undefined
|
|
349
|
+
: {
|
|
350
|
+
label: item.label,
|
|
351
|
+
position: "right",
|
|
352
|
+
}
|
|
353
|
+
}
|
|
341
354
|
radius={props.item.theme?.radius ?? props.theme.button?.radius ?? "md"}
|
|
342
355
|
onClick={handleItemClick}
|
|
343
356
|
icon={renderIcon(item.icon) ?? <IconSquareRounded />}
|
|
@@ -348,12 +361,14 @@ const SidebarCollapsedItem = (props: SidebarItemProps) => {
|
|
|
348
361
|
? ({
|
|
349
362
|
position: "right",
|
|
350
363
|
on: "hover",
|
|
351
|
-
items: item.children
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
364
|
+
items: item.children
|
|
365
|
+
.filter((child) => !child.can || child.can())
|
|
366
|
+
.map((child) => ({
|
|
367
|
+
label: child.label,
|
|
368
|
+
href: child.href,
|
|
369
|
+
icon: renderIcon(child.icon),
|
|
370
|
+
children: child.children?.filter((c) => !c.can || c.can()),
|
|
371
|
+
})),
|
|
357
372
|
} as any)
|
|
358
373
|
: undefined
|
|
359
374
|
}
|
|
@@ -410,6 +425,7 @@ export interface SidebarMenuItem extends SidebarAbstractItem {
|
|
|
410
425
|
rightSection?: ReactNode;
|
|
411
426
|
theme?: SidebarButtonTheme;
|
|
412
427
|
actionProps?: ActionProps;
|
|
428
|
+
can?: () => boolean; // Visibility control: true -> visible, false -> hidden
|
|
413
429
|
}
|
|
414
430
|
|
|
415
431
|
export interface SidebarButtonTheme {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useInject } from "@alepha/react";
|
|
2
2
|
import { type FormModel, useForm } from "@alepha/react/form";
|
|
3
3
|
import {
|
|
4
|
+
Card,
|
|
4
5
|
Flex,
|
|
5
6
|
Pagination,
|
|
6
7
|
Select,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
} from "alepha";
|
|
21
22
|
import { DateTimeProvider, type DurationLike } from "alepha/datetime";
|
|
22
23
|
import { type ReactNode, useEffect, useState } from "react";
|
|
24
|
+
import { ui } from "../../constants/ui.ts";
|
|
23
25
|
import ActionButton from "../buttons/ActionButton.tsx";
|
|
24
26
|
import TypeForm, { type TypeFormProps } from "../form/TypeForm.tsx";
|
|
25
27
|
|
|
@@ -283,11 +285,13 @@ const DataTable = <T extends object, Filters extends TObject>(
|
|
|
283
285
|
return (
|
|
284
286
|
<Flex direction={"column"} gap={"sm"} flex={1}>
|
|
285
287
|
{props.filters ? (
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
<Card withBorder p={"lg"} bg={ui.colors.elevated}>
|
|
289
|
+
<TypeForm
|
|
290
|
+
{...props.typeFormProps}
|
|
291
|
+
form={form as unknown as FormModel<Filters>}
|
|
292
|
+
schema={schema}
|
|
293
|
+
/>
|
|
294
|
+
</Card>
|
|
291
295
|
) : null}
|
|
292
296
|
|
|
293
297
|
<Flex className={"overflow-auto"}>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useInject, useStore } from "@alepha/react";
|
|
2
|
+
import {
|
|
3
|
+
type AlephaTheme,
|
|
4
|
+
mantineThemeAtom,
|
|
5
|
+
type Theme,
|
|
6
|
+
ThemeProvider,
|
|
7
|
+
} from "../providers/ThemeProvider.ts";
|
|
8
|
+
|
|
9
|
+
export const useTheme = () => {
|
|
10
|
+
useStore(mantineThemeAtom);
|
|
11
|
+
|
|
12
|
+
const themeService = useInject(ThemeProvider);
|
|
13
|
+
const currentTheme = themeService.getTheme();
|
|
14
|
+
|
|
15
|
+
// Find the full theme object from the themes array
|
|
16
|
+
const fullTheme =
|
|
17
|
+
themeService.themes.find((t) => t.id === currentTheme.id) ??
|
|
18
|
+
themeService.themes[0];
|
|
19
|
+
|
|
20
|
+
const applyTheme = (theme: Theme | AlephaTheme) => {
|
|
21
|
+
themeService.setTheme({ id: theme.id });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return [fullTheme, applyTheme] as const;
|
|
25
|
+
};
|
package/src/core/index.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { AlephaReactForm } from "@alepha/react/form";
|
|
|
2
2
|
import { AlephaReactHead } from "@alepha/react/head";
|
|
3
3
|
import { AlephaReactI18n } from "@alepha/react/i18n";
|
|
4
4
|
import { $module } from "alepha";
|
|
5
|
-
import type { ReactNode } from "react";
|
|
5
|
+
import type { ComponentType, ReactNode } from "react";
|
|
6
6
|
import type { ControlProps } from "./components/form/Control.tsx";
|
|
7
|
+
import { ThemeProvider } from "./providers/ThemeProvider.ts";
|
|
7
8
|
import { RootRouter } from "./RootRouter.ts";
|
|
8
9
|
import { DialogService } from "./services/DialogService.tsx";
|
|
9
10
|
import { ToastService } from "./services/ToastService.tsx";
|
|
@@ -27,6 +28,8 @@ export { default as ClipboardButton } from "./components/buttons/ClipboardButton
|
|
|
27
28
|
export { default as DarkModeButton } from "./components/buttons/DarkModeButton.tsx";
|
|
28
29
|
export { default as LanguageButton } from "./components/buttons/LanguageButton.tsx";
|
|
29
30
|
export { default as OmnibarButton } from "./components/buttons/OmnibarButton.tsx";
|
|
31
|
+
export type { ThemeButtonProps } from "./components/buttons/ThemeButton.tsx";
|
|
32
|
+
export { default as ThemeButton } from "./components/buttons/ThemeButton.tsx";
|
|
30
33
|
export { default as JsonViewer } from "./components/data/JsonViewer.tsx";
|
|
31
34
|
export { default as AlertDialog } from "./components/dialogs/AlertDialog.tsx";
|
|
32
35
|
export { default as ConfirmDialog } from "./components/dialogs/ConfirmDialog.tsx";
|
|
@@ -77,6 +80,7 @@ export { default as DataTable } from "./components/table/DataTable.tsx";
|
|
|
77
80
|
export * from "./constants/ui.ts";
|
|
78
81
|
export { useDialog } from "./hooks/useDialog.ts";
|
|
79
82
|
export { useToast } from "./hooks/useToast.ts";
|
|
83
|
+
export * from "./providers/ThemeProvider.ts";
|
|
80
84
|
export * from "./RootRouter.ts";
|
|
81
85
|
export type {
|
|
82
86
|
AlertDialogOptions,
|
|
@@ -121,7 +125,7 @@ declare module "@alepha/react" {
|
|
|
121
125
|
/**
|
|
122
126
|
* Optional icon for the page.
|
|
123
127
|
*/
|
|
124
|
-
icon?: ReactNode;
|
|
128
|
+
icon?: ReactNode | ComponentType;
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -134,11 +138,12 @@ declare module "@alepha/react" {
|
|
|
134
138
|
*/
|
|
135
139
|
export const AlephaUI = $module({
|
|
136
140
|
name: "alepha.ui",
|
|
137
|
-
services: [DialogService, ToastService, RootRouter],
|
|
141
|
+
services: [DialogService, ToastService, ThemeProvider, RootRouter],
|
|
138
142
|
register: (alepha) => {
|
|
139
143
|
alepha.with(AlephaReactI18n);
|
|
140
144
|
alepha.with(AlephaReactHead);
|
|
141
145
|
alepha.with(AlephaReactForm);
|
|
146
|
+
alepha.with(ThemeProvider);
|
|
142
147
|
alepha.with(DialogService);
|
|
143
148
|
alepha.with(ToastService);
|
|
144
149
|
},
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { $head } from "@alepha/react/head";
|
|
2
|
+
import type { MantineThemeOverride } from "@mantine/core";
|
|
3
|
+
import { $atom, $inject, Alepha, type Static, t } from "alepha";
|
|
4
|
+
import { $cookie } from "alepha/server/cookies";
|
|
5
|
+
import {
|
|
6
|
+
auroraTheme,
|
|
7
|
+
crystalTheme,
|
|
8
|
+
defaultTheme,
|
|
9
|
+
emberTheme,
|
|
10
|
+
midnightTheme,
|
|
11
|
+
remoraidTheme,
|
|
12
|
+
slateTheme,
|
|
13
|
+
} from "../themes/index.ts";
|
|
14
|
+
|
|
15
|
+
export const mantineThemeAtom = $atom({
|
|
16
|
+
name: "alepha.ui.theme",
|
|
17
|
+
schema: t.object({
|
|
18
|
+
id: t.string(),
|
|
19
|
+
}),
|
|
20
|
+
default: {
|
|
21
|
+
id: "default",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type Theme = Static<typeof mantineThemeAtom.schema>;
|
|
26
|
+
|
|
27
|
+
declare module "alepha" {
|
|
28
|
+
interface State {
|
|
29
|
+
[mantineThemeAtom.key]?: Theme;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AlephaTheme = MantineThemeOverride & {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export class ThemeProvider {
|
|
40
|
+
protected readonly alepha = $inject(Alepha);
|
|
41
|
+
protected themeCookie = $cookie(mantineThemeAtom);
|
|
42
|
+
|
|
43
|
+
public themes: AlephaTheme[] = [
|
|
44
|
+
defaultTheme,
|
|
45
|
+
remoraidTheme,
|
|
46
|
+
midnightTheme,
|
|
47
|
+
slateTheme,
|
|
48
|
+
auroraTheme,
|
|
49
|
+
emberTheme,
|
|
50
|
+
crystalTheme,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
protected themeHead = $head(() => {
|
|
54
|
+
return {
|
|
55
|
+
htmlAttributes: {
|
|
56
|
+
"data-theme": this.getTheme().id,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
public setTheme(theme: Theme) {
|
|
62
|
+
this.themeCookie.set(theme);
|
|
63
|
+
this.alepha.store.set(mantineThemeAtom, theme);
|
|
64
|
+
|
|
65
|
+
if (typeof document === "undefined") return;
|
|
66
|
+
|
|
67
|
+
document.documentElement.removeAttribute("data-theme");
|
|
68
|
+
|
|
69
|
+
if (theme.id !== "default") {
|
|
70
|
+
document.documentElement.setAttribute("data-theme", theme.id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public getTheme() {
|
|
75
|
+
// TODO: make a safe cookie getter, today it crash when Cookie Server is called inside vite pre-render
|
|
76
|
+
try {
|
|
77
|
+
return (
|
|
78
|
+
this.themeCookie.get() ??
|
|
79
|
+
this.alepha.store.get(mantineThemeAtom) ??
|
|
80
|
+
mantineThemeAtom.options.default
|
|
81
|
+
);
|
|
82
|
+
} catch {
|
|
83
|
+
// TODO: atom should take default value if undefined ???
|
|
84
|
+
return (
|
|
85
|
+
this.alepha.store.get(mantineThemeAtom) ??
|
|
86
|
+
mantineThemeAtom.options.default
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { AlephaTheme } from "../providers/ThemeProvider.ts";
|
|
2
|
+
|
|
3
|
+
export const auroraTheme: AlephaTheme = {
|
|
4
|
+
id: "aurora",
|
|
5
|
+
label: "Aurora",
|
|
6
|
+
description: "Vibrant, playful with gradients and hover effects",
|
|
7
|
+
primaryColor: "violet",
|
|
8
|
+
primaryShade: { light: 5, dark: 4 },
|
|
9
|
+
fontFamily:
|
|
10
|
+
'"Nunito", "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
11
|
+
fontFamilyMonospace:
|
|
12
|
+
'"Fira Code", "JetBrains Mono", ui-monospace, Consolas, monospace',
|
|
13
|
+
headings: {
|
|
14
|
+
fontFamily:
|
|
15
|
+
'"Nunito", "Poppins", -apple-system, BlinkMacSystemFont, sans-serif',
|
|
16
|
+
fontWeight: "700",
|
|
17
|
+
textWrap: "wrap",
|
|
18
|
+
sizes: {
|
|
19
|
+
h1: { fontSize: "2.25rem", lineHeight: "1.3" },
|
|
20
|
+
h2: { fontSize: "1.75rem", lineHeight: "1.35" },
|
|
21
|
+
h3: { fontSize: "1.375rem", lineHeight: "1.4" },
|
|
22
|
+
h4: { fontSize: "1.125rem", lineHeight: "1.45" },
|
|
23
|
+
h5: { fontSize: "1rem", lineHeight: "1.5" },
|
|
24
|
+
h6: { fontSize: "0.875rem", lineHeight: "1.5" },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
fontSizes: {
|
|
28
|
+
xs: "0.8rem",
|
|
29
|
+
sm: "0.9rem",
|
|
30
|
+
md: "1rem",
|
|
31
|
+
lg: "1.125rem",
|
|
32
|
+
xl: "1.3rem",
|
|
33
|
+
},
|
|
34
|
+
lineHeights: {
|
|
35
|
+
xs: "1.5",
|
|
36
|
+
sm: "1.55",
|
|
37
|
+
md: "1.6",
|
|
38
|
+
lg: "1.65",
|
|
39
|
+
xl: "1.7",
|
|
40
|
+
},
|
|
41
|
+
radius: {
|
|
42
|
+
xs: "6px",
|
|
43
|
+
sm: "8px",
|
|
44
|
+
md: "12px",
|
|
45
|
+
lg: "16px",
|
|
46
|
+
xl: "24px",
|
|
47
|
+
},
|
|
48
|
+
defaultRadius: "md",
|
|
49
|
+
shadows: {
|
|
50
|
+
xs: "0 2px 4px rgba(139, 92, 246, 0.08)",
|
|
51
|
+
sm: "0 4px 8px rgba(139, 92, 246, 0.1)",
|
|
52
|
+
md: "0 8px 16px rgba(139, 92, 246, 0.12)",
|
|
53
|
+
lg: "0 16px 32px rgba(139, 92, 246, 0.15)",
|
|
54
|
+
xl: "0 24px 48px rgba(139, 92, 246, 0.18)",
|
|
55
|
+
},
|
|
56
|
+
defaultGradient: { from: "violet", to: "pink", deg: 135 },
|
|
57
|
+
colors: {
|
|
58
|
+
dark: [
|
|
59
|
+
"#d4d0dc",
|
|
60
|
+
"#a8a3b3",
|
|
61
|
+
"#7c7689",
|
|
62
|
+
"#5c5568",
|
|
63
|
+
"#454050",
|
|
64
|
+
"#302c38",
|
|
65
|
+
"#252129",
|
|
66
|
+
"#1e1b24",
|
|
67
|
+
"#16141a",
|
|
68
|
+
"#0d0c10",
|
|
69
|
+
],
|
|
70
|
+
gray: [
|
|
71
|
+
"#faf9fb",
|
|
72
|
+
"#f3f1f5",
|
|
73
|
+
"#e8e5ed",
|
|
74
|
+
"#d4d0dc",
|
|
75
|
+
"#a8a3b3",
|
|
76
|
+
"#7c7689",
|
|
77
|
+
"#5c5568",
|
|
78
|
+
"#454050",
|
|
79
|
+
"#302c38",
|
|
80
|
+
"#1e1b24",
|
|
81
|
+
],
|
|
82
|
+
violet: [
|
|
83
|
+
"#f5f3ff",
|
|
84
|
+
"#ede9fe",
|
|
85
|
+
"#ddd6fe",
|
|
86
|
+
"#c4b5fd",
|
|
87
|
+
"#a78bfa",
|
|
88
|
+
"#8b5cf6",
|
|
89
|
+
"#7c3aed",
|
|
90
|
+
"#6d28d9",
|
|
91
|
+
"#5b21b6",
|
|
92
|
+
"#4c1d95",
|
|
93
|
+
],
|
|
94
|
+
pink: [
|
|
95
|
+
"#fdf2f8",
|
|
96
|
+
"#fce7f3",
|
|
97
|
+
"#fbcfe8",
|
|
98
|
+
"#f9a8d4",
|
|
99
|
+
"#f472b6",
|
|
100
|
+
"#ec4899",
|
|
101
|
+
"#db2777",
|
|
102
|
+
"#be185d",
|
|
103
|
+
"#9d174d",
|
|
104
|
+
"#831843",
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
};
|