@alepha/ui 0.13.2 → 0.13.3
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/AdminLayout-JakF7ESb.js +388 -0
- package/dist/admin/AdminLayout-JakF7ESb.js.map +1 -0
- package/dist/admin/AdminLayout-qNsIyl30.js +3 -0
- package/dist/admin/AdminNotifications-BPrxALdS.js +154 -0
- package/dist/admin/AdminNotifications-BPrxALdS.js.map +1 -0
- package/dist/admin/AdminNotifications-DV-35Fi3.js +3 -0
- package/dist/admin/{AdminSessions-CmDVneE2.js → AdminSessions-CMmBtbSw.js} +36 -9
- package/dist/admin/AdminSessions-CMmBtbSw.js.map +1 -0
- package/dist/admin/AdminSessions-Df2VYzlE.js +3 -0
- package/dist/admin/AdminUserCreate-Coa_yi6m.js +103 -0
- package/dist/admin/AdminUserCreate-Coa_yi6m.js.map +1 -0
- package/dist/admin/AdminUserCreate-DjiCcAk0.js +3 -0
- package/dist/admin/AdminUserDetails-BCFwOm9w.js +221 -0
- package/dist/admin/AdminUserDetails-BCFwOm9w.js.map +1 -0
- package/dist/admin/AdminUserDetails-C5yeJNa3.js +3 -0
- package/dist/admin/AdminUserLayout-B8ga5QvP.js +3 -0
- package/dist/admin/AdminUserLayout-CR2OqV9Z.js +153 -0
- package/dist/admin/AdminUserLayout-CR2OqV9Z.js.map +1 -0
- package/dist/admin/AdminUserSessions-A_5KkqTY.js +3 -0
- package/dist/admin/AdminUserSessions-Bcf6-rjG.js +129 -0
- package/dist/admin/AdminUserSessions-Bcf6-rjG.js.map +1 -0
- package/dist/admin/AdminUserSettings-DAsAhFjX.js +3 -0
- package/dist/admin/AdminUserSettings-DRYVdW6S.js +164 -0
- package/dist/admin/AdminUserSettings-DRYVdW6S.js.map +1 -0
- package/dist/admin/AdminUsers-Dd9a5UqO.js +3 -0
- package/dist/admin/{AdminUsers-88De5pev.js → AdminUsers-IN_2yHKt.js} +32 -14
- package/dist/admin/AdminUsers-IN_2yHKt.js.map +1 -0
- package/dist/admin/index.d.ts +5560 -416
- package/dist/admin/index.js +299 -41
- 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-DDsyCNAA.js +4 -0
- package/dist/auth/{Login-OCrvjs9U.js → Login-kBfaRgKG.js} +5 -4
- package/dist/auth/Login-kBfaRgKG.js.map +1 -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 +96 -20
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +280 -95
- package/dist/core/index.js +1381 -392
- package/dist/core/index.js.map +1 -1
- package/package.json +5 -5
- package/src/admin/AdminRouter.ts +116 -29
- package/src/admin/MainRouter.ts +23 -0
- package/src/admin/components/AdminLayout.tsx +86 -103
- 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 +12 -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 +13 -11
- 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 +6 -2
- package/src/auth/index.ts +1 -0
- package/src/core/components/buttons/ActionButton.tsx +11 -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 -15
- 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 +87 -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 +104 -0
- package/src/core/themes/remoraid.ts +278 -0
- package/src/core/themes/slate.ts +81 -0
- 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,10 @@ 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 <ActionButton icon={IconLogin2} href={authRouter.path("login")} />;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
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,12 @@ 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
|
+
|
|
240
248
|
if (props.icon) {
|
|
241
249
|
const icon = isComponentType(props.icon) ? (
|
|
242
250
|
<props.icon size={ui.sizes.icon.md} />
|
|
@@ -254,15 +262,14 @@ const ActionButton = (_props: ActionProps) => {
|
|
|
254
262
|
|
|
255
263
|
if (!props.children) {
|
|
256
264
|
restProps.children = Children.only(icon);
|
|
257
|
-
restProps.
|
|
265
|
+
restProps.px ??= "xs";
|
|
258
266
|
} else {
|
|
259
267
|
restProps.leftSection = icon;
|
|
260
268
|
}
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
if (props.leftSection && !props.children) {
|
|
264
|
-
restProps.
|
|
265
|
-
restProps.p ??= "xs";
|
|
272
|
+
restProps.px ??= "xs";
|
|
266
273
|
}
|
|
267
274
|
|
|
268
275
|
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
|
|
@@ -271,15 +276,17 @@ export const SidebarItem = (props: SidebarItemProps) => {
|
|
|
271
276
|
bottom: 16,
|
|
272
277
|
}}
|
|
273
278
|
/>
|
|
274
|
-
{item.children
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
279
|
+
{item.children
|
|
280
|
+
.filter((child) => !child.can || child.can())
|
|
281
|
+
.map((child, index) => (
|
|
282
|
+
<SidebarItem
|
|
283
|
+
key={index}
|
|
284
|
+
item={child}
|
|
285
|
+
level={level + 1}
|
|
286
|
+
onItemClick={props.onItemClick}
|
|
287
|
+
theme={props.theme}
|
|
288
|
+
/>
|
|
289
|
+
))}
|
|
283
290
|
</Flex>
|
|
284
291
|
)}
|
|
285
292
|
</Flex>
|
|
@@ -338,6 +345,14 @@ const SidebarCollapsedItem = (props: SidebarItemProps) => {
|
|
|
338
345
|
color={"var(--alepha-text)"}
|
|
339
346
|
variant={"subtle"}
|
|
340
347
|
variantActive={"default"}
|
|
348
|
+
tooltip={
|
|
349
|
+
item.children
|
|
350
|
+
? undefined
|
|
351
|
+
: {
|
|
352
|
+
label: item.label,
|
|
353
|
+
position: "right",
|
|
354
|
+
}
|
|
355
|
+
}
|
|
341
356
|
radius={props.item.theme?.radius ?? props.theme.button?.radius ?? "md"}
|
|
342
357
|
onClick={handleItemClick}
|
|
343
358
|
icon={renderIcon(item.icon) ?? <IconSquareRounded />}
|
|
@@ -348,12 +363,14 @@ const SidebarCollapsedItem = (props: SidebarItemProps) => {
|
|
|
348
363
|
? ({
|
|
349
364
|
position: "right",
|
|
350
365
|
on: "hover",
|
|
351
|
-
items: item.children
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
366
|
+
items: item.children
|
|
367
|
+
.filter((child) => !child.can || child.can())
|
|
368
|
+
.map((child) => ({
|
|
369
|
+
label: child.label,
|
|
370
|
+
href: child.href,
|
|
371
|
+
icon: renderIcon(child.icon),
|
|
372
|
+
children: child.children?.filter((c) => !c.can || c.can()),
|
|
373
|
+
})),
|
|
357
374
|
} as any)
|
|
358
375
|
: undefined
|
|
359
376
|
}
|
|
@@ -410,6 +427,7 @@ export interface SidebarMenuItem extends SidebarAbstractItem {
|
|
|
410
427
|
rightSection?: ReactNode;
|
|
411
428
|
theme?: SidebarButtonTheme;
|
|
412
429
|
actionProps?: ActionProps;
|
|
430
|
+
can?: () => boolean; // Visibility control: true -> visible, false -> hidden
|
|
413
431
|
}
|
|
414
432
|
|
|
415
433
|
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
|
+
type Theme,
|
|
5
|
+
ThemeProvider,
|
|
6
|
+
themeAtom,
|
|
7
|
+
} from "../providers/ThemeProvider.ts";
|
|
8
|
+
|
|
9
|
+
export const useTheme = () => {
|
|
10
|
+
useStore(themeAtom);
|
|
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,87 @@
|
|
|
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 themeAtom = $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 themeAtom.schema>;
|
|
26
|
+
|
|
27
|
+
declare module "alepha" {
|
|
28
|
+
interface State {
|
|
29
|
+
[themeAtom.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(themeAtom);
|
|
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(themeAtom, 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(themeAtom) ??
|
|
80
|
+
themeAtom.options.default
|
|
81
|
+
);
|
|
82
|
+
} catch {
|
|
83
|
+
// TODO: atom should take default value if undefined ???
|
|
84
|
+
return this.alepha.store.get(themeAtom) ?? themeAtom.options.default;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -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
|
+
};
|