@alepha/ui 0.16.2 → 0.17.1

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 (175) hide show
  1. package/dist/admin/{AdminApiKeys-CoTOTfgU.js → AdminApiKeys-CF_qOO3u.js} +20 -20
  2. package/dist/admin/AdminApiKeys-CF_qOO3u.js.map +1 -0
  3. package/dist/admin/{AdminAudits-BmsxFbDa.js → AdminAudits-BQno3hZG.js} +7 -8
  4. package/dist/admin/AdminAudits-BQno3hZG.js.map +1 -0
  5. package/dist/admin/{AdminFiles-BBB8knca.js → AdminFiles-kvuUaASF.js} +3 -5
  6. package/dist/admin/{AdminFiles-BBB8knca.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-CsjvpeD1.js → AdminLayout-e-ZP5nWw.js} +1 -1
  14. package/dist/admin/{AdminLayout-CsjvpeD1.js.map → AdminLayout-e-ZP5nWw.js.map} +1 -1
  15. package/dist/admin/{AdminNotifications-LwR6RKrx.js → AdminNotifications-DeHJFf6W.js} +3 -5
  16. package/dist/admin/{AdminNotifications-LwR6RKrx.js.map → AdminNotifications-DeHJFf6W.js.map} +1 -1
  17. package/dist/admin/{AdminParameters-B_83Vie9.js → AdminParameters-iQE8o7a7.js} +43 -36
  18. package/dist/admin/AdminParameters-iQE8o7a7.js.map +1 -0
  19. package/dist/admin/{AdminSessions-CWnPosdd.js → AdminSessions-oKJCbd7w.js} +5 -7
  20. package/dist/admin/AdminSessions-oKJCbd7w.js.map +1 -0
  21. package/dist/admin/{AdminUserAudits-nHv636E_.js → AdminUserAudits-BNCEle_E.js} +6 -8
  22. package/dist/admin/AdminUserAudits-BNCEle_E.js.map +1 -0
  23. package/dist/admin/{AdminUserCreate-CjYD3Kjc.js → AdminUserCreate-CgqeFwCt.js} +6 -7
  24. package/dist/admin/AdminUserCreate-CgqeFwCt.js.map +1 -0
  25. package/dist/admin/{AdminUserDetails-Ccq-LsZ0.js → AdminUserDetails-DDe1A1GP.js} +30 -29
  26. package/dist/admin/AdminUserDetails-DDe1A1GP.js.map +1 -0
  27. package/dist/admin/{AdminUserLayout-7s41DiF_.js → AdminUserLayout-HAlobhWf.js} +18 -16
  28. package/dist/admin/AdminUserLayout-HAlobhWf.js.map +1 -0
  29. package/dist/admin/{AdminUserSessions-Ds3ODq_d.js → AdminUserSessions-Bq1LnVLf.js} +5 -7
  30. package/dist/admin/AdminUserSessions-Bq1LnVLf.js.map +1 -0
  31. package/dist/admin/{AdminUserSettings-CGh4gROo.js → AdminUserSettings-BRsBZoxV.js} +10 -10
  32. package/dist/admin/AdminUserSettings-BRsBZoxV.js.map +1 -0
  33. package/dist/admin/{AdminUsers-CvPiBzQK.js → AdminUsers-D71kIOSn.js} +6 -8
  34. package/dist/admin/AdminUsers-D71kIOSn.js.map +1 -0
  35. package/dist/admin/index.d.ts +7 -83
  36. package/dist/admin/index.d.ts.map +1 -1
  37. package/dist/admin/index.js +49 -70
  38. package/dist/admin/index.js.map +1 -1
  39. package/dist/auth/{Login-DS_OqA0G.js → Login-BS_FYTy0.js} +13 -8
  40. package/dist/auth/Login-BS_FYTy0.js.map +1 -0
  41. package/dist/auth/{Profile-Di7N7HZL.js → Profile-CjDsW378.js} +16 -10
  42. package/dist/auth/Profile-CjDsW378.js.map +1 -0
  43. package/dist/auth/{Register-BRR2_gux.js → Register-C5eqzAaD.js} +21 -12
  44. package/dist/auth/Register-C5eqzAaD.js.map +1 -0
  45. package/dist/auth/{ResetPassword-oQu72lod.js → ResetPassword-XifinVao.js} +14 -8
  46. package/dist/auth/ResetPassword-XifinVao.js.map +1 -0
  47. package/dist/auth/{VerifyEmail-DC6HPZjd.js → VerifyEmail-DTgbeJOO.js} +6 -4
  48. package/dist/auth/VerifyEmail-DTgbeJOO.js.map +1 -0
  49. package/dist/auth/index.d.ts +4 -0
  50. package/dist/auth/index.d.ts.map +1 -1
  51. package/dist/auth/index.js +15 -14
  52. package/dist/auth/index.js.map +1 -1
  53. package/dist/core/index.d.ts +37 -26
  54. package/dist/core/index.d.ts.map +1 -1
  55. package/dist/core/index.js +444 -193
  56. package/dist/core/index.js.map +1 -1
  57. package/dist/demo/DemoDataTable-lnBKWBf8.js +362 -0
  58. package/dist/demo/DemoDataTable-lnBKWBf8.js.map +1 -0
  59. package/dist/demo/{DemoHome-DpRrPlBC.js → DemoHome-CUMZsYaH.js} +6 -7
  60. package/dist/demo/DemoHome-CUMZsYaH.js.map +1 -0
  61. package/dist/demo/{DemoJsonViewer-zeucGKHV.js → DemoJsonViewer-_uokbGaW.js} +17 -19
  62. package/dist/demo/DemoJsonViewer-_uokbGaW.js.map +1 -0
  63. package/dist/demo/{DemoLayout-PhgbAAiQ.js → DemoLayout-DHVoacE6.js} +2 -4
  64. package/dist/demo/{DemoLayout-PhgbAAiQ.js.map → DemoLayout-DHVoacE6.js.map} +1 -1
  65. package/dist/demo/{DemoLogin-DSzP0Lkv.js → DemoLogin-DjJ9314c.js} +22 -17
  66. package/dist/demo/DemoLogin-DjJ9314c.js.map +1 -0
  67. package/dist/demo/{DemoRegister-DavFBsCz.js → DemoRegister-DzkJ5M83.js} +34 -25
  68. package/dist/demo/DemoRegister-DzkJ5M83.js.map +1 -0
  69. package/dist/demo/{DemoResetPassword-BS2rIAQK.js → DemoResetPassword-DWh4_BpQ.js} +27 -21
  70. package/dist/demo/DemoResetPassword-DWh4_BpQ.js.map +1 -0
  71. package/dist/demo/{DemoSidebar-zNkUmHRl.js → DemoSidebar-C1csnGhX.js} +2 -2
  72. package/dist/demo/{DemoSidebar-zNkUmHRl.js.map → DemoSidebar-C1csnGhX.js.map} +1 -1
  73. package/dist/demo/{DemoTypeForm-B9q7oT0b.js → DemoTypeForm-CWz6fJrJ.js} +2 -2
  74. package/dist/demo/{DemoTypeForm-B9q7oT0b.js.map → DemoTypeForm-CWz6fJrJ.js.map} +1 -1
  75. package/dist/demo/{DemoVerifyEmail-Bi4SdWz0.js → DemoVerifyEmail-DbU_tCj8.js} +13 -11
  76. package/dist/demo/DemoVerifyEmail-DbU_tCj8.js.map +1 -0
  77. package/dist/demo/{IconGoogle-CTeZyrek.js → IconGoogle-Ch1m3Uzl.js} +1 -1
  78. package/dist/demo/{IconGoogle-CTeZyrek.js.map → IconGoogle-Ch1m3Uzl.js.map} +1 -1
  79. package/dist/demo/{Showcase-C9btr_SJ.js → Showcase-BzoXNlCn.js} +10 -10
  80. package/dist/demo/Showcase-BzoXNlCn.js.map +1 -0
  81. package/dist/demo/index.d.ts +1 -68
  82. package/dist/demo/index.d.ts.map +1 -1
  83. package/dist/demo/index.js +11 -15
  84. package/dist/demo/index.js.map +1 -1
  85. package/dist/json/index.js +2 -2
  86. package/dist/json/index.js.map +1 -1
  87. package/package.json +9 -5
  88. package/src/admin/AdminRouter.ts +36 -5
  89. package/src/admin/components/audits/AdminAudits.tsx +5 -5
  90. package/src/admin/components/jobs/AdminJobDashboard.tsx +455 -0
  91. package/src/admin/components/jobs/AdminJobExecutions.tsx +693 -0
  92. package/src/admin/components/jobs/AdminJobRegistry.tsx +325 -0
  93. package/src/admin/components/keys/AdminApiKeys.tsx +28 -31
  94. package/src/admin/components/parameters/AdminParameters.tsx +3 -3
  95. package/src/admin/components/parameters/ParameterDetails.tsx +34 -29
  96. package/src/admin/components/parameters/ParameterEmptyState.tsx +5 -5
  97. package/src/admin/components/parameters/ParameterHistory.tsx +11 -19
  98. package/src/admin/components/parameters/ParameterTree.tsx +16 -18
  99. package/src/admin/components/sessions/AdminSessions.tsx +3 -3
  100. package/src/admin/components/shared/AdminResourceHeader.tsx +20 -16
  101. package/src/admin/components/users/AdminUserAudits.tsx +5 -5
  102. package/src/admin/components/users/AdminUserCreate.tsx +3 -3
  103. package/src/admin/components/users/AdminUserDetails.tsx +51 -53
  104. package/src/admin/components/users/AdminUserLayout.tsx +7 -7
  105. package/src/admin/components/users/AdminUserSessions.tsx +3 -3
  106. package/src/admin/components/users/AdminUserSettings.tsx +9 -9
  107. package/src/admin/components/users/AdminUsers.tsx +5 -5
  108. package/src/admin/components/verifications/AdminVerifications.tsx +3 -3
  109. package/src/admin/index.ts +0 -24
  110. package/src/auth/components/Login.tsx +13 -13
  111. package/src/auth/components/Profile.tsx +17 -26
  112. package/src/auth/components/Register.tsx +21 -31
  113. package/src/auth/components/ResetPassword.tsx +13 -22
  114. package/src/auth/components/VerifyEmail.tsx +5 -5
  115. package/src/auth/components/buttons/UserButton.tsx +14 -4
  116. package/src/core/components/buttons/ActionButton.tsx +9 -2
  117. package/src/core/components/data/ErrorViewer.tsx +15 -15
  118. package/src/core/components/dialogs/AlertDialog.tsx +3 -3
  119. package/src/core/components/dialogs/ConfirmDialog.tsx +3 -3
  120. package/src/core/components/dialogs/PromptDialog.tsx +3 -3
  121. package/src/core/components/form/Control.tsx +9 -0
  122. package/src/core/components/form/ControlArray.tsx +6 -7
  123. package/src/core/components/form/ControlObject.tsx +3 -3
  124. package/src/core/components/form/ControlQueryBuilder.tsx +20 -22
  125. package/src/core/components/form/ControlSelect.tsx +4 -0
  126. package/src/core/components/form/TypeForm.tsx +7 -0
  127. package/src/core/components/layout/Breadcrumb.tsx +6 -6
  128. package/src/core/components/layout/Omnibar.tsx +2 -1
  129. package/src/core/components/layout/Sidebar.tsx +5 -1
  130. package/src/core/components/table/ColumnPicker.tsx +47 -31
  131. package/src/core/components/table/DataTable.tsx +277 -201
  132. package/src/core/components/table/DataTableFilters.tsx +8 -0
  133. package/src/core/components/table/DataTableToolbar.tsx +98 -5
  134. package/src/core/components/table/FilterPicker.tsx +28 -26
  135. package/src/core/components/table/types.ts +52 -37
  136. package/src/core/components/table/useTableSelection.ts +83 -0
  137. package/src/core/styles.css +1 -0
  138. package/src/core/utils/parseInput.ts +1 -0
  139. package/src/demo/components/DemoHome.tsx +5 -5
  140. package/src/demo/components/core/DemoDataTable.tsx +209 -5
  141. package/src/demo/components/json/DemoJsonViewer.tsx +1 -1
  142. package/src/demo/components/shared/MacWindow.tsx +7 -7
  143. package/src/demo/components/shared/Showcase.tsx +3 -3
  144. package/src/demo/index.ts +0 -11
  145. package/src/json/components/JsonViewer.tsx +3 -3
  146. package/dist/admin/AdminApiKeys-CoTOTfgU.js.map +0 -1
  147. package/dist/admin/AdminAudits-BmsxFbDa.js.map +0 -1
  148. package/dist/admin/AdminJobs-C604joTz.js +0 -698
  149. package/dist/admin/AdminJobs-C604joTz.js.map +0 -1
  150. package/dist/admin/AdminParameters-B_83Vie9.js.map +0 -1
  151. package/dist/admin/AdminSessions-CWnPosdd.js.map +0 -1
  152. package/dist/admin/AdminUserAudits-nHv636E_.js.map +0 -1
  153. package/dist/admin/AdminUserCreate-CjYD3Kjc.js.map +0 -1
  154. package/dist/admin/AdminUserDetails-Ccq-LsZ0.js.map +0 -1
  155. package/dist/admin/AdminUserLayout-7s41DiF_.js.map +0 -1
  156. package/dist/admin/AdminUserSessions-Ds3ODq_d.js.map +0 -1
  157. package/dist/admin/AdminUserSettings-CGh4gROo.js.map +0 -1
  158. package/dist/admin/AdminUsers-CvPiBzQK.js.map +0 -1
  159. package/dist/admin/rolldown-runtime-CjeV3_4I.js +0 -18
  160. package/dist/auth/Login-DS_OqA0G.js.map +0 -1
  161. package/dist/auth/Profile-Di7N7HZL.js.map +0 -1
  162. package/dist/auth/Register-BRR2_gux.js.map +0 -1
  163. package/dist/auth/ResetPassword-oQu72lod.js.map +0 -1
  164. package/dist/auth/VerifyEmail-DC6HPZjd.js.map +0 -1
  165. package/dist/demo/DemoDataTable-DCsJq8v5.js +0 -149
  166. package/dist/demo/DemoDataTable-DCsJq8v5.js.map +0 -1
  167. package/dist/demo/DemoHome-DpRrPlBC.js.map +0 -1
  168. package/dist/demo/DemoJsonViewer-zeucGKHV.js.map +0 -1
  169. package/dist/demo/DemoLogin-DSzP0Lkv.js.map +0 -1
  170. package/dist/demo/DemoRegister-DavFBsCz.js.map +0 -1
  171. package/dist/demo/DemoResetPassword-BS2rIAQK.js.map +0 -1
  172. package/dist/demo/DemoVerifyEmail-Bi4SdWz0.js.map +0 -1
  173. package/dist/demo/Showcase-C9btr_SJ.js.map +0 -1
  174. package/dist/demo/rolldown-runtime-CjeV3_4I.js +0 -18
  175. package/src/admin/components/jobs/AdminJobs.tsx +0 -772
@@ -1,7 +1,12 @@
1
1
  import { Badge, Divider, Flex } from "@mantine/core";
2
- import { IconRefresh, IconX } from "@tabler/icons-react";
2
+ import {
3
+ IconClipboard,
4
+ IconDownload,
5
+ IconRefresh,
6
+ IconX,
7
+ } from "@tabler/icons-react";
3
8
  import type { TObject } from "alepha";
4
- import { isValidElement, type ReactNode } from "react";
9
+ import { isValidElement, type ReactNode, useCallback } from "react";
5
10
  import { isComponentType } from "../../helpers/isComponentType.ts";
6
11
  import ActionButton, { type ActionProps } from "../buttons/ActionButton.tsx";
7
12
  import ColumnPicker from "./ColumnPicker.tsx";
@@ -14,6 +19,27 @@ import type {
14
19
  FilterVisibility,
15
20
  } from "./types.ts";
16
21
 
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+
24
+ const escapeCsvField = (value: string): string => {
25
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
26
+ return `"${value.replace(/"/g, '""')}"`;
27
+ }
28
+ return value;
29
+ };
30
+
31
+ const extractText = (node: ReactNode): string => {
32
+ if (node == null || typeof node === "boolean") return "";
33
+ if (typeof node === "string" || typeof node === "number") return String(node);
34
+ if (Array.isArray(node)) return node.map(extractText).join("");
35
+ if (typeof node === "object" && "props" in node) {
36
+ return extractText((node as any).props.children);
37
+ }
38
+ return "";
39
+ };
40
+
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+
17
43
  export interface DataTableToolbarProps<
18
44
  T extends object,
19
45
  Filters extends TObject,
@@ -26,6 +52,8 @@ export interface DataTableToolbarProps<
26
52
  onFilterVisibilityChange: (visibility: FilterVisibility) => void;
27
53
  actions?: Array<ActionProps & { label?: ReactNode }>;
28
54
  onRefresh?: () => void;
55
+ items: T[];
56
+ withExport?: boolean;
29
57
  // Checkbox-related props
30
58
  selectedItems?: T[];
31
59
  checkboxActions?: Array<CheckboxAction<T>>;
@@ -41,12 +69,59 @@ const DataTableToolbar = <T extends object, Filters extends TObject>({
41
69
  onFilterVisibilityChange,
42
70
  actions,
43
71
  onRefresh,
72
+ items,
73
+ withExport,
44
74
  selectedItems = [],
45
75
  checkboxActions,
46
76
  onClearSelection,
47
77
  }: DataTableToolbarProps<T, Filters>) => {
48
78
  const hasSelection = selectedItems.length > 0;
49
79
 
80
+ const exportableColumns = useCallback(() => {
81
+ return Object.entries(columns).filter(
82
+ ([key, col]) => !col.actions && columnVisibility[key] !== false,
83
+ );
84
+ }, [columns, columnVisibility]);
85
+
86
+ const buildRows = useCallback((): string[][] => {
87
+ const cols = exportableColumns();
88
+ return items.map((item) =>
89
+ cols.map(([_key, col]) => {
90
+ if (!col.value) return "";
91
+ const node = col.value(item, {} as any);
92
+ return extractText(node);
93
+ }),
94
+ );
95
+ }, [items, exportableColumns]);
96
+
97
+ const buildCsv = useCallback((): string => {
98
+ const cols = exportableColumns();
99
+ const header = cols.map(([_key, col]) => escapeCsvField(col.label));
100
+ const rows = buildRows().map((row) => row.map(escapeCsvField));
101
+ return [header.join(","), ...rows.map((r) => r.join(","))].join("\n");
102
+ }, [exportableColumns, buildRows]);
103
+
104
+ const exportCsv = useCallback(() => {
105
+ const csv = buildCsv();
106
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
107
+ const url = URL.createObjectURL(blob);
108
+ const a = document.createElement("a");
109
+ a.href = url;
110
+ a.download = "export.csv";
111
+ a.click();
112
+ URL.revokeObjectURL(url);
113
+ }, [buildCsv]);
114
+
115
+ const exportClipboard = useCallback(async () => {
116
+ const cols = exportableColumns();
117
+ const header = cols.map(([_key, col]) => col.label);
118
+ const rows = buildRows();
119
+ const text = [header.join("\t"), ...rows.map((r) => r.join("\t"))].join(
120
+ "\n",
121
+ );
122
+ await navigator.clipboard.writeText(text);
123
+ }, [exportableColumns, buildRows]);
124
+
50
125
  const handleCheckboxAction = async (action: CheckboxAction<T>) => {
51
126
  const ctx: CheckboxActionContext<T> = {
52
127
  selectedItems,
@@ -70,6 +145,26 @@ const DataTableToolbar = <T extends object, Filters extends TObject>({
70
145
  visibility={columnVisibility}
71
146
  onVisibilityChange={onColumnVisibilityChange}
72
147
  />
148
+ {withExport && (
149
+ <ActionButton
150
+ variant="subtle"
151
+ icon={IconDownload}
152
+ menu={{
153
+ items: [
154
+ {
155
+ label: "Export as CSV",
156
+ icon: <IconDownload size={14} />,
157
+ onClick: exportCsv,
158
+ },
159
+ {
160
+ label: "Copy to clipboard",
161
+ icon: <IconClipboard size={14} />,
162
+ onClick: exportClipboard,
163
+ },
164
+ ],
165
+ }}
166
+ />
167
+ )}
73
168
 
74
169
  {hasSelection && (
75
170
  <>
@@ -115,9 +210,7 @@ const DataTableToolbar = <T extends object, Filters extends TObject>({
115
210
  props
116
211
  ),
117
212
  )}
118
- <ActionButton icon={IconRefresh} onClick={onRefresh}>
119
- Refresh
120
- </ActionButton>
213
+ <ActionButton variant="subtle" icon={IconRefresh} onClick={onRefresh} />
121
214
  </Flex>
122
215
  </Flex>
123
216
  );
@@ -1,12 +1,4 @@
1
- import {
2
- Button,
3
- Checkbox,
4
- Group,
5
- Popover,
6
- ScrollArea,
7
- Stack,
8
- Text,
9
- } from "@mantine/core";
1
+ import { Checkbox, Flex, Popover, ScrollArea, Text } from "@mantine/core";
10
2
  import { IconFilter } from "@tabler/icons-react";
11
3
  import type { TObject } from "alepha";
12
4
  import { useState } from "react";
@@ -63,9 +55,7 @@ const FilterPicker = ({
63
55
  });
64
56
  };
65
57
 
66
- const visibleCount = filterKeys.filter(
67
- (key) => visibility[key] !== false,
68
- ).length;
58
+ const visibleCount = filterKeys.filter((key) => visibility[key]).length;
69
59
 
70
60
  return (
71
61
  <Popover
@@ -83,7 +73,13 @@ const FilterPicker = ({
83
73
  }}
84
74
  >
85
75
  <Popover.Target>
86
- <ActionButton variant="subtle" icon={IconFilter} />
76
+ <div>
77
+ <ActionButton
78
+ variant="subtle"
79
+ icon={IconFilter}
80
+ onClick={() => setOpened((o) => !o)}
81
+ />
82
+ </div>
87
83
  </Popover.Target>
88
84
  <Popover.Dropdown
89
85
  bg="transparent"
@@ -93,43 +89,49 @@ const FilterPicker = ({
93
89
  backdropFilter: "blur(20px)",
94
90
  }}
95
91
  >
96
- <Stack gap="xs" bg={ui.colors.surface} p="sm" bdrs="sm">
97
- <Group justify="space-between">
92
+ <Flex
93
+ direction="column"
94
+ gap="xs"
95
+ bg={ui.colors.surface}
96
+ p="sm"
97
+ bdrs="sm"
98
+ >
99
+ <Flex justify="space-between">
98
100
  <Text size="sm" fw={500}>
99
101
  Filters ({visibleCount}/{filterKeys.length})
100
102
  </Text>
101
- <Group gap={4}>
102
- <Button
103
+ <Flex gap={4}>
104
+ <ActionButton
103
105
  size="compact-xs"
104
106
  variant="subtle"
105
107
  onClick={handleShowAll}
106
108
  >
107
109
  All
108
- </Button>
109
- <Button
110
+ </ActionButton>
111
+ <ActionButton
110
112
  size="compact-xs"
111
113
  variant="subtle"
112
114
  onClick={handleHideAll}
113
115
  >
114
116
  None
115
- </Button>
116
- </Group>
117
- </Group>
117
+ </ActionButton>
118
+ </Flex>
119
+ </Flex>
118
120
 
119
121
  <ScrollArea.Autosize mah={300}>
120
- <Stack gap={4}>
122
+ <Flex direction="column" gap={4}>
121
123
  {filterKeys.map((key) => (
122
124
  <Checkbox
123
125
  key={key}
124
126
  label={getFieldLabel(schema, key)}
125
- checked={visibility[key] !== false}
127
+ checked={visibility[key] === true}
126
128
  onChange={(e) => handleToggle(key, e.currentTarget.checked)}
127
129
  size="sm"
128
130
  />
129
131
  ))}
130
- </Stack>
132
+ </Flex>
131
133
  </ScrollArea.Autosize>
132
- </Stack>
134
+ </Flex>
133
135
  </Popover.Dropdown>
134
136
  </Popover>
135
137
  );
@@ -1,4 +1,4 @@
1
- import type { TableProps, TableTrProps } from "@mantine/core";
1
+ import type { DrawerProps, TableProps, TableTrProps } from "@mantine/core";
2
2
  import type {
3
3
  Alepha,
4
4
  Async,
@@ -13,6 +13,20 @@ import type { ReactNode } from "react";
13
13
  import type { ActionProps } from "../buttons/ActionButton.tsx";
14
14
  import type { TypeFormProps } from "../form/TypeForm.tsx";
15
15
 
16
+ // -----------------------------------------------------------------------------
17
+ // Constants
18
+ // -----------------------------------------------------------------------------
19
+
20
+ export const DEFAULT_MAX_VISIBLE_COLUMNS = 8;
21
+
22
+ // -----------------------------------------------------------------------------
23
+ // Row Action Types
24
+ // -----------------------------------------------------------------------------
25
+
26
+ export type DataTableRowAction = ActionProps & {
27
+ visible?: boolean;
28
+ };
29
+
16
30
  // -----------------------------------------------------------------------------
17
31
  // Visibility Types
18
32
  // -----------------------------------------------------------------------------
@@ -37,7 +51,7 @@ export interface DataTableColumnContext<Filters extends TObject> {
37
51
 
38
52
  export interface DataTableColumn<T extends object, Filters extends TObject> {
39
53
  label: string;
40
- value: (item: T, ctx: DataTableColumnContext<Filters>) => ReactNode;
54
+ value?: (item: T, ctx: DataTableColumnContext<Filters>) => ReactNode;
41
55
  fit?: boolean;
42
56
  /**
43
57
  * Enable sorting for this column. When true, clicking the header will sort by this column.
@@ -48,6 +62,19 @@ export interface DataTableColumn<T extends object, Filters extends TObject> {
48
62
  * Follows Alepha sort convention: 'field' for ASC, '-field' for DESC.
49
63
  */
50
64
  sortKey?: string;
65
+ /**
66
+ * Row-level actions rendered as ActionButtons.
67
+ * Defaults: variant="subtle", size="xs", preventDefault=true.
68
+ * Use `visible` on each action to conditionally show/hide.
69
+ */
70
+ actions?: (
71
+ item: T,
72
+ ctx: DataTableColumnContext<Filters>,
73
+ ) => DataTableRowAction[];
74
+ /**
75
+ * Hide this column by default. Users can show it via the column picker.
76
+ */
77
+ defaultHidden?: boolean;
51
78
  }
52
79
 
53
80
  // -----------------------------------------------------------------------------
@@ -119,13 +146,28 @@ export interface DataTableProps<T extends object, Filters extends TObject> {
119
146
  */
120
147
  filters?: TObject;
121
148
 
122
- panel?: (item: T) => ReactNode;
123
- canPanel?: (item: T) => boolean;
149
+ panel?:
150
+ | ((item: T) => ReactNode)
151
+ | {
152
+ render: (item: T) => ReactNode;
153
+ can?: (item: T) => boolean;
154
+ };
155
+
156
+ drawer?:
157
+ | ((item: T) => ReactNode)
158
+ | {
159
+ render: (item: T) => ReactNode;
160
+ can?: (item: T) => boolean;
161
+ props?: Omit<DrawerProps, "opened" | "onClose" | "children">;
162
+ };
124
163
 
125
164
  submitOnInit?: boolean;
126
165
  submitEvery?: DurationLike;
127
166
 
128
- withLineNumbers?: boolean;
167
+ /**
168
+ * Enable export button in toolbar (CSV download + clipboard copy).
169
+ */
170
+ withExport?: boolean;
129
171
 
130
172
  /**
131
173
  * Enable row selection with checkboxes. When true, a checkbox column is added as the first column.
@@ -133,8 +175,9 @@ export interface DataTableProps<T extends object, Filters extends TObject> {
133
175
  withCheckbox?: boolean;
134
176
 
135
177
  /**
136
- * Function to get a unique key for each item. Required when withCheckbox is true.
137
- * Used to track selected items across pagination.
178
+ * Function to get a unique key for each item.
179
+ * Used to track selected items, panel expansion, and drawer identity.
180
+ * Falls back to item.id then JSON.stringify if not provided.
138
181
  */
139
182
  getItemKey?: (item: T) => string;
140
183
 
@@ -150,38 +193,10 @@ export interface DataTableProps<T extends object, Filters extends TObject> {
150
193
  */
151
194
  infinityScroll?: boolean;
152
195
 
153
- // -------------------------------------------------------------------------------------------------------------------
154
- // Column Visibility
155
- // -------------------------------------------------------------------------------------------------------------------
156
-
157
- /**
158
- * Initial column visibility state. By default, first 10 columns are visible.
159
- */
160
- defaultColumnVisibility?: ColumnVisibility;
161
-
162
- /**
163
- * Maximum number of columns to show by default. Default is 10.
164
- */
165
- defaultVisibleColumnCount?: number;
166
-
167
- /**
168
- * Callback when column visibility changes.
169
- */
170
- onColumnVisibilityChange?: (visibility: ColumnVisibility) => void;
171
-
172
- // -------------------------------------------------------------------------------------------------------------------
173
- // Filter Visibility
174
- // -------------------------------------------------------------------------------------------------------------------
175
-
176
- /**
177
- * Initial filter visibility state. By default, all filters are visible.
178
- */
179
- defaultFilterVisibility?: FilterVisibility;
180
-
181
196
  /**
182
- * Callback when filter visibility changes.
197
+ * Filter keys to show by default. Default: none (empty array).
183
198
  */
184
- onFilterVisibilityChange?: (visibility: FilterVisibility) => void;
199
+ defaultFilters?: string[];
185
200
 
186
201
  // -------------------------------------------------------------------------------------------------------------------
187
202
  // Mantine Props
@@ -0,0 +1,83 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+
3
+ export interface UseTableSelectionReturn<T> {
4
+ selectedItems: T[];
5
+ allSelected: boolean;
6
+ someSelected: boolean;
7
+ toggleItem: (item: T) => void;
8
+ toggleAll: () => void;
9
+ clear: () => void;
10
+ isSelected: (item: T) => boolean;
11
+ }
12
+
13
+ export const useTableSelection = <T>(
14
+ items: T[],
15
+ getItemKey: (item: T) => string,
16
+ enabled: boolean,
17
+ ): UseTableSelectionReturn<T> => {
18
+ const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
19
+
20
+ const selectedItems = useMemo(() => {
21
+ if (!enabled) return [];
22
+ return items.filter((item) => selectedKeys.has(getItemKey(item)));
23
+ }, [items, selectedKeys, getItemKey, enabled]);
24
+
25
+ const allSelected = useMemo(() => {
26
+ if (items.length === 0) return false;
27
+ return items.every((item) => selectedKeys.has(getItemKey(item)));
28
+ }, [items, selectedKeys, getItemKey]);
29
+
30
+ const someSelected = useMemo(() => {
31
+ if (items.length === 0) return false;
32
+ const count = items.filter((item) =>
33
+ selectedKeys.has(getItemKey(item)),
34
+ ).length;
35
+ return count > 0 && count < items.length;
36
+ }, [items, selectedKeys, getItemKey]);
37
+
38
+ const toggleItem = useCallback(
39
+ (item: T) => {
40
+ const key = getItemKey(item);
41
+ setSelectedKeys((prev) => {
42
+ const next = new Set(prev);
43
+ if (next.has(key)) next.delete(key);
44
+ else next.add(key);
45
+ return next;
46
+ });
47
+ },
48
+ [getItemKey],
49
+ );
50
+
51
+ const toggleAll = useCallback(() => {
52
+ if (allSelected) {
53
+ setSelectedKeys((prev) => {
54
+ const next = new Set(prev);
55
+ for (const item of items) next.delete(getItemKey(item));
56
+ return next;
57
+ });
58
+ } else {
59
+ setSelectedKeys((prev) => {
60
+ const next = new Set(prev);
61
+ for (const item of items) next.add(getItemKey(item));
62
+ return next;
63
+ });
64
+ }
65
+ }, [allSelected, items, getItemKey]);
66
+
67
+ const clear = useCallback(() => setSelectedKeys(new Set()), []);
68
+
69
+ const isSelected = useCallback(
70
+ (item: T) => selectedKeys.has(getItemKey(item)),
71
+ [selectedKeys, getItemKey],
72
+ );
73
+
74
+ return {
75
+ selectedItems,
76
+ allSelected,
77
+ someSelected,
78
+ toggleItem,
79
+ toggleAll,
80
+ clear,
81
+ isSelected,
82
+ };
83
+ };
@@ -3,6 +3,7 @@
3
3
  @import "@mantine/spotlight/styles.css";
4
4
  @import "@mantine/dates/styles.css";
5
5
  @import "@mantine/notifications/styles.css";
6
+ @import "@mantine/charts/styles.css";
6
7
  @import "./components/buttons/DarkModeButton.css";
7
8
 
8
9
  :root {
@@ -109,6 +109,7 @@ export interface GenericControlProps {
109
109
  title?: string;
110
110
  description?: string;
111
111
  icon?: ReactElement | ((props: { size: number }) => ReactNode);
112
+ size?: "xs" | "sm" | "md" | "lg" | "xl";
112
113
  }
113
114
 
114
115
  export interface ControlInput {
@@ -1,4 +1,4 @@
1
- import { Box, Stack, Text, Title } from "@mantine/core";
1
+ import { Flex, Text, Title } from "@mantine/core";
2
2
  import { IconBraces } from "@tabler/icons-react";
3
3
 
4
4
  const components = [
@@ -13,16 +13,16 @@ const components = [
13
13
 
14
14
  const DemoHome = () => {
15
15
  return (
16
- <Stack gap="xl" p="xl">
17
- <Box>
16
+ <Flex direction="column" gap="xl" p="xl">
17
+ <Flex>
18
18
  <Title order={1} mb="xs">
19
19
  Component Showcase
20
20
  </Title>
21
21
  <Text c="dimmed" size="lg">
22
22
  Interactive demos and documentation for @alepha/ui components.
23
23
  </Text>
24
- </Box>
25
- </Stack>
24
+ </Flex>
25
+ </Flex>
26
26
  );
27
27
  };
28
28