@alepha/ui 0.15.2 → 0.15.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.
Files changed (84) hide show
  1. package/dist/admin/AdminAudits-BU-p1g7A.js +3 -0
  2. package/dist/admin/{AdminAudits-C0DPYw0W.js → AdminAudits-Oh7iAfQa.js} +2 -2
  3. package/dist/admin/AdminAudits-Oh7iAfQa.js.map +1 -0
  4. package/dist/admin/{AdminUserCreate-DiXi1EWB.js → AdminUserCreate-BVIm4JdN.js} +2 -2
  5. package/dist/admin/AdminUserCreate-BVIm4JdN.js.map +1 -0
  6. package/dist/admin/{AdminUserCreate-Chr-7hLk.js → AdminUserCreate-C1aInRDk.js} +1 -1
  7. package/dist/admin/{AdminUserLayout-D9bqGt6T.js → AdminUserLayout-BnfBC1gD.js} +2 -2
  8. package/dist/admin/{AdminUserLayout-D9bqGt6T.js.map → AdminUserLayout-BnfBC1gD.js.map} +1 -1
  9. package/dist/admin/{AdminUserLayout-CfeQHH6e.js → AdminUserLayout-gb-nbggz.js} +1 -1
  10. package/dist/admin/{AdminUserSettings-BnzRAcqV.js → AdminUserSettings-DZ9iWhJW.js} +2 -2
  11. package/dist/admin/AdminUserSettings-DZ9iWhJW.js.map +1 -0
  12. package/dist/admin/AdminUserSettings-Dg-wTRzN.js +3 -0
  13. package/dist/admin/{AdminUsers-CYkcUWCg.js → AdminUsers-D6Y5K8Am.js} +2 -2
  14. package/dist/admin/AdminUsers-D6Y5K8Am.js.map +1 -0
  15. package/dist/admin/AdminUsers-RCaxccEW.js +3 -0
  16. package/dist/admin/index.d.ts +37 -33
  17. package/dist/admin/index.d.ts.map +1 -1
  18. package/dist/admin/index.js +20 -13
  19. package/dist/admin/index.js.map +1 -1
  20. package/dist/auth/{Login-BAFVcX_J.js → Login-BBqTosqZ.js} +2 -2
  21. package/dist/auth/Login-BBqTosqZ.js.map +1 -0
  22. package/dist/auth/Login-CoU63mMR.js +4 -0
  23. package/dist/auth/Profile-Bxj8Nwom.js +150 -0
  24. package/dist/auth/Profile-Bxj8Nwom.js.map +1 -0
  25. package/dist/auth/Register-BV_oa_AK.js +4 -0
  26. package/dist/auth/{Register-CZRXEcWy.js → Register-Ce675Crg.js} +4 -4
  27. package/dist/auth/Register-Ce675Crg.js.map +1 -0
  28. package/dist/auth/ResetPassword-D5wC8GAA.js +3 -0
  29. package/dist/auth/{ResetPassword-DTYNsBIj.js → ResetPassword-DWdt7c40.js} +2 -2
  30. package/dist/auth/{ResetPassword-DTYNsBIj.js.map → ResetPassword-DWdt7c40.js.map} +1 -1
  31. package/dist/auth/{VerifyEmail-DolENWGn.js → VerifyEmail-CI4JwByV.js} +2 -2
  32. package/dist/auth/{VerifyEmail-DolENWGn.js.map → VerifyEmail-CI4JwByV.js.map} +1 -1
  33. package/dist/auth/VerifyEmail-DAfqVm5s.js +3 -0
  34. package/dist/auth/index.d.ts +6 -1
  35. package/dist/auth/index.d.ts.map +1 -1
  36. package/dist/auth/index.js +47 -13
  37. package/dist/auth/index.js.map +1 -1
  38. package/dist/core/index.d.ts +111 -29
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +324 -251
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/demo/{DemoLogin-mtkN6340.js → DemoLogin-S-b15cmE.js} +2 -2
  43. package/dist/demo/DemoLogin-S-b15cmE.js.map +1 -0
  44. package/dist/demo/{DemoRegister-C0MW7anp.js → DemoRegister-B29MdAaZ.js} +3 -3
  45. package/dist/demo/DemoRegister-B29MdAaZ.js.map +1 -0
  46. package/dist/demo/index.js +2 -2
  47. package/package.json +3 -2
  48. package/src/admin/AdminRouter.ts +2 -2
  49. package/src/admin/components/audits/AdminAudits.tsx +1 -1
  50. package/src/admin/components/users/AdminUserCreate.tsx +1 -1
  51. package/src/admin/components/users/AdminUserLayout.tsx +1 -1
  52. package/src/admin/components/users/AdminUserSettings.tsx +1 -1
  53. package/src/admin/components/users/AdminUsers.tsx +1 -1
  54. package/src/admin/index.ts +11 -1
  55. package/src/auth/AuthRouter.ts +12 -0
  56. package/src/auth/components/Login.tsx +1 -1
  57. package/src/auth/components/Profile.tsx +157 -0
  58. package/src/auth/components/Register.tsx +2 -2
  59. package/src/auth/components/buttons/UserButton.tsx +34 -2
  60. package/src/auth/index.ts +11 -1
  61. package/src/core/UiRouter.ts +15 -0
  62. package/src/core/atoms/alephaThemeListAtom.ts +3 -1
  63. package/src/core/components/buttons/ActionButton.tsx +2 -2
  64. package/src/core/components/layout/AdminShell.tsx +46 -18
  65. package/src/core/components/layout/AppBar.tsx +235 -18
  66. package/src/core/components/layout/Omnibar.tsx +2 -2
  67. package/src/core/components/layout/Sidebar.tsx +1 -5
  68. package/src/core/index.ts +13 -27
  69. package/dist/admin/AdminAudits-BlGGKLof.js +0 -3
  70. package/dist/admin/AdminAudits-C0DPYw0W.js.map +0 -1
  71. package/dist/admin/AdminUserCreate-DiXi1EWB.js.map +0 -1
  72. package/dist/admin/AdminUserSettings-BnzRAcqV.js.map +0 -1
  73. package/dist/admin/AdminUserSettings-CXs-jtRv.js +0 -3
  74. package/dist/admin/AdminUsers-CYkcUWCg.js.map +0 -1
  75. package/dist/admin/AdminUsers-DdFXzrEn.js +0 -3
  76. package/dist/auth/Login-BAFVcX_J.js.map +0 -1
  77. package/dist/auth/Login-C5PUsp8I.js +0 -4
  78. package/dist/auth/Register-CZRXEcWy.js.map +0 -1
  79. package/dist/auth/Register-DMTs5ep_.js +0 -4
  80. package/dist/auth/ResetPassword-D-mhMtmx.js +0 -3
  81. package/dist/auth/VerifyEmail-BsrCmncc.js +0 -3
  82. package/dist/demo/DemoLogin-mtkN6340.js.map +0 -1
  83. package/dist/demo/DemoRegister-C0MW7anp.js.map +0 -1
  84. package/src/core/RootRouter.ts +0 -9
@@ -5,6 +5,8 @@ import {
5
5
  type AppShellMainProps,
6
6
  type AppShellNavbarProps,
7
7
  type AppShellProps,
8
+ Container,
9
+ type ContainerProps,
8
10
  Flex,
9
11
  } from "@mantine/core";
10
12
  import { useEvents, useStore } from "alepha/react";
@@ -37,7 +39,7 @@ export interface AdminShellProps {
37
39
  * Enable drag-to-resize for the sidebar.
38
40
  * Width and constraints are configured in alephaSidebarAtom.
39
41
  */
40
- resizable?: boolean;
42
+ sidebarResizable?: boolean;
41
43
 
42
44
  noSidebarWhen?: {
43
45
  /**
@@ -45,6 +47,12 @@ export interface AdminShellProps {
45
47
  */
46
48
  paths?: string[];
47
49
  };
50
+
51
+ /**
52
+ * Wrap AppBar and main content in a Mantine Container.
53
+ * Pass `true` for default Container, or ContainerProps to customize.
54
+ */
55
+ container?: boolean | ContainerProps;
48
56
  }
49
57
 
50
58
  const AdminShell = (props: AdminShellProps) => {
@@ -79,7 +87,7 @@ const AdminShell = (props: AdminShellProps) => {
79
87
 
80
88
  const handleResizeStart = useCallback(
81
89
  (e: React.MouseEvent) => {
82
- if (!props.resizable) return;
90
+ if (!props.sidebarResizable) return;
83
91
  e.preventDefault();
84
92
 
85
93
  // If collapsed and hovering, un-collapse first and start from defaultWidth
@@ -98,7 +106,7 @@ const AdminShell = (props: AdminShellProps) => {
98
106
  };
99
107
  }
100
108
  },
101
- [props.resizable, collapsed, sidebar, setSidebar, defaultWidth],
109
+ [props.sidebarResizable, collapsed, sidebar, setSidebar, defaultWidth],
102
110
  );
103
111
 
104
112
  useEffect(() => {
@@ -218,6 +226,10 @@ const AdminShell = (props: AdminShellProps) => {
218
226
  { position: "left" as const, type: "burger" as const },
219
227
  ];
220
228
 
229
+ // Forward container to appBarProps if not already set
230
+ const appBarProps = { ...props.appBarProps };
231
+ appBarProps.container ??= props.container;
232
+
221
233
  const hasSidebar = showSidebar && props.sidebarProps !== undefined;
222
234
  const hasAppBar = hasSidebar || props.appBarProps || props.header;
223
235
 
@@ -229,14 +241,13 @@ const AdminShell = (props: AdminShellProps) => {
229
241
  const isExpandedByHover = collapsed && isHovering;
230
242
  const effectiveCollapsed = collapsed && !isHovering;
231
243
  const hoverWidth = Math.max(defaultWidth, collapsedWidth);
244
+ // When hovering, keep main content at collapsed width (sidebar overlays)
232
245
  const sidebarWidth = hasSidebar
233
- ? effectiveCollapsed
246
+ ? effectiveCollapsed || isExpandedByHover
234
247
  ? collapsedWidth
235
- : isExpandedByHover
236
- ? hoverWidth
237
- : expandedWidth
248
+ : expandedWidth
238
249
  : 0;
239
- const canResize = props.resizable && !collapsed;
250
+ const canResize = props.sidebarResizable && !collapsed;
240
251
 
241
252
  return (
242
253
  <AppShell
@@ -247,10 +258,10 @@ const AdminShell = (props: AdminShellProps) => {
247
258
  navbar={
248
259
  hasSidebar
249
260
  ? {
250
- width: effectiveCollapsed
251
- ? { base: collapsedWidth }
252
- : isExpandedByHover
253
- ? { base: hoverWidth }
261
+ // When hovering, keep collapsed width to avoid pushing content
262
+ width:
263
+ effectiveCollapsed || isExpandedByHover
264
+ ? { base: collapsedWidth }
254
265
  : { base: expandedWidth },
255
266
  breakpoint: "sm",
256
267
  collapsed: { mobile: !opened },
@@ -261,9 +272,7 @@ const AdminShell = (props: AdminShellProps) => {
261
272
  {...props.appShellProps}
262
273
  >
263
274
  <AppShell.Header bg={ui.colors.surface} {...props.appShellHeaderProps}>
264
- {props.header ?? (
265
- <AppBar items={defaultAppBarItems} {...props.appBarProps} />
266
- )}
275
+ {props.header ?? <AppBar items={defaultAppBarItems} {...appBarProps} />}
267
276
  </AppShell.Header>
268
277
 
269
278
  {hasSidebar && (
@@ -271,6 +280,7 @@ const AdminShell = (props: AdminShellProps) => {
271
280
  bg={ui.colors.surface}
272
281
  className="alepha-sidebar-navbar"
273
282
  data-resizing={isResizing}
283
+ data-hover-expanded={isExpandedByHover}
274
284
  onMouseEnter={handleNavbarMouseEnter}
275
285
  onMouseLeave={handleNavbarMouseLeave}
276
286
  style={{
@@ -278,6 +288,12 @@ const AdminShell = (props: AdminShellProps) => {
278
288
  ? `translateX(${collapseEffect.offset}px)`
279
289
  : undefined,
280
290
  opacity: collapseEffect.opacity,
291
+ // When hovering, expand width visually as overlay
292
+ ...(isExpandedByHover && {
293
+ width: hoverWidth,
294
+ zIndex: 200,
295
+ boxShadow: "var(--mantine-shadow-xl)",
296
+ }),
281
297
  }}
282
298
  {...props.appShellNavbarProps}
283
299
  >
@@ -288,10 +304,10 @@ const AdminShell = (props: AdminShellProps) => {
288
304
  {(canResize || isExpandedByHover) && (
289
305
  <Flex
290
306
  pos="absolute"
291
- right={-2}
307
+ right={-6}
292
308
  top={0}
293
309
  bottom={0}
294
- w={4}
310
+ w={12}
295
311
  style={{
296
312
  cursor: "col-resize",
297
313
  userSelect: "none",
@@ -314,7 +330,19 @@ const AdminShell = (props: AdminShellProps) => {
314
330
  data-resizing={isResizing}
315
331
  {...props.appShellMainProps}
316
332
  >
317
- {props.children ?? <NestedView />}
333
+ {props.container ? (
334
+ <Container
335
+ w={"100%"}
336
+ flex={1}
337
+ display="flex"
338
+ style={{ flexDirection: "column" }}
339
+ {...(typeof props.container === "boolean" ? {} : props.container)}
340
+ >
341
+ {props.children ?? <NestedView />}
342
+ </Container>
343
+ ) : (
344
+ (props.children ?? <NestedView />)
345
+ )}
318
346
  </AppShell.Main>
319
347
 
320
348
  {props.footer && (
@@ -1,5 +1,17 @@
1
- import { Divider, Flex, type FlexProps } from "@mantine/core";
2
- import type { ReactNode } from "react";
1
+ import {
2
+ Anchor,
3
+ Container,
4
+ type ContainerProps,
5
+ Divider,
6
+ Flex,
7
+ type FlexProps,
8
+ Image,
9
+ Text,
10
+ } from "@mantine/core";
11
+ import { IconArrowLeft } from "@tabler/icons-react";
12
+ import { Link, useRouter } from "alepha/react/router";
13
+ import type { ComponentType, ReactNode } from "react";
14
+ import ActionButton from "../buttons/ActionButton.tsx";
3
15
  import BurgerButton from "../buttons/BurgerButton.tsx";
4
16
  import DarkModeButton, {
5
17
  type DarkModeButtonProps,
@@ -18,55 +30,146 @@ export type AppBarItem =
18
30
  | AppBarSearch
19
31
  | AppBarLang
20
32
  | AppBarSpacer
21
- | AppBarDivider;
33
+ | AppBarDivider
34
+ | AppBarLogo
35
+ | AppBarBack;
22
36
 
23
- export interface AppBarElement {
37
+ export interface AppBarAbstractItem {
24
38
  position: "left" | "center" | "right";
39
+
40
+ /**
41
+ * Visibility control: return true to show, false to hide.
42
+ */
43
+ can?: () => boolean;
44
+ }
45
+
46
+ export interface AppBarElement extends AppBarAbstractItem {
25
47
  element: ReactNode;
26
48
  }
27
49
 
28
- export interface AppBarBurger {
29
- position: "left" | "center" | "right";
50
+ export interface AppBarBurger extends AppBarAbstractItem {
30
51
  type: "burger";
31
52
  }
32
53
 
33
- export interface AppBarDark {
34
- position: "left" | "center" | "right";
54
+ export interface AppBarDark extends AppBarAbstractItem {
35
55
  type: "dark";
36
56
  props?: DarkModeButtonProps;
37
57
  }
38
58
 
39
- export interface AppBarSearch {
40
- position: "left" | "center" | "right";
59
+ export interface AppBarSearch extends AppBarAbstractItem {
41
60
  type: "search";
42
61
  props?: OmnibarButtonProps;
43
62
  }
44
63
 
45
- export interface AppBarLang {
46
- position: "left" | "center" | "right";
64
+ export interface AppBarLang extends AppBarAbstractItem {
47
65
  type: "lang";
48
66
  props?: LanguageButtonProps;
49
67
  }
50
68
 
51
- export interface AppBarSpacer {
52
- position: "left" | "center" | "right";
69
+ export interface AppBarSpacer extends AppBarAbstractItem {
53
70
  type: "spacer";
54
71
  }
55
72
 
56
- export interface AppBarDivider {
57
- position: "left" | "center" | "right";
73
+ export interface AppBarDivider extends AppBarAbstractItem {
58
74
  type: "divider";
59
75
  }
60
76
 
77
+ export interface AppBarLogo extends AppBarAbstractItem {
78
+ type: "logo";
79
+ props?: {
80
+ /**
81
+ * Logo image source URL.
82
+ */
83
+ src?: string;
84
+
85
+ /**
86
+ * Logo text (used if no src provided).
87
+ */
88
+ text?: string;
89
+
90
+ /**
91
+ * Icon component (used if no src or text provided).
92
+ */
93
+ icon?: ReactNode | ComponentType;
94
+
95
+ /**
96
+ * Link href when logo is clicked.
97
+ */
98
+ href?: string;
99
+
100
+ /**
101
+ * Logo image height in pixels.
102
+ * @default 32
103
+ */
104
+ height?: number;
105
+
106
+ /**
107
+ * Logo image width in pixels.
108
+ */
109
+ width?: number;
110
+
111
+ /**
112
+ * Font weight for text logo.
113
+ * @default 700
114
+ */
115
+ fontWeight?: number;
116
+
117
+ /**
118
+ * Font size for text logo.
119
+ * @default "lg"
120
+ */
121
+ fontSize?: string;
122
+ };
123
+ }
124
+
125
+ export interface AppBarBack extends AppBarAbstractItem {
126
+ type: "back";
127
+ props?: {
128
+ /**
129
+ * Custom label for back button.
130
+ * @default "Back"
131
+ */
132
+ label?: string;
133
+
134
+ /**
135
+ * Show only icon without label.
136
+ * @default true
137
+ */
138
+ iconOnly?: boolean;
139
+
140
+ /**
141
+ * Custom href to navigate to instead of history back.
142
+ */
143
+ href?: string;
144
+
145
+ /**
146
+ * Custom icon component.
147
+ */
148
+ icon?: ReactNode | ComponentType;
149
+ };
150
+ }
151
+
61
152
  export interface AppBarProps {
62
153
  flexProps?: FlexProps;
63
154
  items?: AppBarItem[];
155
+
156
+ /**
157
+ * Wrap the AppBar content in a Mantine Container.
158
+ * Pass `true` for default Container, or ContainerProps to customize.
159
+ */
160
+ container?: boolean | ContainerProps;
64
161
  }
65
162
 
66
163
  const AppBar = (props: AppBarProps) => {
67
164
  const { items = [] } = props;
165
+ const router = useRouter();
68
166
 
69
167
  const renderItem = (item: AppBarItem, index: number) => {
168
+ // Check visibility control
169
+ if (item.can && !item.can()) {
170
+ return null;
171
+ }
172
+
70
173
  if ("type" in item) {
71
174
  if (item.type === "burger") {
72
175
  return <BurgerButton key={index} />;
@@ -86,6 +189,12 @@ const AppBar = (props: AppBarProps) => {
86
189
  if (item.type === "divider") {
87
190
  return <Divider key={index} orientation="vertical" />;
88
191
  }
192
+ if (item.type === "logo") {
193
+ return renderLogo(item, index);
194
+ }
195
+ if (item.type === "back") {
196
+ return renderBack(item, index);
197
+ }
89
198
  }
90
199
  if ("element" in item) {
91
200
  return item.element;
@@ -93,15 +202,111 @@ const AppBar = (props: AppBarProps) => {
93
202
  return null;
94
203
  };
95
204
 
205
+ const renderLogo = (item: AppBarLogo, index: number) => {
206
+ const {
207
+ src,
208
+ text,
209
+ icon,
210
+ href,
211
+ height = 32,
212
+ width,
213
+ fontWeight = 700,
214
+ fontSize = "lg",
215
+ } = item.props ?? {};
216
+
217
+ const logoContent = src ? (
218
+ <Image src={src} h={height} w={width} fit="contain" />
219
+ ) : icon ? (
220
+ typeof icon === "function" ? (
221
+ (() => {
222
+ const IconComponent = icon;
223
+ return <IconComponent />;
224
+ })()
225
+ ) : (
226
+ icon
227
+ )
228
+ ) : text ? (
229
+ <Text fw={fontWeight} size={fontSize}>
230
+ {text}
231
+ </Text>
232
+ ) : null;
233
+
234
+ if (href) {
235
+ return (
236
+ <Anchor
237
+ component={Link}
238
+ key={index}
239
+ href={href}
240
+ underline="never"
241
+ c="inherit"
242
+ >
243
+ {logoContent}
244
+ </Anchor>
245
+ );
246
+ }
247
+
248
+ return <Flex key={index}>{logoContent}</Flex>;
249
+ };
250
+
251
+ const renderBack = (item: AppBarBack, index: number) => {
252
+ const { label = "Back", iconOnly = true, href, icon } = item.props ?? {};
253
+
254
+ const renderIcon = () => {
255
+ if (!icon) {
256
+ return <IconArrowLeft size={18} />;
257
+ }
258
+ if (typeof icon === "function") {
259
+ const IconComponent = icon as ComponentType<{ size?: number }>;
260
+ return <IconComponent size={18} />;
261
+ }
262
+ return icon;
263
+ };
264
+
265
+ const iconElement = renderIcon();
266
+
267
+ const handleClick = () => {
268
+ if (href) {
269
+ router.push(href);
270
+ } else {
271
+ router.back();
272
+ }
273
+ };
274
+
275
+ if (iconOnly) {
276
+ return (
277
+ <ActionButton
278
+ key={index}
279
+ icon={iconElement}
280
+ variant="subtle"
281
+ color="gray"
282
+ onClick={handleClick}
283
+ tooltip={{ label, position: "bottom" }}
284
+ />
285
+ );
286
+ }
287
+
288
+ return (
289
+ <ActionButton
290
+ key={index}
291
+ leftSection={iconElement}
292
+ variant="subtle"
293
+ color="gray"
294
+ onClick={handleClick}
295
+ >
296
+ {label}
297
+ </ActionButton>
298
+ );
299
+ };
300
+
96
301
  const leftItems = items.filter((item) => item.position === "left");
97
302
  const centerItems = items.filter((item) => item.position === "center");
98
303
  const rightItems = items.filter((item) => item.position === "right");
99
304
 
100
- return (
305
+ const content = (
101
306
  <Flex
102
307
  h="100%"
103
308
  align="center"
104
- px="md"
309
+ px={props.container ? 0 : "md"}
105
310
  justify="space-between"
106
311
  {...props.flexProps}
107
312
  >
@@ -128,6 +333,18 @@ const AppBar = (props: AppBarProps) => {
128
333
  </Flex>
129
334
  </Flex>
130
335
  );
336
+
337
+ if (props.container) {
338
+ const containerProps =
339
+ typeof props.container === "boolean" ? {} : props.container;
340
+ return (
341
+ <Container h="100%" {...containerProps}>
342
+ {content}
343
+ </Container>
344
+ );
345
+ }
346
+
347
+ return content;
131
348
  };
132
349
 
133
350
  export default AppBar;
@@ -35,9 +35,9 @@ const Omnibar = (props: OmnibarProps) => {
35
35
  description: page.description,
36
36
  onClick: () => {
37
37
  if (page.staticName) {
38
- return router.go(page.staticName, { params: page.params });
38
+ return router.push(page.staticName, { params: page.params });
39
39
  }
40
- return router.go(page.name);
40
+ return router.push(page.name);
41
41
  },
42
42
  leftSection: renderIcon(page.icon),
43
43
  })),
@@ -265,6 +265,7 @@ export const SidebarItem = (props: SidebarItemProps) => {
265
265
  props.theme.button?.size ??
266
266
  (level === 0 ? "sm" : "xs")
267
267
  }
268
+ tooltip={item.description}
268
269
  c={"var(--mantine-color-text)"}
269
270
  color={"gray"}
270
271
  variant={"subtle"}
@@ -276,11 +277,6 @@ export const SidebarItem = (props: SidebarItemProps) => {
276
277
  {renderIcon(item.icon)}
277
278
  <Flex direction={"column"}>
278
279
  <Flex>{item.label}</Flex>
279
- {item.description && (
280
- <Text size={"xs"} c={"dimmed"}>
281
- {item.description}
282
- </Text>
283
- )}
284
280
  </Flex>
285
281
  </Flex>
286
282
  }
package/src/core/index.ts CHANGED
@@ -5,11 +5,15 @@ import { AlephaReactI18n } from "alepha/react/i18n";
5
5
  import type { ComponentType, ReactNode } from "react";
6
6
  import { alephaSidebarAtom } from "./atoms/alephaSidebarAtom.ts";
7
7
  import { alephaThemeAtom } from "./atoms/alephaThemeAtom.ts";
8
+ import {
9
+ type AlephaThemeListAtom,
10
+ alephaThemeListAtom,
11
+ } from "./atoms/alephaThemeListAtom.ts";
8
12
  import type { ControlProps } from "./components/form/Control.tsx";
9
13
  import { ThemeProvider } from "./providers/ThemeProvider.ts";
10
- import { RootRouter } from "./RootRouter.ts";
11
14
  import { DialogService } from "./services/DialogService.tsx";
12
15
  import { ToastService } from "./services/ToastService.tsx";
16
+ import { UiRouter } from "./UiRouter.ts";
13
17
 
14
18
  // ---------------------------------------------------------------------------------------------------------------------
15
19
 
@@ -98,7 +102,6 @@ export * from "./constants/ui.ts";
98
102
  export { useDialog } from "./hooks/useDialog.ts";
99
103
  export { useToast } from "./hooks/useToast.ts";
100
104
  export * from "./providers/ThemeProvider.ts";
101
- export * from "./RootRouter.ts";
102
105
  export type {
103
106
  AlertDialogOptions,
104
107
  AlertDialogProps,
@@ -110,6 +113,7 @@ export type {
110
113
  } from "./services/DialogService.tsx";
111
114
  export { DialogService } from "./services/DialogService.tsx";
112
115
  export { ToastService } from "./services/ToastService.tsx";
116
+ export * from "./UiRouter.ts";
113
117
  export * from "./utils/extractSchemaFields.ts";
114
118
  export * from "./utils/icons.tsx";
115
119
  export * from "./utils/string.ts";
@@ -180,7 +184,7 @@ declare module "alepha/react/router" {
180
184
  */
181
185
  export const AlephaUI = $module({
182
186
  name: "alepha.ui",
183
- services: [DialogService, ToastService, ThemeProvider, RootRouter],
187
+ services: [DialogService, ToastService, ThemeProvider, UiRouter],
184
188
  register: (alepha) => {
185
189
  alepha.with(AlephaReactI18n);
186
190
  alepha.with(AlephaReactHead);
@@ -192,30 +196,12 @@ export const AlephaUI = $module({
192
196
  });
193
197
 
194
198
  /**
195
- * Register UI components and get the RootRouter instance.
199
+ * Convenience function to configure and inject the UiRouter.
196
200
  */
197
- export const $ui = (
198
- opts: {
199
- // TODO:
200
- // theme?: ThemeOptions;
201
- // root?: string = "/";
202
- } = {},
203
- ) => {
201
+ export const $ui = (options: { themes?: AlephaThemeListAtom } = {}) => {
204
202
  const { alepha } = $context();
205
-
206
- // TODO: Register unique instance ? In order to have multiple ui apps in the same context ?
207
- // app = $ui();
208
- // admin = $ui({ root: "/admin", theme: adminTheme });
209
- // auth = $ui({ root: "/auth", theme: authTheme });
210
- // etc...
211
-
212
- /**
213
- * If multi ui, should we have N themes ? or one $atom theme but with change based on current ui app ?
214
- *
215
- * App (theme=T1) -> Admin (theme=T2) ?
216
- *
217
- * > It can be done with onLeave()/onEnter() of the RootRouter to set the theme atom.
218
- */
219
-
220
- return alepha.inject(RootRouter); // Inject as singleton ?
203
+ if (options.themes) {
204
+ alepha.store.set(alephaThemeListAtom, options.themes);
205
+ }
206
+ return alepha.inject(UiRouter); // Inject as singleton ?
221
207
  };
@@ -1,3 +0,0 @@
1
- import { t as AdminAudits_default } from "./AdminAudits-C0DPYw0W.js";
2
-
3
- export { AdminAudits_default as default };
@@ -1 +0,0 @@
1
- {"version":3,"file":"AdminAudits-C0DPYw0W.js","names":[],"sources":["../../src/admin/components/audits/AdminAudits.tsx"],"sourcesContent":["import { DataTable, Flex, Text } from \"@alepha/ui\";\nimport { Badge, Group, Stack, Tooltip } from \"@mantine/core\";\nimport {\n IconAlertTriangle,\n IconCheck,\n IconInfoCircle,\n IconUser,\n IconX,\n} from \"@tabler/icons-react\";\nimport { type Page, t } from \"alepha\";\nimport type { AdminAuditController, AuditEntity } from \"alepha/api/audits\";\nimport { useClient } from \"alepha/react\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminAuditsProps {\n userRealmName?: string;\n}\n\nconst getSeverityColor = (severity: string) => {\n switch (severity) {\n case \"critical\":\n return \"red\";\n case \"warning\":\n return \"yellow\";\n default:\n return \"blue\";\n }\n};\n\nconst getSeverityIcon = (severity: string) => {\n switch (severity) {\n case \"critical\":\n return <IconAlertTriangle size={12} />;\n case \"warning\":\n return <IconAlertTriangle size={12} />;\n default:\n return <IconInfoCircle size={12} />;\n }\n};\n\nconst getTypeColor = (type: string) => {\n switch (type) {\n case \"auth\":\n return \"blue\";\n case \"user\":\n return \"grape\";\n case \"security\":\n return \"red\";\n case \"system\":\n return \"orange\";\n case \"access\":\n return \"cyan\";\n case \"payment\":\n return \"green\";\n case \"order\":\n return \"teal\";\n default:\n return \"gray\";\n }\n};\n\nconst AdminAudits = (props: AdminAuditsProps) => {\n const client = useClient<AdminAuditController>();\n const router = useRouter<AdminRouter>();\n const { l } = useI18n();\n\n const filters = t.object({\n type: t.optional(t.text()),\n action: t.optional(t.text()),\n severity: t.optional(t.enum([\"info\", \"warning\", \"critical\"])),\n success: t.optional(t.boolean()),\n resourceType: t.optional(t.text()),\n search: t.optional(t.text()),\n from: t.optional(t.datetime()),\n to: t.optional(t.datetime()),\n });\n\n return (\n <Flex flex={1} direction=\"column\">\n <DataTable<AuditEntity, typeof filters>\n submitOnInit\n defaultSize={20}\n typeFormProps={{\n skipSubmitButton: true,\n columns: 4,\n }}\n tableProps={{\n horizontalSpacing: \"xs\",\n verticalSpacing: \"xs\",\n striped: false,\n highlightOnHover: true,\n }}\n filters={filters}\n tableTrProps={(item) => ({\n style: {\n cursor: item.userId ? \"pointer\" : \"default\",\n opacity: item.success ? 1 : 0.85,\n },\n onClick: () => {\n if (item.userId) {\n router.go(\"adminUserDetails\", {\n params: { userId: item.userId },\n });\n }\n },\n })}\n items={async (query) => {\n const response = await client.findAudits({\n query: {\n ...query,\n userRealm: props.userRealmName,\n },\n });\n return response as Page<AuditEntity>;\n }}\n columns={{\n type: {\n label: \"Type\",\n fit: true,\n value: (item) => (\n <Badge size=\"sm\" variant=\"light\" color={getTypeColor(item.type)}>\n {item.type}\n </Badge>\n ),\n },\n action: {\n label: \"Action\",\n fit: true,\n value: (item) => (\n <Badge size=\"sm\" variant=\"outline\">\n {item.action}\n </Badge>\n ),\n },\n severity: {\n label: \"Severity\",\n fit: true,\n value: (item) => (\n <Badge\n size=\"xs\"\n variant=\"light\"\n color={getSeverityColor(item.severity)}\n leftSection={getSeverityIcon(item.severity)}\n >\n {item.severity}\n </Badge>\n ),\n },\n user: {\n label: \"User\",\n fit: true,\n value: (item) =>\n item.userId ? (\n <Tooltip\n label={\n <Stack gap={2}>\n <Text size=\"xs\">{item.userEmail || \"No email\"}</Text>\n <Text size=\"xs\" c=\"dimmed\">\n {item.userRealm || \"default\"}\n </Text>\n </Stack>\n }\n >\n <Group gap={4}>\n <IconUser size={12} />\n <Text size=\"xs\" lineClamp={1} maw={100}>\n {item.userEmail?.split(\"@\")[0] || item.userId.slice(0, 8)}\n </Text>\n </Group>\n </Tooltip>\n ) : (\n <Text size=\"xs\" c=\"dimmed\">\n System\n </Text>\n ),\n },\n description: {\n label: \"Description\",\n value: (item) => (\n <Text size=\"sm\" lineClamp={1}>\n {item.description || \"-\"}\n </Text>\n ),\n },\n resource: {\n label: \"Resource\",\n fit: true,\n value: (item) =>\n item.resourceType ? (\n <Tooltip label={`${item.resourceType}: ${item.resourceId}`}>\n <Badge size=\"xs\" variant=\"dot\" color=\"gray\">\n {item.resourceType}\n </Badge>\n </Tooltip>\n ) : (\n <Text size=\"xs\" c=\"dimmed\">\n -\n </Text>\n ),\n },\n success: {\n label: \"Status\",\n fit: true,\n value: (item) =>\n item.success ? (\n <IconCheck size={14} color=\"var(--mantine-color-green-6)\" />\n ) : (\n <Tooltip label={item.errorMessage || \"Failed\"}>\n <IconX size={14} color=\"var(--mantine-color-red-6)\" />\n </Tooltip>\n ),\n },\n ipAddress: {\n label: \"IP\",\n fit: true,\n value: (item) => (\n <Text size=\"xs\" c=\"dimmed\" ff=\"monospace\">\n {item.ipAddress || \"-\"}\n </Text>\n ),\n },\n createdAt: {\n label: \"Time\",\n fit: true,\n value: (item) => (\n <Tooltip label={l(item.createdAt, { date: \"medium\" })}>\n <Text size=\"xs\" c=\"dimmed\">\n {l(item.createdAt, { date: \"fromNow\" })}\n </Text>\n </Tooltip>\n ),\n },\n }}\n />\n </Flex>\n );\n};\n\nexport default AdminAudits;\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"AdminUserCreate-DiXi1EWB.js","names":["Text"],"sources":["../../src/admin/components/users/AdminUserCreate.tsx"],"sourcesContent":["import { ActionButton, Control, Flex } from \"@alepha/ui\";\nimport { Card, Stack, Text } from \"@mantine/core\";\nimport { t } from \"alepha\";\nimport type { AdminUserController } from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useForm } from \"alepha/react/form\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminUserCreateProps {\n userRealmName?: string;\n}\n\nconst AdminUserCreate = (props: AdminUserCreateProps) => {\n const client = useClient<AdminUserController>();\n const router = useRouter<AdminRouter>();\n\n const form = useForm({\n schema: t.object({\n username: t.optional(\n t.shortText({\n minLength: 3,\n maxLength: 50,\n pattern: \"^[a-zA-Z0-9._-]+$\",\n }),\n ),\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.string()),\n lastName: t.optional(t.string()),\n roles: t.optional(t.array(t.string())),\n enabled: t.optional(t.boolean()),\n password: t.optional(t.string({ minLength: 8 })),\n }),\n handler: async (data) => {\n const user = await client.createUser({\n query: {\n userRealmName: props.userRealmName,\n },\n body: {\n ...data,\n enabled: data.enabled ?? true,\n },\n });\n\n await router.go(\"adminUserDetails\", {\n params: { userId: user.id },\n });\n },\n });\n\n return (\n <Flex flex={1} p=\"md\">\n <Card withBorder p=\"lg\" maw={600} w=\"100%\">\n <form {...form.props}>\n <Stack gap=\"md\">\n <Text size=\"lg\" fw={500}>\n Create New User\n </Text>\n\n <Control title=\"Username\" input={form.input.username} />\n\n <Control title=\"Email\" input={form.input.email} />\n\n <Control title=\"Phone Number\" input={form.input.phoneNumber} />\n\n <Control title=\"First Name\" input={form.input.firstName} />\n\n <Control title=\"Last Name\" input={form.input.lastName} />\n\n <Control title=\"Password\" input={form.input.password} password />\n\n <Control title=\"Roles\" input={form.input.roles} />\n\n <Control title=\"Enabled\" input={form.input.enabled} />\n\n <ActionButton form={form}>Create User</ActionButton>\n </Stack>\n </form>\n </Card>\n </Flex>\n );\n};\n\nexport default AdminUserCreate;\n"],"mappings":";;;;;;;;;AAaA,MAAM,mBAAmB,UAAgC;CACvD,MAAM,SAAS,WAAgC;CAC/C,MAAM,SAAS,WAAwB;CAEvC,MAAM,OAAO,QAAQ;EACnB,QAAQ,EAAE,OAAO;GACf,UAAU,EAAE,SACV,EAAE,UAAU;IACV,WAAW;IACX,WAAW;IACX,SAAS;IACV,CAAC,CACH;GACD,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC;GAC5B,aAAa,EAAE,SAAS,EAAE,MAAM,CAAC;GACjC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC;GACjC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC;GAChC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;GACtC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;GAChC,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC;GACjD,CAAC;EACF,SAAS,OAAO,SAAS;GACvB,MAAM,OAAO,MAAM,OAAO,WAAW;IACnC,OAAO,EACL,eAAe,MAAM,eACtB;IACD,MAAM;KACJ,GAAG;KACH,SAAS,KAAK,WAAW;KAC1B;IACF,CAAC;AAEF,SAAM,OAAO,GAAG,oBAAoB,EAClC,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAC5B,CAAC;;EAEL,CAAC;AAEF,QACE,oBAAC;EAAK,MAAM;EAAG,GAAE;YACf,oBAAC;GAAK;GAAW,GAAE;GAAK,KAAK;GAAK,GAAE;aAClC,oBAAC;IAAK,GAAI,KAAK;cACb,qBAAC;KAAM,KAAI;;MACT,oBAACA;OAAK,MAAK;OAAK,IAAI;iBAAK;QAElB;MAEP,oBAAC;OAAQ,OAAM;OAAW,OAAO,KAAK,MAAM;QAAY;MAExD,oBAAC;OAAQ,OAAM;OAAQ,OAAO,KAAK,MAAM;QAAS;MAElD,oBAAC;OAAQ,OAAM;OAAe,OAAO,KAAK,MAAM;QAAe;MAE/D,oBAAC;OAAQ,OAAM;OAAa,OAAO,KAAK,MAAM;QAAa;MAE3D,oBAAC;OAAQ,OAAM;OAAY,OAAO,KAAK,MAAM;QAAY;MAEzD,oBAAC;OAAQ,OAAM;OAAW,OAAO,KAAK,MAAM;OAAU;QAAW;MAEjE,oBAAC;OAAQ,OAAM;OAAQ,OAAO,KAAK,MAAM;QAAS;MAElD,oBAAC;OAAQ,OAAM;OAAU,OAAO,KAAK,MAAM;QAAW;MAEtD,oBAAC;OAAmB;iBAAM;QAA0B;;MAC9C;KACH;IACF;GACF;;AAIX,8BAAe"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"AdminUserSettings-BnzRAcqV.js","names":[],"sources":["../../src/admin/components/users/AdminUserSettings.tsx"],"sourcesContent":["import { ActionButton, Flex, Text } from \"@alepha/ui\";\nimport { Alert, Card, Group, Loader, Stack } from \"@mantine/core\";\nimport {\n IconAlertCircle,\n IconCheck,\n IconMail,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport type {\n AdminUserController,\n UserController,\n UserEntity,\n} from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useRouter, useRouterState } from \"alepha/react/router\";\nimport { useEffect, useState } from \"react\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminUserSettingsProps {\n userRealmName?: string;\n}\n\nconst AdminUserSettings = (props: AdminUserSettingsProps) => {\n const router = useRouter<AdminRouter>();\n const state = useRouterState();\n const adminClient = useClient<AdminUserController>();\n const userClient = useClient<UserController>();\n const userId = state.params.userId as string;\n\n const [user, setUser] = useState<UserEntity | null>(null);\n const [loading, setLoading] = useState(true);\n const [deleteLoading, setDeleteLoading] = useState(false);\n const [verifyLoading, setVerifyLoading] = useState(false);\n const [verifySuccess, setVerifySuccess] = useState(false);\n\n useEffect(() => {\n const loadUser = async () => {\n try {\n const data = await adminClient.getUser({\n params: { id: userId },\n query: { userRealmName: props.userRealmName },\n });\n setUser(data);\n } finally {\n setLoading(false);\n }\n };\n\n loadUser();\n }, [userId]);\n\n const handleDelete = async () => {\n if (!confirm(\"Are you sure you want to delete this user?\")) {\n return;\n }\n\n setDeleteLoading(true);\n try {\n await adminClient.deleteUser({\n params: { id: userId },\n query: { userRealmName: props.userRealmName },\n });\n await router.go(\"adminUsers\");\n } finally {\n setDeleteLoading(false);\n }\n };\n\n const handleTriggerEmailVerification = async () => {\n if (!user?.email) return;\n\n setVerifyLoading(true);\n setVerifySuccess(false);\n try {\n await userClient.requestEmailVerification({\n query: {\n userRealmName: props.userRealmName,\n method: \"link\",\n verifyUrl: `${window.location.origin}/auth/verify-email`,\n },\n body: { email: user.email },\n });\n setVerifySuccess(true);\n } finally {\n setVerifyLoading(false);\n }\n };\n\n if (loading) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Loader />\n </Flex>\n );\n }\n\n if (!user) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Text c=\"dimmed\">User not found</Text>\n </Flex>\n );\n }\n\n return (\n <Flex flex={1} direction=\"column\" gap=\"md\">\n {user.email && !user.emailVerified && (\n <Card withBorder p=\"lg\">\n <Stack gap=\"md\">\n <Text size=\"lg\" fw={500}>\n Email Verification\n </Text>\n\n <Alert variant=\"light\" color=\"yellow\" icon={<IconMail />}>\n <Text size=\"sm\">\n This user's email ({user.email}) is not verified. You can send a\n verification link to the user.\n </Text>\n </Alert>\n\n {verifySuccess && (\n <Alert variant=\"light\" color=\"green\" icon={<IconCheck />}>\n <Text size=\"sm\">\n Verification link sent successfully to {user.email}.\n </Text>\n </Alert>\n )}\n\n <Group>\n <ActionButton\n leftSection={<IconMail size={16} />}\n loading={verifyLoading}\n onClick={handleTriggerEmailVerification}\n >\n Send Verification Link\n </ActionButton>\n </Group>\n </Stack>\n </Card>\n )}\n\n <Card withBorder p=\"lg\">\n <Stack gap=\"md\">\n <Text size=\"lg\" fw={500} c=\"red\">\n Danger Zone\n </Text>\n\n <Alert variant=\"light\" color=\"red\" icon={<IconAlertCircle />}>\n <Text size=\"sm\">\n Deleting this user will permanently remove their account and all\n associated data. This action cannot be undone.\n </Text>\n </Alert>\n\n <Group>\n <ActionButton\n color=\"red\"\n leftSection={<IconTrash size={16} />}\n loading={deleteLoading}\n onClick={handleDelete}\n >\n Delete User\n </ActionButton>\n </Group>\n </Stack>\n </Card>\n </Flex>\n );\n};\n\nexport default AdminUserSettings;\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { t as AdminUserSettings_default } from "./AdminUserSettings-BnzRAcqV.js";
2
-
3
- export { AdminUserSettings_default as default };
@@ -1 +0,0 @@
1
- {"version":3,"file":"AdminUsers-CYkcUWCg.js","names":["Flex"],"sources":["../../src/admin/components/users/AdminUsers.tsx"],"sourcesContent":["import { DataTable, Text } from \"@alepha/ui\";\nimport { Badge, Flex, Group } from \"@mantine/core\";\nimport { IconCheck, IconUsersPlus, IconX } from \"@tabler/icons-react\";\nimport { type Page, t } from \"alepha\";\nimport {\n type AdminUserController,\n type UserEntity,\n users,\n} from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminUsersProps {\n userRealmName?: string;\n}\n\nconst AdminUsers = (props: AdminUsersProps) => {\n const client = useClient<AdminUserController>();\n const router = useRouter<AdminRouter>();\n const { l } = useI18n();\n\n const filters = t.object({\n query: t.optional(\n t.string({\n $control: {\n query: t.omit(users.schema, [\"id\", \"version\"]),\n },\n }),\n ),\n });\n\n return (\n <Flex flex={1} direction=\"column\">\n <DataTable<UserEntity, typeof filters>\n submitOnInit\n actions={[\n {\n icon: IconUsersPlus,\n href: router.path(\"adminUserCreate\"),\n label: \"Create User\",\n },\n ]}\n defaultSize={10}\n typeFormProps={{\n skipSubmitButton: true,\n columns: 3,\n }}\n tableProps={{\n horizontalSpacing: \"xs\",\n verticalSpacing: \"xs\",\n striped: false,\n highlightOnHover: true,\n }}\n onFilterChange={(key, value, form) => {\n if (key === \"query\") {\n return form.submit();\n }\n }}\n filters={filters}\n tableTrProps={(item) => {\n const baseProps: Record<string, any> = {\n style: { cursor: \"pointer\" },\n onClick: () =>\n router.go(\"adminUserDetails\", {\n params: { userId: item.id },\n }),\n };\n\n if (!item.enabled) {\n baseProps.opacity = 0.5;\n }\n\n return baseProps;\n }}\n items={async (filters) => {\n const response = await client.findUsers({\n query: {\n ...filters,\n userRealmName: props.userRealmName,\n },\n });\n\n return response as Page<UserEntity>;\n }}\n columns={{\n username: {\n label: \"Username\",\n value: (item) => (\n <Text size=\"sm\" fw={500}>\n {item.username || \"-\"}\n </Text>\n ),\n },\n email: {\n label: \"Email\",\n value: (item) => (\n <Group gap=\"xs\">\n <Text size=\"sm\">{item.email || \"-\"}</Text>\n {item.email && (\n <Badge\n size=\"xs\"\n variant=\"light\"\n color={item.emailVerified ? \"green\" : \"gray\"}\n leftSection={\n item.emailVerified ? (\n <IconCheck size={10} />\n ) : (\n <IconX size={10} />\n )\n }\n >\n {item.emailVerified ? \"Verified\" : \"Unverified\"}\n </Badge>\n )}\n </Group>\n ),\n },\n roles: {\n label: \"Roles\",\n value: (item) => (\n <Group gap={4}>\n {item.roles.map((role: string) => (\n <Badge key={role} size=\"xs\" variant=\"outline\">\n {role}\n </Badge>\n ))}\n </Group>\n ),\n },\n enabled: {\n label: \"Status\",\n fit: true,\n value: (item) => (\n <Badge\n size=\"sm\"\n variant=\"light\"\n color={item.enabled ? \"green\" : \"red\"}\n >\n {item.enabled ? \"Active\" : \"Disabled\"}\n </Badge>\n ),\n },\n createdAt: {\n label: \"Created\",\n fit: true,\n value: (item) => (\n <Text size=\"xs\" c=\"dimmed\">\n {l(item.createdAt, { date: \"fromNow\" })}\n </Text>\n ),\n },\n }}\n />\n </Flex>\n );\n};\n\nexport default AdminUsers;\n"],"mappings":";;;;;;;;;;;AAkBA,MAAM,cAAc,UAA2B;CAC7C,MAAM,SAAS,WAAgC;CAC/C,MAAM,SAAS,WAAwB;CACvC,MAAM,EAAE,MAAM,SAAS;CAEvB,MAAM,UAAU,EAAE,OAAO,EACvB,OAAO,EAAE,SACP,EAAE,OAAO,EACP,UAAU,EACR,OAAO,EAAE,KAAK,MAAM,QAAQ,CAAC,MAAM,UAAU,CAAC,EAC/C,EACF,CAAC,CACH,EACF,CAAC;AAEF,QACE,oBAACA;EAAK,MAAM;EAAG,WAAU;YACvB,oBAAC;GACC;GACA,SAAS,CACP;IACE,MAAM;IACN,MAAM,OAAO,KAAK,kBAAkB;IACpC,OAAO;IACR,CACF;GACD,aAAa;GACb,eAAe;IACb,kBAAkB;IAClB,SAAS;IACV;GACD,YAAY;IACV,mBAAmB;IACnB,iBAAiB;IACjB,SAAS;IACT,kBAAkB;IACnB;GACD,iBAAiB,KAAK,OAAO,SAAS;AACpC,QAAI,QAAQ,QACV,QAAO,KAAK,QAAQ;;GAGf;GACT,eAAe,SAAS;IACtB,MAAM,YAAiC;KACrC,OAAO,EAAE,QAAQ,WAAW;KAC5B,eACE,OAAO,GAAG,oBAAoB,EAC5B,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAC5B,CAAC;KACL;AAED,QAAI,CAAC,KAAK,QACR,WAAU,UAAU;AAGtB,WAAO;;GAET,OAAO,OAAO,YAAY;AAQxB,WAPiB,MAAM,OAAO,UAAU,EACtC,OAAO;KACL,GAAG;KACH,eAAe,MAAM;KACtB,EACF,CAAC;;GAIJ,SAAS;IACP,UAAU;KACR,OAAO;KACP,QAAQ,SACN,oBAAC;MAAK,MAAK;MAAK,IAAI;gBACjB,KAAK,YAAY;OACb;KAEV;IACD,OAAO;KACL,OAAO;KACP,QAAQ,SACN,qBAAC;MAAM,KAAI;iBACT,oBAAC;OAAK,MAAK;iBAAM,KAAK,SAAS;QAAW,EACzC,KAAK,SACJ,oBAAC;OACC,MAAK;OACL,SAAQ;OACR,OAAO,KAAK,gBAAgB,UAAU;OACtC,aACE,KAAK,gBACH,oBAAC,aAAU,MAAM,KAAM,GAEvB,oBAAC,SAAM,MAAM,KAAM;iBAItB,KAAK,gBAAgB,aAAa;QAC7B;OAEJ;KAEX;IACD,OAAO;KACL,OAAO;KACP,QAAQ,SACN,oBAAC;MAAM,KAAK;gBACT,KAAK,MAAM,KAAK,SACf,oBAAC;OAAiB,MAAK;OAAK,SAAQ;iBACjC;SADS,KAEJ,CACR;OACI;KAEX;IACD,SAAS;KACP,OAAO;KACP,KAAK;KACL,QAAQ,SACN,oBAAC;MACC,MAAK;MACL,SAAQ;MACR,OAAO,KAAK,UAAU,UAAU;gBAE/B,KAAK,UAAU,WAAW;OACrB;KAEX;IACD,WAAW;KACT,OAAO;KACP,KAAK;KACL,QAAQ,SACN,oBAAC;MAAK,MAAK;MAAK,GAAE;gBACf,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;OAClC;KAEV;IACF;IACD;GACG;;AAIX,yBAAe"}