@alepha/ui 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +2 -30
  2. package/dist/admin/AdminFiles-BM6_7_5A.cjs +4 -0
  3. package/dist/admin/AdminFiles-BaCIMeNt.js +4 -0
  4. package/dist/admin/AdminFiles-CllAxb1B.js +117 -0
  5. package/dist/admin/AdminFiles-CllAxb1B.js.map +1 -0
  6. package/dist/admin/AdminFiles-DC3T8uWZ.cjs +122 -0
  7. package/dist/admin/AdminFiles-DC3T8uWZ.cjs.map +1 -0
  8. package/dist/admin/AdminJobs-BXkFtlVo.js +125 -0
  9. package/dist/admin/AdminJobs-BXkFtlVo.js.map +1 -0
  10. package/dist/admin/AdminJobs-C428qrNQ.cjs +130 -0
  11. package/dist/admin/AdminJobs-C428qrNQ.cjs.map +1 -0
  12. package/dist/admin/AdminJobs-DCPPaJ4i.cjs +4 -0
  13. package/dist/admin/AdminJobs-yC6DarGO.js +4 -0
  14. package/dist/admin/AdminLayout-Bqo4cd33.cjs +4 -0
  15. package/dist/admin/AdminLayout-CQpxfko6.js +4 -0
  16. package/dist/admin/AdminLayout-CiLlywAQ.cjs +93 -0
  17. package/dist/admin/AdminLayout-CiLlywAQ.cjs.map +1 -0
  18. package/dist/admin/AdminLayout-CtkVYk-u.js +88 -0
  19. package/dist/admin/AdminLayout-CtkVYk-u.js.map +1 -0
  20. package/dist/admin/AdminNotifications-DNUeJ-PW.cjs +44 -0
  21. package/dist/admin/AdminNotifications-DNUeJ-PW.cjs.map +1 -0
  22. package/dist/admin/AdminNotifications-DaMu1AQ4.js +4 -0
  23. package/dist/admin/AdminNotifications-DnnulNNV.js +40 -0
  24. package/dist/admin/AdminNotifications-DnnulNNV.js.map +1 -0
  25. package/dist/admin/AdminNotifications-ihgbKVCx.cjs +4 -0
  26. package/dist/admin/AdminParameters-B3hvpLpu.js +40 -0
  27. package/dist/admin/AdminParameters-B3hvpLpu.js.map +1 -0
  28. package/dist/admin/AdminParameters-U4lU1rUF.cjs +4 -0
  29. package/dist/admin/AdminParameters-gdf7036N.cjs +44 -0
  30. package/dist/admin/AdminParameters-gdf7036N.cjs.map +1 -0
  31. package/dist/admin/AdminParameters-prMcCgxf.js +4 -0
  32. package/dist/admin/AdminSessions-BF_P4lHs.cjs +128 -0
  33. package/dist/admin/AdminSessions-BF_P4lHs.cjs.map +1 -0
  34. package/dist/admin/AdminSessions-CATIU61I.cjs +4 -0
  35. package/dist/admin/AdminSessions-DqOXOpYR.js +4 -0
  36. package/dist/admin/AdminSessions-Pjdz-iZx.js +123 -0
  37. package/dist/admin/AdminSessions-Pjdz-iZx.js.map +1 -0
  38. package/dist/admin/AdminUsers-BgTL-zSY.js +4 -0
  39. package/dist/admin/AdminUsers-C1HsrRxn.js +104 -0
  40. package/dist/admin/AdminUsers-C1HsrRxn.js.map +1 -0
  41. package/dist/admin/AdminUsers-HqvxwNGZ.cjs +4 -0
  42. package/dist/admin/AdminUsers-M2uEQbp5.cjs +109 -0
  43. package/dist/admin/AdminUsers-M2uEQbp5.cjs.map +1 -0
  44. package/dist/admin/AdminVerifications-BVssbtfU.cjs +44 -0
  45. package/dist/admin/AdminVerifications-BVssbtfU.cjs.map +1 -0
  46. package/dist/admin/AdminVerifications-Df6DRgNo.js +4 -0
  47. package/dist/admin/AdminVerifications-DxAtcYUR.cjs +4 -0
  48. package/dist/admin/AdminVerifications-VMpm30mS.js +40 -0
  49. package/dist/admin/AdminVerifications-VMpm30mS.js.map +1 -0
  50. package/dist/admin/core-CzO6aavT.js +2507 -0
  51. package/dist/admin/core-CzO6aavT.js.map +1 -0
  52. package/dist/{index.cjs → admin/core-aFtK4l9I.cjs} +287 -204
  53. package/dist/admin/core-aFtK4l9I.cjs.map +1 -0
  54. package/dist/admin/index.cjs +87 -0
  55. package/dist/admin/index.cjs.map +1 -0
  56. package/dist/admin/index.d.cts +1739 -0
  57. package/dist/admin/index.d.ts +1745 -0
  58. package/dist/admin/index.js +78 -0
  59. package/dist/admin/index.js.map +1 -0
  60. package/dist/auth/IconGoogle-B17BTQyD.cjs +69 -0
  61. package/dist/auth/IconGoogle-B17BTQyD.cjs.map +1 -0
  62. package/dist/auth/IconGoogle-Bfmuv9Rv.js +58 -0
  63. package/dist/auth/IconGoogle-Bfmuv9Rv.js.map +1 -0
  64. package/dist/auth/Login-BTBmbnWl.cjs +181 -0
  65. package/dist/auth/Login-BTBmbnWl.cjs.map +1 -0
  66. package/dist/auth/Login-BcQOtG3v.js +5 -0
  67. package/dist/auth/Login-Btmd70Um.cjs +5 -0
  68. package/dist/auth/Login-JeXFsUf5.js +176 -0
  69. package/dist/auth/Login-JeXFsUf5.js.map +1 -0
  70. package/dist/auth/Register-CPQnvXCZ.js +318 -0
  71. package/dist/auth/Register-CPQnvXCZ.js.map +1 -0
  72. package/dist/auth/Register-CbesZal3.cjs +5 -0
  73. package/dist/auth/Register-DpI_JdyO.js +5 -0
  74. package/dist/auth/Register-HP3rP71B.cjs +323 -0
  75. package/dist/auth/Register-HP3rP71B.cjs.map +1 -0
  76. package/dist/auth/ResetPassword-B-tkzV7g.cjs +248 -0
  77. package/dist/auth/ResetPassword-B-tkzV7g.cjs.map +1 -0
  78. package/dist/auth/ResetPassword-BlK3xEpU.js +4 -0
  79. package/dist/auth/ResetPassword-BzUjGG_-.js +243 -0
  80. package/dist/auth/ResetPassword-BzUjGG_-.js.map +1 -0
  81. package/dist/auth/ResetPassword-W3xjOnWy.cjs +4 -0
  82. package/dist/auth/chunk-DhGyd7sr.js +28 -0
  83. package/dist/auth/core-D1MHij1j.js +1795 -0
  84. package/dist/auth/core-D1MHij1j.js.map +1 -0
  85. package/dist/auth/core-rDZ9d92K.cjs +1824 -0
  86. package/dist/auth/core-rDZ9d92K.cjs.map +1 -0
  87. package/dist/auth/index.cjs +211 -0
  88. package/dist/auth/index.cjs.map +1 -0
  89. package/dist/auth/index.d.cts +6265 -0
  90. package/dist/auth/index.d.ts +6274 -0
  91. package/dist/auth/index.js +206 -0
  92. package/dist/auth/index.js.map +1 -0
  93. package/dist/core/index.cjs +2620 -0
  94. package/dist/core/index.cjs.map +1 -0
  95. package/dist/core/index.d.cts +2737 -0
  96. package/dist/core/index.d.ts +2743 -0
  97. package/dist/{index.js → core/index.js} +298 -126
  98. package/dist/core/index.js.map +1 -0
  99. package/package.json +32 -14
  100. package/src/admin/AdminRouter.ts +58 -0
  101. package/src/admin/components/AdminFiles.tsx +117 -0
  102. package/src/admin/components/AdminJobs.tsx +158 -0
  103. package/src/admin/components/AdminLayout.tsx +114 -0
  104. package/src/admin/components/AdminNotifications.tsx +20 -0
  105. package/src/admin/components/AdminParameters.tsx +24 -0
  106. package/src/admin/components/AdminSessions.tsx +159 -0
  107. package/src/admin/components/AdminUsers.tsx +137 -0
  108. package/src/admin/components/AdminVerifications.tsx +25 -0
  109. package/src/admin/index.ts +29 -0
  110. package/src/auth/AuthI18n.ts +118 -0
  111. package/src/auth/AuthRouter.ts +53 -0
  112. package/src/auth/components/Login.tsx +193 -0
  113. package/src/auth/components/Register.tsx +421 -0
  114. package/src/auth/components/ResetPassword.tsx +259 -0
  115. package/src/auth/components/buttons/UserButton.tsx +118 -0
  116. package/src/auth/components/icons/IconGithub.tsx +21 -0
  117. package/src/auth/components/icons/IconGoogle.tsx +30 -0
  118. package/src/auth/index.ts +27 -0
  119. package/src/{RootRouter.ts → core/RootRouter.ts} +2 -1
  120. package/src/{components → core/components}/buttons/ActionButton.tsx +49 -6
  121. package/src/core/components/buttons/ClipboardButton.tsx +56 -0
  122. package/src/{components → core/components}/buttons/DarkModeButton.tsx +7 -8
  123. package/src/{components → core/components}/buttons/LanguageButton.tsx +2 -2
  124. package/src/{components → core/components}/buttons/OmnibarButton.tsx +1 -1
  125. package/src/{components → core/components}/dialogs/AlertDialog.tsx +1 -1
  126. package/src/{components → core/components}/dialogs/ConfirmDialog.tsx +1 -1
  127. package/src/{components → core/components}/dialogs/PromptDialog.tsx +1 -1
  128. package/src/{components → core/components}/form/Control.tsx +1 -0
  129. package/src/{components → core/components}/layout/AdminShell.tsx +38 -7
  130. package/src/{components → core/components}/layout/AlephaMantineProvider.tsx +12 -8
  131. package/src/{components → core/components}/layout/AppBar.tsx +1 -1
  132. package/src/{components → core/components}/layout/Omnibar.tsx +1 -1
  133. package/src/{components → core/components}/layout/Sidebar.tsx +29 -26
  134. package/src/{components → core/components}/table/DataTable.tsx +1 -1
  135. package/src/{constants → core/constants}/ui.ts +9 -0
  136. package/src/{index.ts → core/index.ts} +3 -0
  137. package/src/{services → core/services}/DialogService.tsx +3 -3
  138. package/src/{services → core/services}/ToastService.tsx +3 -1
  139. package/src/{utils → core/utils}/extractSchemaFields.ts +2 -8
  140. package/src/{utils → core/utils}/icons.tsx +5 -15
  141. package/src/{utils → core/utils}/parseInput.ts +34 -26
  142. package/dist/AlephaMantineProvider-CGpgWDt8.cjs +0 -3
  143. package/dist/AlephaMantineProvider-D8cHYAge.js +0 -152
  144. package/dist/AlephaMantineProvider-D8cHYAge.js.map +0 -1
  145. package/dist/AlephaMantineProvider-DuvZFAuk.cjs +0 -175
  146. package/dist/AlephaMantineProvider-DuvZFAuk.cjs.map +0 -1
  147. package/dist/AlephaMantineProvider-twBqV4IO.js +0 -3
  148. package/dist/index.cjs.map +0 -1
  149. package/dist/index.d.cts +0 -821
  150. package/dist/index.d.cts.map +0 -1
  151. package/dist/index.d.ts +0 -821
  152. package/dist/index.d.ts.map +0 -1
  153. package/dist/index.js.map +0 -1
  154. /package/src/{components → core/components}/buttons/BurgerButton.tsx +0 -0
  155. /package/src/{components → core/components}/buttons/ToggleSidebarButton.tsx +0 -0
  156. /package/src/{components → core/components}/data/JsonViewer.tsx +0 -0
  157. /package/src/{components → core/components}/form/ControlDate.tsx +0 -0
  158. /package/src/{components → core/components}/form/ControlNumber.tsx +0 -0
  159. /package/src/{components → core/components}/form/ControlQueryBuilder.tsx +0 -0
  160. /package/src/{components → core/components}/form/ControlSelect.tsx +0 -0
  161. /package/src/{components → core/components}/form/TypeForm.tsx +0 -0
  162. /package/src/{hooks → core/hooks}/useDialog.ts +0 -0
  163. /package/src/{hooks → core/hooks}/useToast.ts +0 -0
  164. /package/src/{utils → core/utils}/string.ts +0 -0
@@ -0,0 +1,118 @@
1
+ import { useAuth } from "@alepha/react/auth";
2
+ import {
3
+ ActionButton,
4
+ type ActionMenuConfig,
5
+ type ActionMenuItem,
6
+ type ActionProps,
7
+ ui,
8
+ } from "@alepha/ui";
9
+ import { Avatar } from "@mantine/core";
10
+ import { IconLogout, IconUser } from "@tabler/icons-react";
11
+ import type { ReactNode } from "react";
12
+
13
+ export interface UserButtonProps
14
+ extends Omit<ActionProps, "menu" | "icon" | "onClick"> {
15
+ /**
16
+ * Additional menu items to display before the logout option
17
+ */
18
+ menuItems?: ActionMenuItem[];
19
+
20
+ /**
21
+ * Custom logout label (default: "Sign out")
22
+ */
23
+ logoutLabel?: string;
24
+
25
+ /**
26
+ * Menu configuration overrides
27
+ */
28
+ menuConfig?: Partial<Omit<ActionMenuConfig, "items">>;
29
+
30
+ /**
31
+ * Whether to show a divider before logout (default: true when menuItems provided)
32
+ */
33
+ showLogoutDivider?: boolean;
34
+
35
+ /**
36
+ * Custom icon to use instead of user avatar (default: IconUser)
37
+ */
38
+ icon?: ReactNode;
39
+ }
40
+
41
+ const UserButton = (props: UserButtonProps) => {
42
+ const {
43
+ menuItems = [],
44
+ logoutLabel = "Sign out",
45
+ menuConfig,
46
+ showLogoutDivider = menuItems.length > 0,
47
+ icon,
48
+ children,
49
+ ...buttonProps
50
+ } = props;
51
+
52
+ const auth = useAuth<{
53
+ username?: string;
54
+ email?: string;
55
+ picture?: string;
56
+ }>();
57
+
58
+ if (!auth.user) {
59
+ return null;
60
+ }
61
+
62
+ const userLabel = auth.user.username || auth.user.email;
63
+
64
+ const items: ActionMenuItem[] = [];
65
+
66
+ // Add user info label if available
67
+ if (auth.user.email && auth.user.username) {
68
+ items.push({
69
+ type: "label",
70
+ label: auth.user.email,
71
+ });
72
+ }
73
+
74
+ // Add custom menu items
75
+ items.push(...menuItems);
76
+
77
+ // Add divider before logout if needed
78
+ if (showLogoutDivider && items.length > 0) {
79
+ items.push({ type: "divider" });
80
+ }
81
+
82
+ // Add logout item
83
+ items.push({
84
+ label: logoutLabel,
85
+ icon: <IconLogout size={ui.sizes.icon.md} />,
86
+ color: "red",
87
+ onClick: () => auth.logout(),
88
+ });
89
+
90
+ // Use leftSection for Avatar (JSX element), icon prop for component types
91
+ const hasAvatar = !icon && auth.user.picture;
92
+
93
+ return (
94
+ <ActionButton
95
+ {...buttonProps}
96
+ icon={hasAvatar ? undefined : (icon ?? IconUser)}
97
+ leftSection={
98
+ hasAvatar ? (
99
+ <Avatar
100
+ src={`/api/files/${auth.user.picture}`}
101
+ size={24}
102
+ radius="xl"
103
+ />
104
+ ) : undefined
105
+ }
106
+ menu={{
107
+ position: "bottom-end",
108
+ width: 200,
109
+ ...menuConfig,
110
+ items,
111
+ }}
112
+ >
113
+ {children ?? userLabel}
114
+ </ActionButton>
115
+ );
116
+ };
117
+
118
+ export default UserButton;
@@ -0,0 +1,21 @@
1
+ const IconGithub = () => {
2
+ return (
3
+ <svg
4
+ width="24"
5
+ height="24"
6
+ viewBox="0 0 1024 1024"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ >
10
+ <path
11
+ fillRule="evenodd"
12
+ clipRule="evenodd"
13
+ d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"
14
+ transform="scale(64)"
15
+ fill={"var(--alepha-text)"}
16
+ />
17
+ </svg>
18
+ );
19
+ };
20
+
21
+ export default IconGithub;
@@ -0,0 +1,30 @@
1
+ const IconGoogle = () => {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ height="24"
6
+ viewBox="0 0 24 24"
7
+ width="24"
8
+ >
9
+ <path
10
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
11
+ fill="#4285F4"
12
+ />
13
+ <path
14
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
15
+ fill="#34A853"
16
+ />
17
+ <path
18
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
19
+ fill="#FBBC05"
20
+ />
21
+ <path
22
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
23
+ fill="#EA4335"
24
+ />
25
+ <path d="M1 1h22v22H1z" fill="none" />
26
+ </svg>
27
+ );
28
+ };
29
+
30
+ export default IconGoogle;
@@ -0,0 +1,27 @@
1
+ import { AlephaReactAuth } from "@alepha/react/auth";
2
+ import { AlephaReactI18n } from "@alepha/react/i18n";
3
+ import { AlephaUI } from "@alepha/ui";
4
+ import { $module } from "alepha";
5
+ import { AuthI18n } from "./AuthI18n.ts";
6
+ import { AuthRouter } from "./AuthRouter.ts";
7
+
8
+ // ---------------------------------------------------------------------------------------------------------------------
9
+
10
+ export * from "./AuthRouter.ts";
11
+ export type { UserButtonProps } from "./components/buttons/UserButton.tsx";
12
+ export { default as UserButton } from "./components/buttons/UserButton.tsx";
13
+ export { default as Login } from "./components/Login.tsx";
14
+ export { default as Register } from "./components/Register.tsx";
15
+ export { default as ResetPassword } from "./components/ResetPassword.tsx";
16
+
17
+ // ---------------------------------------------------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Login UI Module
21
+ *
22
+ * @module alepha.ui.auth
23
+ */
24
+ export const AlephaUIAuth = $module({
25
+ name: "alepha.ui.auth",
26
+ services: [AlephaUI, AlephaReactAuth, AlephaReactI18n, AuthRouter, AuthI18n],
27
+ });
@@ -1,8 +1,9 @@
1
1
  import { $page } from "@alepha/react";
2
+ import AlephaMantineProvider from "./components/layout/AlephaMantineProvider.tsx";
2
3
 
3
4
  export class RootRouter {
4
5
  public readonly root = $page({
5
6
  path: "/",
6
- lazy: () => import("./components/layout/AlephaMantineProvider.tsx"),
7
+ component: AlephaMantineProvider,
7
8
  });
8
9
  }
@@ -8,6 +8,8 @@ import {
8
8
  } from "@alepha/react";
9
9
  import { type FormModel, useFormState } from "@alepha/react/form";
10
10
  import {
11
+ Anchor,
12
+ type AnchorProps,
11
13
  Button,
12
14
  type ButtonProps,
13
15
  Flex,
@@ -21,7 +23,14 @@ import {
21
23
  type TooltipProps,
22
24
  } from "@mantine/core";
23
25
  import { IconCheck, IconChevronRight } from "@tabler/icons-react";
24
- import type { ButtonHTMLAttributes, ReactNode } from "react";
26
+ import {
27
+ type ButtonHTMLAttributes,
28
+ Children,
29
+ type ComponentType,
30
+ isValidElement,
31
+ type ReactNode,
32
+ } from "react";
33
+ import { ui } from "../../constants/ui.ts";
25
34
 
26
35
  export interface ActionMenuItem {
27
36
  /**
@@ -97,11 +106,13 @@ export interface ActionMenuConfig {
97
106
  on?: "hover" | "click";
98
107
 
99
108
  targetProps?: MenuTargetProps;
109
+
100
110
  menuProps?: MenuProps;
101
111
  }
102
112
 
103
113
  export interface ActionCommonProps extends ButtonProps {
104
114
  children?: ReactNode;
115
+
105
116
  textVisibleFrom?: "xs" | "sm" | "md" | "lg" | "xl";
106
117
 
107
118
  /**
@@ -127,7 +138,7 @@ export interface ActionCommonProps extends ButtonProps {
127
138
  * Icon to display on the left side of the button.
128
139
  * If no children are provided, the button will be styled as an icon-only button.
129
140
  */
130
- icon?: ReactNode;
141
+ icon?: ReactNode | ComponentType;
131
142
 
132
143
  /**
133
144
  * Additional props to pass to the ThemeIcon wrapping the icon.
@@ -223,11 +234,13 @@ const ActionMenuItem = (props: {
223
234
  };
224
235
 
225
236
  const ActionButton = (_props: ActionProps) => {
226
- const props = { variant: "subtle", ..._props };
237
+ const props = { variant: "default", ..._props };
227
238
  const { tooltip, menu, icon, ...restProps } = props;
228
239
 
229
240
  if (props.icon) {
230
- const icon = (
241
+ const icon = isComponentType(props.icon) ? (
242
+ <props.icon size={ui.sizes.icon.md} />
243
+ ) : (
231
244
  <ThemeIcon
232
245
  w={24} // TODO: make size configurable
233
246
  variant={"transparent"}
@@ -235,11 +248,12 @@ const ActionButton = (_props: ActionProps) => {
235
248
  c={"var(--mantine-color-text)"}
236
249
  {...props.themeIconProps}
237
250
  >
238
- {props.icon}
251
+ {props.icon as ReactNode}
239
252
  </ThemeIcon>
240
253
  );
254
+
241
255
  if (!props.children) {
242
- restProps.children = icon;
256
+ restProps.children = Children.only(icon);
243
257
  restProps.p ??= "xs";
244
258
  } else {
245
259
  restProps.leftSection = icon;
@@ -509,6 +523,7 @@ export interface ActionNavigationButtonProps extends ButtonProps {
509
523
  classNameActive?: string;
510
524
  variantActive?: ButtonProps["variant"];
511
525
  target?: string;
526
+ anchorProps?: AnchorProps;
512
527
  }
513
528
 
514
529
  /**
@@ -533,6 +548,14 @@ const ActionNavigationButton = (props: ActionNavigationButtonProps) => {
533
548
  buttonProps.className = `${className} ${classNameActive}`.trim();
534
549
  }
535
550
 
551
+ if (props.anchorProps) {
552
+ return (
553
+ <Anchor component={"a"} {...anchorProps} {...props.anchorProps}>
554
+ {props.children}
555
+ </Anchor>
556
+ );
557
+ }
558
+
536
559
  return (
537
560
  <Button
538
561
  component={"a"}
@@ -566,3 +589,23 @@ const ActionHrefButton = (props: ActionNavigationButtonProps) => {
566
589
  </Button>
567
590
  );
568
591
  };
592
+
593
+ // ---------------------------------------------------------------------------------------------------------------------
594
+
595
+ export function isComponentType(param: any): param is ComponentType<any> {
596
+ if (isValidElement(param)) return false;
597
+ return (
598
+ typeof param === "function" ||
599
+ (typeof param === "object" && param !== null && "$$typeof" in param)
600
+ );
601
+ }
602
+
603
+ export const renderIcon = (icon: ReactNode | ComponentType): ReactNode => {
604
+ if (!icon) return null;
605
+ if (isValidElement(icon)) return icon;
606
+ if (isComponentType(icon)) {
607
+ const IconComponent = icon;
608
+ return <IconComponent size={ui.sizes.icon.md} />;
609
+ }
610
+ return icon as ReactNode;
611
+ };
@@ -0,0 +1,56 @@
1
+ import { CopyButton, Tooltip } from "@mantine/core";
2
+ import { IconCheck, IconCopy } from "@tabler/icons-react";
3
+ import ActionButton, { type ActionCommonProps } from "./ActionButton.tsx";
4
+
5
+ export interface ClipboardButtonProps
6
+ extends Omit<ActionCommonProps, "onClick" | "icon"> {
7
+ /**
8
+ * The value to copy to the clipboard
9
+ */
10
+ value: string;
11
+
12
+ /**
13
+ * Timeout in ms to show the "Copied" state (default: 2000)
14
+ */
15
+ timeout?: number;
16
+
17
+ /**
18
+ * Label to show in tooltip when not copied (default: "Copy")
19
+ */
20
+ copyLabel?: string;
21
+
22
+ /**
23
+ * Label to show in tooltip when copied (default: "Copied")
24
+ */
25
+ copiedLabel?: string;
26
+ }
27
+
28
+ const ClipboardButton = (props: ClipboardButtonProps) => {
29
+ const {
30
+ value,
31
+ timeout = 2000,
32
+ copyLabel = "Copy",
33
+ copiedLabel = "Copied",
34
+ children,
35
+ ...buttonProps
36
+ } = props;
37
+
38
+ return (
39
+ <CopyButton value={value} timeout={timeout}>
40
+ {({ copied, copy }) => (
41
+ <Tooltip label={copied ? copiedLabel : copyLabel} openDelay={500}>
42
+ <ActionButton
43
+ color={copied ? "teal" : undefined}
44
+ onClick={copy}
45
+ icon={copied ? IconCheck : IconCopy}
46
+ {...buttonProps}
47
+ >
48
+ {children}
49
+ </ActionButton>
50
+ </Tooltip>
51
+ )}
52
+ </CopyButton>
53
+ );
54
+ };
55
+
56
+ export default ClipboardButton;
@@ -73,19 +73,18 @@ const DarkModeButton = (props: DarkModeButtonProps) => {
73
73
  return (
74
74
  <ActionButton
75
75
  onClick={toggleColorScheme}
76
- variant={props.variant ?? "outline"}
76
+ variant={props.variant ?? "default"}
77
77
  size={props.size ?? "sm"}
78
78
  aria-label="Toggle color scheme"
79
79
  px={"xs"}
80
+ c={colorScheme !== "default" ? undefined : "transparent"}
80
81
  fullWidth={props.fullWidth ?? false}
81
82
  icon={
82
- colorScheme === "dark" ? (
83
- <IconSun size={20} />
84
- ) : colorScheme === "light" ? (
85
- <IconMoon size={20} />
86
- ) : (
87
- <Flex h={20} w={20} />
88
- )
83
+ colorScheme === "dark"
84
+ ? IconSun
85
+ : colorScheme === "light"
86
+ ? IconMoon
87
+ : IconSun
89
88
  }
90
89
  {...props.actionProps}
91
90
  />
@@ -11,8 +11,8 @@ const LanguageButton = (props: LanguageButtonProps) => {
11
11
  const i18n = useI18n();
12
12
  return (
13
13
  <ActionButton
14
- icon={<IconLanguage />}
15
- variant={"outline"}
14
+ variant={"default"}
15
+ icon={IconLanguage}
16
16
  menu={{
17
17
  items: i18n.languages.map((lang) => ({
18
18
  label: i18n.tr(lang),
@@ -11,7 +11,7 @@ export interface OmnibarButtonProps {
11
11
  const OmnibarButton = (props: OmnibarButtonProps) => {
12
12
  return (
13
13
  <ActionButton
14
- variant={"outline"}
14
+ variant={"default"}
15
15
  miw={256}
16
16
  onClick={spotlight.open}
17
17
  justify={"space-between"}
@@ -1,5 +1,5 @@
1
1
  import { Button, Group, Text } from "@mantine/core";
2
- import type { AlertDialogProps } from "../../services/DialogService";
2
+ import type { AlertDialogProps } from "../../services/DialogService.tsx";
3
3
 
4
4
  const AlertDialog = ({ options, onClose }: AlertDialogProps) => (
5
5
  <>
@@ -1,5 +1,5 @@
1
1
  import { Button, Group, Text } from "@mantine/core";
2
- import type { ConfirmDialogProps } from "../../services/DialogService";
2
+ import type { ConfirmDialogProps } from "../../services/DialogService.tsx";
3
3
 
4
4
  const ConfirmDialog = ({ options, onConfirm }: ConfirmDialogProps) => (
5
5
  <>
@@ -1,6 +1,6 @@
1
1
  import { Button, Group, Text, TextInput } from "@mantine/core";
2
2
  import { useEffect, useRef, useState } from "react";
3
- import type { PromptDialogProps } from "../../services/DialogService";
3
+ import type { PromptDialogProps } from "../../services/DialogService.tsx";
4
4
 
5
5
  const PromptDialog = ({ options, onSubmit }: PromptDialogProps) => {
6
6
  const [value, setValue] = useState(options?.defaultValue || "");
@@ -297,6 +297,7 @@ const Control = (_props: ControlProps) => {
297
297
  type={getInputType()}
298
298
  {...props.input.props}
299
299
  {...textInputProps}
300
+ inputWrapperOrder={["label", "input", "description", "error"]}
300
301
  />
301
302
  );
302
303
  //endregion
@@ -1,4 +1,4 @@
1
- import { NestedView, useEvents, useStore } from "@alepha/react";
1
+ import { NestedView, useEvents, useRouter, useStore } from "@alepha/react";
2
2
  import {
3
3
  AppShell,
4
4
  type AppShellFooterProps,
@@ -7,7 +7,7 @@ import {
7
7
  type AppShellNavbarProps,
8
8
  type AppShellProps,
9
9
  } from "@mantine/core";
10
- import type { ReactNode } from "react";
10
+ import { type ReactNode, useState } from "react";
11
11
  import { ui } from "../../constants/ui.ts";
12
12
  import AppBar, { type AppBarProps } from "./AppBar.tsx";
13
13
  import { Sidebar, type SidebarProps } from "./Sidebar.tsx";
@@ -23,6 +23,13 @@ export interface AdminShellProps {
23
23
  header?: ReactNode;
24
24
  footer?: ReactNode;
25
25
  children?: ReactNode;
26
+
27
+ noSidebarWhen?: {
28
+ /**
29
+ * Paths where the sidebar should be hidden.
30
+ */
31
+ paths?: string[];
32
+ };
26
33
  }
27
34
 
28
35
  declare module "alepha" {
@@ -40,14 +47,35 @@ declare module "alepha" {
40
47
  }
41
48
 
42
49
  const AdminShell = (props: AdminShellProps) => {
50
+ const router = useRouter();
43
51
  const [opened, setOpened] = useStore("alepha.ui.sidebar.opened");
44
52
  const [collapsed] = useStore(
45
53
  "alepha.ui.sidebar.collapsed",
46
54
  props.sidebarProps?.collapsed,
47
55
  );
48
56
 
57
+ const shouldShowSidebar = () => {
58
+ if (props.noSidebarWhen?.paths) {
59
+ for (const path of props.noSidebarWhen.paths) {
60
+ if (
61
+ router.isActive(path, {
62
+ startWith: true,
63
+ })
64
+ ) {
65
+ return false;
66
+ }
67
+ }
68
+ }
69
+ return true;
70
+ };
71
+
72
+ const [showSidebar, setShowSidebar] = useState(shouldShowSidebar());
73
+
49
74
  useEvents(
50
75
  {
76
+ "react:transition:end": () => {
77
+ setShowSidebar(shouldShowSidebar());
78
+ },
51
79
  "react:transition:begin": () => {
52
80
  setOpened(false);
53
81
  },
@@ -60,12 +88,15 @@ const AdminShell = (props: AdminShellProps) => {
60
88
  { position: "left" as const, type: "burger" as const },
61
89
  ];
62
90
 
91
+ const hasSidebar = showSidebar && props.sidebarProps !== undefined;
92
+ const hasAppBar = hasSidebar || props.appBarProps || props.header;
93
+
63
94
  return (
64
95
  <AppShell
65
96
  padding="md"
66
- header={{ height: 60 }}
97
+ header={hasAppBar ? { height: 60 } : undefined}
67
98
  navbar={
68
- props.sidebarProps !== undefined
99
+ hasSidebar
69
100
  ? {
70
101
  width: collapsed ? { base: 72 } : { base: 300 },
71
102
  breakpoint: "sm",
@@ -82,13 +113,13 @@ const AdminShell = (props: AdminShellProps) => {
82
113
  )}
83
114
  </AppShell.Header>
84
115
 
85
- {props.sidebarProps !== undefined && (
116
+ {hasSidebar && (
86
117
  <AppShell.Navbar bg={ui.colors.surface} {...props.appShellNavbarProps}>
87
- <Sidebar collapsed={collapsed} {...props.sidebarProps} />
118
+ <Sidebar collapsed={collapsed} {...(props.sidebarProps ?? {})} />
88
119
  </AppShell.Navbar>
89
120
  )}
90
121
 
91
- <AppShell.Main {...props.appShellMainProps}>
122
+ <AppShell.Main display={"flex"} flex={1} {...props.appShellMainProps}>
92
123
  {props.children ?? <NestedView />}
93
124
  </AppShell.Main>
94
125
 
@@ -1,4 +1,5 @@
1
1
  import { NestedView, useEvents } from "@alepha/react";
2
+ import { FormValidationError } from "@alepha/react/form";
2
3
  import type {
3
4
  ColorSchemeScriptProps,
4
5
  MantineProviderProps,
@@ -33,8 +34,17 @@ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
33
34
  "react:transition:end": () => {
34
35
  nprogress.complete();
35
36
  },
36
- "react:action:error": () => {
37
- toast.danger("An error occurred while processing your action.");
37
+ "react:action:error": ({ error }) => {
38
+ if (error instanceof FormValidationError) {
39
+ // Validation errors are handled by the form component
40
+ return;
41
+ }
42
+
43
+ toast.danger({
44
+ title: error.name || "Error",
45
+ message:
46
+ error.message ?? "An error occurred while processing your action.",
47
+ });
38
48
  },
39
49
  },
40
50
  [],
@@ -49,12 +59,6 @@ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
49
59
  <MantineProvider
50
60
  {...props.mantine}
51
61
  theme={{
52
- primaryColor: "gray",
53
- primaryShade: {
54
- light: 9,
55
- dark: 8,
56
- },
57
- cursorType: "pointer",
58
62
  ...props.mantine?.theme,
59
63
  }}
60
64
  >
@@ -119,7 +119,7 @@ const AppBar = (props: AppBarProps) => {
119
119
  </Flex>
120
120
  ))}
121
121
  </Flex>
122
- <Flex flex={1} gap="md" align={"center"} justify={"end"}>
122
+ <Flex flex={1} align={"center"} justify={"end"}>
123
123
  {rightItems.map((item, index) => (
124
124
  <Flex key={index} ml={index === 0 ? 0 : "md"} align="center">
125
125
  {renderItem(item, index)}
@@ -20,7 +20,7 @@ const Omnibar = (props: OmnibarProps) => {
20
20
  id: page.name,
21
21
  label: page.label ?? page.name,
22
22
  description: page.description,
23
- onClick: () => router.go(page.path ?? page.name),
23
+ onClick: () => router.go(page.name),
24
24
  leftSection: page.icon,
25
25
  })),
26
26
  [],