@alepha/ui 0.16.1 → 0.17.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 (218) hide show
  1. package/dist/admin/{AdminApiKeys-GMORg-1l.js → AdminApiKeys-CF_qOO3u.js} +20 -19
  2. package/dist/admin/AdminApiKeys-CF_qOO3u.js.map +1 -0
  3. package/dist/admin/{AdminAudits-pkWrjq1Z.js → AdminAudits-BQno3hZG.js} +7 -7
  4. package/dist/admin/AdminAudits-BQno3hZG.js.map +1 -0
  5. package/dist/admin/{AdminFiles-WeQbsCsl.js → AdminFiles-kvuUaASF.js} +3 -4
  6. package/dist/admin/{AdminFiles-WeQbsCsl.js.map → AdminFiles-kvuUaASF.js.map} +1 -1
  7. package/dist/admin/AdminJobDashboard-CrPxp0W1.js +485 -0
  8. package/dist/admin/AdminJobDashboard-CrPxp0W1.js.map +1 -0
  9. package/dist/admin/AdminJobExecutions-D-b4Zt7W.js +678 -0
  10. package/dist/admin/AdminJobExecutions-D-b4Zt7W.js.map +1 -0
  11. package/dist/admin/AdminJobRegistry-CNX5cpDx.js +301 -0
  12. package/dist/admin/AdminJobRegistry-CNX5cpDx.js.map +1 -0
  13. package/dist/admin/{AdminLayout-BqZiXx4H.js → AdminLayout-e-ZP5nWw.js} +6 -9
  14. package/dist/admin/AdminLayout-e-ZP5nWw.js.map +1 -0
  15. package/dist/admin/{AdminNotifications-Ds5Un0NJ.js → AdminNotifications-DeHJFf6W.js} +3 -4
  16. package/dist/admin/{AdminNotifications-Ds5Un0NJ.js.map → AdminNotifications-DeHJFf6W.js.map} +1 -1
  17. package/dist/admin/AdminParameters-iQE8o7a7.js +774 -0
  18. package/dist/admin/AdminParameters-iQE8o7a7.js.map +1 -0
  19. package/dist/admin/{AdminSessions-DzIOxM3b.js → AdminSessions-oKJCbd7w.js} +5 -6
  20. package/dist/admin/AdminSessions-oKJCbd7w.js.map +1 -0
  21. package/dist/admin/{AdminUserAudits-CiUPN2BC.js → AdminUserAudits-BNCEle_E.js} +6 -7
  22. package/dist/admin/AdminUserAudits-BNCEle_E.js.map +1 -0
  23. package/dist/admin/{AdminUserCreate-BwQKr4xE.js → AdminUserCreate-CgqeFwCt.js} +6 -6
  24. package/dist/admin/AdminUserCreate-CgqeFwCt.js.map +1 -0
  25. package/dist/admin/{AdminUserDetails-uqtC5aJ1.js → AdminUserDetails-DDe1A1GP.js} +30 -28
  26. package/dist/admin/AdminUserDetails-DDe1A1GP.js.map +1 -0
  27. package/dist/admin/{AdminUserLayout-CiPay35T.js → AdminUserLayout-HAlobhWf.js} +20 -19
  28. package/dist/admin/AdminUserLayout-HAlobhWf.js.map +1 -0
  29. package/dist/admin/{AdminUserSessions-DAE8Nf1F.js → AdminUserSessions-Bq1LnVLf.js} +5 -6
  30. package/dist/admin/AdminUserSessions-Bq1LnVLf.js.map +1 -0
  31. package/dist/admin/{AdminUserSettings-EbahaV2a.js → AdminUserSettings-BRsBZoxV.js} +10 -9
  32. package/dist/admin/AdminUserSettings-BRsBZoxV.js.map +1 -0
  33. package/dist/admin/{AdminUsers-Dcjh0KNW.js → AdminUsers-D71kIOSn.js} +6 -7
  34. package/dist/admin/AdminUsers-D71kIOSn.js.map +1 -0
  35. package/dist/admin/index.d.ts +21 -85
  36. package/dist/admin/index.d.ts.map +1 -1
  37. package/dist/admin/index.js +66 -88
  38. package/dist/admin/index.js.map +1 -1
  39. package/dist/auth/{AuthLayout-Dj5K4SIN.js → AuthLayout-CdJcrPs4.js} +2 -3
  40. package/dist/auth/{AuthLayout-Dj5K4SIN.js.map → AuthLayout-CdJcrPs4.js.map} +1 -1
  41. package/dist/{demo/IconGoogle-CbBF8Hqq.js → auth/IconGoogle-Bm18QD2q.js} +2 -4
  42. package/dist/auth/{IconGoogle-DpSlPZ1u.js.map → IconGoogle-Bm18QD2q.js.map} +1 -1
  43. package/dist/auth/{Login-BBqTosqZ.js → Login-BS_FYTy0.js} +19 -13
  44. package/dist/auth/Login-BS_FYTy0.js.map +1 -0
  45. package/dist/auth/{Profile-Bxj8Nwom.js → Profile-CjDsW378.js} +17 -12
  46. package/dist/auth/Profile-CjDsW378.js.map +1 -0
  47. package/dist/auth/{Register-Ce675Crg.js → Register-C5eqzAaD.js} +27 -17
  48. package/dist/auth/Register-C5eqzAaD.js.map +1 -0
  49. package/dist/auth/{ResetPassword-DWdt7c40.js → ResetPassword-XifinVao.js} +17 -10
  50. package/dist/auth/ResetPassword-XifinVao.js.map +1 -0
  51. package/dist/auth/{VerifyEmail-CI4JwByV.js → VerifyEmail-DTgbeJOO.js} +9 -6
  52. package/dist/auth/VerifyEmail-DTgbeJOO.js.map +1 -0
  53. package/dist/auth/index.d.ts +18 -14
  54. package/dist/auth/index.d.ts.map +1 -1
  55. package/dist/auth/index.js +19 -18
  56. package/dist/auth/index.js.map +1 -1
  57. package/dist/auth/rolldown-runtime-CjeV3_4I.js +18 -0
  58. package/dist/core/index.d.ts +182 -92
  59. package/dist/core/index.d.ts.map +1 -1
  60. package/dist/core/index.js +789 -476
  61. package/dist/core/index.js.map +1 -1
  62. package/dist/demo/DemoDataTable-lnBKWBf8.js +362 -0
  63. package/dist/demo/DemoDataTable-lnBKWBf8.js.map +1 -0
  64. package/dist/demo/{DemoHome-Cce2bWmg.js → DemoHome-CUMZsYaH.js} +6 -6
  65. package/dist/demo/DemoHome-CUMZsYaH.js.map +1 -0
  66. package/dist/demo/{DemoJsonViewer-Dgdk3Txb.js → DemoJsonViewer-_uokbGaW.js} +18 -19
  67. package/dist/demo/DemoJsonViewer-_uokbGaW.js.map +1 -0
  68. package/dist/demo/{DemoLayout-B20TEuhV.js → DemoLayout-DHVoacE6.js} +4 -5
  69. package/dist/demo/DemoLayout-DHVoacE6.js.map +1 -0
  70. package/dist/demo/{DemoLogin-CvCG2WVh.js → DemoLogin-DjJ9314c.js} +27 -24
  71. package/dist/demo/DemoLogin-DjJ9314c.js.map +1 -0
  72. package/dist/demo/{DemoRegister-CmeHbOAs.js → DemoRegister-DzkJ5M83.js} +39 -32
  73. package/dist/demo/DemoRegister-DzkJ5M83.js.map +1 -0
  74. package/dist/demo/{DemoResetPassword-CKO5iA_6.js → DemoResetPassword-DWh4_BpQ.js} +30 -26
  75. package/dist/demo/DemoResetPassword-DWh4_BpQ.js.map +1 -0
  76. package/dist/demo/{DemoSidebar-MVmQKfMt.js → DemoSidebar-C1csnGhX.js} +4 -5
  77. package/dist/demo/DemoSidebar-C1csnGhX.js.map +1 -0
  78. package/dist/demo/{DemoTypeForm-w-qtfRlC.js → DemoTypeForm-CWz6fJrJ.js} +4 -5
  79. package/dist/demo/DemoTypeForm-CWz6fJrJ.js.map +1 -0
  80. package/dist/demo/{DemoVerifyEmail-C8FFJT5A.js → DemoVerifyEmail-DbU_tCj8.js} +16 -16
  81. package/dist/demo/DemoVerifyEmail-DbU_tCj8.js.map +1 -0
  82. package/dist/{auth/IconGoogle-DpSlPZ1u.js → demo/IconGoogle-Ch1m3Uzl.js} +2 -4
  83. package/dist/demo/{IconGoogle-CbBF8Hqq.js.map → IconGoogle-Ch1m3Uzl.js.map} +1 -1
  84. package/dist/demo/{Showcase-CQrMWars.js → Showcase-BzoXNlCn.js} +11 -13
  85. package/dist/demo/Showcase-BzoXNlCn.js.map +1 -0
  86. package/dist/demo/index.d.ts +3 -70
  87. package/dist/demo/index.d.ts.map +1 -1
  88. package/dist/demo/index.js +11 -15
  89. package/dist/demo/index.js.map +1 -1
  90. package/dist/json/index.js +2 -2
  91. package/dist/json/index.js.map +1 -1
  92. package/package.json +11 -5
  93. package/src/admin/AdminRouter.ts +51 -29
  94. package/src/admin/components/AdminLayout.tsx +6 -9
  95. package/src/admin/components/audits/AdminAudits.tsx +5 -5
  96. package/src/admin/components/jobs/AdminJobDashboard.tsx +455 -0
  97. package/src/admin/components/jobs/AdminJobExecutions.tsx +693 -0
  98. package/src/admin/components/jobs/AdminJobRegistry.tsx +325 -0
  99. package/src/admin/components/keys/AdminApiKeys.tsx +28 -31
  100. package/src/admin/components/parameters/AdminParameters.tsx +156 -78
  101. package/src/admin/components/parameters/ParameterDetails.tsx +173 -108
  102. package/src/admin/components/parameters/ParameterEmptyState.tsx +27 -0
  103. package/src/admin/components/parameters/ParameterHistory.tsx +22 -35
  104. package/src/admin/components/parameters/ParameterTree.tsx +283 -109
  105. package/src/admin/components/parameters/types.ts +3 -3
  106. package/src/admin/components/sessions/AdminSessions.tsx +3 -3
  107. package/src/admin/components/shared/AdminResourceHeader.tsx +20 -16
  108. package/src/admin/components/users/AdminUserAudits.tsx +5 -5
  109. package/src/admin/components/users/AdminUserCreate.tsx +3 -3
  110. package/src/admin/components/users/AdminUserDetails.tsx +51 -53
  111. package/src/admin/components/users/AdminUserLayout.tsx +7 -7
  112. package/src/admin/components/users/AdminUserSessions.tsx +3 -3
  113. package/src/admin/components/users/AdminUserSettings.tsx +9 -9
  114. package/src/admin/components/users/AdminUsers.tsx +5 -5
  115. package/src/admin/components/verifications/AdminVerifications.tsx +3 -3
  116. package/src/admin/index.ts +0 -24
  117. package/src/admin/primitives/$uiAdmin.ts +2 -2
  118. package/src/auth/AuthRouter.ts +1 -0
  119. package/src/auth/components/Login.tsx +13 -13
  120. package/src/auth/components/Profile.tsx +17 -26
  121. package/src/auth/components/Register.tsx +21 -31
  122. package/src/auth/components/ResetPassword.tsx +13 -22
  123. package/src/auth/components/VerifyEmail.tsx +5 -5
  124. package/src/auth/components/buttons/UserButton.tsx +14 -4
  125. package/src/core/components/buttons/ActionButton.tsx +13 -17
  126. package/src/core/components/buttons/DarkModeButton.tsx +8 -4
  127. package/src/core/components/buttons/ToggleSidebarButton.tsx +3 -5
  128. package/src/core/components/data/ErrorViewer.tsx +15 -15
  129. package/src/core/components/dialogs/AlertDialog.tsx +3 -3
  130. package/src/core/components/dialogs/ConfirmDialog.tsx +3 -3
  131. package/src/core/components/dialogs/PromptDialog.tsx +3 -3
  132. package/src/core/components/form/Control.tsx +19 -32
  133. package/src/core/components/form/ControlArray.tsx +206 -96
  134. package/src/core/components/form/ControlObject.tsx +3 -3
  135. package/src/core/components/form/ControlQueryBuilder.tsx +20 -22
  136. package/src/core/components/form/ControlSelect.tsx +4 -0
  137. package/src/core/components/form/TypeForm.browser.spec.tsx +727 -0
  138. package/src/core/components/form/TypeForm.tsx +7 -0
  139. package/src/core/components/layout/AlephaMantineProvider.tsx +1 -0
  140. package/src/core/components/layout/Breadcrumb.tsx +91 -0
  141. package/src/core/components/layout/{AdminShell.tsx → DashboardShell.tsx} +77 -32
  142. package/src/core/components/layout/Omnibar.tsx +2 -1
  143. package/src/core/components/layout/Sidebar.tsx +63 -19
  144. package/src/core/components/table/ColumnPicker.tsx +47 -31
  145. package/src/core/components/table/DataTable.tsx +277 -201
  146. package/src/core/components/table/DataTableFilters.tsx +8 -0
  147. package/src/core/components/table/DataTableToolbar.tsx +98 -5
  148. package/src/core/components/table/FilterPicker.tsx +28 -26
  149. package/src/core/components/table/types.ts +52 -37
  150. package/src/core/components/table/useTableSelection.ts +83 -0
  151. package/src/core/constants/ui.ts +1 -1
  152. package/src/core/helpers/renderIcon.tsx +5 -2
  153. package/src/core/index.ts +9 -5
  154. package/src/core/styles.css +8 -7
  155. package/src/core/utils/parseInput.ts +1 -0
  156. package/src/core/utils/string.ts +28 -4
  157. package/src/demo/components/DemoHome.tsx +5 -5
  158. package/src/demo/components/DemoLayout.tsx +6 -2
  159. package/src/demo/components/core/DemoDataTable.tsx +209 -5
  160. package/src/demo/components/json/DemoJsonViewer.tsx +1 -1
  161. package/src/demo/components/shared/MacWindow.tsx +7 -7
  162. package/src/demo/components/shared/Showcase.tsx +3 -3
  163. package/src/demo/index.ts +0 -11
  164. package/src/json/components/JsonViewer.tsx +3 -3
  165. package/dist/admin/AdminApiKeys-DsmGnHNh.js +0 -3
  166. package/dist/admin/AdminApiKeys-GMORg-1l.js.map +0 -1
  167. package/dist/admin/AdminAudits-8SM96viT.js +0 -3
  168. package/dist/admin/AdminAudits-pkWrjq1Z.js.map +0 -1
  169. package/dist/admin/AdminFiles-B56ocq4H.js +0 -3
  170. package/dist/admin/AdminJobs-B-q9iGO3.js +0 -697
  171. package/dist/admin/AdminJobs-B-q9iGO3.js.map +0 -1
  172. package/dist/admin/AdminJobs-CED1syCn.js +0 -3
  173. package/dist/admin/AdminLayout-BqZiXx4H.js.map +0 -1
  174. package/dist/admin/AdminNotifications-B0B1rdc4.js +0 -3
  175. package/dist/admin/AdminParameters-BU3lATdJ.js +0 -3
  176. package/dist/admin/AdminParameters-CfDUpc78.js +0 -575
  177. package/dist/admin/AdminParameters-CfDUpc78.js.map +0 -1
  178. package/dist/admin/AdminSessions-BDGK2MS6.js +0 -3
  179. package/dist/admin/AdminSessions-DzIOxM3b.js.map +0 -1
  180. package/dist/admin/AdminUserAudits-CiUPN2BC.js.map +0 -1
  181. package/dist/admin/AdminUserAudits-Cj79gENT.js +0 -3
  182. package/dist/admin/AdminUserCreate-BwQKr4xE.js.map +0 -1
  183. package/dist/admin/AdminUserCreate-Cq-mUmBs.js +0 -3
  184. package/dist/admin/AdminUserDetails-DRjVAPFd.js +0 -3
  185. package/dist/admin/AdminUserDetails-uqtC5aJ1.js.map +0 -1
  186. package/dist/admin/AdminUserLayout-CGzmHHby.js +0 -3
  187. package/dist/admin/AdminUserLayout-CiPay35T.js.map +0 -1
  188. package/dist/admin/AdminUserSessions-DAE8Nf1F.js.map +0 -1
  189. package/dist/admin/AdminUserSessions-DcdzuNZ9.js +0 -3
  190. package/dist/admin/AdminUserSettings-D7V6-ceX.js +0 -3
  191. package/dist/admin/AdminUserSettings-EbahaV2a.js.map +0 -1
  192. package/dist/admin/AdminUsers-D9nyzGqQ.js +0 -3
  193. package/dist/admin/AdminUsers-Dcjh0KNW.js.map +0 -1
  194. package/dist/auth/Login-BBqTosqZ.js.map +0 -1
  195. package/dist/auth/Login-CoU63mMR.js +0 -4
  196. package/dist/auth/Profile-Bxj8Nwom.js.map +0 -1
  197. package/dist/auth/Register-BV_oa_AK.js +0 -4
  198. package/dist/auth/Register-Ce675Crg.js.map +0 -1
  199. package/dist/auth/ResetPassword-D5wC8GAA.js +0 -3
  200. package/dist/auth/ResetPassword-DWdt7c40.js.map +0 -1
  201. package/dist/auth/VerifyEmail-CI4JwByV.js.map +0 -1
  202. package/dist/auth/VerifyEmail-DAfqVm5s.js +0 -3
  203. package/dist/demo/DemoDataTable-CguplbR7.js +0 -150
  204. package/dist/demo/DemoDataTable-CguplbR7.js.map +0 -1
  205. package/dist/demo/DemoHome-Cce2bWmg.js.map +0 -1
  206. package/dist/demo/DemoHome-DC9qkMNe.js +0 -3
  207. package/dist/demo/DemoJsonViewer-DIssGVlJ.js +0 -4
  208. package/dist/demo/DemoJsonViewer-Dgdk3Txb.js.map +0 -1
  209. package/dist/demo/DemoLayout-B20TEuhV.js.map +0 -1
  210. package/dist/demo/DemoLayout-DSRyf4qJ.js +0 -3
  211. package/dist/demo/DemoLogin-CvCG2WVh.js.map +0 -1
  212. package/dist/demo/DemoRegister-CmeHbOAs.js.map +0 -1
  213. package/dist/demo/DemoResetPassword-CKO5iA_6.js.map +0 -1
  214. package/dist/demo/DemoSidebar-MVmQKfMt.js.map +0 -1
  215. package/dist/demo/DemoTypeForm-w-qtfRlC.js.map +0 -1
  216. package/dist/demo/DemoVerifyEmail-C8FFJT5A.js.map +0 -1
  217. package/dist/demo/Showcase-CQrMWars.js.map +0 -1
  218. package/src/admin/components/jobs/AdminJobs.tsx +0 -772
@@ -0,0 +1,27 @@
1
+ import { Text } from "@alepha/ui";
2
+ import { Flex } from "@mantine/core";
3
+ import { IconArrowLeft } from "@tabler/icons-react";
4
+
5
+ /**
6
+ * Empty state displayed when no parameter is selected.
7
+ * Invites user to select a parameter from the tree.
8
+ */
9
+ const ParameterEmptyState = () => {
10
+ return (
11
+ <Flex flex={1} p={"xl"} align="center">
12
+ <Flex direction="column" align="center" gap="md">
13
+ <IconArrowLeft size={32} color="var(--mantine-color-dimmed)" />
14
+ <Flex direction="column" align="center" gap={4}>
15
+ <Text fw={500} c="dimmed">
16
+ No Parameter Selected
17
+ </Text>
18
+ <Text size="xs" c="dimmed" ta="center" maw={240}>
19
+ Choose a parameter from the tree to view and edit its configuration
20
+ </Text>
21
+ </Flex>
22
+ </Flex>
23
+ </Flex>
24
+ );
25
+ };
26
+
27
+ export default ParameterEmptyState;
@@ -1,13 +1,5 @@
1
1
  import { ActionButton, Flex, Text } from "@alepha/ui";
2
- import {
3
- Badge,
4
- Card,
5
- Group,
6
- Loader,
7
- ScrollArea,
8
- Stack,
9
- Timeline,
10
- } from "@mantine/core";
2
+ import { Badge, Loader, ScrollArea, Timeline } from "@mantine/core";
11
3
  import { IconHistory } from "@tabler/icons-react";
12
4
  import type { Parameter } from "alepha/api/parameters";
13
5
  import { useI18n } from "alepha/react/i18n";
@@ -21,7 +13,6 @@ export interface ParameterHistoryProps {
21
13
  }
22
14
 
23
15
  const ParameterHistory = ({
24
- selectedConfig,
25
16
  history,
26
17
  loading,
27
18
  onRollback,
@@ -29,16 +20,6 @@ const ParameterHistory = ({
29
20
  const { l } = useI18n();
30
21
 
31
22
  const renderContent = () => {
32
- if (!selectedConfig) {
33
- return (
34
- <Flex flex={1} justify="center" align="center">
35
- <Text c="dimmed" size="xs">
36
- Select a parameter
37
- </Text>
38
- </Flex>
39
- );
40
- }
41
-
42
23
  if (loading) {
43
24
  return (
44
25
  <Flex flex={1} justify="center" align="center">
@@ -51,7 +32,7 @@ const ParameterHistory = ({
51
32
  return (
52
33
  <Flex flex={1} justify="center" align="center">
53
34
  <Text c="dimmed" size="xs">
54
- No history
35
+ Empty
55
36
  </Text>
56
37
  </Flex>
57
38
  );
@@ -73,7 +54,7 @@ const ParameterHistory = ({
73
54
  </Text>
74
55
  }
75
56
  title={
76
- <Group gap="xs">
57
+ <Flex gap="xs">
77
58
  <Text size="xs" fw={500}>
78
59
  Version {version.version}
79
60
  </Text>
@@ -84,10 +65,10 @@ const ParameterHistory = ({
84
65
  >
85
66
  {version.status}
86
67
  </Badge>
87
- </Group>
68
+ </Flex>
88
69
  }
89
70
  >
90
- <Stack gap={4} mt={4}>
71
+ <Flex direction="column" gap={4} mt={4}>
91
72
  <Text size="xs" c="dimmed">
92
73
  {l(version.createdAt, { date: "fromNow" })}
93
74
  </Text>
@@ -115,7 +96,7 @@ const ParameterHistory = ({
115
96
  Rollback to this version
116
97
  </ActionButton>
117
98
  )}
118
- </Stack>
99
+ </Flex>
119
100
  </Timeline.Item>
120
101
  ))}
121
102
  </Timeline>
@@ -124,22 +105,28 @@ const ParameterHistory = ({
124
105
  };
125
106
 
126
107
  return (
127
- <Card
128
- withBorder
129
- w={300}
108
+ <Flex
109
+ w={160}
130
110
  h="100%"
131
- style={{ flexShrink: 0, overflow: "hidden" }}
111
+ p="xs"
112
+ style={{
113
+ flexShrink: 0,
114
+ overflow: "hidden",
115
+ display: "flex",
116
+ flexDirection: "column",
117
+ borderLeft: "1px solid var(--mantine-color-default-border)",
118
+ }}
132
119
  >
133
- <Stack gap="xs" h="100%">
134
- <Group gap="xs">
120
+ <Flex direction="column" gap="xs" h="100%" style={{ minHeight: 0 }}>
121
+ <Flex gap="xs">
135
122
  <IconHistory size={16} color="var(--mantine-color-dimmed)" />
136
123
  <Text size="sm" fw={500}>
137
- Version History
124
+ History
138
125
  </Text>
139
- </Group>
126
+ </Flex>
140
127
  {renderContent()}
141
- </Stack>
142
- </Card>
128
+ </Flex>
129
+ </Flex>
143
130
  );
144
131
  };
145
132
 
@@ -1,14 +1,10 @@
1
1
  import { ActionButton, Text } from "@alepha/ui";
2
2
  import {
3
- Box,
4
- Card,
5
- Group,
3
+ Collapse,
4
+ Flex,
6
5
  ScrollArea,
7
- Stack,
8
- Tooltip,
9
- Tree,
10
- type TreeNodeData,
11
- useTree,
6
+ TextInput,
7
+ UnstyledButton,
12
8
  } from "@mantine/core";
13
9
  import {
14
10
  IconChevronDown,
@@ -16,130 +12,308 @@ import {
16
12
  IconFolder,
17
13
  IconFolderOpen,
18
14
  IconRefresh,
15
+ IconSearch,
19
16
  IconSettings,
20
17
  } from "@tabler/icons-react";
21
- import type { ConfigTreeNode } from "alepha/api/parameters";
22
- import { type HTMLAttributes, useMemo } from "react";
18
+ import type { ParameterTreeNode } from "alepha/api/parameters";
19
+ import { memo, useCallback, useMemo, useState } from "react";
23
20
 
24
21
  export interface ParameterTreeProps {
25
- treeData: ConfigTreeNode[];
22
+ treeData: ParameterTreeNode[];
26
23
  selectedConfig: string | null;
27
24
  onSelect: (name: string) => void;
28
25
  onRefresh: () => void;
29
26
  }
30
27
 
31
- const ParameterTree = ({
32
- treeData,
33
- selectedConfig,
34
- onSelect,
35
- onRefresh,
36
- }: ParameterTreeProps) => {
37
- const tree = useTree({
38
- initialExpandedState: {},
39
- });
40
-
41
- const mantineTreeData = useMemo((): TreeNodeData[] => {
42
- const convert = (nodes: ConfigTreeNode[]): TreeNodeData[] => {
43
- return nodes.map((node) => ({
44
- value: node.path,
45
- label: node.name,
46
- children: node.children.length > 0 ? convert(node.children) : undefined,
47
- }));
48
- };
49
- return convert(treeData);
50
- }, [treeData]);
51
-
52
- const renderNode = ({
28
+ /**
29
+ * Filters tree nodes by search query.
30
+ */
31
+ const filterTree = (
32
+ nodes: ParameterTreeNode[],
33
+ query: string,
34
+ ): ParameterTreeNode[] => {
35
+ if (!query.trim()) return nodes;
36
+
37
+ const lowerQuery = query.toLowerCase();
38
+
39
+ return nodes
40
+ .map((node) => {
41
+ const filteredChildren = filterTree(node.children, query);
42
+ const nameMatches = node.name.toLowerCase().includes(lowerQuery);
43
+ const pathMatches = node.path.toLowerCase().includes(lowerQuery);
44
+
45
+ if (nameMatches || pathMatches || filteredChildren.length > 0) {
46
+ return {
47
+ ...node,
48
+ children: filteredChildren,
49
+ };
50
+ }
51
+
52
+ return null;
53
+ })
54
+ .filter((node): node is ParameterTreeNode => node !== null);
55
+ };
56
+
57
+ interface TreeNodeProps {
58
+ node: ParameterTreeNode;
59
+ level: number;
60
+ selectedConfig: string | null;
61
+ onSelect: (name: string) => void;
62
+ expandedNodes: Set<string>;
63
+ onToggle: (path: string) => void;
64
+ }
65
+
66
+ /**
67
+ * Memoized tree node to prevent unnecessary re-renders.
68
+ */
69
+ const TreeNode = memo(
70
+ ({
53
71
  node,
54
- expanded,
55
- hasChildren,
56
- elementProps,
57
- }: {
58
- node: TreeNodeData;
59
- expanded: boolean;
60
- hasChildren: boolean;
61
- elementProps: HTMLAttributes<HTMLDivElement>;
62
- }) => {
72
+ level,
73
+ selectedConfig,
74
+ onSelect,
75
+ expandedNodes,
76
+ onToggle,
77
+ }: TreeNodeProps) => {
78
+ const [isHovered, setIsHovered] = useState(false);
79
+ const hasChildren = node.children.length > 0;
80
+ const isExpanded = expandedNodes.has(node.path);
81
+ const isSelected = selectedConfig === node.path;
63
82
  const isLeaf = !hasChildren;
64
- const isSelected = selectedConfig === node.value;
83
+
84
+ const handleClick = useCallback(() => {
85
+ if (hasChildren) {
86
+ onToggle(node.path);
87
+ } else {
88
+ onSelect(node.path);
89
+ }
90
+ }, [hasChildren, node.path, onToggle, onSelect]);
91
+
92
+ const handleMouseEnter = useCallback(() => setIsHovered(true), []);
93
+ const handleMouseLeave = useCallback(() => setIsHovered(false), []);
65
94
 
66
95
  return (
67
- <Group
68
- gap="xs"
69
- wrap="nowrap"
70
- {...elementProps}
71
- onClick={(e) => {
72
- elementProps.onClick?.(e);
73
- if (isLeaf) {
74
- onSelect(node.value);
75
- }
76
- }}
77
- style={{
78
- ...elementProps.style,
79
- cursor: isLeaf ? "pointer" : "default",
80
- backgroundColor: isSelected
81
- ? "var(--mantine-color-blue-light)"
82
- : undefined,
83
- borderRadius: "var(--mantine-radius-sm)",
84
- paddingTop: 4,
85
- paddingBottom: 4,
86
- paddingRight: 8,
87
- }}
88
- >
89
- {hasChildren ? (
90
- <>
91
- {expanded ? (
92
- <IconChevronDown size={14} color="var(--mantine-color-dimmed)" />
93
- ) : (
94
- <IconChevronRight size={14} color="var(--mantine-color-dimmed)" />
95
- )}
96
- {expanded ? (
97
- <IconFolderOpen size={16} color="var(--mantine-color-blue-6)" />
96
+ <Flex>
97
+ <UnstyledButton
98
+ onClick={handleClick}
99
+ onMouseEnter={handleMouseEnter}
100
+ onMouseLeave={handleMouseLeave}
101
+ w="100%"
102
+ style={{ display: "block" }}
103
+ >
104
+ <Flex
105
+ gap={6}
106
+ wrap="nowrap"
107
+ p="4px 8px"
108
+ pl={8 + level * 16}
109
+ style={{
110
+ borderRadius: "var(--mantine-radius-sm)",
111
+ backgroundColor:
112
+ isSelected || isHovered
113
+ ? "var(--mantine-color-default-hover)"
114
+ : undefined,
115
+ }}
116
+ >
117
+ {hasChildren ? (
118
+ <>
119
+ <Flex
120
+ style={{
121
+ display: "flex",
122
+ alignItems: "center",
123
+ justifyContent: "center",
124
+ width: 16,
125
+ }}
126
+ >
127
+ {isExpanded ? (
128
+ <IconChevronDown
129
+ size={14}
130
+ color="var(--mantine-color-dimmed)"
131
+ />
132
+ ) : (
133
+ <IconChevronRight
134
+ size={14}
135
+ color="var(--mantine-color-dimmed)"
136
+ />
137
+ )}
138
+ </Flex>
139
+ {isExpanded ? (
140
+ <IconFolderOpen
141
+ size={16}
142
+ color="var(--mantine-color-dimmed)"
143
+ style={{ flexShrink: 0 }}
144
+ />
145
+ ) : (
146
+ <IconFolder
147
+ size={16}
148
+ color="var(--mantine-color-dimmed)"
149
+ style={{ flexShrink: 0 }}
150
+ />
151
+ )}
152
+ </>
98
153
  ) : (
99
- <IconFolder size={16} color="var(--mantine-color-blue-6)" />
154
+ <>
155
+ <Flex w={16} />
156
+ <IconSettings
157
+ size={16}
158
+ color={
159
+ isSelected
160
+ ? "var(--mantine-color-blue-6)"
161
+ : "var(--mantine-color-dimmed)"
162
+ }
163
+ style={{ flexShrink: 0 }}
164
+ />
165
+ </>
100
166
  )}
101
- </>
102
- ) : (
103
- <>
104
- <Box w={14} />
105
- <IconSettings size={16} color="var(--mantine-color-gray-6)" />
106
- </>
167
+ <Text
168
+ size="sm"
169
+ fw={isSelected ? 600 : 400}
170
+ c={isSelected ? undefined : isLeaf ? undefined : "dimmed"}
171
+ style={{
172
+ whiteSpace: "nowrap",
173
+ overflow: "hidden",
174
+ textOverflow: "ellipsis",
175
+ }}
176
+ >
177
+ {node.name}
178
+ </Text>
179
+ </Flex>
180
+ </UnstyledButton>
181
+
182
+ {hasChildren && (
183
+ <Collapse in={isExpanded}>
184
+ {node.children.map((child: ParameterTreeNode) => (
185
+ <TreeNode
186
+ key={child.path}
187
+ node={child}
188
+ level={level + 1}
189
+ selectedConfig={selectedConfig}
190
+ onSelect={onSelect}
191
+ expandedNodes={expandedNodes}
192
+ onToggle={onToggle}
193
+ />
194
+ ))}
195
+ </Collapse>
107
196
  )}
108
- <Text size="sm" fw={isSelected ? 500 : 400}>
109
- {node.label}
110
- </Text>
111
- </Group>
197
+ </Flex>
112
198
  );
199
+ },
200
+ );
201
+
202
+ TreeNode.displayName = "TreeNode";
203
+
204
+ /**
205
+ * Collects all folder paths to expand by default.
206
+ */
207
+ const collectAllFolderPaths = (nodes: ParameterTreeNode[]): Set<string> => {
208
+ const paths = new Set<string>();
209
+
210
+ const traverse = (nodeList: ParameterTreeNode[]) => {
211
+ for (const node of nodeList) {
212
+ if (node.children.length > 0) {
213
+ paths.add(node.path);
214
+ traverse(node.children);
215
+ }
216
+ }
113
217
  };
114
218
 
219
+ traverse(nodes);
220
+ return paths;
221
+ };
222
+
223
+ const ParameterTree = ({
224
+ treeData,
225
+ selectedConfig,
226
+ onSelect,
227
+ onRefresh,
228
+ }: ParameterTreeProps) => {
229
+ const [searchQuery, setSearchQuery] = useState("");
230
+ const [expandedNodes, setExpandedNodes] = useState<Set<string>>(() =>
231
+ collectAllFolderPaths(treeData),
232
+ );
233
+
234
+ // Filter tree by search query
235
+ const filteredTreeData = useMemo(
236
+ () => filterTree(treeData, searchQuery),
237
+ [treeData, searchQuery],
238
+ );
239
+
240
+ const handleToggle = useCallback((path: string) => {
241
+ setExpandedNodes((prev) => {
242
+ const next = new Set(prev);
243
+ if (next.has(path)) {
244
+ next.delete(path);
245
+ } else {
246
+ next.add(path);
247
+ }
248
+ return next;
249
+ });
250
+ }, []);
251
+
252
+ const handleSearchChange = useCallback(
253
+ (e: React.ChangeEvent<HTMLInputElement>) => {
254
+ setSearchQuery(e.currentTarget.value);
255
+ },
256
+ [],
257
+ );
258
+
115
259
  return (
116
- <Card withBorder w={280} h="100%" style={{ flexShrink: 0 }}>
117
- <Stack gap="xs" h="100%">
118
- <Group justify="space-between">
119
- <Text size="sm" fw={500}>
260
+ <Flex
261
+ w={280}
262
+ h="100%"
263
+ p="sm"
264
+ style={{
265
+ flexShrink: 0,
266
+ display: "flex",
267
+ flexDirection: "column",
268
+ borderRight: "1px solid var(--mantine-color-default-border)",
269
+ }}
270
+ >
271
+ <Flex direction="column" gap="sm" h="100%" style={{ minHeight: 0 }}>
272
+ <Flex justify="space-between" gap="xs">
273
+ <Text size="sm" fw={600}>
120
274
  Parameters
121
275
  </Text>
122
- <Tooltip label="Refresh">
123
- <ActionButton
124
- variant="subtle"
125
- size="compact-xs"
126
- onClick={onRefresh}
127
- >
128
- <IconRefresh size={14} />
129
- </ActionButton>
130
- </Tooltip>
131
- </Group>
132
- <ScrollArea flex={1} offsetScrollbars>
133
- <Tree
134
- data={mantineTreeData}
135
- tree={tree}
136
- levelOffset={20}
137
- expandOnClick
138
- renderNode={renderNode}
139
- />
276
+ <ActionButton
277
+ variant="subtle"
278
+ size="compact-xs"
279
+ onClick={onRefresh}
280
+ tooltip="Refresh"
281
+ >
282
+ <IconRefresh size={14} />
283
+ </ActionButton>
284
+ </Flex>
285
+
286
+ <TextInput
287
+ placeholder="Search..."
288
+ size="xs"
289
+ leftSection={<IconSearch size={14} />}
290
+ value={searchQuery}
291
+ onChange={handleSearchChange}
292
+ />
293
+
294
+ <ScrollArea flex={1} offsetScrollbars style={{ minHeight: 0 }}>
295
+ {filteredTreeData.length === 0 ? (
296
+ <Text size="xs" c="dimmed" ta="center" py="md">
297
+ {searchQuery ? "No matching parameters" : "No parameters"}
298
+ </Text>
299
+ ) : (
300
+ <Flex direction="column" gap={0} style={{ gap: 1 }}>
301
+ {filteredTreeData.map((node) => (
302
+ <TreeNode
303
+ key={node.path}
304
+ node={node}
305
+ level={0}
306
+ selectedConfig={selectedConfig}
307
+ onSelect={onSelect}
308
+ expandedNodes={expandedNodes}
309
+ onToggle={handleToggle}
310
+ />
311
+ ))}
312
+ </Flex>
313
+ )}
140
314
  </ScrollArea>
141
- </Stack>
142
- </Card>
315
+ </Flex>
316
+ </Flex>
143
317
  );
144
318
  };
145
319
 
@@ -1,10 +1,10 @@
1
1
  import type { Parameter } from "alepha/api/parameters";
2
2
 
3
- export interface ConfigValue {
3
+ export interface ParameterValue {
4
4
  current?: Parameter;
5
5
  next?: Parameter;
6
6
  /**
7
- * Default value from the registered $config primitive.
7
+ * Default value from the registered $parameter primitive.
8
8
  */
9
9
  defaultValue?: unknown;
10
10
  /**
@@ -12,7 +12,7 @@ export interface ConfigValue {
12
12
  */
13
13
  currentValue?: unknown;
14
14
  /**
15
- * TypeBox/JSON schema for the configuration (as JSON from API).
15
+ * TypeBox/JSON schema for the parameter (as JSON from API).
16
16
  */
17
17
  schema?: Record<string, unknown>;
18
18
  }
@@ -1,5 +1,5 @@
1
1
  import { ActionButton, DataTable, Flex, Text } from "@alepha/ui";
2
- import { Badge, Group } from "@mantine/core";
2
+ import { Badge } from "@mantine/core";
3
3
  import {
4
4
  IconDeviceDesktop,
5
5
  IconDeviceMobile,
@@ -120,7 +120,7 @@ const AdminSessions = (props: AdminSessionsProps) => {
120
120
  label: "Device",
121
121
  fit: true,
122
122
  value: (item) => (
123
- <Group gap={4}>
123
+ <Flex gap={4}>
124
124
  {item.userAgent ? (
125
125
  <>
126
126
  <Badge
@@ -139,7 +139,7 @@ const AdminSessions = (props: AdminSessionsProps) => {
139
139
  -
140
140
  </Text>
141
141
  )}
142
- </Group>
142
+ </Flex>
143
143
  ),
144
144
  },
145
145
  ip: {