@alepha/ui 0.13.5 → 0.13.7

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 (157) hide show
  1. package/dist/admin/AdminAudits-CwvH8e8c.js +215 -0
  2. package/dist/admin/AdminAudits-CwvH8e8c.js.map +1 -0
  3. package/dist/admin/AdminAudits-Dv8Vk_6r.js +3 -0
  4. package/dist/admin/AdminFiles-5CPA3lQk.js +3 -0
  5. package/dist/admin/{AdminFiles-B_jfB_Py.js → AdminFiles-C_w1tb_x.js} +4 -3
  6. package/dist/admin/AdminFiles-C_w1tb_x.js.map +1 -0
  7. package/dist/admin/AdminLayout-BnSmtA4x.js +3 -0
  8. package/dist/admin/AdminLayout-XiSivwWH.js +39 -0
  9. package/dist/admin/AdminLayout-XiSivwWH.js.map +1 -0
  10. package/dist/admin/AdminNotifications-DLjmZWtf.js +3 -0
  11. package/dist/admin/{AdminNotifications-BFEjqpqx.js → AdminNotifications-DuYy74AN.js} +3 -3
  12. package/dist/admin/AdminNotifications-DuYy74AN.js.map +1 -0
  13. package/dist/admin/AdminParameters-DYg48Jwe.js +3 -0
  14. package/dist/admin/AdminParameters-YagqWTG3.js +575 -0
  15. package/dist/admin/AdminParameters-YagqWTG3.js.map +1 -0
  16. package/dist/admin/{AdminSessions-D7DESfWK.js → AdminSessions-BCjgJ-93.js} +4 -4
  17. package/dist/admin/AdminSessions-BCjgJ-93.js.map +1 -0
  18. package/dist/admin/AdminSessions-DEh2uN-4.js +3 -0
  19. package/dist/admin/AdminUserAudits-B_PUXCKC.js +177 -0
  20. package/dist/admin/AdminUserAudits-B_PUXCKC.js.map +1 -0
  21. package/dist/admin/AdminUserAudits-D7cTcElL.js +3 -0
  22. package/dist/admin/{AdminUserCreate-Bhxsn92l.js → AdminUserCreate-DzfRbGZ4.js} +4 -4
  23. package/dist/admin/AdminUserCreate-DzfRbGZ4.js.map +1 -0
  24. package/dist/admin/{AdminUserCreate-CYI_xW5T.js → AdminUserCreate-oUA1KDIl.js} +1 -1
  25. package/dist/admin/{AdminUserDetails-C2y1Ig4n.js → AdminUserDetails-DeTrJm-t.js} +5 -5
  26. package/dist/admin/AdminUserDetails-DeTrJm-t.js.map +1 -0
  27. package/dist/admin/{AdminUserDetails-Cmzx9HxH.js → AdminUserDetails-y1H5DW8Y.js} +1 -1
  28. package/dist/admin/{AdminUserLayout-sW6cjZL0.js → AdminUserLayout-CsfrrZkD.js} +4 -7
  29. package/dist/admin/AdminUserLayout-CsfrrZkD.js.map +1 -0
  30. package/dist/admin/{AdminUserLayout-DGSf612u.js → AdminUserLayout-Dejnz13m.js} +1 -1
  31. package/dist/admin/AdminUserSessions-Bbhcpz4k.js +3 -0
  32. package/dist/admin/{AdminUserSessions-CvN15wPe.js → AdminUserSessions-DO9H85O-.js} +4 -4
  33. package/dist/admin/AdminUserSessions-DO9H85O-.js.map +1 -0
  34. package/dist/admin/{AdminUserSettings-DvaaxgcV.js → AdminUserSettings-B3jA8g3p.js} +4 -4
  35. package/dist/admin/AdminUserSettings-B3jA8g3p.js.map +1 -0
  36. package/dist/admin/AdminUserSettings-CE0xpbQc.js +3 -0
  37. package/dist/admin/AdminUsers-CegGZDhW.js +3 -0
  38. package/dist/admin/{AdminUsers-BR3C-jrg.js → AdminUsers-ebbrJBT0.js} +13 -17
  39. package/dist/admin/AdminUsers-ebbrJBT0.js.map +1 -0
  40. package/dist/admin/index.d.ts +2044 -1044
  41. package/dist/admin/index.js +65 -62
  42. package/dist/admin/index.js.map +1 -1
  43. package/dist/auth/AuthLayout-BAZJHzDG.js +23 -0
  44. package/dist/auth/AuthLayout-BAZJHzDG.js.map +1 -0
  45. package/dist/auth/{Login-7HlBjDeV.js → Login-CeNZZjrr.js} +80 -44
  46. package/dist/auth/Login-CeNZZjrr.js.map +1 -0
  47. package/dist/auth/Login-hQcu1nlu.js +4 -0
  48. package/dist/auth/Register-B6HBNVHS.js +4 -0
  49. package/dist/auth/{Register-CuQr3kgi.js → Register-s4ENeyiE.js} +131 -91
  50. package/dist/auth/Register-s4ENeyiE.js.map +1 -0
  51. package/dist/auth/ResetPassword-Cjd-W-Nu.js +3 -0
  52. package/dist/auth/ResetPassword-GLIFkJT7.js +278 -0
  53. package/dist/auth/ResetPassword-GLIFkJT7.js.map +1 -0
  54. package/dist/auth/index.d.ts +471 -426
  55. package/dist/auth/index.js +26 -18
  56. package/dist/auth/index.js.map +1 -1
  57. package/dist/core/index.d.ts +400 -130
  58. package/dist/core/index.js +1751 -1369
  59. package/dist/core/index.js.map +1 -1
  60. package/package.json +15 -11
  61. package/src/admin/AdminRouter.ts +70 -16
  62. package/src/admin/components/AdminLayout.tsx +41 -61
  63. package/src/admin/components/audits/AdminAudits.tsx +240 -0
  64. package/src/admin/components/{AdminFiles.tsx → files/AdminFiles.tsx} +1 -1
  65. package/src/admin/components/{AdminJobs.tsx → jobs/AdminJobs.tsx} +1 -1
  66. package/src/admin/components/parameters/AdminParameters.tsx +137 -0
  67. package/src/admin/components/parameters/ParameterDetails.tsx +228 -0
  68. package/src/admin/components/parameters/ParameterHistory.tsx +146 -0
  69. package/src/admin/components/parameters/ParameterTree.tsx +146 -0
  70. package/src/admin/components/parameters/types.ts +35 -0
  71. package/src/admin/components/{AdminSessions.tsx → sessions/AdminSessions.tsx} +1 -1
  72. package/src/admin/components/users/AdminUserAudits.tsx +183 -0
  73. package/src/admin/components/{AdminUserCreate.tsx → users/AdminUserCreate.tsx} +1 -1
  74. package/src/admin/components/{AdminUserLayout.tsx → users/AdminUserLayout.tsx} +1 -4
  75. package/src/admin/components/{AdminUserSettings.tsx → users/AdminUserSettings.tsx} +1 -1
  76. package/src/admin/components/{AdminUsers.tsx → users/AdminUsers.tsx} +10 -12
  77. package/src/admin/index.ts +24 -16
  78. package/src/auth/AuthRouter.ts +23 -17
  79. package/src/auth/components/AuthLayout.tsx +6 -3
  80. package/src/auth/components/Login.tsx +109 -47
  81. package/src/auth/components/Register.tsx +158 -94
  82. package/src/auth/components/ResetPassword.tsx +51 -5
  83. package/src/auth/components/buttons/UserButton.tsx +2 -0
  84. package/src/core/atoms/alephaThemeAtom.ts +13 -0
  85. package/src/core/atoms/alephaThemeListAtom.ts +10 -0
  86. package/src/core/atoms/themes/default.ts +6 -0
  87. package/src/core/{themes → atoms/themes}/midnight.ts +3 -5
  88. package/src/core/components/buttons/ActionButton.tsx +33 -26
  89. package/src/core/components/buttons/DarkModeButton.tsx +0 -1
  90. package/src/core/components/buttons/ThemeButton.tsx +10 -7
  91. package/src/core/components/buttons/ToggleSidebarButton.tsx +19 -16
  92. package/src/core/components/data/ErrorViewer.tsx +171 -0
  93. package/src/core/components/data/JsonViewer.tsx +147 -138
  94. package/src/core/components/form/Control.tsx +95 -18
  95. package/src/core/components/form/ControlArray.tsx +377 -0
  96. package/src/core/components/form/ControlObject.tsx +127 -0
  97. package/src/core/components/form/TypeForm.tsx +99 -37
  98. package/src/core/components/layout/AdminShell.tsx +14 -1
  99. package/src/core/components/layout/AlephaMantineProvider.tsx +7 -3
  100. package/src/core/components/layout/Omnibar.tsx +1 -1
  101. package/src/core/components/layout/Sidebar.tsx +47 -14
  102. package/src/core/components/table/ColumnPicker.tsx +126 -0
  103. package/src/core/components/table/DataTable.tsx +354 -181
  104. package/src/core/components/table/DataTableFilters.tsx +64 -0
  105. package/src/core/components/table/DataTablePagination.tsx +59 -0
  106. package/src/core/components/table/DataTableToolbar.tsx +126 -0
  107. package/src/core/components/table/FilterPicker.tsx +138 -0
  108. package/src/core/components/table/types.ts +199 -0
  109. package/src/core/helpers/isComponentType.ts +9 -0
  110. package/src/core/helpers/renderIcon.tsx +13 -0
  111. package/src/core/hooks/useTheme.ts +24 -18
  112. package/src/core/index.ts +24 -3
  113. package/src/core/interfaces/AlephaTheme.ts +8 -0
  114. package/src/core/providers/ThemeProvider.ts +44 -62
  115. package/src/core/services/DialogService.tsx +24 -0
  116. package/src/core/utils/parseInput.ts +2 -2
  117. package/styles.css +1 -1
  118. package/dist/admin/AdminFiles-B-0UcHVV.js +0 -3
  119. package/dist/admin/AdminFiles-B_jfB_Py.js.map +0 -1
  120. package/dist/admin/AdminLayout-BMtiXAzS.js +0 -396
  121. package/dist/admin/AdminLayout-BMtiXAzS.js.map +0 -1
  122. package/dist/admin/AdminLayout-BNo3GoHR.js +0 -3
  123. package/dist/admin/AdminNotifications-BFEjqpqx.js.map +0 -1
  124. package/dist/admin/AdminNotifications-DJs2ZjNj.js +0 -3
  125. package/dist/admin/AdminSessions-D7DESfWK.js.map +0 -1
  126. package/dist/admin/AdminSessions-PS2M8iXi.js +0 -3
  127. package/dist/admin/AdminUserCreate-Bhxsn92l.js.map +0 -1
  128. package/dist/admin/AdminUserDetails-C2y1Ig4n.js.map +0 -1
  129. package/dist/admin/AdminUserLayout-sW6cjZL0.js.map +0 -1
  130. package/dist/admin/AdminUserSessions-CvN15wPe.js.map +0 -1
  131. package/dist/admin/AdminUserSessions-D-aOcZgV.js +0 -3
  132. package/dist/admin/AdminUserSettings-CEMhIYrI.js +0 -3
  133. package/dist/admin/AdminUserSettings-DvaaxgcV.js.map +0 -1
  134. package/dist/admin/AdminUsers-BR3C-jrg.js.map +0 -1
  135. package/dist/admin/AdminUsers-CMW9vN09.js +0 -3
  136. package/dist/auth/AuthLayout-CzwUKD9y.js +0 -19
  137. package/dist/auth/AuthLayout-CzwUKD9y.js.map +0 -1
  138. package/dist/auth/Login-7HlBjDeV.js.map +0 -1
  139. package/dist/auth/Login-C-e27DGb.js +0 -4
  140. package/dist/auth/Register-CuQr3kgi.js.map +0 -1
  141. package/dist/auth/Register-DbvXwgbG.js +0 -4
  142. package/dist/auth/ResetPassword-BzU-cdd4.js +0 -243
  143. package/dist/auth/ResetPassword-BzU-cdd4.js.map +0 -1
  144. package/dist/auth/ResetPassword-DSvrdpaA.js +0 -3
  145. package/src/admin/AdminSidebar.ts +0 -31
  146. package/src/admin/components/AdminParameters.tsx +0 -24
  147. package/src/core/themes/aurora.ts +0 -107
  148. package/src/core/themes/crystal.ts +0 -107
  149. package/src/core/themes/default.ts +0 -7
  150. package/src/core/themes/ember.ts +0 -107
  151. package/src/core/themes/index.ts +0 -7
  152. package/src/core/themes/remoraid.ts +0 -278
  153. package/src/core/themes/slate.ts +0 -81
  154. /package/src/admin/components/{AdminNotifications.tsx → notifications/AdminNotifications.tsx} +0 -0
  155. /package/src/admin/components/{AdminUserDetails.tsx → users/AdminUserDetails.tsx} +0 -0
  156. /package/src/admin/components/{AdminUserSessions.tsx → users/AdminUserSessions.tsx} +0 -0
  157. /package/src/admin/components/{AdminVerifications.tsx → verifications/AdminVerifications.tsx} +0 -0
@@ -1,5 +1,5 @@
1
1
  import type { FormModel } from "@alepha/react/form";
2
- import { Flex, Grid } from "@mantine/core";
2
+ import { Card, Flex, type FlexProps, Grid } from "@mantine/core";
3
3
  import type { TObject } from "alepha";
4
4
  import type { ReactNode } from "react";
5
5
  import ActionButton, {
@@ -21,17 +21,37 @@ export interface TypeFormProps<T extends TObject> {
21
21
  };
22
22
  schema?: TObject;
23
23
  children?: (input: FormModel<T>["input"]) => ReactNode;
24
+
25
+ /**
26
+ * Control props applied to all fields.
27
+ */
24
28
  controlProps?: Partial<Omit<ControlProps, "input">>;
29
+
30
+ /**
31
+ * Per-field control props override.
32
+ * Keys are field names from the schema.
33
+ */
34
+ fieldControlProps?: Record<string, Partial<Omit<ControlProps, "input">>>;
25
35
  skipFormElement?: boolean;
26
36
  skipSubmitButton?: boolean;
27
37
  submitButtonProps?: Partial<Omit<ActionSubmitButtonProps, "form">>;
28
38
  resetButtonProps?: Partial<Omit<ActionSubmitButtonProps, "form">>;
39
+
40
+ fill?: boolean;
41
+ flexProps?: FlexProps;
29
42
  }
30
43
 
31
44
  /**
32
45
  * TypeForm component that automatically renders all form inputs based on schema.
33
46
  * Uses the Control component to render individual fields and Mantine Grid for responsive layout.
34
47
  *
48
+ * Supports all field types including:
49
+ * - Primitive types (string, number, boolean, etc.)
50
+ * - Enum types (rendered as Select)
51
+ * - Arrays of primitives (rendered as MultiSelect/TagsInput)
52
+ * - Arrays of objects (rendered as ControlArray)
53
+ * - Nested objects (rendered as ControlObject)
54
+ *
35
55
  * @example
36
56
  * ```tsx
37
57
  * import { t } from "alepha";
@@ -44,6 +64,15 @@ export interface TypeFormProps<T extends TObject> {
44
64
  * email: t.text(),
45
65
  * age: t.integer(),
46
66
  * subscribe: t.boolean(),
67
+ * address: t.object({
68
+ * street: t.text(),
69
+ * city: t.text(),
70
+ * }),
71
+ * tags: t.array(t.text()),
72
+ * contacts: t.array(t.object({
73
+ * name: t.text(),
74
+ * email: t.text(),
75
+ * })),
47
76
  * }),
48
77
  * handler: (values) => {
49
78
  * console.log(values);
@@ -59,6 +88,7 @@ const TypeForm = <T extends TObject>(props: TypeFormProps<T>) => {
59
88
  columns = 3,
60
89
  children,
61
90
  controlProps,
91
+ fieldControlProps,
62
92
  skipFormElement = false,
63
93
  skipSubmitButton = false,
64
94
  submitButtonProps,
@@ -71,30 +101,9 @@ const TypeForm = <T extends TObject>(props: TypeFormProps<T>) => {
71
101
 
72
102
  const fieldNames = Object.keys(schema.properties);
73
103
 
74
- // Filter out unsupported field types (objects only, arrays are now supported)
75
- const supportedFields = fieldNames.filter((fieldName) => {
76
- const field = form.input[fieldName as keyof typeof form.input];
77
- if (!field || typeof field !== "object" || !("schema" in field)) {
78
- return false;
79
- }
80
-
81
- const schema: any = field.schema;
82
-
83
- // Skip if it's an object (not supported by Control)
84
- // Arrays are now supported via ControlSelect (MultiSelect/TagsInput)
85
- if ("type" in schema) {
86
- if (schema.type === "object") {
87
- return false;
88
- }
89
- }
90
-
91
- // Check if it has properties (nested object)
92
- if ("properties" in schema && schema.properties) {
93
- return false;
94
- }
95
-
96
- return true;
97
- });
104
+ // Filter to only include valid InputFields
105
+ // All types are now supported: primitives, enums, arrays, objects
106
+ const supportedFields = fieldNames;
98
107
 
99
108
  // Handle column configuration with defaults: xs=1, sm=2, lg=3
100
109
  const colSpan =
@@ -122,15 +131,40 @@ const TypeForm = <T extends TObject>(props: TypeFormProps<T>) => {
122
131
  <Grid>
123
132
  {supportedFields.map((fieldName) => {
124
133
  const field = form.input[fieldName as keyof typeof form.input];
134
+ const fieldSchema: any = schema.properties[fieldName];
125
135
 
126
136
  // Type guard to ensure field has the expected structure
127
- if (!field || typeof field !== "object" || !("schema" in field)) {
137
+ if (!field || !fieldSchema) {
128
138
  return null;
129
139
  }
130
140
 
141
+ // Determine if this is a complex type (object or array of objects)
142
+ // that should span full width
143
+ const isObject =
144
+ fieldSchema &&
145
+ "type" in fieldSchema &&
146
+ fieldSchema.type === "object";
147
+
148
+ const isArrayOfObjects =
149
+ fieldSchema &&
150
+ "type" in fieldSchema &&
151
+ fieldSchema.type === "array" &&
152
+ "items" in fieldSchema &&
153
+ fieldSchema.items &&
154
+ "properties" in fieldSchema.items;
155
+
156
+ // Complex types span full width, primitives use grid columns
157
+ const span = isObject || isArrayOfObjects ? 12 : colSpan;
158
+
159
+ // Merge control props: base controlProps + field-specific overrides
160
+ const mergedControlProps = {
161
+ ...controlProps,
162
+ ...fieldControlProps?.[fieldName],
163
+ };
164
+
131
165
  return (
132
- <Grid.Col key={fieldName} span={colSpan}>
133
- <Control input={field as any} {...controlProps} />
166
+ <Grid.Col key={fieldName} span={span}>
167
+ <Control input={field} {...mergedControlProps} />
134
168
  </Grid.Col>
135
169
  );
136
170
  })}
@@ -139,15 +173,34 @@ const TypeForm = <T extends TObject>(props: TypeFormProps<T>) => {
139
173
  };
140
174
 
141
175
  const content = (
142
- <Flex direction={"column"} gap={"sm"}>
143
- {renderFields()}
176
+ <Flex
177
+ direction={"column"}
178
+ gap={"sm"}
179
+ flex={props.fill ? 1 : undefined}
180
+ {...props.flexProps}
181
+ >
182
+ <Flex direction={"column"} gap={"sm"} flex={1}>
183
+ {renderFields()}
184
+ </Flex>
144
185
  {!skipSubmitButton && (
145
- <Flex gap={"sm"}>
146
- <ActionButton form={form} {...submitButtonProps}>
147
- {submitButtonProps?.children ?? "Submit"}
148
- </ActionButton>
149
- <ActionButton type={"reset"}>Reset</ActionButton>
150
- </Flex>
186
+ <Card w={"100%"} withBorder>
187
+ <Flex gap={"sm"} flex={1}>
188
+ <Flex></Flex>
189
+ <Flex flex={1}></Flex>
190
+ <Flex gap={"sm"}>
191
+ <ActionButton variant={"subtle"} type={"reset"}>
192
+ Reset
193
+ </ActionButton>
194
+ <ActionButton
195
+ intent={"primary"}
196
+ form={form}
197
+ {...submitButtonProps}
198
+ >
199
+ {submitButtonProps?.children ?? "Submit"}
200
+ </ActionButton>
201
+ </Flex>
202
+ </Flex>
203
+ </Card>
151
204
  )}
152
205
  </Flex>
153
206
  );
@@ -156,7 +209,16 @@ const TypeForm = <T extends TObject>(props: TypeFormProps<T>) => {
156
209
  return content;
157
210
  }
158
211
 
159
- return <form {...form.props}>{content}</form>;
212
+ return (
213
+ <Flex
214
+ component={"form"}
215
+ flex={props.fill ? 1 : undefined}
216
+ {...form.props}
217
+ {...props.flexProps}
218
+ >
219
+ {content}
220
+ </Flex>
221
+ );
160
222
  };
161
223
 
162
224
  export default TypeForm;
@@ -91,6 +91,10 @@ const AdminShell = (props: AdminShellProps) => {
91
91
  const hasSidebar = showSidebar && props.sidebarProps !== undefined;
92
92
  const hasAppBar = hasSidebar || props.appBarProps || props.header;
93
93
 
94
+ const headerHeight = hasAppBar ? 60 : 0;
95
+ const footerHeight = props.footer ? 24 : 0;
96
+ const sidebarWidth = hasSidebar ? (collapsed ? 78 : 300) : 0;
97
+
94
98
  return (
95
99
  <AppShell
96
100
  w={"100%"}
@@ -121,7 +125,16 @@ const AdminShell = (props: AdminShellProps) => {
121
125
  </AppShell.Navbar>
122
126
  )}
123
127
 
124
- <AppShell.Main display={"flex"} flex={1} {...props.appShellMainProps}>
128
+ <AppShell.Main
129
+ pl={sidebarWidth}
130
+ pt={headerHeight}
131
+ pb={footerHeight}
132
+ pr={0}
133
+ display={"flex"}
134
+ flex={1}
135
+ style={{ flexDirection: "column" }}
136
+ {...props.appShellMainProps}
137
+ >
125
138
  {props.children ?? <NestedView />}
126
139
  </AppShell.Main>
127
140
 
@@ -22,7 +22,7 @@ export interface AlephaMantineProviderProps {
22
22
  navigationProgress?: NavigationProgressProps;
23
23
  notifications?: NotificationsProps;
24
24
  modals?: ModalsProviderProps;
25
- omnibar?: OmnibarProps;
25
+ omnibar?: OmnibarProps | false;
26
26
  }
27
27
 
28
28
  const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
@@ -53,14 +53,18 @@ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
53
53
  [],
54
54
  );
55
55
 
56
+ const defaultColorScheme =
57
+ props.mantine?.defaultColorScheme ?? theme.defaultColorScheme;
58
+
56
59
  return (
57
60
  <>
58
61
  <ColorSchemeScript
59
- defaultColorScheme={props.mantine?.defaultColorScheme}
62
+ defaultColorScheme={defaultColorScheme}
60
63
  {...props.colorSchemeScript}
61
64
  />
62
65
  <MantineProvider
63
66
  {...props.mantine}
67
+ defaultColorScheme={defaultColorScheme}
64
68
  theme={{
65
69
  // Spread all theme properties from the selected theme
66
70
  ...theme,
@@ -71,7 +75,7 @@ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
71
75
  <Notifications {...props.notifications} />
72
76
  <NavigationProgress {...props.navigationProgress} />
73
77
  <ModalsProvider {...props.modals}>
74
- <Omnibar {...props.omnibar} />
78
+ {props.omnibar !== false && <Omnibar {...props.omnibar} />}
75
79
  {props.children ?? <NestedView />}
76
80
  </ModalsProvider>
77
81
  </MantineProvider>
@@ -3,7 +3,7 @@ import { Spotlight, type SpotlightActionData } from "@mantine/spotlight";
3
3
  import { IconSearch } from "@tabler/icons-react";
4
4
  import { type ReactNode, useMemo } from "react";
5
5
  import { ui } from "../../constants/ui.ts";
6
- import { renderIcon } from "../buttons/ActionButton.tsx";
6
+ import { renderIcon } from "../../helpers/renderIcon.tsx";
7
7
 
8
8
  export interface OmnibarProps {
9
9
  shortcut?: string | string[];
@@ -14,13 +14,13 @@ import {
14
14
  type ComponentType,
15
15
  type ReactNode,
16
16
  useCallback,
17
+ useMemo,
17
18
  useState,
18
19
  } from "react";
19
- import ActionButton, {
20
- type ActionProps,
21
- renderIcon,
22
- } from "../buttons/ActionButton.tsx";
20
+ import { renderIcon } from "../../helpers/renderIcon.tsx";
21
+ import ActionButton, { type ActionProps } from "../buttons/ActionButton.tsx";
23
22
  import OmnibarButton from "../buttons/OmnibarButton.tsx";
23
+ import ToggleSidebarButton from "../buttons/ToggleSidebarButton.tsx";
24
24
 
25
25
  export interface SidebarProps {
26
26
  menu?: SidebarNode[];
@@ -35,6 +35,15 @@ export interface SidebarProps {
35
35
  hide?: {
36
36
  paths?: string[];
37
37
  };
38
+
39
+ /**
40
+ * Automatically populate the menu from the router's pages.
41
+ */
42
+ autoPopulateMenu?:
43
+ | boolean
44
+ | {
45
+ startsWith: string;
46
+ };
38
47
  }
39
48
 
40
49
  export const Sidebar = (props: SidebarProps) => {
@@ -63,6 +72,10 @@ export const Sidebar = (props: SidebarProps) => {
63
72
  return <OmnibarButton collapsed={props.collapsed} key={key} />;
64
73
  }
65
74
 
75
+ if (item.type === "toggle") {
76
+ return <ToggleSidebarButton key={key} />;
77
+ }
78
+
66
79
  if (item.type === "section") {
67
80
  if (props.collapsed) return;
68
81
  return (
@@ -114,16 +127,29 @@ export const Sidebar = (props: SidebarProps) => {
114
127
  );
115
128
  };
116
129
 
130
+ const getSidebarNodes = (): SidebarNode[] => {
131
+ if (props.menu) return props.menu;
132
+ if (props.autoPopulateMenu) {
133
+ const items = router.concretePages.map((page) => ({
134
+ label: page.label ?? page.name,
135
+ description: page.description?.slice(0, 32),
136
+ icon: renderIcon(page.icon),
137
+ href: router.path(page.name),
138
+ })) as SidebarMenuItem[];
139
+ if (
140
+ typeof props.autoPopulateMenu === "object" &&
141
+ props.autoPopulateMenu.startsWith
142
+ ) {
143
+ const startsWith = props.autoPopulateMenu.startsWith;
144
+ return items.filter((item) => item.href?.startsWith(startsWith));
145
+ }
146
+ }
147
+ return [];
148
+ };
149
+
117
150
  const padding = "md";
118
- const gap = props.gap;
119
- const menu =
120
- props.menu ??
121
- (router.concretePages.map((page) => ({
122
- label: page.label ?? page.name,
123
- description: page.description,
124
- icon: renderIcon(page.icon),
125
- href: router.path(page.name),
126
- })) as SidebarMenuItem[]);
151
+ const gap = props.menu ? props.gap : "xs";
152
+ const menu = useMemo(() => getSidebarNodes(), []);
127
153
 
128
154
  return (
129
155
  <Flex
@@ -229,6 +255,8 @@ export const SidebarItem = (props: SidebarItemProps) => {
229
255
  props.theme.button?.size ??
230
256
  (level === 0 ? "sm" : "xs")
231
257
  }
258
+ c={"var(--mantine-color-text)"}
259
+ color={"gray"}
232
260
  variant={"subtle"}
233
261
  variantActive={"default"}
234
262
  radius={props.item.theme?.radius ?? props.theme.button?.radius ?? "md"}
@@ -385,7 +413,8 @@ export type SidebarNode =
385
413
  | SidebarDivider
386
414
  | SidebarSearch
387
415
  | SidebarElement
388
- | SidebarSection;
416
+ | SidebarSection
417
+ | SidebarToggle;
389
418
 
390
419
  export interface SidebarAbstractItem {
391
420
  position?: "top" | "bottom";
@@ -407,6 +436,10 @@ export interface SidebarSearch extends SidebarAbstractItem {
407
436
  type: "search";
408
437
  }
409
438
 
439
+ export interface SidebarToggle extends SidebarAbstractItem {
440
+ type: "toggle";
441
+ }
442
+
410
443
  export interface SidebarSection extends SidebarAbstractItem {
411
444
  type: "section";
412
445
  label: string;
@@ -0,0 +1,126 @@
1
+ import {
2
+ Button,
3
+ Checkbox,
4
+ Group,
5
+ Popover,
6
+ ScrollArea,
7
+ Stack,
8
+ Text,
9
+ } from "@mantine/core";
10
+ import { IconColumns } from "@tabler/icons-react";
11
+ import type { TObject } from "alepha";
12
+ import { useState } from "react";
13
+ import { ui } from "../../constants/ui.ts";
14
+ import ActionButton from "../buttons/ActionButton.tsx";
15
+ import type { ColumnVisibility, DataTableColumn } from "./types.ts";
16
+
17
+ export interface ColumnPickerProps<T extends object, Filters extends TObject> {
18
+ columns: { [key: string]: DataTableColumn<T, Filters> };
19
+ visibility: ColumnVisibility;
20
+ onVisibilityChange: (visibility: ColumnVisibility) => void;
21
+ }
22
+
23
+ const ColumnPicker = <T extends object, Filters extends TObject>({
24
+ columns,
25
+ visibility,
26
+ onVisibilityChange,
27
+ }: ColumnPickerProps<T, Filters>) => {
28
+ const [opened, setOpened] = useState(false);
29
+ const columnEntries = Object.entries(columns);
30
+
31
+ const handleShowAll = () => {
32
+ const newVisibility = columnEntries.reduce(
33
+ (acc, [key]) => ({ ...acc, [key]: true }),
34
+ {} as ColumnVisibility,
35
+ );
36
+ onVisibilityChange(newVisibility);
37
+ };
38
+
39
+ const handleHideAll = () => {
40
+ const newVisibility = columnEntries.reduce(
41
+ (acc, [key]) => ({ ...acc, [key]: false }),
42
+ {} as ColumnVisibility,
43
+ );
44
+ onVisibilityChange(newVisibility);
45
+ };
46
+
47
+ const handleToggle = (key: string, checked: boolean) => {
48
+ onVisibilityChange({
49
+ ...visibility,
50
+ [key]: checked,
51
+ });
52
+ };
53
+
54
+ const visibleCount = columnEntries.filter(
55
+ ([key]) => visibility[key] !== false,
56
+ ).length;
57
+
58
+ return (
59
+ <Popover
60
+ width={280}
61
+ position="bottom-start"
62
+ shadow="md"
63
+ opened={opened}
64
+ onChange={setOpened}
65
+ closeOnClickOutside
66
+ closeOnEscape
67
+ transitionProps={{
68
+ transition: "fade-up",
69
+ duration: 200,
70
+ timingFunction: "ease",
71
+ }}
72
+ >
73
+ <Popover.Target>
74
+ <ActionButton variant="subtle" icon={IconColumns} />
75
+ </Popover.Target>
76
+ <Popover.Dropdown
77
+ bg="transparent"
78
+ p="xs"
79
+ bd={`1px solid ${ui.colors.border}`}
80
+ style={{
81
+ backdropFilter: "blur(20px)",
82
+ }}
83
+ >
84
+ <Stack gap="xs" bg={ui.colors.surface} p="sm" bdrs="sm">
85
+ <Group justify="space-between">
86
+ <Text size="sm" fw={500}>
87
+ Columns ({visibleCount}/{columnEntries.length})
88
+ </Text>
89
+ <Group gap={4}>
90
+ <Button
91
+ size="compact-xs"
92
+ variant="subtle"
93
+ onClick={handleShowAll}
94
+ >
95
+ All
96
+ </Button>
97
+ <Button
98
+ size="compact-xs"
99
+ variant="subtle"
100
+ onClick={handleHideAll}
101
+ >
102
+ None
103
+ </Button>
104
+ </Group>
105
+ </Group>
106
+
107
+ <ScrollArea.Autosize mah={300}>
108
+ <Stack gap={4}>
109
+ {columnEntries.map(([key, col]) => (
110
+ <Checkbox
111
+ key={key}
112
+ label={col.label}
113
+ checked={visibility[key] !== false}
114
+ onChange={(e) => handleToggle(key, e.currentTarget.checked)}
115
+ size="sm"
116
+ />
117
+ ))}
118
+ </Stack>
119
+ </ScrollArea.Autosize>
120
+ </Stack>
121
+ </Popover.Dropdown>
122
+ </Popover>
123
+ );
124
+ };
125
+
126
+ export default ColumnPicker;