@alepha/ui 0.17.2 → 0.18.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.
- package/dist/admin/{AdminApiKeys-CF_qOO3u.js → AdminApiKeys-C-6_Q-lH.js} +56 -192
- package/dist/admin/AdminApiKeys-C-6_Q-lH.js.map +1 -0
- package/dist/admin/{AdminAudits-BQno3hZG.js → AdminAudits-Bgbf04hO.js} +25 -61
- package/dist/admin/AdminAudits-Bgbf04hO.js.map +1 -0
- package/dist/admin/{AdminFiles-kvuUaASF.js → AdminFiles-B9a7G3cY.js} +6 -8
- package/dist/admin/AdminFiles-B9a7G3cY.js.map +1 -0
- package/dist/admin/{AdminJobDashboard-CrPxp0W1.js → AdminJobDashboard-DaTwf5OY.js} +55 -186
- package/dist/admin/AdminJobDashboard-DaTwf5OY.js.map +1 -0
- package/dist/admin/{AdminJobExecutions-D-b4Zt7W.js → AdminJobExecutions-B9cek5dl.js} +132 -168
- package/dist/admin/AdminJobExecutions-B9cek5dl.js.map +1 -0
- package/dist/admin/{AdminJobRegistry-CNX5cpDx.js → AdminJobRegistry-DFgV3oqx.js} +60 -83
- package/dist/admin/AdminJobRegistry-DFgV3oqx.js.map +1 -0
- package/dist/admin/AdminLayout-DHsvWxVB.js +70 -0
- package/dist/admin/AdminLayout-DHsvWxVB.js.map +1 -0
- package/dist/admin/{AdminParameters-DCGbpt2c.js → AdminParameters-DHw9ATgl.js} +53 -53
- package/dist/admin/AdminParameters-DHw9ATgl.js.map +1 -0
- package/dist/admin/{AdminSessions-DyhW6RZv.js → AdminSessions-BhGJPI3z.js} +11 -18
- package/dist/admin/AdminSessions-BhGJPI3z.js.map +1 -0
- package/dist/admin/{AdminUserLayout-CrBj4UuI.js → AdminUserLayout-BdC4Te8m.js} +112 -151
- package/dist/admin/AdminUserLayout-BdC4Te8m.js.map +1 -0
- package/dist/admin/AdminUserProfile-DAt23fqY.js +69 -0
- package/dist/admin/AdminUserProfile-DAt23fqY.js.map +1 -0
- package/dist/admin/AdminUserSessions-1uzcx02z.js +109 -0
- package/dist/admin/AdminUserSessions-1uzcx02z.js.map +1 -0
- package/dist/admin/AdminUsers-C85c3eiQ.js +121 -0
- package/dist/admin/AdminUsers-C85c3eiQ.js.map +1 -0
- package/dist/{auth/AuthLayout-CdJcrPs4.js → admin/AuthLayout-DFJvCvzw.js} +3 -3
- package/dist/{auth/AuthLayout-CdJcrPs4.js.map → admin/AuthLayout-DFJvCvzw.js.map} +1 -1
- package/dist/{auth/IconGoogle-Bm18QD2q.js → admin/IconGoogle-CSQLPYwX.js} +1 -1
- package/dist/{auth/IconGoogle-Bm18QD2q.js.map → admin/IconGoogle-CSQLPYwX.js.map} +1 -1
- package/dist/{demo/DemoLogin-DjJ9314c.js → admin/Login-BGheURrg.js} +15 -129
- package/dist/{auth/Login-BS_FYTy0.js.map → admin/Login-BGheURrg.js.map} +1 -1
- package/dist/{auth/Profile-CjDsW378.js → admin/Profile-B-c9pCPf.js} +5 -5
- package/dist/{auth/Profile-CjDsW378.js.map → admin/Profile-B-c9pCPf.js.map} +1 -1
- package/dist/{demo/DemoRegister-DzkJ5M83.js → admin/Register-Cs10l8vX.js} +20 -146
- package/dist/{auth/Register-C5eqzAaD.js.map → admin/Register-Cs10l8vX.js.map} +1 -1
- package/dist/{demo/DemoResetPassword-DWh4_BpQ.js → admin/ResetPassword-BwDdfkGH.js} +20 -82
- package/dist/{auth/ResetPassword-XifinVao.js.map → admin/ResetPassword-BwDdfkGH.js.map} +1 -1
- package/dist/{demo/DemoVerifyEmail-DbU_tCj8.js → admin/VerifyEmail-DfXHAiQl.js} +15 -32
- package/dist/{auth/VerifyEmail-DTgbeJOO.js.map → admin/VerifyEmail-DfXHAiQl.js.map} +1 -1
- package/dist/admin/auth-Dr0Cf8I7.js +319 -0
- package/dist/admin/auth-Dr0Cf8I7.js.map +1 -0
- package/dist/admin/core-2xoLiT0o.js +4031 -0
- package/dist/admin/core-2xoLiT0o.js.map +1 -0
- package/dist/admin/index.d.ts +739 -13
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +79 -111
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/rolldown-runtime-CjeV3_4I.js +18 -0
- package/dist/auth/AuthLayout-CAE1pX9s.js +22 -0
- package/dist/auth/AuthLayout-CAE1pX9s.js.map +1 -0
- package/dist/auth/{Login-BS_FYTy0.js → Login-Denw_UGy.js} +8 -8
- package/dist/auth/Login-Denw_UGy.js.map +1 -0
- package/dist/auth/Profile-BMX_Ar_s.js +155 -0
- package/dist/auth/Profile-BMX_Ar_s.js.map +1 -0
- package/dist/auth/{Register-C5eqzAaD.js → Register-6hi_cpfF.js} +8 -8
- package/dist/auth/Register-6hi_cpfF.js.map +1 -0
- package/dist/auth/{ResetPassword-XifinVao.js → ResetPassword-CqfTk1FI.js} +6 -6
- package/dist/auth/ResetPassword-CqfTk1FI.js.map +1 -0
- package/dist/auth/{VerifyEmail-DTgbeJOO.js → VerifyEmail-nWiSTMjF.js} +5 -5
- package/dist/auth/VerifyEmail-nWiSTMjF.js.map +1 -0
- package/dist/auth/core-niW0sFLv.js +2264 -0
- package/dist/auth/core-niW0sFLv.js.map +1 -0
- package/dist/auth/index.d.ts +336 -8
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +18 -22
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +1033 -843
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1626 -1354
- package/dist/core/index.js.map +1 -1
- package/dist/demo/AuthLayout-jLa0aKsI.js +22 -0
- package/dist/demo/AuthLayout-jLa0aKsI.js.map +1 -0
- package/dist/demo/DemoButton-BmaWZVwf.js +178 -0
- package/dist/demo/DemoButton-BmaWZVwf.js.map +1 -0
- package/dist/demo/{DemoDataTable-lnBKWBf8.js → DemoDataTable-Z9xyV221.js} +18 -18
- package/dist/demo/DemoDataTable-Z9xyV221.js.map +1 -0
- package/dist/demo/DemoDialog-4ItHLf9t.js +101 -0
- package/dist/demo/DemoDialog-4ItHLf9t.js.map +1 -0
- package/dist/demo/DemoFlex-EtVq8QfX.js +105 -0
- package/dist/demo/DemoFlex-EtVq8QfX.js.map +1 -0
- package/dist/demo/DemoHeading-BS-vGfkI.js +18 -0
- package/dist/demo/DemoHeading-BS-vGfkI.js.map +1 -0
- package/dist/demo/{DemoHome-CUMZsYaH.js → DemoHome-Clbn8AmS.js} +9 -12
- package/dist/demo/DemoHome-Clbn8AmS.js.map +1 -0
- package/dist/demo/DemoJsonViewer-DkIX_ky2.js +109 -0
- package/dist/demo/DemoJsonViewer-DkIX_ky2.js.map +1 -0
- package/dist/demo/DemoLayout-C56xb5EE.js +73 -0
- package/dist/demo/DemoLayout-C56xb5EE.js.map +1 -0
- package/dist/demo/DemoLogin-BZwpicOS.js +128 -0
- package/dist/demo/DemoLogin-BZwpicOS.js.map +1 -0
- package/dist/demo/DemoRegister-C7_qc4MJ.js +140 -0
- package/dist/demo/DemoRegister-C7_qc4MJ.js.map +1 -0
- package/dist/demo/DemoResetPassword-BI1Ct4Dw.js +76 -0
- package/dist/demo/DemoResetPassword-BI1Ct4Dw.js.map +1 -0
- package/dist/demo/{DemoSidebar-C1csnGhX.js → DemoSidebar-CcBo4ltC.js} +6 -9
- package/dist/demo/DemoSidebar-CcBo4ltC.js.map +1 -0
- package/dist/demo/DemoText-CzXuUn3g.js +124 -0
- package/dist/demo/DemoText-CzXuUn3g.js.map +1 -0
- package/dist/demo/DemoToast-BgHDhWrX.js +95 -0
- package/dist/demo/DemoToast-BgHDhWrX.js.map +1 -0
- package/dist/demo/{DemoTypeForm-CWz6fJrJ.js → DemoTypeForm-DDzWoMSV.js} +4 -4
- package/dist/demo/{DemoTypeForm-CWz6fJrJ.js.map → DemoTypeForm-DDzWoMSV.js.map} +1 -1
- package/dist/demo/DemoVerifyEmail-C_Irdnov.js +30 -0
- package/dist/demo/DemoVerifyEmail-C_Irdnov.js.map +1 -0
- package/dist/demo/IconGoogle-CSQLPYwX.js +56 -0
- package/dist/demo/IconGoogle-CSQLPYwX.js.map +1 -0
- package/dist/demo/Login-hSOU3jZc.js +219 -0
- package/dist/demo/Login-hSOU3jZc.js.map +1 -0
- package/dist/demo/Profile-CWqti7FB.js +155 -0
- package/dist/demo/Profile-CWqti7FB.js.map +1 -0
- package/dist/demo/Register-a70LPgs2.js +375 -0
- package/dist/demo/Register-a70LPgs2.js.map +1 -0
- package/dist/demo/ResetPassword-DWN0lzr5.js +286 -0
- package/dist/demo/ResetPassword-DWN0lzr5.js.map +1 -0
- package/dist/demo/Showcase-Dq3MISpd.js +232 -0
- package/dist/demo/Showcase-Dq3MISpd.js.map +1 -0
- package/dist/demo/VerifyEmail-DZWL72K4.js +135 -0
- package/dist/demo/VerifyEmail-DZWL72K4.js.map +1 -0
- package/dist/demo/auth-d6n3xbug.js +257 -0
- package/dist/demo/auth-d6n3xbug.js.map +1 -0
- package/dist/demo/core-RCUw1Q-a.js +4217 -0
- package/dist/demo/core-RCUw1Q-a.js.map +1 -0
- package/dist/demo/index.d.ts +17 -6
- package/dist/demo/index.d.ts.map +1 -1
- package/dist/demo/index.js +92 -24
- package/dist/demo/index.js.map +1 -1
- package/dist/demo/rolldown-runtime-CjeV3_4I.js +18 -0
- package/package.json +16 -20
- package/src/admin/AdminRouter.ts +10 -39
- package/src/admin/components/AdminLayout.tsx +42 -10
- package/src/admin/components/audits/AdminAudits.tsx +10 -64
- package/src/admin/components/files/AdminFiles.tsx +2 -3
- package/src/admin/components/jobs/AdminJobDashboard.tsx +36 -142
- package/src/admin/components/jobs/AdminJobExecutions.tsx +117 -175
- package/src/admin/components/jobs/AdminJobRegistry.tsx +58 -73
- package/src/admin/components/keys/AdminApiKeys.tsx +21 -169
- package/src/admin/components/parameters/AdminParameters.tsx +4 -4
- package/src/admin/components/parameters/ParameterEmptyState.tsx +1 -2
- package/src/admin/components/parameters/ParameterHistory.tsx +3 -3
- package/src/admin/components/parameters/ParameterTree.tsx +2 -8
- package/src/admin/components/parameters/types.ts +3 -3
- package/src/admin/components/sessions/AdminSessions.tsx +8 -16
- package/src/admin/components/users/AdminUserLayout.tsx +113 -150
- package/src/admin/components/users/AdminUserProfile.tsx +50 -0
- package/src/admin/components/users/AdminUserSessions.tsx +106 -126
- package/src/admin/components/users/AdminUsers.tsx +46 -62
- package/src/admin/index.ts +0 -4
- package/src/auth/components/buttons/UserButton.tsx +1 -1
- package/src/auth/index.ts +0 -4
- package/src/core/UiRouter.ts +1 -1
- package/src/core/atoms/alephaSidebarAtom.ts +7 -31
- package/src/core/components/{layout/AlephaMantineProvider.tsx → AlephaMantineProvider.tsx} +3 -4
- package/src/core/components/Flex.tsx +63 -0
- package/src/core/components/Heading.tsx +19 -0
- package/src/core/components/Text.tsx +140 -0
- package/src/core/components/buttons/ActionButton.tsx +12 -1
- package/src/core/components/buttons/BurgerButton.tsx +3 -3
- package/src/core/components/buttons/LanguageButton.tsx +1 -1
- package/src/core/components/buttons/ToggleSidebarButton.tsx +1 -4
- package/src/core/components/data/DetailDrawer.tsx +144 -0
- package/src/core/components/data/DetailList.tsx +64 -0
- package/src/core/components/data/StatCards.tsx +50 -0
- package/src/core/components/layout/AppBar.tsx +11 -10
- package/src/core/components/layout/Breadcrumb.tsx +8 -8
- package/src/core/components/layout/Container.tsx +15 -0
- package/src/core/components/layout/DashboardShell.tsx +23 -238
- package/src/core/components/layout/Omnibar.tsx +1 -2
- package/src/core/components/layout/Sidebar.tsx +103 -71
- package/src/core/components/layout/index.ts +65 -0
- package/src/core/{components/form → form/components}/Control.tsx +32 -14
- package/src/core/{components/form → form/components}/ControlArray.tsx +2 -5
- package/src/core/{components/form → form/components}/ControlDate.tsx +1 -4
- package/src/core/{components/form → form/components}/ControlNumber.tsx +1 -4
- package/src/core/{components/form → form/components}/ControlObject.tsx +1 -4
- package/src/core/{components/form → form/components}/ControlQueryBuilder.tsx +7 -7
- package/src/core/{components/form → form/components}/ControlSelect.tsx +2 -4
- package/src/core/{components/form → form/components}/TypeForm.browser.spec.tsx +22 -64
- package/src/core/{components/form → form/components}/TypeForm.tsx +1 -3
- package/src/core/form/factories/dialogForm.tsx +31 -0
- package/src/core/form/index.ts +23 -0
- package/src/core/{utils → form/utils}/parseInput.ts +2 -4
- package/src/core/index.ts +43 -51
- package/src/core/interfaces/AlephaIntent.ts +6 -0
- package/src/core/interfaces/AlephaTheme.ts +0 -1
- package/src/core/json/factories/dialogJson.tsx +24 -0
- package/src/core/json/index.ts +2 -0
- package/src/core/primitives/$ui.ts +17 -0
- package/src/core/services/DialogService.tsx +1 -48
- package/src/core/styles.css +1 -8
- package/src/core/{components/table → table/components}/ColumnPicker.tsx +2 -3
- package/src/core/{components/table → table/components}/DataTable.tsx +8 -9
- package/src/core/{components/table → table/components}/DataTableFilters.tsx +6 -3
- package/src/core/{components/table → table/components}/DataTableToolbar.tsx +4 -5
- package/src/core/{components/table → table/components}/FilterPicker.tsx +2 -3
- package/src/core/table/index.ts +12 -0
- package/src/core/{components/table → table/interfaces}/types.ts +2 -2
- package/src/demo/DemoRouter.ts +87 -6
- package/src/demo/components/DemoHome.tsx +6 -10
- package/src/demo/components/DemoLayout.tsx +38 -8
- package/src/demo/components/auth/DemoLogin.tsx +1 -1
- package/src/demo/components/auth/DemoRegister.tsx +1 -1
- package/src/demo/components/auth/DemoResetPassword.tsx +1 -1
- package/src/demo/components/auth/DemoVerifyEmail.tsx +1 -1
- package/src/demo/components/core/DemoButton.tsx +160 -0
- package/src/demo/components/core/DemoFlex.tsx +101 -0
- package/src/demo/components/core/DemoHeading.tsx +13 -0
- package/src/demo/components/core/DemoText.tsx +110 -0
- package/src/demo/components/json/DemoJsonViewer.tsx +1 -1
- package/src/demo/components/layout/DemoDialog.tsx +103 -0
- package/src/demo/components/{core → layout}/DemoSidebar.tsx +0 -1
- package/src/demo/components/layout/DemoToast.tsx +96 -0
- package/src/demo/components/shared/MacWindow.tsx +149 -74
- package/src/demo/components/shared/Showcase.tsx +4 -8
- package/src/demo/index.ts +1 -4
- package/src/demo/primitives/$uiDemo.ts +10 -0
- package/dist/admin/AdminApiKeys-CF_qOO3u.js.map +0 -1
- package/dist/admin/AdminAudits-BQno3hZG.js.map +0 -1
- package/dist/admin/AdminFiles-kvuUaASF.js.map +0 -1
- package/dist/admin/AdminJobDashboard-CrPxp0W1.js.map +0 -1
- package/dist/admin/AdminJobExecutions-D-b4Zt7W.js.map +0 -1
- package/dist/admin/AdminJobRegistry-CNX5cpDx.js.map +0 -1
- package/dist/admin/AdminLayout-e-ZP5nWw.js +0 -37
- package/dist/admin/AdminLayout-e-ZP5nWw.js.map +0 -1
- package/dist/admin/AdminParameters-DCGbpt2c.js.map +0 -1
- package/dist/admin/AdminSessions-DyhW6RZv.js.map +0 -1
- package/dist/admin/AdminUserAudits-D1GcREEE.js +0 -177
- package/dist/admin/AdminUserAudits-D1GcREEE.js.map +0 -1
- package/dist/admin/AdminUserCreate-DR8LA0tv.js +0 -104
- package/dist/admin/AdminUserCreate-DR8LA0tv.js.map +0 -1
- package/dist/admin/AdminUserDetails-CDkZNHQD.js +0 -477
- package/dist/admin/AdminUserDetails-CDkZNHQD.js.map +0 -1
- package/dist/admin/AdminUserLayout-CrBj4UuI.js.map +0 -1
- package/dist/admin/AdminUserSessions-srgFHrqy.js +0 -129
- package/dist/admin/AdminUserSessions-srgFHrqy.js.map +0 -1
- package/dist/admin/AdminUserSettings-BFuxl-xT.js +0 -167
- package/dist/admin/AdminUserSettings-BFuxl-xT.js.map +0 -1
- package/dist/admin/AdminUsers-D1pDpiwK.js +0 -118
- package/dist/admin/AdminUsers-D1pDpiwK.js.map +0 -1
- package/dist/demo/DemoDataTable-lnBKWBf8.js.map +0 -1
- package/dist/demo/DemoHome-CUMZsYaH.js.map +0 -1
- package/dist/demo/DemoJsonViewer-_uokbGaW.js +0 -429
- package/dist/demo/DemoJsonViewer-_uokbGaW.js.map +0 -1
- package/dist/demo/DemoLayout-DHVoacE6.js +0 -46
- package/dist/demo/DemoLayout-DHVoacE6.js.map +0 -1
- package/dist/demo/DemoLogin-DjJ9314c.js.map +0 -1
- package/dist/demo/DemoRegister-DzkJ5M83.js.map +0 -1
- package/dist/demo/DemoResetPassword-DWh4_BpQ.js.map +0 -1
- package/dist/demo/DemoSidebar-C1csnGhX.js.map +0 -1
- package/dist/demo/DemoVerifyEmail-DbU_tCj8.js.map +0 -1
- package/dist/demo/Showcase-BzoXNlCn.js +0 -185
- package/dist/demo/Showcase-BzoXNlCn.js.map +0 -1
- package/dist/json/index.d.ts +0 -57
- package/dist/json/index.d.ts.map +0 -1
- package/dist/json/index.js +0 -325
- package/dist/json/index.js.map +0 -1
- package/src/admin/components/users/AdminUserAudits.tsx +0 -184
- package/src/admin/components/users/AdminUserCreate.tsx +0 -85
- package/src/admin/components/users/AdminUserDetails.tsx +0 -431
- package/src/admin/components/users/AdminUserSettings.tsx +0 -171
- package/src/core/components/data/ErrorViewer.tsx +0 -171
- package/src/json/extensions/DialogService.tsx +0 -31
- package/src/json/index.ts +0 -18
- package/src/json/styles.css +0 -1
- /package/dist/{demo → auth}/IconGoogle-Ch1m3Uzl.js +0 -0
- /package/dist/{demo → auth}/IconGoogle-Ch1m3Uzl.js.map +0 -0
- /package/src/{json → core/json}/components/JsonViewer.css +0 -0
- /package/src/{json → core/json}/components/JsonViewer.tsx +0 -0
- /package/src/core/{components/table → table/components}/DataTablePagination.tsx +0 -0
- /package/src/core/{components/table → table/components}/useTableSelection.ts +0 -0
|
@@ -0,0 +1,4217 @@
|
|
|
1
|
+
import { $atom, $context, $inject, $module, Alepha, AlephaError, TypeBoxError, t } from "alepha";
|
|
2
|
+
import { AlephaReactForm, FormValidationError, useForm, useFormState } from "alepha/react/form";
|
|
3
|
+
import { $head, AlephaReactHead } from "alepha/react/head";
|
|
4
|
+
import { AlephaReactI18n, useI18n } from "alepha/react/i18n";
|
|
5
|
+
import { $cookie } from "alepha/server/cookies";
|
|
6
|
+
import { ModalsProvider, modals } from "@mantine/modals";
|
|
7
|
+
import { ActionIcon, Anchor, AppShell, Autocomplete, Badge, Burger, Button, Card, Checkbox, ColorInput, ColorSchemeScript, Container, Divider, Drawer, Fieldset, FileInput, Flex, Grid, Image, Input, Kbd, Loader, MantineProvider, Menu, MultiSelect, NumberInput, Pagination, Paper, PasswordInput, Popover, ScrollArea, SegmentedControl, Select, Slider, Switch, Table, TagsInput, Text, TextInput, Textarea, ThemeIcon, Tooltip, Tree, UnstyledButton, getTreeExpandedState, useMantineColorScheme, useMantineTheme, useTree } from "@mantine/core";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Children, Fragment as Fragment$1, createElement, forwardRef, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
+
import { Notifications, notifications } from "@mantine/notifications";
|
|
11
|
+
import { IconAlertTriangle, IconArrowDown, IconArrowLeft, IconArrowUp, IconArrowsSort, IconAt, IconCalendar, IconCheck, IconChevronDown, IconChevronRight, IconClipboard, IconClock, IconColorPicker, IconColumns, IconCopy, IconDownload, IconFile, IconFilter, IconGripVertical, IconHash, IconInfoCircle, IconInfoTriangle, IconKey, IconLanguage, IconLayoutSidebarLeftCollapse, IconLayoutSidebarRightCollapse, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconPlus, IconRefresh, IconSearch, IconSelector, IconSquareRounded, IconSun, IconToggleLeft, IconTrash, IconX } from "@tabler/icons-react";
|
|
12
|
+
import { $page, Link, NestedView, useActive, useRouter, useRouterState } from "alepha/react/router";
|
|
13
|
+
import { NavigationProgress, nprogress } from "@mantine/nprogress";
|
|
14
|
+
import { ClientOnly, useAction, useAlepha, useEvents, useInject, useStore } from "alepha/react";
|
|
15
|
+
import { Spotlight, spotlight } from "@mantine/spotlight";
|
|
16
|
+
import { currentUserAtom } from "alepha/security";
|
|
17
|
+
import { useOs } from "@mantine/hooks";
|
|
18
|
+
import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
|
|
19
|
+
import { parseQueryString } from "alepha/orm";
|
|
20
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
21
|
+
|
|
22
|
+
//#region ../../src/core/atoms/alephaSidebarAtom.ts
|
|
23
|
+
const alephaSidebarAtom = $atom({
|
|
24
|
+
name: "alepha.ui.sidebar",
|
|
25
|
+
schema: t.object({
|
|
26
|
+
closed: t.boolean(),
|
|
27
|
+
collapsed: t.boolean(),
|
|
28
|
+
expandedWidth: t.number(),
|
|
29
|
+
collapsedWidth: t.number()
|
|
30
|
+
}),
|
|
31
|
+
default: {
|
|
32
|
+
closed: true,
|
|
33
|
+
collapsed: false,
|
|
34
|
+
expandedWidth: 300,
|
|
35
|
+
collapsedWidth: 78
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region ../../src/core/atoms/alephaThemeAtom.ts
|
|
41
|
+
const alephaThemeAtom = $atom({
|
|
42
|
+
name: "alepha.ui.theme",
|
|
43
|
+
schema: t.object({ index: t.integer() }),
|
|
44
|
+
default: { index: 0 }
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region ../../src/core/atoms/themes/default.ts
|
|
49
|
+
const defaultTheme = {
|
|
50
|
+
name: "Default",
|
|
51
|
+
description: "Default Alepha Theme"
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region ../../src/core/atoms/themes/midnight.ts
|
|
56
|
+
const midnightTheme = {
|
|
57
|
+
name: "Midnight",
|
|
58
|
+
description: "Clean, developer-focused design",
|
|
59
|
+
primaryColor: "pink",
|
|
60
|
+
primaryShade: {
|
|
61
|
+
light: 7,
|
|
62
|
+
dark: 8
|
|
63
|
+
},
|
|
64
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"",
|
|
65
|
+
fontFamilyMonospace: "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace",
|
|
66
|
+
headings: {
|
|
67
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif",
|
|
68
|
+
fontWeight: "600",
|
|
69
|
+
textWrap: "wrap",
|
|
70
|
+
sizes: {
|
|
71
|
+
h1: {
|
|
72
|
+
fontSize: "2rem",
|
|
73
|
+
lineHeight: "1.25"
|
|
74
|
+
},
|
|
75
|
+
h2: {
|
|
76
|
+
fontSize: "1.5rem",
|
|
77
|
+
lineHeight: "1.3"
|
|
78
|
+
},
|
|
79
|
+
h3: {
|
|
80
|
+
fontSize: "1.25rem",
|
|
81
|
+
lineHeight: "1.4"
|
|
82
|
+
},
|
|
83
|
+
h4: {
|
|
84
|
+
fontSize: "1rem",
|
|
85
|
+
lineHeight: "1.5"
|
|
86
|
+
},
|
|
87
|
+
h5: {
|
|
88
|
+
fontSize: "0.875rem",
|
|
89
|
+
lineHeight: "1.5"
|
|
90
|
+
},
|
|
91
|
+
h6: {
|
|
92
|
+
fontSize: "0.75rem",
|
|
93
|
+
lineHeight: "1.5"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
radius: {
|
|
98
|
+
xs: "3px",
|
|
99
|
+
sm: "6px",
|
|
100
|
+
md: "6px",
|
|
101
|
+
lg: "8px",
|
|
102
|
+
xl: "12px"
|
|
103
|
+
},
|
|
104
|
+
defaultRadius: "sm",
|
|
105
|
+
colors: {
|
|
106
|
+
dark: [
|
|
107
|
+
"#d0d7de",
|
|
108
|
+
"#8b949e",
|
|
109
|
+
"#6e7681",
|
|
110
|
+
"#484f58",
|
|
111
|
+
"#30363d",
|
|
112
|
+
"#21262d",
|
|
113
|
+
"#161b22",
|
|
114
|
+
"#151b23",
|
|
115
|
+
"#0d1117",
|
|
116
|
+
"#010409"
|
|
117
|
+
],
|
|
118
|
+
gray: [
|
|
119
|
+
"#f6f8fa",
|
|
120
|
+
"#eaeef2",
|
|
121
|
+
"#d0d7de",
|
|
122
|
+
"#afb8c1",
|
|
123
|
+
"#8c959f",
|
|
124
|
+
"#6e7781",
|
|
125
|
+
"#57606a",
|
|
126
|
+
"#424a53",
|
|
127
|
+
"#32383f",
|
|
128
|
+
"#24292f"
|
|
129
|
+
],
|
|
130
|
+
blue: [
|
|
131
|
+
"#ddf4ff",
|
|
132
|
+
"#b6e3ff",
|
|
133
|
+
"#80ccff",
|
|
134
|
+
"#54aeff",
|
|
135
|
+
"#218bff",
|
|
136
|
+
"#0969da",
|
|
137
|
+
"#0550ae",
|
|
138
|
+
"#033d8b",
|
|
139
|
+
"#0a3069",
|
|
140
|
+
"#002155"
|
|
141
|
+
],
|
|
142
|
+
green: [
|
|
143
|
+
"#dafbe1",
|
|
144
|
+
"#aceebb",
|
|
145
|
+
"#6fdd8b",
|
|
146
|
+
"#4ac26b",
|
|
147
|
+
"#2da44e",
|
|
148
|
+
"#1a7f37",
|
|
149
|
+
"#116329",
|
|
150
|
+
"#044f1e",
|
|
151
|
+
"#003d16",
|
|
152
|
+
"#002d11"
|
|
153
|
+
],
|
|
154
|
+
red: [
|
|
155
|
+
"#ffebe9",
|
|
156
|
+
"#ffcecb",
|
|
157
|
+
"#ffaba8",
|
|
158
|
+
"#ff8182",
|
|
159
|
+
"#fa4549",
|
|
160
|
+
"#cf222e",
|
|
161
|
+
"#a40e26",
|
|
162
|
+
"#82071e",
|
|
163
|
+
"#660018",
|
|
164
|
+
"#4c0014"
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region ../../src/core/atoms/alephaThemeListAtom.ts
|
|
171
|
+
const alephaThemeListAtom = $atom({
|
|
172
|
+
name: "alepha.ui.themeList",
|
|
173
|
+
schema: t.array(t.json()),
|
|
174
|
+
default: [defaultTheme, midnightTheme]
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region ../../src/core/providers/ThemeProvider.ts
|
|
179
|
+
var ThemeProvider = class {
|
|
180
|
+
alepha = $inject(Alepha);
|
|
181
|
+
cookie = $cookie({
|
|
182
|
+
name: "theme",
|
|
183
|
+
schema: alephaThemeAtom.schema,
|
|
184
|
+
ttl: [1, "year"]
|
|
185
|
+
});
|
|
186
|
+
head = $head(() => {
|
|
187
|
+
const theme = this.getTheme();
|
|
188
|
+
if (!theme || !theme.name) return {};
|
|
189
|
+
return { htmlAttributes: { "data-theme": theme.name } };
|
|
190
|
+
});
|
|
191
|
+
setTheme(index) {
|
|
192
|
+
const newTheme = this.alepha.store.get(alephaThemeListAtom)[index];
|
|
193
|
+
if (!newTheme) throw new AlephaError(`Theme with index ${index} not found`);
|
|
194
|
+
this.cookie.set({ index });
|
|
195
|
+
this.alepha.store.set(alephaThemeAtom, { index });
|
|
196
|
+
if (typeof document === "undefined") return;
|
|
197
|
+
document.documentElement.removeAttribute("data-theme");
|
|
198
|
+
if (newTheme.name) document.documentElement.setAttribute("data-theme", newTheme.name);
|
|
199
|
+
}
|
|
200
|
+
getTheme() {
|
|
201
|
+
const index = this.getThemeIndex();
|
|
202
|
+
const list = this.alepha.store.get(alephaThemeListAtom);
|
|
203
|
+
return list[index] || list[0] || defaultTheme;
|
|
204
|
+
}
|
|
205
|
+
getThemeIndex() {
|
|
206
|
+
try {
|
|
207
|
+
return this.cookie.get()?.index ?? this.alepha.store.get(alephaThemeAtom)?.index ?? 0;
|
|
208
|
+
} catch {
|
|
209
|
+
return this.alepha.store.get(alephaThemeAtom)?.index ?? 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region ../../src/core/components/dialogs/AlertDialog.tsx
|
|
216
|
+
const AlertDialog = ({ options, onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text, {
|
|
217
|
+
mb: "md",
|
|
218
|
+
children: options.message
|
|
219
|
+
}), /* @__PURE__ */ jsx(Flex, {
|
|
220
|
+
justify: "flex-end",
|
|
221
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
222
|
+
onClick: onClose,
|
|
223
|
+
children: options?.okLabel || "OK"
|
|
224
|
+
})
|
|
225
|
+
})] });
|
|
226
|
+
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region ../../src/core/components/dialogs/ConfirmDialog.tsx
|
|
229
|
+
const ConfirmDialog = ({ options, onConfirm }) => /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text, {
|
|
230
|
+
mb: "md",
|
|
231
|
+
children: options.message
|
|
232
|
+
}), /* @__PURE__ */ jsxs(Flex, {
|
|
233
|
+
justify: "flex-end",
|
|
234
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
235
|
+
variant: "subtle",
|
|
236
|
+
onClick: () => onConfirm(false),
|
|
237
|
+
children: options?.cancelLabel || "Cancel"
|
|
238
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
239
|
+
color: options?.confirmColor || "blue",
|
|
240
|
+
onClick: () => onConfirm(true),
|
|
241
|
+
children: options?.confirmLabel || "Confirm"
|
|
242
|
+
})]
|
|
243
|
+
})] });
|
|
244
|
+
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region ../../src/core/components/dialogs/PromptDialog.tsx
|
|
247
|
+
const PromptDialog = ({ options, onSubmit }) => {
|
|
248
|
+
const [value, setValue] = useState(options?.defaultValue || "");
|
|
249
|
+
const inputRef = useRef(null);
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
inputRef.current?.focus();
|
|
252
|
+
}, []);
|
|
253
|
+
const handleSubmit = () => {
|
|
254
|
+
if (!options?.required || value.trim()) onSubmit(value);
|
|
255
|
+
};
|
|
256
|
+
const handleKeyDown = (event) => {
|
|
257
|
+
if (event.key === "Enter") handleSubmit();
|
|
258
|
+
};
|
|
259
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
260
|
+
options?.message && /* @__PURE__ */ jsx(Text, {
|
|
261
|
+
mb: "md",
|
|
262
|
+
children: options.message
|
|
263
|
+
}),
|
|
264
|
+
/* @__PURE__ */ jsx(TextInput, {
|
|
265
|
+
ref: inputRef,
|
|
266
|
+
label: options?.label,
|
|
267
|
+
placeholder: options?.placeholder,
|
|
268
|
+
value,
|
|
269
|
+
onChange: (event) => setValue(event.currentTarget.value),
|
|
270
|
+
onKeyDown: handleKeyDown,
|
|
271
|
+
required: options?.required,
|
|
272
|
+
mb: "md"
|
|
273
|
+
}),
|
|
274
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
275
|
+
justify: "flex-end",
|
|
276
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
277
|
+
variant: "subtle",
|
|
278
|
+
onClick: () => onSubmit(null),
|
|
279
|
+
children: options?.cancelLabel || "Cancel"
|
|
280
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
281
|
+
onClick: handleSubmit,
|
|
282
|
+
disabled: options?.required && !value.trim(),
|
|
283
|
+
children: options?.submitLabel || "OK"
|
|
284
|
+
})]
|
|
285
|
+
})
|
|
286
|
+
] });
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region ../../src/core/services/DialogService.tsx
|
|
291
|
+
var DialogService = class {
|
|
292
|
+
options = { default: {
|
|
293
|
+
centered: true,
|
|
294
|
+
withCloseButton: true,
|
|
295
|
+
size: "md",
|
|
296
|
+
overlayProps: {
|
|
297
|
+
backgroundOpacity: .55,
|
|
298
|
+
blur: 3
|
|
299
|
+
},
|
|
300
|
+
transitionProps: {
|
|
301
|
+
transition: "pop",
|
|
302
|
+
duration: 200
|
|
303
|
+
}
|
|
304
|
+
} };
|
|
305
|
+
/**
|
|
306
|
+
* Show an alert dialog with a message
|
|
307
|
+
*/
|
|
308
|
+
alert(options) {
|
|
309
|
+
return new Promise((resolve) => {
|
|
310
|
+
const modalId = this.open({
|
|
311
|
+
...options,
|
|
312
|
+
title: options?.title || "Alert",
|
|
313
|
+
content: /* @__PURE__ */ jsx(AlertDialog, {
|
|
314
|
+
options,
|
|
315
|
+
onClose: () => {
|
|
316
|
+
this.close(modalId);
|
|
317
|
+
resolve();
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Show a confirmation dialog that returns a promise
|
|
325
|
+
*/
|
|
326
|
+
confirm(options) {
|
|
327
|
+
return new Promise((resolve) => {
|
|
328
|
+
const modalId = this.open({
|
|
329
|
+
...options,
|
|
330
|
+
title: options?.title || "Confirm",
|
|
331
|
+
closeOnClickOutside: false,
|
|
332
|
+
closeOnEscape: false,
|
|
333
|
+
content: /* @__PURE__ */ jsx(ConfirmDialog, {
|
|
334
|
+
options,
|
|
335
|
+
onConfirm: (confirmed) => {
|
|
336
|
+
this.close(modalId);
|
|
337
|
+
resolve(confirmed);
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Show a prompt dialog to get user input
|
|
345
|
+
*/
|
|
346
|
+
prompt(options) {
|
|
347
|
+
return new Promise((resolve) => {
|
|
348
|
+
const modalId = this.open({
|
|
349
|
+
...options,
|
|
350
|
+
title: options?.title || "Input",
|
|
351
|
+
closeOnClickOutside: false,
|
|
352
|
+
closeOnEscape: false,
|
|
353
|
+
content: /* @__PURE__ */ jsx(PromptDialog, {
|
|
354
|
+
options,
|
|
355
|
+
onSubmit: (value) => {
|
|
356
|
+
this.close(modalId);
|
|
357
|
+
resolve(value);
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Open a custom dialog with provided content
|
|
365
|
+
*/
|
|
366
|
+
open(options) {
|
|
367
|
+
return modals.open({
|
|
368
|
+
...this.options.default,
|
|
369
|
+
...options,
|
|
370
|
+
children: options?.content || options?.message
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Close the currently open dialog or a specific dialog by ID
|
|
375
|
+
*/
|
|
376
|
+
close(modalId) {
|
|
377
|
+
if (modalId) modals.close(modalId);
|
|
378
|
+
else modals.closeAll();
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region ../../src/core/services/ToastService.tsx
|
|
384
|
+
var ToastService = class {
|
|
385
|
+
raw = notifications;
|
|
386
|
+
options = { default: {
|
|
387
|
+
radius: "md",
|
|
388
|
+
withBorder: true,
|
|
389
|
+
withCloseButton: true,
|
|
390
|
+
autoClose: 5e3,
|
|
391
|
+
position: "top-center"
|
|
392
|
+
} };
|
|
393
|
+
show(options) {
|
|
394
|
+
notifications.show({
|
|
395
|
+
...this.options.default,
|
|
396
|
+
...options
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
info(options) {
|
|
400
|
+
if (typeof options === "string") options = { message: options };
|
|
401
|
+
this.show({
|
|
402
|
+
color: "blue",
|
|
403
|
+
icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 20 }),
|
|
404
|
+
title: "Info",
|
|
405
|
+
message: "Information notification",
|
|
406
|
+
...options
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
success(options) {
|
|
410
|
+
if (typeof options === "string") options = { message: options };
|
|
411
|
+
this.show({
|
|
412
|
+
color: "green",
|
|
413
|
+
icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }),
|
|
414
|
+
title: "Success",
|
|
415
|
+
message: "Operation completed successfully",
|
|
416
|
+
...options
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
warning(options) {
|
|
420
|
+
if (typeof options === "string") options = { message: options };
|
|
421
|
+
this.show({
|
|
422
|
+
color: "yellow",
|
|
423
|
+
icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20 }),
|
|
424
|
+
title: "Warning",
|
|
425
|
+
message: "Please review this warning",
|
|
426
|
+
...options
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
danger(options) {
|
|
430
|
+
if (typeof options === "string") options = { message: options };
|
|
431
|
+
this.show({
|
|
432
|
+
color: "red",
|
|
433
|
+
icon: /* @__PURE__ */ jsx(IconX, { size: 20 }),
|
|
434
|
+
title: "Error",
|
|
435
|
+
message: "An error occurred",
|
|
436
|
+
...options
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region ../../src/core/UiRouter.ts
|
|
443
|
+
/**
|
|
444
|
+
* UI Router defining the root page with AlephaMantineProvider.
|
|
445
|
+
*
|
|
446
|
+
* - Use UiRouter when you need Alepha's Mantine-based UI components and theming.
|
|
447
|
+
* - Prefer to use $ui() for convenience. (Custom Factory of UiRouter)
|
|
448
|
+
*/
|
|
449
|
+
var UiRouter = class {
|
|
450
|
+
root = $page({
|
|
451
|
+
path: "/",
|
|
452
|
+
component: AlephaMantineProvider
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region ../../src/core/hooks/useTheme.ts
|
|
458
|
+
/**
|
|
459
|
+
* Hook to get and set the current theme.
|
|
460
|
+
*
|
|
461
|
+
* Returns a tuple with the current theme and a function to set the theme.
|
|
462
|
+
*
|
|
463
|
+
* ```tsx
|
|
464
|
+
* const [theme, setTheme] = useTheme();
|
|
465
|
+
* ```
|
|
466
|
+
*/
|
|
467
|
+
const useTheme = () => {
|
|
468
|
+
useStore(alephaThemeAtom);
|
|
469
|
+
const themeProvider = useInject(ThemeProvider);
|
|
470
|
+
const theme = themeProvider.getTheme();
|
|
471
|
+
const setTheme = (theme) => {
|
|
472
|
+
themeProvider.setTheme(theme.index);
|
|
473
|
+
};
|
|
474
|
+
return [theme, setTheme];
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region ../../src/core/hooks/useToast.ts
|
|
479
|
+
/**
|
|
480
|
+
* Use this hook to access the Toast Service for showing notifications.
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```tsx
|
|
484
|
+
* const toast = useToast();
|
|
485
|
+
* toast.success({ message: "Operation completed successfully!" });
|
|
486
|
+
* toast.error({ title: "Error", message: "Something went wrong" });
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
const useToast = () => {
|
|
490
|
+
return useInject(ToastService);
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region ../../src/core/components/layout/Omnibar.tsx
|
|
495
|
+
const Omnibar = (props) => {
|
|
496
|
+
const shortcut = props.shortcut ?? "mod+K";
|
|
497
|
+
const searchPlaceholder = props.searchPlaceholder ?? "Search...";
|
|
498
|
+
const nothingFound = props.nothingFound ?? "Nothing found...";
|
|
499
|
+
const router = useRouter();
|
|
500
|
+
const [user] = useStore(currentUserAtom);
|
|
501
|
+
return /* @__PURE__ */ jsx(Spotlight, {
|
|
502
|
+
actions: useMemo(() => router.concretePages.filter((page) => {
|
|
503
|
+
if (page.can && !page.can()) return false;
|
|
504
|
+
return true;
|
|
505
|
+
}).map((page) => ({
|
|
506
|
+
id: page.name,
|
|
507
|
+
label: page.label ?? page.name,
|
|
508
|
+
description: page.description,
|
|
509
|
+
onClick: () => {
|
|
510
|
+
if (page.staticName) return router.push(page.staticName, { params: page.params });
|
|
511
|
+
return router.push(page.name);
|
|
512
|
+
},
|
|
513
|
+
leftSection: renderIcon(page.icon)
|
|
514
|
+
})), [user]),
|
|
515
|
+
shortcut,
|
|
516
|
+
limit: 10,
|
|
517
|
+
searchProps: {
|
|
518
|
+
leftSection: /* @__PURE__ */ jsx(IconSearch, { size: ui.sizes.icon.md }),
|
|
519
|
+
placeholder: searchPlaceholder
|
|
520
|
+
},
|
|
521
|
+
nothingFound
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region ../../src/core/components/AlephaMantineProvider.tsx
|
|
527
|
+
const AlephaMantineProvider = (props) => {
|
|
528
|
+
const toast = useToast();
|
|
529
|
+
const [theme] = useTheme();
|
|
530
|
+
useEvents({
|
|
531
|
+
"react:transition:begin": () => {
|
|
532
|
+
nprogress.start();
|
|
533
|
+
},
|
|
534
|
+
"react:transition:end": () => {
|
|
535
|
+
nprogress.complete();
|
|
536
|
+
},
|
|
537
|
+
"react:action:error": ({ error, type }) => {
|
|
538
|
+
if (type === "transition" || error instanceof FormValidationError || error instanceof TypeBoxError) return;
|
|
539
|
+
toast.danger({
|
|
540
|
+
title: error.name || "Error",
|
|
541
|
+
message: error.message ?? "An error occurred while processing your action."
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}, []);
|
|
545
|
+
const defaultColorScheme = props.mantine?.defaultColorScheme ?? theme.defaultColorScheme;
|
|
546
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ColorSchemeScript, {
|
|
547
|
+
defaultColorScheme,
|
|
548
|
+
...props.colorSchemeScript
|
|
549
|
+
}), /* @__PURE__ */ jsxs(MantineProvider, {
|
|
550
|
+
...props.mantine,
|
|
551
|
+
defaultColorScheme,
|
|
552
|
+
theme: {
|
|
553
|
+
...theme,
|
|
554
|
+
...props.mantine?.theme
|
|
555
|
+
},
|
|
556
|
+
children: [
|
|
557
|
+
/* @__PURE__ */ jsx(Notifications, { ...props.notifications }),
|
|
558
|
+
/* @__PURE__ */ jsx(NavigationProgress, { ...props.navigationProgress }),
|
|
559
|
+
/* @__PURE__ */ jsxs(ModalsProvider, {
|
|
560
|
+
...props.modals,
|
|
561
|
+
children: [props.omnibar !== false && /* @__PURE__ */ jsx(Omnibar, { ...props.omnibar }), props.children ?? /* @__PURE__ */ jsx(NestedView, {})]
|
|
562
|
+
})
|
|
563
|
+
]
|
|
564
|
+
})] });
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region ../../src/core/constants/ui.ts
|
|
569
|
+
const ui = {
|
|
570
|
+
colors: {
|
|
571
|
+
transparent: "transparent",
|
|
572
|
+
background: "var(--alepha-background)",
|
|
573
|
+
surface: "var(--alepha-surface)",
|
|
574
|
+
elevated: "var(--alepha-elevated)",
|
|
575
|
+
border: "var(--alepha-border)"
|
|
576
|
+
},
|
|
577
|
+
sizes: { icon: {
|
|
578
|
+
xs: 14,
|
|
579
|
+
sm: 16,
|
|
580
|
+
md: 20,
|
|
581
|
+
lg: 24,
|
|
582
|
+
xl: 32
|
|
583
|
+
} }
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
//#endregion
|
|
587
|
+
//#region ../../src/core/helpers/isComponentType.ts
|
|
588
|
+
function isComponentType(param) {
|
|
589
|
+
if (isValidElement(param)) return false;
|
|
590
|
+
return typeof param === "function" || typeof param === "object" && param !== null && "$$typeof" in param;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region ../../src/core/components/buttons/ActionButton.tsx
|
|
595
|
+
const ActionMenuItem = (props) => {
|
|
596
|
+
const { item, index } = props;
|
|
597
|
+
const router = useRouter();
|
|
598
|
+
const action = useAction({ handler: async (e) => {
|
|
599
|
+
await item.onClick?.();
|
|
600
|
+
} }, [item.onClick]);
|
|
601
|
+
if (item.type === "divider") return /* @__PURE__ */ jsx(Menu.Divider, {}, index);
|
|
602
|
+
if (item.type === "label") return /* @__PURE__ */ jsx(Menu.Label, { children: item.label }, index);
|
|
603
|
+
if (item.children && item.children.length > 0) return /* @__PURE__ */ jsxs(Menu, {
|
|
604
|
+
trigger: "hover",
|
|
605
|
+
position: "right-start",
|
|
606
|
+
offset: 2,
|
|
607
|
+
children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Menu.Item, {
|
|
608
|
+
leftSection: item.icon,
|
|
609
|
+
rightSection: /* @__PURE__ */ jsx(IconChevronRight, { size: 14 }),
|
|
610
|
+
children: item.label
|
|
611
|
+
}) }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: item.children.map((child, childIndex) => /* @__PURE__ */ jsx(ActionMenuItem, {
|
|
612
|
+
item: child,
|
|
613
|
+
index: childIndex
|
|
614
|
+
}, childIndex)) })]
|
|
615
|
+
}, index);
|
|
616
|
+
const menuItemProps = {};
|
|
617
|
+
if (props.item.onClick) menuItemProps.onClick = action.run;
|
|
618
|
+
else if (props.item.href) Object.assign(menuItemProps, router.anchor(props.item.href));
|
|
619
|
+
return /* @__PURE__ */ jsx(Menu.Item, {
|
|
620
|
+
leftSection: item.icon,
|
|
621
|
+
onClick: item.onClick,
|
|
622
|
+
color: item.color,
|
|
623
|
+
rightSection: item.active ? /* @__PURE__ */ jsx(ThemeIcon, {
|
|
624
|
+
size: "xs",
|
|
625
|
+
variant: "transparent",
|
|
626
|
+
children: /* @__PURE__ */ jsx(IconCheck, {})
|
|
627
|
+
}) : void 0,
|
|
628
|
+
...menuItemProps,
|
|
629
|
+
children: item.label
|
|
630
|
+
}, index);
|
|
631
|
+
};
|
|
632
|
+
const ActionButton = (_props) => {
|
|
633
|
+
const theme = useMantineTheme();
|
|
634
|
+
const props = { ..._props };
|
|
635
|
+
const { tooltip, menu, icon, ...restProps } = props;
|
|
636
|
+
if (props.variant === "subtle" || props.variant === "outline") restProps.color ??= "gray";
|
|
637
|
+
if (props.intent) {
|
|
638
|
+
if (props.intent === "none") restProps.color ??= "gray";
|
|
639
|
+
else if (props.intent === "primary") restProps.color ??= theme.primaryColor;
|
|
640
|
+
else if (props.intent === "success") {
|
|
641
|
+
restProps.c ??= "white";
|
|
642
|
+
restProps.color ??= "green";
|
|
643
|
+
} else if (props.intent === "danger") {
|
|
644
|
+
restProps.c ??= "white";
|
|
645
|
+
restProps.color ??= "red";
|
|
646
|
+
} else if (props.intent === "warning") restProps.color ??= "yellow";
|
|
647
|
+
else if (props.intent === "info") {
|
|
648
|
+
restProps.c ??= "white";
|
|
649
|
+
restProps.color ??= "blue";
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (props.icon) {
|
|
653
|
+
const sizes = ui.sizes.icon;
|
|
654
|
+
const icon = isComponentType(props.icon) ? /* @__PURE__ */ jsx(props.icon, { size: sizes[props.size || "md"] }) : /* @__PURE__ */ jsx("span", { children: props.icon });
|
|
655
|
+
if (!props.children) {
|
|
656
|
+
restProps.children = Children.only(icon);
|
|
657
|
+
restProps.px ??= "xs";
|
|
658
|
+
} else restProps.leftSection = icon;
|
|
659
|
+
}
|
|
660
|
+
if (props.leftSection && !props.children) restProps.px ??= "xs";
|
|
661
|
+
if (props.textVisibleFrom) {
|
|
662
|
+
const { children, textVisibleFrom, leftSection, ...rest } = restProps;
|
|
663
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex, {
|
|
664
|
+
w: "100%",
|
|
665
|
+
visibleFrom: textVisibleFrom,
|
|
666
|
+
children: /* @__PURE__ */ jsx(ActionButton, {
|
|
667
|
+
flex: 1,
|
|
668
|
+
...rest,
|
|
669
|
+
leftSection,
|
|
670
|
+
tooltip,
|
|
671
|
+
menu,
|
|
672
|
+
children
|
|
673
|
+
})
|
|
674
|
+
}), /* @__PURE__ */ jsx(Flex, {
|
|
675
|
+
w: "100%",
|
|
676
|
+
hiddenFrom: textVisibleFrom,
|
|
677
|
+
children: /* @__PURE__ */ jsx(ActionButton, {
|
|
678
|
+
px: "xs",
|
|
679
|
+
...rest,
|
|
680
|
+
tooltip,
|
|
681
|
+
menu,
|
|
682
|
+
children: leftSection
|
|
683
|
+
})
|
|
684
|
+
})] });
|
|
685
|
+
}
|
|
686
|
+
const renderAction = () => {
|
|
687
|
+
if ("href" in restProps && restProps.href) {
|
|
688
|
+
if (restProps.href.startsWith("http") || restProps.target) return /* @__PURE__ */ jsx(ActionHrefButton, {
|
|
689
|
+
...restProps,
|
|
690
|
+
href: restProps.href,
|
|
691
|
+
children: restProps.children
|
|
692
|
+
});
|
|
693
|
+
return /* @__PURE__ */ jsx(ActionNavigationButton, {
|
|
694
|
+
...restProps,
|
|
695
|
+
href: restProps.href,
|
|
696
|
+
children: restProps.children
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
delete restProps.classNameActive;
|
|
700
|
+
delete restProps.variantActive;
|
|
701
|
+
delete restProps.propsActive;
|
|
702
|
+
if ("action" in restProps && restProps.action) return /* @__PURE__ */ jsx(ActionHookButton, {
|
|
703
|
+
...restProps,
|
|
704
|
+
action: restProps.action,
|
|
705
|
+
children: restProps.children
|
|
706
|
+
});
|
|
707
|
+
if ("onClick" in restProps && restProps.onClick) return /* @__PURE__ */ jsx(ActionClickButton, {
|
|
708
|
+
...restProps,
|
|
709
|
+
onClick: restProps.onClick,
|
|
710
|
+
children: restProps.children
|
|
711
|
+
});
|
|
712
|
+
if ("form" in restProps && restProps.form) {
|
|
713
|
+
if (restProps.type === "reset") return /* @__PURE__ */ jsx(ActionResetButton, {
|
|
714
|
+
...restProps,
|
|
715
|
+
form: restProps.form,
|
|
716
|
+
children: restProps.children
|
|
717
|
+
});
|
|
718
|
+
return /* @__PURE__ */ jsx(ActionSubmitButton, {
|
|
719
|
+
...restProps,
|
|
720
|
+
form: restProps.form,
|
|
721
|
+
children: restProps.children
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
725
|
+
...restProps,
|
|
726
|
+
children: restProps.children
|
|
727
|
+
});
|
|
728
|
+
};
|
|
729
|
+
let actionElement = renderAction();
|
|
730
|
+
if (menu) actionElement = /* @__PURE__ */ jsxs(Menu, {
|
|
731
|
+
position: menu.position || "bottom-start",
|
|
732
|
+
width: menu.width || 200,
|
|
733
|
+
shadow: menu.shadow || "md",
|
|
734
|
+
trigger: menu.on === "hover" ? "hover" : "click",
|
|
735
|
+
...menu.menuProps,
|
|
736
|
+
children: [/* @__PURE__ */ jsx(Menu.Target, {
|
|
737
|
+
...menu.targetProps,
|
|
738
|
+
children: actionElement
|
|
739
|
+
}), /* @__PURE__ */ jsx(Menu.Dropdown, { children: menu.items.map((item, index) => /* @__PURE__ */ jsx(ActionMenuItem, {
|
|
740
|
+
item,
|
|
741
|
+
index
|
|
742
|
+
}, index)) })]
|
|
743
|
+
});
|
|
744
|
+
if (tooltip) {
|
|
745
|
+
const defaultTooltipProps = { openDelay: 1e3 };
|
|
746
|
+
return /* @__PURE__ */ jsx(Tooltip, { ...typeof tooltip === "string" || typeof tooltip === "number" ? {
|
|
747
|
+
...defaultTooltipProps,
|
|
748
|
+
label: tooltip,
|
|
749
|
+
children: actionElement
|
|
750
|
+
} : {
|
|
751
|
+
...defaultTooltipProps,
|
|
752
|
+
...tooltip,
|
|
753
|
+
children: actionElement
|
|
754
|
+
} });
|
|
755
|
+
}
|
|
756
|
+
return actionElement;
|
|
757
|
+
};
|
|
758
|
+
/**
|
|
759
|
+
* Action button that submits a form with loading and disabled state handling.
|
|
760
|
+
*/
|
|
761
|
+
const ActionSubmitButton = (props) => {
|
|
762
|
+
const { form, ...buttonProps } = props;
|
|
763
|
+
const state = useFormState(form);
|
|
764
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
765
|
+
...buttonProps,
|
|
766
|
+
loading: state.loading,
|
|
767
|
+
disabled: state.loading,
|
|
768
|
+
type: "submit",
|
|
769
|
+
children: props.children
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
const ActionResetButton = (props) => {
|
|
773
|
+
const { form, ...buttonProps } = props;
|
|
774
|
+
const state = useFormState(form);
|
|
775
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
776
|
+
...buttonProps,
|
|
777
|
+
disabled: state.loading,
|
|
778
|
+
type: "reset",
|
|
779
|
+
children: props.children
|
|
780
|
+
});
|
|
781
|
+
};
|
|
782
|
+
/**
|
|
783
|
+
* Action button that integrates with useAction hook return value.
|
|
784
|
+
* Automatically handles loading state and executes the action on click.
|
|
785
|
+
*
|
|
786
|
+
* @example
|
|
787
|
+
* ```tsx
|
|
788
|
+
* const saveAction = useAction({
|
|
789
|
+
* handler: async (data) => {
|
|
790
|
+
* await api.save(data);
|
|
791
|
+
* }
|
|
792
|
+
* }, []);
|
|
793
|
+
*
|
|
794
|
+
* <ActionButton action={saveAction}>
|
|
795
|
+
* Save
|
|
796
|
+
* </ActionButton>
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
799
|
+
const ActionHookButton = (props) => {
|
|
800
|
+
const { action, ...buttonProps } = props;
|
|
801
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
802
|
+
...buttonProps,
|
|
803
|
+
disabled: action.loading || props.disabled,
|
|
804
|
+
loading: action.loading,
|
|
805
|
+
onClick: () => action.run(),
|
|
806
|
+
children: props.children
|
|
807
|
+
});
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* Basic action button that handles click events with loading and error handling.
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* ```tsx
|
|
814
|
+
* <ActionButton onClick={() => api.doSomething()}>
|
|
815
|
+
* Do Something
|
|
816
|
+
* </ActionButton>
|
|
817
|
+
* ```
|
|
818
|
+
*/
|
|
819
|
+
const ActionClickButton = ({ preventDefault, ...props }) => {
|
|
820
|
+
const action = useAction({ handler: async (e) => {
|
|
821
|
+
if (preventDefault) e.preventDefault();
|
|
822
|
+
await props.onClick(e);
|
|
823
|
+
} }, [props.onClick, preventDefault]);
|
|
824
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
825
|
+
...props,
|
|
826
|
+
disabled: action.loading || props.disabled,
|
|
827
|
+
loading: action.loading,
|
|
828
|
+
onClick: action.run,
|
|
829
|
+
children: props.children
|
|
830
|
+
});
|
|
831
|
+
};
|
|
832
|
+
/**
|
|
833
|
+
* Action for navigation with active state support.
|
|
834
|
+
*/
|
|
835
|
+
const ActionNavigationButton = (props) => {
|
|
836
|
+
const { active: options, classNameActive, variantActive, propsActive, routerGoOptions, onClick: propsOnClick, anchor, ...buttonProps } = props;
|
|
837
|
+
const router = useRouter();
|
|
838
|
+
const { isPending, isActive } = useActive(options ? {
|
|
839
|
+
href: props.href,
|
|
840
|
+
...options
|
|
841
|
+
} : { href: props.href });
|
|
842
|
+
const anchorProps = router.anchor(props.href, routerGoOptions);
|
|
843
|
+
if (propsActive && isActive) Object.assign(buttonProps, propsActive);
|
|
844
|
+
const combinedOnClick = (e) => {
|
|
845
|
+
propsOnClick?.(e);
|
|
846
|
+
anchorProps.onClick?.(e);
|
|
847
|
+
};
|
|
848
|
+
const className = buttonProps.className || "";
|
|
849
|
+
if (isActive && options !== false && classNameActive) buttonProps.className = `${className} ${classNameActive}`.trim();
|
|
850
|
+
if (props.anchorProps || anchor) return /* @__PURE__ */ jsx(Anchor, {
|
|
851
|
+
component: "a",
|
|
852
|
+
...anchorProps,
|
|
853
|
+
...buttonProps,
|
|
854
|
+
...props.anchorProps,
|
|
855
|
+
onClick: combinedOnClick,
|
|
856
|
+
children: props.children
|
|
857
|
+
});
|
|
858
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
859
|
+
component: "a",
|
|
860
|
+
loading: isPending,
|
|
861
|
+
...buttonProps,
|
|
862
|
+
...anchorProps,
|
|
863
|
+
onClick: combinedOnClick,
|
|
864
|
+
variant: isActive && options !== false ? variantActive ?? "filled" : buttonProps.variant ?? "subtle",
|
|
865
|
+
children: props.children
|
|
866
|
+
});
|
|
867
|
+
};
|
|
868
|
+
const ActionHrefButton = (props) => {
|
|
869
|
+
const { active: options, classNameActive, variantActive, propsActive, routerGoOptions, target, ...buttonProps } = props;
|
|
870
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
871
|
+
component: "a",
|
|
872
|
+
target,
|
|
873
|
+
...buttonProps,
|
|
874
|
+
children: props.children
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
//#endregion
|
|
879
|
+
//#region ../../src/core/components/buttons/BurgerButton.tsx
|
|
880
|
+
const BurgerButton = (props) => {
|
|
881
|
+
const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
|
|
882
|
+
return /* @__PURE__ */ jsx(Burger, {
|
|
883
|
+
opened: !sidebar.closed,
|
|
884
|
+
onClick: () => setSidebar({
|
|
885
|
+
...sidebar,
|
|
886
|
+
closed: !sidebar.closed
|
|
887
|
+
}),
|
|
888
|
+
hiddenFrom: "md",
|
|
889
|
+
size: "sm",
|
|
890
|
+
...props
|
|
891
|
+
});
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
//#endregion
|
|
895
|
+
//#region ../../src/core/components/buttons/DarkModeButton.tsx
|
|
896
|
+
/**
|
|
897
|
+
* SSR-safe dark mode toggle button.
|
|
898
|
+
*
|
|
899
|
+
* Uses CSS-based icon switching to avoid hydration mismatches.
|
|
900
|
+
* Both icons are rendered, CSS hides the wrong one based on
|
|
901
|
+
* `data-mantine-color-scheme` attribute.
|
|
902
|
+
*/
|
|
903
|
+
const DarkModeButton = (props) => {
|
|
904
|
+
const { setColorScheme } = useMantineColorScheme();
|
|
905
|
+
const toggleColorScheme = () => {
|
|
906
|
+
setColorScheme((document.documentElement.getAttribute("data-mantine-color-scheme") ?? "light") === "dark" ? "light" : "dark");
|
|
907
|
+
};
|
|
908
|
+
const size = props.size ?? "md";
|
|
909
|
+
const iconSize = ui.sizes.icon[size] ?? ui.sizes.icon.md;
|
|
910
|
+
return /* @__PURE__ */ jsx(ActionButton, {
|
|
911
|
+
onClick: toggleColorScheme,
|
|
912
|
+
variant: props.variant ?? "subtle",
|
|
913
|
+
size,
|
|
914
|
+
"aria-label": "Toggle color scheme",
|
|
915
|
+
icon: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconSun, {
|
|
916
|
+
size: iconSize,
|
|
917
|
+
className: "alepha-light-hidden"
|
|
918
|
+
}), /* @__PURE__ */ jsx(IconMoon, {
|
|
919
|
+
size: iconSize,
|
|
920
|
+
className: "alepha-dark-hidden"
|
|
921
|
+
})] }),
|
|
922
|
+
...props
|
|
923
|
+
});
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region ../../src/core/components/buttons/LanguageButton.tsx
|
|
928
|
+
const LanguageButton = (props) => {
|
|
929
|
+
const i18n = useI18n();
|
|
930
|
+
return /* @__PURE__ */ jsx(ActionButton, {
|
|
931
|
+
variant: "subtle",
|
|
932
|
+
icon: IconLanguage,
|
|
933
|
+
menu: { items: i18n.languages.map((lang) => ({
|
|
934
|
+
label: i18n.tr(lang),
|
|
935
|
+
onClick: () => i18n.setLang(lang),
|
|
936
|
+
active: i18n.lang === lang
|
|
937
|
+
})) },
|
|
938
|
+
...props
|
|
939
|
+
});
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
//#endregion
|
|
943
|
+
//#region ../../src/core/components/buttons/OmnibarButton.tsx
|
|
944
|
+
const OmnibarButton = (props) => {
|
|
945
|
+
const os = useOs();
|
|
946
|
+
const shortcut = os === "macos" || os === "ios" ? "⌘" : "Ctrl";
|
|
947
|
+
if (props.collapsed) return /* @__PURE__ */ jsx(ActionButton, {
|
|
948
|
+
variant: "subtle",
|
|
949
|
+
onClick: spotlight.open,
|
|
950
|
+
radius: "md",
|
|
951
|
+
icon: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
|
|
952
|
+
tooltip: {
|
|
953
|
+
label: "Search",
|
|
954
|
+
position: "right"
|
|
955
|
+
},
|
|
956
|
+
...props.actionProps
|
|
957
|
+
});
|
|
958
|
+
return /* @__PURE__ */ jsx(ActionButton, {
|
|
959
|
+
variant: "default",
|
|
960
|
+
onClick: spotlight.open,
|
|
961
|
+
justify: "space-between",
|
|
962
|
+
rightSection: /* @__PURE__ */ jsxs(Kbd, {
|
|
963
|
+
visibleFrom: "sm",
|
|
964
|
+
size: "sm",
|
|
965
|
+
children: [/* @__PURE__ */ jsx(ClientOnly, { children: shortcut }), "+K"]
|
|
966
|
+
}),
|
|
967
|
+
radius: "md",
|
|
968
|
+
...props.actionProps,
|
|
969
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
970
|
+
align: "center",
|
|
971
|
+
gap: "xs",
|
|
972
|
+
children: [/* @__PURE__ */ jsx(IconSearch, {
|
|
973
|
+
size: 16,
|
|
974
|
+
color: "gray"
|
|
975
|
+
}), /* @__PURE__ */ jsx(Flex, {
|
|
976
|
+
visibleFrom: "sm",
|
|
977
|
+
miw: 192,
|
|
978
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
979
|
+
size: "xs",
|
|
980
|
+
c: "dimmed",
|
|
981
|
+
children: "Search..."
|
|
982
|
+
})
|
|
983
|
+
})]
|
|
984
|
+
})
|
|
985
|
+
});
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region ../../src/core/components/buttons/ToggleSidebarButton.tsx
|
|
990
|
+
const ToggleSidebarButton = (props) => {
|
|
991
|
+
const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
|
|
992
|
+
return /* @__PURE__ */ jsx(ActionButton, {
|
|
993
|
+
icon: sidebar.collapsed ? IconLayoutSidebarRightCollapse : IconLayoutSidebarLeftCollapse,
|
|
994
|
+
visibleFrom: "md",
|
|
995
|
+
variant: "subtle",
|
|
996
|
+
size: "md",
|
|
997
|
+
onClick: () => {
|
|
998
|
+
setSidebar({
|
|
999
|
+
...sidebar,
|
|
1000
|
+
collapsed: !sidebar.collapsed
|
|
1001
|
+
});
|
|
1002
|
+
},
|
|
1003
|
+
tooltip: {
|
|
1004
|
+
position: "right",
|
|
1005
|
+
label: sidebar.collapsed ? "Show sidebar" : "Hide sidebar"
|
|
1006
|
+
},
|
|
1007
|
+
...props
|
|
1008
|
+
});
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region ../../src/core/components/Flex.tsx
|
|
1013
|
+
const Flex$1 = forwardRef((props, ref) => {
|
|
1014
|
+
const { fill, center, centerX, centerY, col, ...rest } = props;
|
|
1015
|
+
if (fill) rest.flex ??= 1;
|
|
1016
|
+
if (col) rest.direction ??= "column";
|
|
1017
|
+
if (center) {
|
|
1018
|
+
rest.align ??= "center";
|
|
1019
|
+
rest.justify ??= "center";
|
|
1020
|
+
}
|
|
1021
|
+
if (centerX) rest.justify ??= "center";
|
|
1022
|
+
if (centerY) rest.align ??= "center";
|
|
1023
|
+
return /* @__PURE__ */ jsx(Flex, {
|
|
1024
|
+
ref,
|
|
1025
|
+
...rest
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
Flex$1.displayName = "Flex";
|
|
1029
|
+
|
|
1030
|
+
//#endregion
|
|
1031
|
+
//#region ../../src/core/components/Heading.tsx
|
|
1032
|
+
const Heading = (props) => {
|
|
1033
|
+
return /* @__PURE__ */ jsx("h1", { children: "Heading" });
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region ../../src/core/components/layout/AppBar.tsx
|
|
1038
|
+
const AppBar = (props) => {
|
|
1039
|
+
const { items = [] } = props;
|
|
1040
|
+
const router = useRouter();
|
|
1041
|
+
const renderItem = (item, index) => {
|
|
1042
|
+
if (item.can && !item.can()) return null;
|
|
1043
|
+
if ("type" in item) {
|
|
1044
|
+
if (item.type === "burger") return /* @__PURE__ */ jsx(BurgerButton, {}, index);
|
|
1045
|
+
if (item.type === "dark") return /* @__PURE__ */ jsx(DarkModeButton, { ...item.props }, index);
|
|
1046
|
+
if (item.type === "search") return /* @__PURE__ */ jsx(OmnibarButton, { ...item.props }, index);
|
|
1047
|
+
if (item.type === "lang") return /* @__PURE__ */ jsx(LanguageButton, { ...item.props }, index);
|
|
1048
|
+
if (item.type === "spacer") return /* @__PURE__ */ jsx(Flex$1, { w: 16 }, index);
|
|
1049
|
+
if (item.type === "divider") return /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }, index);
|
|
1050
|
+
if (item.type === "logo") return renderLogo(item, index);
|
|
1051
|
+
if (item.type === "back") return renderBack(item, index);
|
|
1052
|
+
}
|
|
1053
|
+
if ("element" in item) return item.element;
|
|
1054
|
+
return null;
|
|
1055
|
+
};
|
|
1056
|
+
const renderLogo = (item, index) => {
|
|
1057
|
+
const { src, text, icon, href, height = 32, width, fontWeight = 700, fontSize = "lg" } = item.props ?? {};
|
|
1058
|
+
const logoContent = src ? /* @__PURE__ */ jsx(Image, {
|
|
1059
|
+
src,
|
|
1060
|
+
h: height,
|
|
1061
|
+
w: width,
|
|
1062
|
+
fit: "contain"
|
|
1063
|
+
}) : icon ? typeof icon === "function" ? /* @__PURE__ */ jsx(icon, {}) : icon : text ? /* @__PURE__ */ jsx(Text$1, {
|
|
1064
|
+
fw: fontWeight,
|
|
1065
|
+
size: fontSize,
|
|
1066
|
+
children: text
|
|
1067
|
+
}) : null;
|
|
1068
|
+
if (href) return /* @__PURE__ */ jsx(Anchor, {
|
|
1069
|
+
component: Link,
|
|
1070
|
+
href,
|
|
1071
|
+
underline: "never",
|
|
1072
|
+
c: "inherit",
|
|
1073
|
+
children: logoContent
|
|
1074
|
+
}, index);
|
|
1075
|
+
return /* @__PURE__ */ jsx(Flex$1, { children: logoContent }, index);
|
|
1076
|
+
};
|
|
1077
|
+
const renderBack = (item, index) => {
|
|
1078
|
+
const { label = "Back", iconOnly = true, href, icon } = item.props ?? {};
|
|
1079
|
+
const renderIcon = () => {
|
|
1080
|
+
if (!icon) return /* @__PURE__ */ jsx(IconArrowLeft, { size: 18 });
|
|
1081
|
+
if (typeof icon === "function") return /* @__PURE__ */ jsx(icon, { size: 18 });
|
|
1082
|
+
return icon;
|
|
1083
|
+
};
|
|
1084
|
+
const iconElement = renderIcon();
|
|
1085
|
+
const handleClick = () => {
|
|
1086
|
+
if (href) router.push(href);
|
|
1087
|
+
else router.back();
|
|
1088
|
+
};
|
|
1089
|
+
if (iconOnly) return /* @__PURE__ */ jsx(ActionButton, {
|
|
1090
|
+
icon: iconElement,
|
|
1091
|
+
variant: "subtle",
|
|
1092
|
+
color: "gray",
|
|
1093
|
+
onClick: handleClick,
|
|
1094
|
+
tooltip: {
|
|
1095
|
+
label,
|
|
1096
|
+
position: "bottom"
|
|
1097
|
+
}
|
|
1098
|
+
}, index);
|
|
1099
|
+
return /* @__PURE__ */ jsx(ActionButton, {
|
|
1100
|
+
leftSection: iconElement,
|
|
1101
|
+
variant: "subtle",
|
|
1102
|
+
color: "gray",
|
|
1103
|
+
onClick: handleClick,
|
|
1104
|
+
children: label
|
|
1105
|
+
}, index);
|
|
1106
|
+
};
|
|
1107
|
+
const leftItems = items.filter((item) => item.position === "left");
|
|
1108
|
+
const centerItems = items.filter((item) => item.position === "center");
|
|
1109
|
+
const rightItems = items.filter((item) => item.position === "right");
|
|
1110
|
+
const content = /* @__PURE__ */ jsxs(Flex$1, {
|
|
1111
|
+
h: "100%",
|
|
1112
|
+
align: "center",
|
|
1113
|
+
px: props.container ? 0 : "md",
|
|
1114
|
+
justify: "space-between",
|
|
1115
|
+
...props.flexProps,
|
|
1116
|
+
children: [
|
|
1117
|
+
/* @__PURE__ */ jsx(Flex$1, {
|
|
1118
|
+
flex: 1,
|
|
1119
|
+
children: leftItems.map((item, index) => /* @__PURE__ */ jsx(Flex$1, {
|
|
1120
|
+
ml: index === 0 ? 0 : "md",
|
|
1121
|
+
align: "center",
|
|
1122
|
+
children: renderItem(item, index)
|
|
1123
|
+
}, index))
|
|
1124
|
+
}),
|
|
1125
|
+
/* @__PURE__ */ jsx(Flex$1, { children: centerItems.map((item, index) => /* @__PURE__ */ jsx(Flex$1, {
|
|
1126
|
+
mx: "md",
|
|
1127
|
+
align: "center",
|
|
1128
|
+
children: renderItem(item, index)
|
|
1129
|
+
}, index)) }),
|
|
1130
|
+
/* @__PURE__ */ jsx(Flex$1, {
|
|
1131
|
+
flex: 1,
|
|
1132
|
+
align: "center",
|
|
1133
|
+
justify: "end",
|
|
1134
|
+
children: rightItems.map((item, index) => /* @__PURE__ */ jsx(Flex$1, {
|
|
1135
|
+
ml: index === 0 ? 0 : "md",
|
|
1136
|
+
align: "center",
|
|
1137
|
+
children: renderItem(item, index)
|
|
1138
|
+
}, index))
|
|
1139
|
+
})
|
|
1140
|
+
]
|
|
1141
|
+
});
|
|
1142
|
+
if (props.container) return /* @__PURE__ */ jsx(Container, {
|
|
1143
|
+
h: "100%",
|
|
1144
|
+
...typeof props.container === "boolean" ? {} : props.container,
|
|
1145
|
+
children: content
|
|
1146
|
+
});
|
|
1147
|
+
return content;
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region ../../src/core/components/layout/Breadcrumb.tsx
|
|
1152
|
+
/**
|
|
1153
|
+
* Automatic breadcrumb component that reads the current route hierarchy
|
|
1154
|
+
* from the Alepha router's layer stack.
|
|
1155
|
+
*
|
|
1156
|
+
* Pages should define a `label` in their `$page()` options for best results.
|
|
1157
|
+
* Falls back to the page name converted to Title Case.
|
|
1158
|
+
*/
|
|
1159
|
+
const Breadcrumb = ({ home = "Home", separator, size = "sm", ...groupProps }) => {
|
|
1160
|
+
const state = useRouterState();
|
|
1161
|
+
const router = useRouter();
|
|
1162
|
+
const crumbs = [];
|
|
1163
|
+
if (home !== false) crumbs.push({
|
|
1164
|
+
label: home,
|
|
1165
|
+
href: "/"
|
|
1166
|
+
});
|
|
1167
|
+
for (let i = 1; i < state.layers.length; i++) {
|
|
1168
|
+
const layer = state.layers[i];
|
|
1169
|
+
const route = layer.route;
|
|
1170
|
+
if (route?.path === "/" || route?.path === "") continue;
|
|
1171
|
+
const label = route?.label ?? toTitleCase(layer.name);
|
|
1172
|
+
const href = router.path(layer.name);
|
|
1173
|
+
crumbs.push({
|
|
1174
|
+
label,
|
|
1175
|
+
href
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
if (crumbs.length === 0) return null;
|
|
1179
|
+
const sep = separator ?? /* @__PURE__ */ jsx(IconChevronRight, {
|
|
1180
|
+
size: 12,
|
|
1181
|
+
color: "#9ca3af"
|
|
1182
|
+
});
|
|
1183
|
+
return /* @__PURE__ */ jsx(Flex$1, {
|
|
1184
|
+
gap: "sm",
|
|
1185
|
+
...groupProps,
|
|
1186
|
+
children: crumbs.map((crumb, i) => /* @__PURE__ */ jsxs(Flex$1, {
|
|
1187
|
+
align: "center",
|
|
1188
|
+
gap: "sm",
|
|
1189
|
+
children: [i > 0 && sep, i < crumbs.length - 1 ? /* @__PURE__ */ jsx(ActionButton, {
|
|
1190
|
+
anchor: true,
|
|
1191
|
+
href: crumb.href,
|
|
1192
|
+
size,
|
|
1193
|
+
c: "dimmed",
|
|
1194
|
+
children: crumb.label
|
|
1195
|
+
}) : /* @__PURE__ */ jsx(Text$1, {
|
|
1196
|
+
size,
|
|
1197
|
+
fw: 500,
|
|
1198
|
+
children: crumb.label
|
|
1199
|
+
})]
|
|
1200
|
+
}, crumb.href))
|
|
1201
|
+
});
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
//#endregion
|
|
1205
|
+
//#region ../../src/core/components/layout/Container.tsx
|
|
1206
|
+
const Container$1 = forwardRef((props, ref) => {
|
|
1207
|
+
return /* @__PURE__ */ jsx(Container, {
|
|
1208
|
+
ref,
|
|
1209
|
+
...props
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
Container$1.displayName = "Container";
|
|
1213
|
+
|
|
1214
|
+
//#endregion
|
|
1215
|
+
//#region ../../src/core/components/layout/Sidebar.tsx
|
|
1216
|
+
const Sidebar = (props) => {
|
|
1217
|
+
const router = useRouter();
|
|
1218
|
+
const { onItemClick } = props;
|
|
1219
|
+
const divider = (key, fill, collapsed) => {
|
|
1220
|
+
return /* @__PURE__ */ jsx(Flex$1, {
|
|
1221
|
+
h: 1,
|
|
1222
|
+
bg: "var(--mantine-color-default-border)",
|
|
1223
|
+
my: "xs",
|
|
1224
|
+
mx: fill ? "calc(-1 * var(--mantine-spacing-md))" : collapsed ? 0 : "sm"
|
|
1225
|
+
}, key);
|
|
1226
|
+
};
|
|
1227
|
+
const renderNode = (item, key, collapsed) => {
|
|
1228
|
+
if ("type" in item) {
|
|
1229
|
+
if (item.type === "spacer") {
|
|
1230
|
+
if (collapsed) return null;
|
|
1231
|
+
return /* @__PURE__ */ jsx(Flex$1, { h: 16 }, key);
|
|
1232
|
+
}
|
|
1233
|
+
if (item.type === "divider") return divider(key, item.fill, collapsed);
|
|
1234
|
+
if (item.type === "search") return /* @__PURE__ */ jsx(Flex$1, {
|
|
1235
|
+
mb: "xs",
|
|
1236
|
+
children: /* @__PURE__ */ jsx(OmnibarButton, { collapsed })
|
|
1237
|
+
}, key);
|
|
1238
|
+
if (item.type === "toggle") return /* @__PURE__ */ jsx(ToggleSidebarButton, {}, key);
|
|
1239
|
+
if (item.type === "section") {
|
|
1240
|
+
if (item.children && item.children.length > 0) {
|
|
1241
|
+
if (!item.children.some((child) => !("can" in child) || !child.can || child.can())) return null;
|
|
1242
|
+
}
|
|
1243
|
+
if (collapsed) return /* @__PURE__ */ jsxs(Fragment$1, { children: [divider(`${key}-d`, void 0, collapsed), item.children?.map((child, index) => renderNode(child, `s${key}-${index}`, collapsed))] }, key);
|
|
1244
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Flex$1, {
|
|
1245
|
+
mt: "md",
|
|
1246
|
+
align: "center",
|
|
1247
|
+
gap: "xs",
|
|
1248
|
+
children: [renderIcon(item.icon, ui.sizes.icon.sm), /* @__PURE__ */ jsx(Text$1, {
|
|
1249
|
+
size: "xs",
|
|
1250
|
+
c: "dimmed",
|
|
1251
|
+
tt: "uppercase",
|
|
1252
|
+
fw: "bold",
|
|
1253
|
+
children: item.label
|
|
1254
|
+
})]
|
|
1255
|
+
}), item.children?.map((child, index) => renderNode(child, `s${key}-${index}`, collapsed))] }, key);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if ("element" in item) return /* @__PURE__ */ jsx(Fragment$1, { children: item.element }, key);
|
|
1259
|
+
if (item.can && !item.can()) return null;
|
|
1260
|
+
if (item.children && item.children.length > 0) {
|
|
1261
|
+
if (!item.children.some((child) => !child.can || child.can())) return null;
|
|
1262
|
+
}
|
|
1263
|
+
if (collapsed) return /* @__PURE__ */ jsx(SidebarCollapsedItem, {
|
|
1264
|
+
item,
|
|
1265
|
+
level: 0,
|
|
1266
|
+
onItemClick,
|
|
1267
|
+
theme: props.theme ?? {}
|
|
1268
|
+
}, key);
|
|
1269
|
+
return /* @__PURE__ */ jsx(SidebarItem, {
|
|
1270
|
+
item,
|
|
1271
|
+
level: 0,
|
|
1272
|
+
onItemClick,
|
|
1273
|
+
theme: props.theme ?? {}
|
|
1274
|
+
}, key);
|
|
1275
|
+
};
|
|
1276
|
+
const getSidebarNodes = () => {
|
|
1277
|
+
if (props.items) return props.items;
|
|
1278
|
+
if (props.autoPopulateMenu) {
|
|
1279
|
+
const items = router.concretePages.filter((page) => !page.can || page.can()).map((page) => ({
|
|
1280
|
+
label: page.label ?? page.name,
|
|
1281
|
+
icon: renderIcon(page.icon),
|
|
1282
|
+
href: router.path(page.name)
|
|
1283
|
+
}));
|
|
1284
|
+
if (typeof props.autoPopulateMenu === "object" && props.autoPopulateMenu.startsWith) {
|
|
1285
|
+
const startsWith = props.autoPopulateMenu.startsWith;
|
|
1286
|
+
return items.filter((item) => item.href?.startsWith(startsWith));
|
|
1287
|
+
}
|
|
1288
|
+
return items;
|
|
1289
|
+
}
|
|
1290
|
+
return [];
|
|
1291
|
+
};
|
|
1292
|
+
const padding = "md";
|
|
1293
|
+
const gap = props.items ? props.gap ?? 4 : "xs";
|
|
1294
|
+
const menu = useMemo(() => getSidebarNodes(), [props.items, props.autoPopulateMenu]);
|
|
1295
|
+
const renderSidebar = (collapsed) => /* @__PURE__ */ jsxs(Flex$1, {
|
|
1296
|
+
flex: 1,
|
|
1297
|
+
py: padding,
|
|
1298
|
+
direction: "column",
|
|
1299
|
+
...props.flexProps,
|
|
1300
|
+
children: [
|
|
1301
|
+
/* @__PURE__ */ jsx(Flex$1, {
|
|
1302
|
+
gap,
|
|
1303
|
+
px: padding,
|
|
1304
|
+
direction: "column",
|
|
1305
|
+
children: menu.filter((it) => it.position === "top").map((item, index) => renderNode(item, index, collapsed))
|
|
1306
|
+
}),
|
|
1307
|
+
/* @__PURE__ */ jsx(Flex$1, {
|
|
1308
|
+
gap,
|
|
1309
|
+
px: padding,
|
|
1310
|
+
direction: "column",
|
|
1311
|
+
flex: 1,
|
|
1312
|
+
children: menu.filter((it) => !it.position).map((item, index) => renderNode(item, index, collapsed))
|
|
1313
|
+
}),
|
|
1314
|
+
/* @__PURE__ */ jsx(Flex$1, {
|
|
1315
|
+
gap,
|
|
1316
|
+
px: padding,
|
|
1317
|
+
direction: "column",
|
|
1318
|
+
children: menu.filter((it) => it.position === "bottom").map((item, index) => renderNode(item, index, collapsed))
|
|
1319
|
+
})
|
|
1320
|
+
]
|
|
1321
|
+
});
|
|
1322
|
+
if (props.collapsed) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex$1, {
|
|
1323
|
+
flex: 1,
|
|
1324
|
+
direction: "column",
|
|
1325
|
+
visibleFrom: "md",
|
|
1326
|
+
children: renderSidebar(true)
|
|
1327
|
+
}), /* @__PURE__ */ jsx(Flex$1, {
|
|
1328
|
+
flex: 1,
|
|
1329
|
+
direction: "column",
|
|
1330
|
+
hiddenFrom: "md",
|
|
1331
|
+
children: renderSidebar(false)
|
|
1332
|
+
})] });
|
|
1333
|
+
return renderSidebar(false);
|
|
1334
|
+
};
|
|
1335
|
+
const SidebarItem = (props) => {
|
|
1336
|
+
const { item, level } = props;
|
|
1337
|
+
const maxLevel = 2;
|
|
1338
|
+
const router = useRouter();
|
|
1339
|
+
const isActive = useCallback((item) => {
|
|
1340
|
+
if (!item.children) return false;
|
|
1341
|
+
for (const child of item.children) {
|
|
1342
|
+
if (child.href) {
|
|
1343
|
+
if (router.isActive(child.href)) return true;
|
|
1344
|
+
}
|
|
1345
|
+
if (isActive(child)) return true;
|
|
1346
|
+
}
|
|
1347
|
+
return false;
|
|
1348
|
+
}, []);
|
|
1349
|
+
const [isOpen, setIsOpen] = useState(isActive(item));
|
|
1350
|
+
useEvents({ "react:transition:end": () => {
|
|
1351
|
+
if (isActive(item)) setIsOpen(true);
|
|
1352
|
+
} }, []);
|
|
1353
|
+
if (level > maxLevel) return null;
|
|
1354
|
+
const handleItemClick = (e) => {
|
|
1355
|
+
if (!props.item.target) e.preventDefault();
|
|
1356
|
+
if (item.children && item.children.length > 0) setIsOpen(!isOpen);
|
|
1357
|
+
else {
|
|
1358
|
+
props.onItemClick?.(item);
|
|
1359
|
+
item.onClick?.();
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
return /* @__PURE__ */ jsxs(Flex$1, {
|
|
1363
|
+
direction: "column",
|
|
1364
|
+
ps: level === 0 ? 0 : 32,
|
|
1365
|
+
pos: "relative",
|
|
1366
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
1367
|
+
w: "100%",
|
|
1368
|
+
justify: "space-between",
|
|
1369
|
+
href: props.item.href,
|
|
1370
|
+
target: props.item.target,
|
|
1371
|
+
size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
|
|
1372
|
+
bd: 0,
|
|
1373
|
+
fw: "normal",
|
|
1374
|
+
variant: "default",
|
|
1375
|
+
propsActive: {
|
|
1376
|
+
variant: "outline",
|
|
1377
|
+
fw: "bold"
|
|
1378
|
+
},
|
|
1379
|
+
radius: props.item.theme?.radius ?? props.theme.button?.radius ?? "md",
|
|
1380
|
+
onClick: handleItemClick,
|
|
1381
|
+
leftSection: /* @__PURE__ */ jsxs(Flex$1, {
|
|
1382
|
+
w: "100%",
|
|
1383
|
+
align: "center",
|
|
1384
|
+
gap: "sm",
|
|
1385
|
+
children: [renderIcon(item.icon, ui.sizes.icon.sm), /* @__PURE__ */ jsx(Flex$1, {
|
|
1386
|
+
direction: "column",
|
|
1387
|
+
children: /* @__PURE__ */ jsx(Flex$1, { children: item.label })
|
|
1388
|
+
})]
|
|
1389
|
+
}),
|
|
1390
|
+
rightSection: item.children ? /* @__PURE__ */ jsx(Flex$1, { children: isOpen ? /* @__PURE__ */ jsx(IconChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(IconChevronRight, { size: 14 }) }) : props.item.rightSection,
|
|
1391
|
+
...props.item.actionProps
|
|
1392
|
+
}), item.children && isOpen && /* @__PURE__ */ jsxs(Flex$1, {
|
|
1393
|
+
direction: "column",
|
|
1394
|
+
"data-parent-level": level,
|
|
1395
|
+
children: [/* @__PURE__ */ jsx(Flex$1, { style: {
|
|
1396
|
+
position: "absolute",
|
|
1397
|
+
width: 1,
|
|
1398
|
+
background: "linear-gradient(to bottom, transparent, var(--mantine-color-default-border), transparent)",
|
|
1399
|
+
top: 48,
|
|
1400
|
+
left: 20 + 32 * level,
|
|
1401
|
+
bottom: 16
|
|
1402
|
+
} }), item.children.filter((child) => !child.can || child.can()).map((child, index) => /* @__PURE__ */ jsx(SidebarItem, {
|
|
1403
|
+
item: child,
|
|
1404
|
+
level: level + 1,
|
|
1405
|
+
onItemClick: props.onItemClick,
|
|
1406
|
+
theme: props.theme
|
|
1407
|
+
}, index))]
|
|
1408
|
+
})]
|
|
1409
|
+
});
|
|
1410
|
+
};
|
|
1411
|
+
const SidebarCollapsedItem = (props) => {
|
|
1412
|
+
const { item, level } = props;
|
|
1413
|
+
const router = useRouter();
|
|
1414
|
+
const handleItemClick = () => {
|
|
1415
|
+
props.onItemClick?.(item);
|
|
1416
|
+
item.onClick?.();
|
|
1417
|
+
};
|
|
1418
|
+
const hasChildren = item.children && item.children.length > 0;
|
|
1419
|
+
const menu = hasChildren ? {
|
|
1420
|
+
on: "hover",
|
|
1421
|
+
position: "right",
|
|
1422
|
+
items: item.children.filter((child) => !child.can || child.can()).map((child) => ({
|
|
1423
|
+
label: child.label,
|
|
1424
|
+
icon: renderIcon(child.icon, ui.sizes.icon.sm),
|
|
1425
|
+
href: child.href,
|
|
1426
|
+
active: child.href ? router.isActive(child.href, { startWith: child.activeStartsWith }) : void 0
|
|
1427
|
+
}))
|
|
1428
|
+
} : void 0;
|
|
1429
|
+
return /* @__PURE__ */ jsx(ActionButton, {
|
|
1430
|
+
size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
|
|
1431
|
+
variant: "subtle",
|
|
1432
|
+
variantActive: "default",
|
|
1433
|
+
tooltip: hasChildren ? void 0 : {
|
|
1434
|
+
label: item.label,
|
|
1435
|
+
position: "right"
|
|
1436
|
+
},
|
|
1437
|
+
radius: props.item.theme?.radius ?? props.theme.button?.radius ?? "md",
|
|
1438
|
+
onClick: hasChildren ? void 0 : handleItemClick,
|
|
1439
|
+
icon: renderIcon(item.icon, ui.sizes.icon.sm) ?? /* @__PURE__ */ jsx(IconSquareRounded, { size: ui.sizes.icon.sm }),
|
|
1440
|
+
href: hasChildren ? void 0 : props.item.href,
|
|
1441
|
+
target: hasChildren ? void 0 : props.item.target,
|
|
1442
|
+
menu,
|
|
1443
|
+
...props.item.actionProps
|
|
1444
|
+
});
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
//#endregion
|
|
1448
|
+
//#region ../../src/core/components/layout/DashboardShell.tsx
|
|
1449
|
+
const DashboardShell = (props) => {
|
|
1450
|
+
const router = useRouter();
|
|
1451
|
+
const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
|
|
1452
|
+
const collapsed = props.sidebarProps?.collapsed !== void 0 ? props.sidebarProps.collapsed : sidebar.collapsed;
|
|
1453
|
+
const { collapsedWidth, expandedWidth } = sidebar;
|
|
1454
|
+
const shouldShowSidebar = () => {
|
|
1455
|
+
if (props.noSidebarWhen?.paths) {
|
|
1456
|
+
for (const path of props.noSidebarWhen.paths) if (router.isActive(path, { startWith: true })) return false;
|
|
1457
|
+
}
|
|
1458
|
+
return true;
|
|
1459
|
+
};
|
|
1460
|
+
const [showSidebar, setShowSidebar] = useState(shouldShowSidebar());
|
|
1461
|
+
useEvents({
|
|
1462
|
+
"react:transition:end": () => {
|
|
1463
|
+
setShowSidebar(shouldShowSidebar());
|
|
1464
|
+
},
|
|
1465
|
+
"react:transition:begin": () => {
|
|
1466
|
+
setSidebar({
|
|
1467
|
+
...sidebar,
|
|
1468
|
+
closed: true
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}, [sidebar]);
|
|
1472
|
+
const defaultAppBarItems = [{
|
|
1473
|
+
position: "left",
|
|
1474
|
+
type: "burger"
|
|
1475
|
+
}];
|
|
1476
|
+
const appBarProps = { ...props.appBarProps };
|
|
1477
|
+
appBarProps.container ??= props.container;
|
|
1478
|
+
const hasSidebar = showSidebar && props.sidebarProps !== void 0;
|
|
1479
|
+
const hasAppBar = props.appBarProps || props.header;
|
|
1480
|
+
let footerElement = props.footer;
|
|
1481
|
+
if (props.footerHeight) footerElement ??= /* @__PURE__ */ jsx(Flex$1, { h: props.footerHeight });
|
|
1482
|
+
const hHeight = props.headerHeight ?? 60;
|
|
1483
|
+
const fHeight = props.footerHeight ?? 24;
|
|
1484
|
+
const headerHeight = hasAppBar ? hHeight : 0;
|
|
1485
|
+
const footerHeight = footerElement ? fHeight : 0;
|
|
1486
|
+
return /* @__PURE__ */ jsxs(AppShell, {
|
|
1487
|
+
layout: "alt",
|
|
1488
|
+
w: "100%",
|
|
1489
|
+
flex: 1,
|
|
1490
|
+
header: hasAppBar ? { height: hHeight } : void 0,
|
|
1491
|
+
navbar: hasSidebar ? {
|
|
1492
|
+
width: { base: collapsed ? collapsedWidth : expandedWidth },
|
|
1493
|
+
breakpoint: "md",
|
|
1494
|
+
collapsed: { mobile: sidebar.closed }
|
|
1495
|
+
} : void 0,
|
|
1496
|
+
footer: footerElement ? { height: fHeight } : void 0,
|
|
1497
|
+
...props.appShellProps,
|
|
1498
|
+
children: [
|
|
1499
|
+
hasAppBar && /* @__PURE__ */ jsx(AppShell.Header, {
|
|
1500
|
+
...props.appShellHeaderProps,
|
|
1501
|
+
children: props.header ?? /* @__PURE__ */ jsx(AppBar, {
|
|
1502
|
+
items: defaultAppBarItems,
|
|
1503
|
+
...appBarProps
|
|
1504
|
+
})
|
|
1505
|
+
}),
|
|
1506
|
+
hasSidebar && /* @__PURE__ */ jsxs(AppShell.Navbar, {
|
|
1507
|
+
className: "alepha-sidebar-navbar",
|
|
1508
|
+
...props.appShellNavbarProps,
|
|
1509
|
+
children: [
|
|
1510
|
+
props.navbarHeader ? /* @__PURE__ */ jsx(Flex$1, {
|
|
1511
|
+
style: { borderBottom: "1px solid var(--mantine-color-default-border)" },
|
|
1512
|
+
h: headerHeight,
|
|
1513
|
+
children: props.navbarHeader
|
|
1514
|
+
}) : null,
|
|
1515
|
+
/* @__PURE__ */ jsx(Sidebar, {
|
|
1516
|
+
...props.sidebarProps ?? {},
|
|
1517
|
+
collapsed
|
|
1518
|
+
}),
|
|
1519
|
+
props.navbarFooter ? /* @__PURE__ */ jsx(Flex$1, {
|
|
1520
|
+
style: { borderTop: "1px solid var(--mantine-color-default-border)" },
|
|
1521
|
+
h: footerHeight,
|
|
1522
|
+
children: props.navbarFooter
|
|
1523
|
+
}) : null
|
|
1524
|
+
]
|
|
1525
|
+
}),
|
|
1526
|
+
/* @__PURE__ */ jsx(AppShell.Main, {
|
|
1527
|
+
pos: "relative",
|
|
1528
|
+
...props.appShellMainProps,
|
|
1529
|
+
children: props.children ?? /* @__PURE__ */ jsx(NestedView, {})
|
|
1530
|
+
}),
|
|
1531
|
+
footerElement && /* @__PURE__ */ jsx(AppShell.Footer, {
|
|
1532
|
+
...props.appShellFooterProps,
|
|
1533
|
+
children: footerElement
|
|
1534
|
+
})
|
|
1535
|
+
]
|
|
1536
|
+
});
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
//#endregion
|
|
1540
|
+
//#region ../../src/core/components/Text.tsx
|
|
1541
|
+
const INTENT_COLORS = {
|
|
1542
|
+
primary: "blue",
|
|
1543
|
+
info: "cyan",
|
|
1544
|
+
success: "green",
|
|
1545
|
+
warning: "yellow",
|
|
1546
|
+
danger: "red"
|
|
1547
|
+
};
|
|
1548
|
+
const Text$1 = forwardRef((props, ref) => {
|
|
1549
|
+
const { intent, bold, italic, light, muted, small, uppercase, capitalize, center, monospace, title, ...rest } = props;
|
|
1550
|
+
if (intent) rest.c ??= INTENT_COLORS[intent];
|
|
1551
|
+
if (bold) rest.fw ??= 700;
|
|
1552
|
+
if (light) rest.fw ??= 300;
|
|
1553
|
+
if (italic) rest.fs ??= "italic";
|
|
1554
|
+
if (muted) rest.c ??= "dimmed";
|
|
1555
|
+
if (small) rest.size ??= "sm";
|
|
1556
|
+
if (uppercase) rest.tt ??= "uppercase";
|
|
1557
|
+
if (capitalize) rest.tt ??= "capitalize";
|
|
1558
|
+
if (center) rest.ta ??= "center";
|
|
1559
|
+
if (monospace) rest.ff ??= "monospace";
|
|
1560
|
+
if (title) rest.size ??= "xl";
|
|
1561
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
1562
|
+
ref,
|
|
1563
|
+
...rest
|
|
1564
|
+
});
|
|
1565
|
+
});
|
|
1566
|
+
Text$1.displayName = "Text";
|
|
1567
|
+
|
|
1568
|
+
//#endregion
|
|
1569
|
+
//#region ../../src/core/form/utils/parseInput.ts
|
|
1570
|
+
const parseInput = (props, form) => {
|
|
1571
|
+
const disabled = false;
|
|
1572
|
+
const id = props.input.props.id;
|
|
1573
|
+
const label = props.title ?? ("title" in props.input.schema && typeof props.input.schema.title === "string" ? props.input.schema.title : void 0) ?? prettyName(props.input.path);
|
|
1574
|
+
const description = props.description ?? ("description" in props.input.schema && typeof props.input.schema.description === "string" ? props.input.schema.description : void 0);
|
|
1575
|
+
const error = form.error && form.error instanceof TypeBoxError ? form.error.value.message : void 0;
|
|
1576
|
+
const icon = !props.icon ? getDefaultIcon({
|
|
1577
|
+
type: props.input.schema && "type" in props.input.schema ? String(props.input.schema.type) : void 0,
|
|
1578
|
+
format: props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0,
|
|
1579
|
+
name: props.input.props.name,
|
|
1580
|
+
isEnum: props.input.schema && "enum" in props.input.schema && Boolean(props.input.schema.enum),
|
|
1581
|
+
isArray: props.input.schema && "type" in props.input.schema && props.input.schema.type === "array"
|
|
1582
|
+
}) : isValidElement(props.icon) ? props.icon : createElement(props.icon, { size: ui.sizes.icon.md });
|
|
1583
|
+
const format = props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0;
|
|
1584
|
+
const required = props.input.required;
|
|
1585
|
+
const schema = props.input.schema;
|
|
1586
|
+
const inputProps = {
|
|
1587
|
+
label,
|
|
1588
|
+
description,
|
|
1589
|
+
error,
|
|
1590
|
+
required,
|
|
1591
|
+
disabled
|
|
1592
|
+
};
|
|
1593
|
+
if ("minLength" in schema && typeof schema.minLength === "number") inputProps.minLength = schema.minLength;
|
|
1594
|
+
if ("maxLength" in schema && typeof schema.maxLength === "number") inputProps.maxLength = schema.maxLength;
|
|
1595
|
+
if ("minimum" in schema && typeof schema.minimum === "number") inputProps.minimum = schema.minimum;
|
|
1596
|
+
if ("maximum" in schema && typeof schema.maximum === "number") inputProps.maximum = schema.maximum;
|
|
1597
|
+
return {
|
|
1598
|
+
id,
|
|
1599
|
+
icon,
|
|
1600
|
+
format,
|
|
1601
|
+
schema: props.input.schema,
|
|
1602
|
+
inputProps
|
|
1603
|
+
};
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region ../../src/core/form/components/ControlArray.tsx
|
|
1608
|
+
/**
|
|
1609
|
+
* Custom hook to sync array items with form state.
|
|
1610
|
+
* Uses form events as the source of truth, eliminating dual-state issues.
|
|
1611
|
+
*/
|
|
1612
|
+
const useArrayItems = (input) => {
|
|
1613
|
+
const alepha = useAlepha();
|
|
1614
|
+
const keyCounter = useRef(0);
|
|
1615
|
+
const [items, setItemsState] = useState(() => {
|
|
1616
|
+
const defaultValue = input?.props?.defaultValue;
|
|
1617
|
+
if (Array.isArray(defaultValue)) return defaultValue.map((value) => ({
|
|
1618
|
+
key: keyCounter.current++,
|
|
1619
|
+
value
|
|
1620
|
+
}));
|
|
1621
|
+
return [];
|
|
1622
|
+
});
|
|
1623
|
+
const syncFromFormValue = useCallback((formValue) => {
|
|
1624
|
+
if (!Array.isArray(formValue)) {
|
|
1625
|
+
setItemsState([]);
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
setItemsState((prevItems) => {
|
|
1629
|
+
if (prevItems.length === formValue.length) {
|
|
1630
|
+
if (prevItems.every((item, i) => item.value === formValue[i])) return prevItems;
|
|
1631
|
+
}
|
|
1632
|
+
keyCounter.current = 0;
|
|
1633
|
+
return formValue.map((value) => ({
|
|
1634
|
+
key: keyCounter.current++,
|
|
1635
|
+
value
|
|
1636
|
+
}));
|
|
1637
|
+
});
|
|
1638
|
+
}, []);
|
|
1639
|
+
useEffect(() => {
|
|
1640
|
+
if (!input?.form) return;
|
|
1641
|
+
const formId = input.form.id;
|
|
1642
|
+
const fieldPath = input.path;
|
|
1643
|
+
const listeners = [alepha.events.on("form:reset", (event) => {
|
|
1644
|
+
if (event.id === formId) {
|
|
1645
|
+
const defaultValue = input.props?.defaultValue;
|
|
1646
|
+
keyCounter.current = 0;
|
|
1647
|
+
if (Array.isArray(defaultValue)) setItemsState(defaultValue.map((value) => ({
|
|
1648
|
+
key: keyCounter.current++,
|
|
1649
|
+
value
|
|
1650
|
+
})));
|
|
1651
|
+
else setItemsState([]);
|
|
1652
|
+
}
|
|
1653
|
+
}), alepha.events.on("form:change", (event) => {
|
|
1654
|
+
if (event.id === formId && event.path === fieldPath) syncFromFormValue(event.value);
|
|
1655
|
+
})];
|
|
1656
|
+
return () => {
|
|
1657
|
+
for (const unsub of listeners) unsub();
|
|
1658
|
+
};
|
|
1659
|
+
}, [
|
|
1660
|
+
alepha,
|
|
1661
|
+
input,
|
|
1662
|
+
syncFromFormValue
|
|
1663
|
+
]);
|
|
1664
|
+
return {
|
|
1665
|
+
items,
|
|
1666
|
+
setItems: useCallback((newItems) => {
|
|
1667
|
+
setItemsState(newItems);
|
|
1668
|
+
input?.set(newItems.map((item) => item.value));
|
|
1669
|
+
}, [input]),
|
|
1670
|
+
nextKey: useCallback(() => keyCounter.current++, [])
|
|
1671
|
+
};
|
|
1672
|
+
};
|
|
1673
|
+
/**
|
|
1674
|
+
* Creates a proper InputField for an array item that integrates with the form system.
|
|
1675
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
1676
|
+
*/
|
|
1677
|
+
const createArrayItemInput = (parentInput, itemSchema, index, _itemKey, value, onValueChange) => {
|
|
1678
|
+
return {
|
|
1679
|
+
schema: itemSchema,
|
|
1680
|
+
path: `${parentInput.path}/${index}`,
|
|
1681
|
+
required: false,
|
|
1682
|
+
form: parentInput.form,
|
|
1683
|
+
props: {
|
|
1684
|
+
id: `${parentInput.props.id}-${index}`,
|
|
1685
|
+
name: `${parentInput.props.name}[${index}]`,
|
|
1686
|
+
defaultValue: value
|
|
1687
|
+
},
|
|
1688
|
+
set: onValueChange
|
|
1689
|
+
};
|
|
1690
|
+
};
|
|
1691
|
+
/**
|
|
1692
|
+
* Creates a proper InputField for a nested object field within an array item.
|
|
1693
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
1694
|
+
*/
|
|
1695
|
+
const createArrayItemFieldInput = (parentInput, itemSchema, fieldName, index, _itemKey, itemValue, onFieldChange) => {
|
|
1696
|
+
return {
|
|
1697
|
+
schema: itemSchema.properties[fieldName],
|
|
1698
|
+
path: `${parentInput.path}/${index}/${fieldName}`,
|
|
1699
|
+
required: itemSchema.required?.includes(fieldName) ?? false,
|
|
1700
|
+
form: parentInput.form,
|
|
1701
|
+
props: {
|
|
1702
|
+
id: `${parentInput.props.id}-${index}-${fieldName}`,
|
|
1703
|
+
name: `${parentInput.props.name}[${index}].${fieldName}`,
|
|
1704
|
+
defaultValue: itemValue?.[fieldName]
|
|
1705
|
+
},
|
|
1706
|
+
set: (value) => onFieldChange(fieldName, value)
|
|
1707
|
+
};
|
|
1708
|
+
};
|
|
1709
|
+
/**
|
|
1710
|
+
* ControlArray component for editing arrays of schema items.
|
|
1711
|
+
*
|
|
1712
|
+
* Features:
|
|
1713
|
+
* - Dynamic add/remove of items
|
|
1714
|
+
* - Supports arrays of objects with nested fields
|
|
1715
|
+
* - Supports arrays of primitives
|
|
1716
|
+
* - Grid layout for object items
|
|
1717
|
+
* - Min/max constraints
|
|
1718
|
+
* - Syncs with form state (handles external updates and resets)
|
|
1719
|
+
*
|
|
1720
|
+
* @example
|
|
1721
|
+
* ```tsx
|
|
1722
|
+
* // For a schema like:
|
|
1723
|
+
* // t.object({
|
|
1724
|
+
* // contacts: t.array(t.object({
|
|
1725
|
+
* // name: t.text(),
|
|
1726
|
+
* // email: t.text({ format: "email" }),
|
|
1727
|
+
* // }))
|
|
1728
|
+
* // })
|
|
1729
|
+
*
|
|
1730
|
+
* <ControlArray
|
|
1731
|
+
* input={form.input.contacts}
|
|
1732
|
+
* columns={2}
|
|
1733
|
+
* addLabel="Add contact"
|
|
1734
|
+
* controlProps={{
|
|
1735
|
+
* email: { text: { placeholder: "email@example.com" } }
|
|
1736
|
+
* }}
|
|
1737
|
+
* />
|
|
1738
|
+
* ```
|
|
1739
|
+
*/
|
|
1740
|
+
const ControlArray = (props) => {
|
|
1741
|
+
const { inputProps } = parseInput(props, {});
|
|
1742
|
+
const { items, setItems, nextKey } = useArrayItems(props.input);
|
|
1743
|
+
if (!props.input?.props) return null;
|
|
1744
|
+
const schema = props.input.schema;
|
|
1745
|
+
if (!schema || !("items" in schema)) return null;
|
|
1746
|
+
const itemSchema = schema.items;
|
|
1747
|
+
const isObjectItem = itemSchema && "properties" in itemSchema;
|
|
1748
|
+
const { min = 0, max = Number.POSITIVE_INFINITY, columns = 1 } = props;
|
|
1749
|
+
const handleAdd = () => {
|
|
1750
|
+
if (items.length >= max) return;
|
|
1751
|
+
let newValue;
|
|
1752
|
+
if (isObjectItem) {
|
|
1753
|
+
newValue = {};
|
|
1754
|
+
const objSchema = itemSchema;
|
|
1755
|
+
for (const [key, propSchema] of Object.entries(objSchema.properties)) if ("default" in propSchema) newValue[key] = propSchema.default;
|
|
1756
|
+
} else newValue = "";
|
|
1757
|
+
setItems([...items, {
|
|
1758
|
+
key: nextKey(),
|
|
1759
|
+
value: newValue
|
|
1760
|
+
}]);
|
|
1761
|
+
};
|
|
1762
|
+
const handleRemove = (index) => {
|
|
1763
|
+
if (items.length <= min) return;
|
|
1764
|
+
setItems(items.filter((_, i) => i !== index));
|
|
1765
|
+
};
|
|
1766
|
+
const handleItemChange = (index, value) => {
|
|
1767
|
+
const newItems = [...items];
|
|
1768
|
+
newItems[index] = {
|
|
1769
|
+
...newItems[index],
|
|
1770
|
+
value
|
|
1771
|
+
};
|
|
1772
|
+
setItems(newItems);
|
|
1773
|
+
};
|
|
1774
|
+
const handleFieldChange = (index, field, value) => {
|
|
1775
|
+
const newItems = [...items];
|
|
1776
|
+
newItems[index] = {
|
|
1777
|
+
...newItems[index],
|
|
1778
|
+
value: {
|
|
1779
|
+
...newItems[index].value,
|
|
1780
|
+
[field]: value
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
setItems(newItems);
|
|
1784
|
+
};
|
|
1785
|
+
const colSpan = 12 / columns;
|
|
1786
|
+
const objectItemSchema = isObjectItem ? itemSchema : null;
|
|
1787
|
+
const fieldNames = objectItemSchema ? Object.keys(objectItemSchema.properties) : [];
|
|
1788
|
+
const renderItems = () => /* @__PURE__ */ jsxs(Flex, {
|
|
1789
|
+
direction: "column",
|
|
1790
|
+
gap: "sm",
|
|
1791
|
+
children: [items.map((item, index) => /* @__PURE__ */ jsxs(Flex, {
|
|
1792
|
+
gap: "sm",
|
|
1793
|
+
align: "flex-start",
|
|
1794
|
+
p: "xs",
|
|
1795
|
+
bg: ui.colors.surface,
|
|
1796
|
+
style: { borderRadius: "var(--mantine-radius-sm)" },
|
|
1797
|
+
children: [
|
|
1798
|
+
props.sortable && /* @__PURE__ */ jsx(ActionIcon, {
|
|
1799
|
+
variant: "subtle",
|
|
1800
|
+
color: "gray",
|
|
1801
|
+
style: { cursor: "grab" },
|
|
1802
|
+
children: /* @__PURE__ */ jsx(IconGripVertical, { size: 16 })
|
|
1803
|
+
}),
|
|
1804
|
+
objectItemSchema ? /* @__PURE__ */ jsx(Grid, {
|
|
1805
|
+
style: { flex: 1 },
|
|
1806
|
+
gutter: "sm",
|
|
1807
|
+
children: fieldNames.map((fieldName) => {
|
|
1808
|
+
const fieldControlProps = props.controlProps?.[fieldName] ?? {};
|
|
1809
|
+
const fieldInput = createArrayItemFieldInput(props.input, objectItemSchema, fieldName, index, item.key, item.value, (field, value) => handleFieldChange(index, field, value));
|
|
1810
|
+
return /* @__PURE__ */ jsx(Grid.Col, {
|
|
1811
|
+
span: colSpan,
|
|
1812
|
+
children: /* @__PURE__ */ jsx(Control, {
|
|
1813
|
+
input: fieldInput,
|
|
1814
|
+
...fieldControlProps
|
|
1815
|
+
})
|
|
1816
|
+
}, fieldName);
|
|
1817
|
+
})
|
|
1818
|
+
}) : /* @__PURE__ */ jsx(Flex, {
|
|
1819
|
+
style: { flex: 1 },
|
|
1820
|
+
children: /* @__PURE__ */ jsx(Control, {
|
|
1821
|
+
input: createArrayItemInput(props.input, itemSchema, index, item.key, item.value, (value) => handleItemChange(index, value)),
|
|
1822
|
+
...props.itemControlProps
|
|
1823
|
+
})
|
|
1824
|
+
}),
|
|
1825
|
+
/* @__PURE__ */ jsx(ActionIcon, {
|
|
1826
|
+
variant: "subtle",
|
|
1827
|
+
color: "red",
|
|
1828
|
+
onClick: () => handleRemove(index),
|
|
1829
|
+
disabled: items.length <= min,
|
|
1830
|
+
children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
|
|
1831
|
+
})
|
|
1832
|
+
]
|
|
1833
|
+
}, item.key)), /* @__PURE__ */ jsxs(UnstyledButton, {
|
|
1834
|
+
onClick: handleAdd,
|
|
1835
|
+
disabled: items.length >= max,
|
|
1836
|
+
style: {
|
|
1837
|
+
display: "flex",
|
|
1838
|
+
alignItems: "center",
|
|
1839
|
+
justifyContent: "center",
|
|
1840
|
+
gap: 6,
|
|
1841
|
+
padding: "8px 12px",
|
|
1842
|
+
borderRadius: "var(--mantine-radius-sm)",
|
|
1843
|
+
border: "1px dashed var(--mantine-color-dimmed)",
|
|
1844
|
+
color: "var(--mantine-color-dimmed)",
|
|
1845
|
+
fontSize: "var(--mantine-font-size-sm)",
|
|
1846
|
+
cursor: items.length >= max ? "not-allowed" : "pointer",
|
|
1847
|
+
opacity: items.length >= max ? .5 : 1,
|
|
1848
|
+
transition: "all 150ms ease"
|
|
1849
|
+
},
|
|
1850
|
+
onMouseEnter: (e) => {
|
|
1851
|
+
if (items.length < max) {
|
|
1852
|
+
e.currentTarget.style.borderColor = "var(--mantine-color-blue-filled)";
|
|
1853
|
+
e.currentTarget.style.color = "var(--mantine-color-blue-filled)";
|
|
1854
|
+
e.currentTarget.style.background = "var(--mantine-color-blue-light)";
|
|
1855
|
+
}
|
|
1856
|
+
},
|
|
1857
|
+
onMouseLeave: (e) => {
|
|
1858
|
+
e.currentTarget.style.borderColor = "var(--mantine-color-dimmed)";
|
|
1859
|
+
e.currentTarget.style.color = "var(--mantine-color-dimmed)";
|
|
1860
|
+
e.currentTarget.style.background = "transparent";
|
|
1861
|
+
},
|
|
1862
|
+
children: [/* @__PURE__ */ jsx(IconPlus, { size: 14 }), props.addLabel ?? "Add"]
|
|
1863
|
+
})]
|
|
1864
|
+
});
|
|
1865
|
+
if (props.variant === "plain") return /* @__PURE__ */ jsxs(Flex, {
|
|
1866
|
+
direction: "column",
|
|
1867
|
+
gap: "xs",
|
|
1868
|
+
children: [
|
|
1869
|
+
inputProps.label && /* @__PURE__ */ jsx(Text, {
|
|
1870
|
+
size: "sm",
|
|
1871
|
+
fw: 500,
|
|
1872
|
+
children: inputProps.label
|
|
1873
|
+
}),
|
|
1874
|
+
inputProps.description && /* @__PURE__ */ jsx(Text, {
|
|
1875
|
+
size: "sm",
|
|
1876
|
+
c: "dimmed",
|
|
1877
|
+
children: inputProps.description
|
|
1878
|
+
}),
|
|
1879
|
+
renderItems(),
|
|
1880
|
+
inputProps.error && /* @__PURE__ */ jsx(Text, {
|
|
1881
|
+
size: "sm",
|
|
1882
|
+
c: "red",
|
|
1883
|
+
children: inputProps.error
|
|
1884
|
+
})
|
|
1885
|
+
]
|
|
1886
|
+
});
|
|
1887
|
+
return /* @__PURE__ */ jsx(Fieldset, {
|
|
1888
|
+
legend: inputProps.label,
|
|
1889
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
1890
|
+
direction: "column",
|
|
1891
|
+
gap: "xs",
|
|
1892
|
+
children: [
|
|
1893
|
+
inputProps.description && /* @__PURE__ */ jsx(Text, {
|
|
1894
|
+
size: "sm",
|
|
1895
|
+
c: "dimmed",
|
|
1896
|
+
children: inputProps.description
|
|
1897
|
+
}),
|
|
1898
|
+
renderItems(),
|
|
1899
|
+
inputProps.error && /* @__PURE__ */ jsx(Text, {
|
|
1900
|
+
size: "sm",
|
|
1901
|
+
c: "red",
|
|
1902
|
+
children: inputProps.error
|
|
1903
|
+
})
|
|
1904
|
+
]
|
|
1905
|
+
})
|
|
1906
|
+
});
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
//#endregion
|
|
1910
|
+
//#region ../../src/core/form/components/ControlDate.tsx
|
|
1911
|
+
/**
|
|
1912
|
+
* ControlDate component for handling date, datetime, and time inputs.
|
|
1913
|
+
*
|
|
1914
|
+
* Features:
|
|
1915
|
+
* - DateInput for date format
|
|
1916
|
+
* - DateTimePicker for date-time format
|
|
1917
|
+
* - TimeInput for time format
|
|
1918
|
+
*
|
|
1919
|
+
* Automatically detects date formats from schema and renders appropriate picker.
|
|
1920
|
+
*/
|
|
1921
|
+
const ControlDate = (props) => {
|
|
1922
|
+
const { inputProps, id, icon, format } = parseInput(props, useFormState(props.input));
|
|
1923
|
+
if (!props.input?.props) return null;
|
|
1924
|
+
if (props.datetime || format === "date-time") {
|
|
1925
|
+
const dateTimePickerProps = typeof props.datetime === "object" ? props.datetime : {};
|
|
1926
|
+
return /* @__PURE__ */ jsx(DateTimePicker, {
|
|
1927
|
+
...inputProps,
|
|
1928
|
+
id,
|
|
1929
|
+
leftSection: icon,
|
|
1930
|
+
defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
|
|
1931
|
+
onChange: (value) => {
|
|
1932
|
+
props.input.set(value ? new Date(value).toISOString() : void 0);
|
|
1933
|
+
},
|
|
1934
|
+
...dateTimePickerProps
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
if (props.date || format === "date") {
|
|
1938
|
+
const dateInputProps = typeof props.date === "object" ? props.date : {};
|
|
1939
|
+
return /* @__PURE__ */ jsx(DateInput, {
|
|
1940
|
+
...inputProps,
|
|
1941
|
+
id,
|
|
1942
|
+
leftSection: icon,
|
|
1943
|
+
defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
|
|
1944
|
+
onChange: (value) => {
|
|
1945
|
+
props.input.set(value ? new Date(value).toISOString().slice(0, 10) : void 0);
|
|
1946
|
+
},
|
|
1947
|
+
...dateInputProps
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
if (props.time || format === "time") {
|
|
1951
|
+
const timeInputProps = typeof props.time === "object" ? props.time : {};
|
|
1952
|
+
return /* @__PURE__ */ jsx(TimeInput, {
|
|
1953
|
+
...inputProps,
|
|
1954
|
+
id,
|
|
1955
|
+
leftSection: icon,
|
|
1956
|
+
defaultValue: props.input.props.defaultValue,
|
|
1957
|
+
onChange: (event) => {
|
|
1958
|
+
props.input.set(event.currentTarget.value);
|
|
1959
|
+
},
|
|
1960
|
+
...timeInputProps
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
return null;
|
|
1964
|
+
};
|
|
1965
|
+
|
|
1966
|
+
//#endregion
|
|
1967
|
+
//#region ../../src/core/form/components/ControlNumber.tsx
|
|
1968
|
+
/**
|
|
1969
|
+
*
|
|
1970
|
+
*/
|
|
1971
|
+
const ControlNumber = (props) => {
|
|
1972
|
+
const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
|
|
1973
|
+
const ref = useRef(null);
|
|
1974
|
+
const [value, setValue] = useState(props.input.props.defaultValue);
|
|
1975
|
+
useEvents({ "form:reset": (event) => {
|
|
1976
|
+
if (event.id === props.input?.form.id && ref.current) setValue(props.input.props.defaultValue);
|
|
1977
|
+
} }, [props.input]);
|
|
1978
|
+
if (!props.input?.props) return null;
|
|
1979
|
+
const { type, ...inputPropsWithoutType } = props.input.props;
|
|
1980
|
+
if (props.sliderProps) {
|
|
1981
|
+
const min = props.sliderProps.min ?? inputProps.minimum ?? 0;
|
|
1982
|
+
const max = props.sliderProps.max ?? inputProps.maximum ?? 100;
|
|
1983
|
+
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
1984
|
+
...inputProps,
|
|
1985
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1986
|
+
style: {
|
|
1987
|
+
height: 32,
|
|
1988
|
+
padding: 8
|
|
1989
|
+
},
|
|
1990
|
+
children: /* @__PURE__ */ jsx(Slider, {
|
|
1991
|
+
...inputProps,
|
|
1992
|
+
ref,
|
|
1993
|
+
id,
|
|
1994
|
+
...inputPropsWithoutType,
|
|
1995
|
+
...props.sliderProps,
|
|
1996
|
+
value,
|
|
1997
|
+
min,
|
|
1998
|
+
max,
|
|
1999
|
+
label: () => value,
|
|
2000
|
+
onChange: (val) => {
|
|
2001
|
+
setValue(val);
|
|
2002
|
+
props.input.set(val);
|
|
2003
|
+
}
|
|
2004
|
+
})
|
|
2005
|
+
})
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
return /* @__PURE__ */ jsx(NumberInput, {
|
|
2009
|
+
...inputProps,
|
|
2010
|
+
ref,
|
|
2011
|
+
id,
|
|
2012
|
+
leftSection: icon,
|
|
2013
|
+
...inputPropsWithoutType,
|
|
2014
|
+
...props.numberInputProps,
|
|
2015
|
+
value: value ?? "",
|
|
2016
|
+
onChange: (val) => {
|
|
2017
|
+
const newValue = val !== null ? Number(val) : void 0;
|
|
2018
|
+
setValue(newValue);
|
|
2019
|
+
props.input.set(newValue);
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
};
|
|
2023
|
+
|
|
2024
|
+
//#endregion
|
|
2025
|
+
//#region ../../src/core/form/components/ControlObject.tsx
|
|
2026
|
+
/**
|
|
2027
|
+
* ControlObject component for editing nested object schemas.
|
|
2028
|
+
*
|
|
2029
|
+
* Features:
|
|
2030
|
+
* - Renders all properties of an object schema
|
|
2031
|
+
* - Supports grid layout with configurable columns
|
|
2032
|
+
* - Per-field customization via controlProps
|
|
2033
|
+
* - Recursive support for deeply nested objects
|
|
2034
|
+
*
|
|
2035
|
+
* The form system provides nested InputFields under the `.items` property.
|
|
2036
|
+
* For example: form.input.address.items.street
|
|
2037
|
+
*
|
|
2038
|
+
* @example
|
|
2039
|
+
* ```tsx
|
|
2040
|
+
* // For a schema like:
|
|
2041
|
+
* // t.object({
|
|
2042
|
+
* // address: t.object({
|
|
2043
|
+
* // street: t.text(),
|
|
2044
|
+
* // city: t.text(),
|
|
2045
|
+
* // zip: t.text(),
|
|
2046
|
+
* // })
|
|
2047
|
+
* // })
|
|
2048
|
+
*
|
|
2049
|
+
* <ControlObject
|
|
2050
|
+
* input={form.input.address}
|
|
2051
|
+
* columns={2}
|
|
2052
|
+
* controlProps={{
|
|
2053
|
+
* zip: { text: { maxLength: 10 } }
|
|
2054
|
+
* }}
|
|
2055
|
+
* />
|
|
2056
|
+
* ```
|
|
2057
|
+
*/
|
|
2058
|
+
const ControlObject = (props) => {
|
|
2059
|
+
const { inputProps } = parseInput(props, {});
|
|
2060
|
+
if (!props.input?.props) return null;
|
|
2061
|
+
const schema = props.input.schema;
|
|
2062
|
+
if (!schema?.properties) return null;
|
|
2063
|
+
const fieldNames = Object.keys(schema.properties);
|
|
2064
|
+
const colSpan = 12 / (props.columns ?? 1);
|
|
2065
|
+
const nestedItems = props.input.items;
|
|
2066
|
+
const renderFields = () => /* @__PURE__ */ jsx(Grid, { children: fieldNames.map((fieldName) => {
|
|
2067
|
+
const fieldControlProps = props.controlProps?.[fieldName] ?? {};
|
|
2068
|
+
const field = nestedItems?.[fieldName];
|
|
2069
|
+
if (!field) return null;
|
|
2070
|
+
return /* @__PURE__ */ jsx(Grid.Col, {
|
|
2071
|
+
span: colSpan,
|
|
2072
|
+
children: /* @__PURE__ */ jsx(Control, {
|
|
2073
|
+
input: field,
|
|
2074
|
+
...fieldControlProps
|
|
2075
|
+
})
|
|
2076
|
+
}, fieldName);
|
|
2077
|
+
}) });
|
|
2078
|
+
if (props.variant === "plain") return renderFields();
|
|
2079
|
+
return /* @__PURE__ */ jsx(Fieldset, {
|
|
2080
|
+
legend: inputProps.label,
|
|
2081
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
2082
|
+
direction: "column",
|
|
2083
|
+
gap: "xs",
|
|
2084
|
+
children: [
|
|
2085
|
+
inputProps.description && /* @__PURE__ */ jsx(Text, {
|
|
2086
|
+
size: "sm",
|
|
2087
|
+
c: "dimmed",
|
|
2088
|
+
children: inputProps.description
|
|
2089
|
+
}),
|
|
2090
|
+
renderFields(),
|
|
2091
|
+
inputProps.error && /* @__PURE__ */ jsx(Text, {
|
|
2092
|
+
size: "sm",
|
|
2093
|
+
c: "red",
|
|
2094
|
+
children: inputProps.error
|
|
2095
|
+
})
|
|
2096
|
+
]
|
|
2097
|
+
})
|
|
2098
|
+
});
|
|
2099
|
+
};
|
|
2100
|
+
|
|
2101
|
+
//#endregion
|
|
2102
|
+
//#region ../../src/core/form/components/ControlQueryBuilder.tsx
|
|
2103
|
+
/**
|
|
2104
|
+
* Query builder with text input and help popover.
|
|
2105
|
+
* Generates query strings for parseQueryString syntax.
|
|
2106
|
+
*/
|
|
2107
|
+
const ControlQueryBuilder = ({ schema, value = "", onChange, placeholder = "Enter query or click for assistance...", ...textInputProps }) => {
|
|
2108
|
+
const [helpOpened, setHelpOpened] = useState(false);
|
|
2109
|
+
const [textValue, setTextValue] = useState(value);
|
|
2110
|
+
const inputRef = useRef(null);
|
|
2111
|
+
const fields = schema ? extractSchemaFields(schema) : [];
|
|
2112
|
+
const [error, setError] = useState(null);
|
|
2113
|
+
const isValid = (value) => {
|
|
2114
|
+
try {
|
|
2115
|
+
parseQueryString(value.trim());
|
|
2116
|
+
} catch (e) {
|
|
2117
|
+
setError(e.message);
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2120
|
+
setError(null);
|
|
2121
|
+
return true;
|
|
2122
|
+
};
|
|
2123
|
+
const handleTextChange = (newValue) => {
|
|
2124
|
+
setTextValue(newValue);
|
|
2125
|
+
if (isValid(newValue)) onChange?.(newValue);
|
|
2126
|
+
};
|
|
2127
|
+
const handleClear = () => {
|
|
2128
|
+
setTextValue("");
|
|
2129
|
+
onChange?.("");
|
|
2130
|
+
isValid("");
|
|
2131
|
+
};
|
|
2132
|
+
const handleInsert = (text) => {
|
|
2133
|
+
const newValue = textValue ? `${textValue}${text} ` : `${text} `;
|
|
2134
|
+
setTextValue(newValue);
|
|
2135
|
+
if (isValid(newValue)) onChange?.(newValue);
|
|
2136
|
+
setTimeout(() => {
|
|
2137
|
+
inputRef.current?.focus();
|
|
2138
|
+
const length = inputRef.current?.value.length || 0;
|
|
2139
|
+
inputRef.current?.setSelectionRange(length, length);
|
|
2140
|
+
}, 0);
|
|
2141
|
+
};
|
|
2142
|
+
useEvents({ "form:change": (event) => {
|
|
2143
|
+
if (event.id === inputRef.current?.form?.id) {
|
|
2144
|
+
if (event.path === textInputProps["data-path"]) setTextValue(event.value ?? "");
|
|
2145
|
+
}
|
|
2146
|
+
} }, []);
|
|
2147
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
2148
|
+
width: 800,
|
|
2149
|
+
position: "bottom-start",
|
|
2150
|
+
shadow: "md",
|
|
2151
|
+
opened: helpOpened,
|
|
2152
|
+
onChange: setHelpOpened,
|
|
2153
|
+
closeOnClickOutside: true,
|
|
2154
|
+
closeOnEscape: true,
|
|
2155
|
+
transitionProps: {
|
|
2156
|
+
transition: "fade-up",
|
|
2157
|
+
duration: 200,
|
|
2158
|
+
timingFunction: "ease"
|
|
2159
|
+
},
|
|
2160
|
+
children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(TextInput, {
|
|
2161
|
+
ref: inputRef,
|
|
2162
|
+
placeholder,
|
|
2163
|
+
value: textValue,
|
|
2164
|
+
onChange: (e) => handleTextChange(e.currentTarget.value),
|
|
2165
|
+
onFocus: () => setHelpOpened(true),
|
|
2166
|
+
leftSection: error ? /* @__PURE__ */ jsx(IconInfoTriangle, { size: 16 }) : /* @__PURE__ */ jsx(IconFilter, { size: 16 }),
|
|
2167
|
+
rightSection: textValue && /* @__PURE__ */ jsx(ActionIcon, {
|
|
2168
|
+
size: "sm",
|
|
2169
|
+
variant: "subtle",
|
|
2170
|
+
color: "gray",
|
|
2171
|
+
onClick: handleClear,
|
|
2172
|
+
children: /* @__PURE__ */ jsx(IconX, { size: 14 })
|
|
2173
|
+
}),
|
|
2174
|
+
...textInputProps
|
|
2175
|
+
}) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
|
|
2176
|
+
bg: "transparent",
|
|
2177
|
+
p: "xs",
|
|
2178
|
+
bd: `1px solid ${ui.colors.border}`,
|
|
2179
|
+
style: { backdropFilter: "blur(20px)" },
|
|
2180
|
+
children: /* @__PURE__ */ jsx(QueryHelp, {
|
|
2181
|
+
fields,
|
|
2182
|
+
onInsert: handleInsert
|
|
2183
|
+
})
|
|
2184
|
+
})]
|
|
2185
|
+
});
|
|
2186
|
+
};
|
|
2187
|
+
function QueryHelp({ fields, onInsert }) {
|
|
2188
|
+
return /* @__PURE__ */ jsxs(Flex, {
|
|
2189
|
+
gap: "md",
|
|
2190
|
+
align: "flex-start",
|
|
2191
|
+
wrap: "nowrap",
|
|
2192
|
+
bg: ui.colors.surface,
|
|
2193
|
+
p: "sm",
|
|
2194
|
+
bdrs: "sm",
|
|
2195
|
+
children: [
|
|
2196
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
2197
|
+
direction: "column",
|
|
2198
|
+
gap: "md",
|
|
2199
|
+
style: { flex: 1 },
|
|
2200
|
+
children: [
|
|
2201
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
2202
|
+
direction: "column",
|
|
2203
|
+
gap: "xs",
|
|
2204
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2205
|
+
size: "sm",
|
|
2206
|
+
fw: 600,
|
|
2207
|
+
children: "Operators"
|
|
2208
|
+
}), /* @__PURE__ */ jsx(Flex, {
|
|
2209
|
+
direction: "column",
|
|
2210
|
+
gap: 4,
|
|
2211
|
+
children: Object.entries(OPERATOR_INFO).map(([key, info]) => /* @__PURE__ */ jsxs(Flex, {
|
|
2212
|
+
gap: "xs",
|
|
2213
|
+
wrap: "nowrap",
|
|
2214
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
2215
|
+
px: "xs",
|
|
2216
|
+
size: "xs",
|
|
2217
|
+
h: 24,
|
|
2218
|
+
variant: "default",
|
|
2219
|
+
justify: "center",
|
|
2220
|
+
miw: 48,
|
|
2221
|
+
onClick: () => onInsert(info.symbol),
|
|
2222
|
+
children: info.symbol
|
|
2223
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2224
|
+
size: "xs",
|
|
2225
|
+
c: "dimmed",
|
|
2226
|
+
style: { flex: 1 },
|
|
2227
|
+
children: info.label
|
|
2228
|
+
})]
|
|
2229
|
+
}, key))
|
|
2230
|
+
})]
|
|
2231
|
+
}),
|
|
2232
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
2233
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
2234
|
+
direction: "column",
|
|
2235
|
+
gap: "xs",
|
|
2236
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2237
|
+
size: "sm",
|
|
2238
|
+
fw: 600,
|
|
2239
|
+
children: "Logic"
|
|
2240
|
+
}), /* @__PURE__ */ jsxs(Flex, {
|
|
2241
|
+
direction: "column",
|
|
2242
|
+
gap: 4,
|
|
2243
|
+
children: [/* @__PURE__ */ jsxs(Flex, {
|
|
2244
|
+
gap: "xs",
|
|
2245
|
+
wrap: "nowrap",
|
|
2246
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
2247
|
+
px: "xs",
|
|
2248
|
+
size: "xs",
|
|
2249
|
+
h: 24,
|
|
2250
|
+
variant: "default",
|
|
2251
|
+
justify: "center",
|
|
2252
|
+
miw: 48,
|
|
2253
|
+
onClick: () => onInsert("&"),
|
|
2254
|
+
children: "&"
|
|
2255
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2256
|
+
size: "xs",
|
|
2257
|
+
c: "dimmed",
|
|
2258
|
+
children: "AND"
|
|
2259
|
+
})]
|
|
2260
|
+
}), /* @__PURE__ */ jsxs(Flex, {
|
|
2261
|
+
gap: "xs",
|
|
2262
|
+
wrap: "nowrap",
|
|
2263
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
2264
|
+
px: "xs",
|
|
2265
|
+
size: "xs",
|
|
2266
|
+
h: 24,
|
|
2267
|
+
variant: "default",
|
|
2268
|
+
justify: "center",
|
|
2269
|
+
miw: 48,
|
|
2270
|
+
onClick: () => onInsert("|"),
|
|
2271
|
+
children: "|"
|
|
2272
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2273
|
+
size: "xs",
|
|
2274
|
+
c: "dimmed",
|
|
2275
|
+
children: "OR"
|
|
2276
|
+
})]
|
|
2277
|
+
})]
|
|
2278
|
+
})]
|
|
2279
|
+
})
|
|
2280
|
+
]
|
|
2281
|
+
}),
|
|
2282
|
+
fields.length > 0 && /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }),
|
|
2283
|
+
fields.length > 0 && /* @__PURE__ */ jsxs(Flex, {
|
|
2284
|
+
direction: "column",
|
|
2285
|
+
gap: "xs",
|
|
2286
|
+
style: { flex: 2 },
|
|
2287
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2288
|
+
size: "sm",
|
|
2289
|
+
fw: 600,
|
|
2290
|
+
children: "Fields"
|
|
2291
|
+
}), /* @__PURE__ */ jsx(Flex, {
|
|
2292
|
+
direction: "column",
|
|
2293
|
+
gap: 4,
|
|
2294
|
+
style: {
|
|
2295
|
+
maxHeight: 300,
|
|
2296
|
+
overflowY: "auto"
|
|
2297
|
+
},
|
|
2298
|
+
children: fields.map((field) => /* @__PURE__ */ jsxs(Flex, {
|
|
2299
|
+
gap: "xs",
|
|
2300
|
+
wrap: "nowrap",
|
|
2301
|
+
align: "flex-start",
|
|
2302
|
+
children: [
|
|
2303
|
+
/* @__PURE__ */ jsx(ActionButton, {
|
|
2304
|
+
px: "xs",
|
|
2305
|
+
size: "xs",
|
|
2306
|
+
h: 24,
|
|
2307
|
+
variant: "default",
|
|
2308
|
+
justify: "end",
|
|
2309
|
+
miw: 120,
|
|
2310
|
+
onClick: () => onInsert(field.path),
|
|
2311
|
+
children: field.path
|
|
2312
|
+
}),
|
|
2313
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
2314
|
+
mt: 3,
|
|
2315
|
+
direction: "column",
|
|
2316
|
+
gap: 2,
|
|
2317
|
+
style: {
|
|
2318
|
+
flex: 1,
|
|
2319
|
+
minWidth: 0
|
|
2320
|
+
},
|
|
2321
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2322
|
+
size: "xs",
|
|
2323
|
+
c: "dimmed",
|
|
2324
|
+
lineClamp: 1,
|
|
2325
|
+
children: field.description || field.type
|
|
2326
|
+
}), field.enum && /* @__PURE__ */ jsx(Flex, {
|
|
2327
|
+
gap: 0,
|
|
2328
|
+
wrap: "wrap",
|
|
2329
|
+
children: field.enum.map((enumValue) => /* @__PURE__ */ jsx(ActionButton, {
|
|
2330
|
+
px: "xs",
|
|
2331
|
+
size: "xs",
|
|
2332
|
+
h: 24,
|
|
2333
|
+
onClick: () => onInsert(enumValue),
|
|
2334
|
+
children: enumValue
|
|
2335
|
+
}, enumValue))
|
|
2336
|
+
})]
|
|
2337
|
+
}),
|
|
2338
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
2339
|
+
size: "xs",
|
|
2340
|
+
variant: "light",
|
|
2341
|
+
style: { flexShrink: 0 },
|
|
2342
|
+
children: field.type
|
|
2343
|
+
})
|
|
2344
|
+
]
|
|
2345
|
+
}, field.path))
|
|
2346
|
+
})]
|
|
2347
|
+
})
|
|
2348
|
+
]
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
//#endregion
|
|
2353
|
+
//#region ../../src/core/form/components/ControlSelect.tsx
|
|
2354
|
+
/**
|
|
2355
|
+
* ControlSelect component for handling Select, MultiSelect, and TagsInput.
|
|
2356
|
+
*
|
|
2357
|
+
* Features:
|
|
2358
|
+
* - Basic Select with enum support
|
|
2359
|
+
* - MultiSelect for array of enums
|
|
2360
|
+
* - TagsInput for array of strings (no enum)
|
|
2361
|
+
* - Future: Lazy loading
|
|
2362
|
+
* - Future: Searchable/filterable options
|
|
2363
|
+
* - Future: Custom option rendering
|
|
2364
|
+
*
|
|
2365
|
+
* Automatically detects enum values and array types from schema.
|
|
2366
|
+
*/
|
|
2367
|
+
const ControlSelect = (props) => {
|
|
2368
|
+
const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
|
|
2369
|
+
const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
|
|
2370
|
+
let itemsEnum;
|
|
2371
|
+
if (isArray && "items" in props.input.schema && props.input.schema.items) {
|
|
2372
|
+
const items = props.input.schema.items;
|
|
2373
|
+
if ("enum" in items && Array.isArray(items.enum)) itemsEnum = items.enum;
|
|
2374
|
+
}
|
|
2375
|
+
const enumValues = props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum : [];
|
|
2376
|
+
const [data, setData] = useState([]);
|
|
2377
|
+
useEffect(() => {
|
|
2378
|
+
if (!props.input?.props) return;
|
|
2379
|
+
if (props.loader) props.loader().then(setData);
|
|
2380
|
+
else setData(enumValues);
|
|
2381
|
+
}, [props.input, props.loader]);
|
|
2382
|
+
if (!props.input?.props) return null;
|
|
2383
|
+
if (props.segmented) {
|
|
2384
|
+
const segmentedControlProps = typeof props.segmented === "object" ? props.segmented : {};
|
|
2385
|
+
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
2386
|
+
...inputProps,
|
|
2387
|
+
children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
2388
|
+
disabled: inputProps.disabled,
|
|
2389
|
+
defaultValue: String(props.input.props.defaultValue),
|
|
2390
|
+
...segmentedControlProps,
|
|
2391
|
+
onChange: (value) => {
|
|
2392
|
+
props.input.set(value);
|
|
2393
|
+
},
|
|
2394
|
+
data: data.slice(0, 10)
|
|
2395
|
+
}) })
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
if (props.autocomplete) {
|
|
2399
|
+
const autocompleteProps = typeof props.autocomplete === "object" ? props.autocomplete : {};
|
|
2400
|
+
return /* @__PURE__ */ jsx(Autocomplete, {
|
|
2401
|
+
...inputProps,
|
|
2402
|
+
size: props.size,
|
|
2403
|
+
id,
|
|
2404
|
+
leftSection: icon,
|
|
2405
|
+
data,
|
|
2406
|
+
...props.input.props,
|
|
2407
|
+
...autocompleteProps
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
if (isArray && !itemsEnum || props.tags) {
|
|
2411
|
+
const tagsInputProps = typeof props.tags === "object" ? props.tags : {};
|
|
2412
|
+
return /* @__PURE__ */ jsx(TagsInput, {
|
|
2413
|
+
...inputProps,
|
|
2414
|
+
size: props.size,
|
|
2415
|
+
id,
|
|
2416
|
+
leftSection: icon,
|
|
2417
|
+
defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
|
|
2418
|
+
onChange: (value) => {
|
|
2419
|
+
props.input.set(value);
|
|
2420
|
+
},
|
|
2421
|
+
...tagsInputProps
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
if (isArray && itemsEnum || props.multi) {
|
|
2425
|
+
const data = itemsEnum?.map((value) => ({
|
|
2426
|
+
value,
|
|
2427
|
+
label: value
|
|
2428
|
+
})) || [];
|
|
2429
|
+
const multiSelectProps = typeof props.multi === "object" ? props.multi : {};
|
|
2430
|
+
return /* @__PURE__ */ jsx(MultiSelect, {
|
|
2431
|
+
...inputProps,
|
|
2432
|
+
size: props.size,
|
|
2433
|
+
id,
|
|
2434
|
+
leftSection: icon,
|
|
2435
|
+
data,
|
|
2436
|
+
defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
|
|
2437
|
+
onChange: (value) => {
|
|
2438
|
+
props.input.set(value);
|
|
2439
|
+
},
|
|
2440
|
+
...multiSelectProps
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
const selectProps = typeof props.select === "object" ? props.select : {};
|
|
2444
|
+
return /* @__PURE__ */ jsx(Select, {
|
|
2445
|
+
...inputProps,
|
|
2446
|
+
size: props.size,
|
|
2447
|
+
id,
|
|
2448
|
+
leftSection: icon,
|
|
2449
|
+
rightSection: null,
|
|
2450
|
+
data,
|
|
2451
|
+
...props.input.props,
|
|
2452
|
+
...selectProps
|
|
2453
|
+
});
|
|
2454
|
+
};
|
|
2455
|
+
|
|
2456
|
+
//#endregion
|
|
2457
|
+
//#region ../../src/core/form/components/Control.tsx
|
|
2458
|
+
/**
|
|
2459
|
+
* Generic form control that renders the appropriate input based on the schema and props.
|
|
2460
|
+
*
|
|
2461
|
+
* Supports:
|
|
2462
|
+
* - TextInput (with format detection: email, url, tel)
|
|
2463
|
+
* - Textarea
|
|
2464
|
+
* - NumberInput (for number/integer types)
|
|
2465
|
+
* - FileInput
|
|
2466
|
+
* - ColorInput (for color format)
|
|
2467
|
+
* - Select (for enum types)
|
|
2468
|
+
* - Autocomplete
|
|
2469
|
+
* - PasswordInput
|
|
2470
|
+
* - Switch (for boolean types)
|
|
2471
|
+
* - SegmentedControl (for enum types)
|
|
2472
|
+
* - DateInput (for date format)
|
|
2473
|
+
* - DateTimePicker (for date-time format)
|
|
2474
|
+
* - TimeInput (for time format)
|
|
2475
|
+
* - QueryBuilder (for building type-safe queries with autocomplete)
|
|
2476
|
+
* - ControlObject (for nested object schemas)
|
|
2477
|
+
* - ControlArray (for arrays of objects)
|
|
2478
|
+
* - Custom component
|
|
2479
|
+
*
|
|
2480
|
+
* Automatically handles labels, descriptions, error messages, required state, and default icons.
|
|
2481
|
+
*/
|
|
2482
|
+
const Control = (_props) => {
|
|
2483
|
+
const form = useFormState(_props.input, ["error"]);
|
|
2484
|
+
if (!_props.input?.props) return null;
|
|
2485
|
+
const { inputProps, id, icon, format, schema } = parseInput(_props, form);
|
|
2486
|
+
const props = {
|
|
2487
|
+
..._props,
|
|
2488
|
+
...schema.$control
|
|
2489
|
+
};
|
|
2490
|
+
if (props.query) return /* @__PURE__ */ jsx(ControlQueryBuilder, {
|
|
2491
|
+
...props.input.props,
|
|
2492
|
+
...inputProps,
|
|
2493
|
+
schema: props.query,
|
|
2494
|
+
value: props.input.props.value,
|
|
2495
|
+
onChange: (value) => {
|
|
2496
|
+
props.input.set(value);
|
|
2497
|
+
}
|
|
2498
|
+
});
|
|
2499
|
+
if (props.custom) {
|
|
2500
|
+
const Custom = props.custom;
|
|
2501
|
+
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
2502
|
+
...inputProps,
|
|
2503
|
+
children: /* @__PURE__ */ jsx(Flex, {
|
|
2504
|
+
flex: 1,
|
|
2505
|
+
mt: "calc(var(--mantine-spacing-xs) / 2)",
|
|
2506
|
+
children: /* @__PURE__ */ jsx(Custom, {
|
|
2507
|
+
defaultValue: props.input.props.defaultValue,
|
|
2508
|
+
onChange: (value) => {
|
|
2509
|
+
props.input.set(value);
|
|
2510
|
+
}
|
|
2511
|
+
})
|
|
2512
|
+
})
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
const isObject = props.input.schema && "type" in props.input.schema && props.input.schema.type === "object" && "properties" in props.input.schema;
|
|
2516
|
+
if (props.object || isObject) {
|
|
2517
|
+
const controlObjectProps = typeof props.object === "object" ? props.object : {};
|
|
2518
|
+
return /* @__PURE__ */ jsx(ControlObject, {
|
|
2519
|
+
input: props.input,
|
|
2520
|
+
title: props.title,
|
|
2521
|
+
description: props.description,
|
|
2522
|
+
...controlObjectProps
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
|
|
2526
|
+
const isArrayOfObjects = isArray && "items" in props.input.schema && props.input.schema.items && typeof props.input.schema.items === "object" && "properties" in props.input.schema.items;
|
|
2527
|
+
if (props.array || isArrayOfObjects) {
|
|
2528
|
+
const controlArrayProps = typeof props.array === "object" ? props.array : {};
|
|
2529
|
+
return /* @__PURE__ */ jsx(ControlArray, {
|
|
2530
|
+
input: props.input,
|
|
2531
|
+
title: props.title,
|
|
2532
|
+
description: props.description,
|
|
2533
|
+
...controlArrayProps
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
if (props.number || props.input.schema && "type" in props.input.schema && (props.input.schema.type === "number" || props.input.schema.type === "integer")) {
|
|
2537
|
+
const controlNumberProps = typeof props.number === "object" ? props.number : {};
|
|
2538
|
+
if (props.slider) controlNumberProps.sliderProps ??= {};
|
|
2539
|
+
return /* @__PURE__ */ jsx(ControlNumber, {
|
|
2540
|
+
size: props.size,
|
|
2541
|
+
input: props.input,
|
|
2542
|
+
title: props.title,
|
|
2543
|
+
description: props.description,
|
|
2544
|
+
icon,
|
|
2545
|
+
...controlNumberProps
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
if (props.file) {
|
|
2549
|
+
const fileInputProps = typeof props.file === "object" ? props.file : {};
|
|
2550
|
+
return /* @__PURE__ */ jsx(FileInput, {
|
|
2551
|
+
...inputProps,
|
|
2552
|
+
size: props.size,
|
|
2553
|
+
id,
|
|
2554
|
+
leftSection: icon,
|
|
2555
|
+
onChange: (file) => {
|
|
2556
|
+
props.input.set(file);
|
|
2557
|
+
},
|
|
2558
|
+
...fileInputProps
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
if (props.color || format === "color") {
|
|
2562
|
+
const colorInputProps = typeof props.color === "object" ? props.color : {};
|
|
2563
|
+
return /* @__PURE__ */ jsx(ColorInput, {
|
|
2564
|
+
...inputProps,
|
|
2565
|
+
size: props.size,
|
|
2566
|
+
id,
|
|
2567
|
+
leftSection: icon,
|
|
2568
|
+
...props.input.props,
|
|
2569
|
+
...colorInputProps
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
if (props.input.schema && "enum" in props.input.schema && props.input.schema.enum || isArray && !isArrayOfObjects || props.select) {
|
|
2573
|
+
const opts = typeof props.select === "object" ? props.select : {};
|
|
2574
|
+
if (props.segmented) opts.segmented ??= {};
|
|
2575
|
+
return /* @__PURE__ */ jsx(ControlSelect, {
|
|
2576
|
+
size: props.size,
|
|
2577
|
+
input: props.input,
|
|
2578
|
+
title: props.title,
|
|
2579
|
+
description: props.description,
|
|
2580
|
+
icon,
|
|
2581
|
+
...opts
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
if (props.input.schema && "type" in props.input.schema && props.input.schema.type === "boolean") {
|
|
2585
|
+
if (props.switch) {
|
|
2586
|
+
const switchProps = typeof props.switch === "object" ? props.switch : {};
|
|
2587
|
+
return /* @__PURE__ */ jsx(Switch, {
|
|
2588
|
+
...inputProps,
|
|
2589
|
+
size: props.size,
|
|
2590
|
+
id,
|
|
2591
|
+
color: "blue",
|
|
2592
|
+
defaultChecked: props.input.props.defaultValue,
|
|
2593
|
+
onChange: (event) => {
|
|
2594
|
+
props.input.set(event.currentTarget.checked);
|
|
2595
|
+
},
|
|
2596
|
+
...switchProps
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
const opts = {
|
|
2600
|
+
input: props.input,
|
|
2601
|
+
select: { data: [{
|
|
2602
|
+
value: "true",
|
|
2603
|
+
label: "Yes"
|
|
2604
|
+
}, {
|
|
2605
|
+
value: "false",
|
|
2606
|
+
label: "No"
|
|
2607
|
+
}] }
|
|
2608
|
+
};
|
|
2609
|
+
return /* @__PURE__ */ jsx(ControlSelect, {
|
|
2610
|
+
size: props.size,
|
|
2611
|
+
title: props.title,
|
|
2612
|
+
description: props.description,
|
|
2613
|
+
icon,
|
|
2614
|
+
...opts
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
if (props.password || props.input.props.name?.includes("password")) {
|
|
2618
|
+
const passwordInputProps = typeof props.password === "object" ? props.password : {};
|
|
2619
|
+
return /* @__PURE__ */ jsx(PasswordInput, {
|
|
2620
|
+
...inputProps,
|
|
2621
|
+
size: props.size,
|
|
2622
|
+
id,
|
|
2623
|
+
leftSection: icon,
|
|
2624
|
+
...props.input.props,
|
|
2625
|
+
...passwordInputProps
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
if (props.area) {
|
|
2629
|
+
const textAreaProps = typeof props.area === "object" ? props.area : {};
|
|
2630
|
+
return /* @__PURE__ */ jsx(Textarea, {
|
|
2631
|
+
...inputProps,
|
|
2632
|
+
size: props.size,
|
|
2633
|
+
id,
|
|
2634
|
+
leftSection: icon,
|
|
2635
|
+
...props.input.props,
|
|
2636
|
+
...textAreaProps
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
if (props.date || props.datetime || props.time || format === "date" || format === "date-time" || format === "time") return /* @__PURE__ */ jsx(ControlDate, {
|
|
2640
|
+
size: props.size,
|
|
2641
|
+
input: props.input,
|
|
2642
|
+
title: props.title,
|
|
2643
|
+
description: props.description,
|
|
2644
|
+
icon,
|
|
2645
|
+
date: props.date,
|
|
2646
|
+
datetime: props.datetime,
|
|
2647
|
+
time: props.time
|
|
2648
|
+
});
|
|
2649
|
+
const textInputProps = typeof props.text === "object" ? props.text : {};
|
|
2650
|
+
const getInputType = () => {
|
|
2651
|
+
switch (format) {
|
|
2652
|
+
case "email": return "email";
|
|
2653
|
+
case "url":
|
|
2654
|
+
case "uri": return "url";
|
|
2655
|
+
case "tel":
|
|
2656
|
+
case "phone": return "tel";
|
|
2657
|
+
default: return;
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
return /* @__PURE__ */ jsx(TextInput, {
|
|
2661
|
+
...inputProps,
|
|
2662
|
+
size: props.size,
|
|
2663
|
+
id,
|
|
2664
|
+
leftSection: icon,
|
|
2665
|
+
type: getInputType(),
|
|
2666
|
+
...props.input.props,
|
|
2667
|
+
...textInputProps,
|
|
2668
|
+
inputWrapperOrder: [
|
|
2669
|
+
"label",
|
|
2670
|
+
"input",
|
|
2671
|
+
"description",
|
|
2672
|
+
"error"
|
|
2673
|
+
]
|
|
2674
|
+
});
|
|
2675
|
+
};
|
|
2676
|
+
|
|
2677
|
+
//#endregion
|
|
2678
|
+
//#region ../../src/core/form/components/TypeForm.tsx
|
|
2679
|
+
/**
|
|
2680
|
+
* TypeForm component that automatically renders all form inputs based on schema.
|
|
2681
|
+
* Uses the Control component to render individual fields and Mantine Grid for responsive layout.
|
|
2682
|
+
*
|
|
2683
|
+
* Supports all field types including:
|
|
2684
|
+
* - Primitive types (string, number, boolean, etc.)
|
|
2685
|
+
* - Enum types (rendered as Select)
|
|
2686
|
+
* - Arrays of primitives (rendered as MultiSelect/TagsInput)
|
|
2687
|
+
* - Arrays of objects (rendered as ControlArray)
|
|
2688
|
+
* - Nested objects (rendered as ControlObject)
|
|
2689
|
+
*
|
|
2690
|
+
* @example
|
|
2691
|
+
* ```tsx
|
|
2692
|
+
* import { t } from "alepha";
|
|
2693
|
+
* import { useForm } from "alepha/react/form";
|
|
2694
|
+
* import { TypeForm } from "@alepha/ui";
|
|
2695
|
+
*
|
|
2696
|
+
* const form = useForm({
|
|
2697
|
+
* schema: t.object({
|
|
2698
|
+
* username: t.text(),
|
|
2699
|
+
* email: t.text(),
|
|
2700
|
+
* age: t.integer(),
|
|
2701
|
+
* subscribe: t.boolean(),
|
|
2702
|
+
* address: t.object({
|
|
2703
|
+
* street: t.text(),
|
|
2704
|
+
* city: t.text(),
|
|
2705
|
+
* }),
|
|
2706
|
+
* tags: t.array(t.text()),
|
|
2707
|
+
* contacts: t.array(t.object({
|
|
2708
|
+
* name: t.text(),
|
|
2709
|
+
* email: t.text(),
|
|
2710
|
+
* })),
|
|
2711
|
+
* }),
|
|
2712
|
+
* handler: (values) => {
|
|
2713
|
+
* console.log(values);
|
|
2714
|
+
* },
|
|
2715
|
+
* });
|
|
2716
|
+
*
|
|
2717
|
+
* return <TypeForm form={form} columns={2} />;
|
|
2718
|
+
* ```
|
|
2719
|
+
*/
|
|
2720
|
+
const TypeForm = (props) => {
|
|
2721
|
+
const { form, columns = 3, children, controlProps, fieldControlProps, skipFormElement = false, skipSubmitButton = false, submitButtonProps, fill = true, size } = props;
|
|
2722
|
+
const schema = props.schema || form.options.schema;
|
|
2723
|
+
if (!schema?.properties) return null;
|
|
2724
|
+
const supportedFields = Object.keys(schema.properties);
|
|
2725
|
+
const colSpan = typeof columns === "number" ? {
|
|
2726
|
+
xs: 12,
|
|
2727
|
+
sm: 6,
|
|
2728
|
+
lg: 12 / columns
|
|
2729
|
+
} : {
|
|
2730
|
+
base: columns.base ? 12 / columns.base : void 0,
|
|
2731
|
+
xs: columns.xs ? 12 / columns.xs : 12,
|
|
2732
|
+
sm: columns.sm ? 12 / columns.sm : 6,
|
|
2733
|
+
md: columns.md ? 12 / columns.md : void 0,
|
|
2734
|
+
lg: columns.lg ? 12 / columns.lg : 4,
|
|
2735
|
+
xl: columns.xl ? 12 / columns.xl : void 0
|
|
2736
|
+
};
|
|
2737
|
+
const renderFields = () => {
|
|
2738
|
+
if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(form.input) });
|
|
2739
|
+
return /* @__PURE__ */ jsx(Grid, { children: supportedFields.map((fieldName) => {
|
|
2740
|
+
const field = form.input[fieldName];
|
|
2741
|
+
const fieldSchema = schema.properties[fieldName];
|
|
2742
|
+
if (!field || !fieldSchema) return null;
|
|
2743
|
+
const isObject = fieldSchema && "type" in fieldSchema && fieldSchema.type === "object";
|
|
2744
|
+
const isArrayOfObjects = fieldSchema && "type" in fieldSchema && fieldSchema.type === "array" && "items" in fieldSchema && fieldSchema.items && "properties" in fieldSchema.items;
|
|
2745
|
+
const span = isObject || isArrayOfObjects ? 12 : colSpan;
|
|
2746
|
+
const mergedControlProps = {
|
|
2747
|
+
...controlProps,
|
|
2748
|
+
...fieldControlProps?.[fieldName]
|
|
2749
|
+
};
|
|
2750
|
+
if (size) mergedControlProps.size = size;
|
|
2751
|
+
return /* @__PURE__ */ jsx(Grid.Col, {
|
|
2752
|
+
span,
|
|
2753
|
+
children: /* @__PURE__ */ jsx(Control, {
|
|
2754
|
+
input: field,
|
|
2755
|
+
...mergedControlProps
|
|
2756
|
+
})
|
|
2757
|
+
}, fieldName);
|
|
2758
|
+
}) });
|
|
2759
|
+
};
|
|
2760
|
+
const content = /* @__PURE__ */ jsxs(Flex, {
|
|
2761
|
+
direction: "column",
|
|
2762
|
+
gap: "sm",
|
|
2763
|
+
flex: fill ? 1 : void 0,
|
|
2764
|
+
...props.flexProps,
|
|
2765
|
+
children: [/* @__PURE__ */ jsx(Flex, {
|
|
2766
|
+
direction: "column",
|
|
2767
|
+
gap: "sm",
|
|
2768
|
+
flex: 1,
|
|
2769
|
+
children: renderFields()
|
|
2770
|
+
}), !skipSubmitButton && /* @__PURE__ */ jsx(Card, {
|
|
2771
|
+
w: "100%",
|
|
2772
|
+
withBorder: true,
|
|
2773
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
2774
|
+
gap: "sm",
|
|
2775
|
+
flex: 1,
|
|
2776
|
+
children: [
|
|
2777
|
+
/* @__PURE__ */ jsx(Flex, {}),
|
|
2778
|
+
/* @__PURE__ */ jsx(Flex, { flex: 1 }),
|
|
2779
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
2780
|
+
gap: "sm",
|
|
2781
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
2782
|
+
variant: "subtle",
|
|
2783
|
+
type: "reset",
|
|
2784
|
+
children: "Reset"
|
|
2785
|
+
}), /* @__PURE__ */ jsx(ActionButton, {
|
|
2786
|
+
intent: "primary",
|
|
2787
|
+
form,
|
|
2788
|
+
...submitButtonProps,
|
|
2789
|
+
children: submitButtonProps?.children ?? "Submit"
|
|
2790
|
+
})]
|
|
2791
|
+
})
|
|
2792
|
+
]
|
|
2793
|
+
})
|
|
2794
|
+
})]
|
|
2795
|
+
});
|
|
2796
|
+
if (skipFormElement) return content;
|
|
2797
|
+
return /* @__PURE__ */ jsx(Flex, {
|
|
2798
|
+
component: "form",
|
|
2799
|
+
flex: fill ? 1 : void 0,
|
|
2800
|
+
...form.props,
|
|
2801
|
+
...props.flexProps,
|
|
2802
|
+
children: content
|
|
2803
|
+
});
|
|
2804
|
+
};
|
|
2805
|
+
|
|
2806
|
+
//#endregion
|
|
2807
|
+
//#region ../../src/core/helpers/renderIcon.tsx
|
|
2808
|
+
const renderIcon = (icon, size) => {
|
|
2809
|
+
if (!icon) return null;
|
|
2810
|
+
if (isValidElement(icon)) return icon;
|
|
2811
|
+
if (isComponentType(icon)) return /* @__PURE__ */ jsx(icon, { size: size ?? ui.sizes.icon.md });
|
|
2812
|
+
return icon;
|
|
2813
|
+
};
|
|
2814
|
+
|
|
2815
|
+
//#endregion
|
|
2816
|
+
//#region ../../src/core/hooks/useDialog.ts
|
|
2817
|
+
/**
|
|
2818
|
+
* Use this hook to access the Dialog Service for showing various dialog types.
|
|
2819
|
+
*
|
|
2820
|
+
* @example
|
|
2821
|
+
* ```tsx
|
|
2822
|
+
* const dialog = useDialog();
|
|
2823
|
+
* await dialog.alert({ title: "Alert", message: "This is an alert message" });
|
|
2824
|
+
* const confirmed = await dialog.confirm({ title: "Confirm", message: "Are you sure?" });
|
|
2825
|
+
* const input = await dialog.prompt({ title: "Input", message: "Enter your name:" });
|
|
2826
|
+
* ```
|
|
2827
|
+
*/
|
|
2828
|
+
const useDialog = () => {
|
|
2829
|
+
return useInject(DialogService);
|
|
2830
|
+
};
|
|
2831
|
+
|
|
2832
|
+
//#endregion
|
|
2833
|
+
//#region ../../src/core/json/components/JsonViewer.tsx
|
|
2834
|
+
const SIZE_CONFIG = {
|
|
2835
|
+
xs: {
|
|
2836
|
+
icon: 14,
|
|
2837
|
+
levelOffset: 16
|
|
2838
|
+
},
|
|
2839
|
+
sm: {
|
|
2840
|
+
icon: 16,
|
|
2841
|
+
levelOffset: 20
|
|
2842
|
+
},
|
|
2843
|
+
md: {
|
|
2844
|
+
icon: 18,
|
|
2845
|
+
levelOffset: 24
|
|
2846
|
+
},
|
|
2847
|
+
lg: {
|
|
2848
|
+
icon: 20,
|
|
2849
|
+
levelOffset: 28
|
|
2850
|
+
},
|
|
2851
|
+
xl: {
|
|
2852
|
+
icon: 22,
|
|
2853
|
+
levelOffset: 32
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
const STYLES = {
|
|
2857
|
+
root: { fontFamily: "var(--mantine-font-family-monospace)" },
|
|
2858
|
+
chevron: {
|
|
2859
|
+
flexShrink: 0,
|
|
2860
|
+
color: "var(--mantine-color-dimmed)"
|
|
2861
|
+
},
|
|
2862
|
+
key: {
|
|
2863
|
+
color: "var(--mantine-color-cyan-text)",
|
|
2864
|
+
fontWeight: 500
|
|
2865
|
+
},
|
|
2866
|
+
colon: { color: "var(--mantine-color-dimmed)" },
|
|
2867
|
+
string: { color: "var(--mantine-color-teal-text)" },
|
|
2868
|
+
number: { color: "var(--mantine-color-blue-text)" },
|
|
2869
|
+
boolean: { color: "var(--mantine-color-violet-text)" },
|
|
2870
|
+
null: {
|
|
2871
|
+
color: "var(--mantine-color-dimmed)",
|
|
2872
|
+
fontStyle: "italic"
|
|
2873
|
+
},
|
|
2874
|
+
preview: { color: "var(--mantine-color-dimmed)" }
|
|
2875
|
+
};
|
|
2876
|
+
const getValueType = (val) => {
|
|
2877
|
+
if (val === null) return "null";
|
|
2878
|
+
if (val === void 0) return "undefined";
|
|
2879
|
+
if (Array.isArray(val)) return "array";
|
|
2880
|
+
return typeof val;
|
|
2881
|
+
};
|
|
2882
|
+
function buildTreeNodes(data, path = [], key, isArrayItem = false, maxDepth = 10) {
|
|
2883
|
+
const currentPath = key !== void 0 ? [...path, key] : path;
|
|
2884
|
+
const nodeId = currentPath.length > 0 ? currentPath.join(".") : "root";
|
|
2885
|
+
if (currentPath.length > maxDepth) return {
|
|
2886
|
+
value: nodeId,
|
|
2887
|
+
label: key ?? "",
|
|
2888
|
+
nodeValue: data,
|
|
2889
|
+
nodeKey: key,
|
|
2890
|
+
path: currentPath,
|
|
2891
|
+
isArrayItem
|
|
2892
|
+
};
|
|
2893
|
+
const type = getValueType(data);
|
|
2894
|
+
if (type === "object" || type === "array") {
|
|
2895
|
+
const children = (type === "array" ? data.map((v, i) => [String(i), v]) : Object.entries(data)).map(([k, v]) => buildTreeNodes(v, currentPath, k, type === "array", maxDepth)).filter((n) => n !== null);
|
|
2896
|
+
return {
|
|
2897
|
+
value: nodeId,
|
|
2898
|
+
label: key ?? "",
|
|
2899
|
+
nodeValue: data,
|
|
2900
|
+
nodeKey: key,
|
|
2901
|
+
path: currentPath,
|
|
2902
|
+
isArrayItem,
|
|
2903
|
+
children: children.length > 0 ? children : void 0
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
return {
|
|
2907
|
+
value: nodeId,
|
|
2908
|
+
label: key ?? "",
|
|
2909
|
+
nodeValue: data,
|
|
2910
|
+
nodeKey: key,
|
|
2911
|
+
path: currentPath,
|
|
2912
|
+
isArrayItem
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
function getExpandedIds(nodes, targetDepth, currentDepth = 0) {
|
|
2916
|
+
if (currentDepth >= targetDepth) return [];
|
|
2917
|
+
const ids = [];
|
|
2918
|
+
for (const node of nodes) if (node.children) {
|
|
2919
|
+
ids.push(node.value);
|
|
2920
|
+
ids.push(...getExpandedIds(node.children, targetDepth, currentDepth + 1));
|
|
2921
|
+
}
|
|
2922
|
+
return ids;
|
|
2923
|
+
}
|
|
2924
|
+
const CopyButton = ({ value, iconSize }) => {
|
|
2925
|
+
const [copied, setCopied] = useState(false);
|
|
2926
|
+
const handleCopy = useCallback((e) => {
|
|
2927
|
+
e.stopPropagation();
|
|
2928
|
+
navigator.clipboard.writeText(value);
|
|
2929
|
+
setCopied(true);
|
|
2930
|
+
setTimeout(() => setCopied(false), 1500);
|
|
2931
|
+
}, [value]);
|
|
2932
|
+
return /* @__PURE__ */ jsx(ActionIcon, {
|
|
2933
|
+
size: iconSize + 4,
|
|
2934
|
+
variant: "transparent",
|
|
2935
|
+
c: copied ? "green" : "dimmed",
|
|
2936
|
+
onClick: handleCopy,
|
|
2937
|
+
className: "alepha-json-viewer-copy",
|
|
2938
|
+
children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: iconSize }) : /* @__PURE__ */ jsx(IconCopy, { size: iconSize })
|
|
2939
|
+
});
|
|
2940
|
+
};
|
|
2941
|
+
const RowNode = ({ node, expanded, hasChildren, elementProps, size, config, showQuotes, showCopyButton, renderValue }) => {
|
|
2942
|
+
const { nodeValue, nodeKey, path, isArrayItem, isRoot } = node;
|
|
2943
|
+
const type = getValueType(nodeValue);
|
|
2944
|
+
const isExpandable = type === "object" || type === "array";
|
|
2945
|
+
const getPreview = () => {
|
|
2946
|
+
if (!isExpandable) return null;
|
|
2947
|
+
const count = (type === "array" ? nodeValue : Object.keys(nodeValue)).length;
|
|
2948
|
+
const label = type === "array" ? "item" : "key";
|
|
2949
|
+
if (!expanded) return /* @__PURE__ */ jsx(Text, {
|
|
2950
|
+
fs: "italic",
|
|
2951
|
+
component: "span",
|
|
2952
|
+
size,
|
|
2953
|
+
style: STYLES.preview,
|
|
2954
|
+
children: count === 0 ? type === "array" ? "[]" : "{}" : type === "array" ? `[ ${count} ${count === 1 ? label : `${label}s`} ]` : `{ ${count} ${count === 1 ? label : `${label}s`} }`
|
|
2955
|
+
});
|
|
2956
|
+
return null;
|
|
2957
|
+
};
|
|
2958
|
+
const getCopyValue = () => isExpandable ? JSON.stringify(nodeValue, null, 2) : String(nodeValue ?? "");
|
|
2959
|
+
return /* @__PURE__ */ jsxs(Flex, {
|
|
2960
|
+
gap: 6,
|
|
2961
|
+
wrap: "nowrap",
|
|
2962
|
+
...elementProps,
|
|
2963
|
+
className: `alepha-json-viewer-row ${elementProps.className || ""}`,
|
|
2964
|
+
children: [
|
|
2965
|
+
hasChildren ? expanded ? /* @__PURE__ */ jsx(IconChevronDown, {
|
|
2966
|
+
size: config.icon,
|
|
2967
|
+
style: STYLES.chevron
|
|
2968
|
+
}) : /* @__PURE__ */ jsx(IconChevronRight, {
|
|
2969
|
+
size: config.icon,
|
|
2970
|
+
style: STYLES.chevron
|
|
2971
|
+
}) : /* @__PURE__ */ jsx("span", { style: {
|
|
2972
|
+
width: config.icon,
|
|
2973
|
+
flexShrink: 0
|
|
2974
|
+
} }),
|
|
2975
|
+
nodeKey !== void 0 && !isArrayItem && /* @__PURE__ */ jsxs(Text, {
|
|
2976
|
+
component: "span",
|
|
2977
|
+
size,
|
|
2978
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2979
|
+
style: STYLES.key,
|
|
2980
|
+
children: showQuotes ? `"${nodeKey}"` : nodeKey
|
|
2981
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2982
|
+
style: STYLES.colon,
|
|
2983
|
+
children: ":"
|
|
2984
|
+
})]
|
|
2985
|
+
}),
|
|
2986
|
+
nodeKey !== void 0 && isArrayItem && /* @__PURE__ */ jsxs(Text, {
|
|
2987
|
+
component: "span",
|
|
2988
|
+
size,
|
|
2989
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2990
|
+
style: STYLES.key,
|
|
2991
|
+
children: nodeKey
|
|
2992
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2993
|
+
style: STYLES.colon,
|
|
2994
|
+
children: ":"
|
|
2995
|
+
})]
|
|
2996
|
+
}),
|
|
2997
|
+
hasChildren ? getPreview() : isExpandable ? type === "array" ? /* @__PURE__ */ jsx(Text, {
|
|
2998
|
+
component: "span",
|
|
2999
|
+
size,
|
|
3000
|
+
style: STYLES.preview,
|
|
3001
|
+
children: "[]"
|
|
3002
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
3003
|
+
component: "span",
|
|
3004
|
+
size,
|
|
3005
|
+
style: STYLES.preview,
|
|
3006
|
+
children: "{}"
|
|
3007
|
+
}) : renderValue(nodeValue, nodeKey, path),
|
|
3008
|
+
showCopyButton && /* @__PURE__ */ jsx(CopyButton, {
|
|
3009
|
+
value: getCopyValue(),
|
|
3010
|
+
iconSize: config.icon
|
|
3011
|
+
})
|
|
3012
|
+
]
|
|
3013
|
+
});
|
|
3014
|
+
};
|
|
3015
|
+
const JsonViewer = ({ data, defaultExpandedDepth = 2, maxDepth = 10, size = "sm", showQuotes = false, showCopyButton = true, formatValue }) => {
|
|
3016
|
+
const config = SIZE_CONFIG[size] || SIZE_CONFIG.sm;
|
|
3017
|
+
const treeData = useMemo(() => {
|
|
3018
|
+
const type = getValueType(data);
|
|
3019
|
+
if (type === "object" || type === "array") {
|
|
3020
|
+
const children = (type === "array" ? data.map((v, i) => [String(i), v]) : Object.entries(data)).map(([k, v]) => buildTreeNodes(v, [], k, type === "array", maxDepth)).filter((n) => n !== null);
|
|
3021
|
+
return [{
|
|
3022
|
+
value: "root",
|
|
3023
|
+
label: "",
|
|
3024
|
+
nodeValue: data,
|
|
3025
|
+
nodeKey: void 0,
|
|
3026
|
+
path: [],
|
|
3027
|
+
isArrayItem: false,
|
|
3028
|
+
isRoot: true,
|
|
3029
|
+
children: children.length > 0 ? children : void 0
|
|
3030
|
+
}];
|
|
3031
|
+
}
|
|
3032
|
+
const node = buildTreeNodes(data, [], void 0, false, maxDepth);
|
|
3033
|
+
return node ? [node] : [];
|
|
3034
|
+
}, [data, maxDepth]);
|
|
3035
|
+
const tree = useTree({ initialExpandedState: useMemo(() => {
|
|
3036
|
+
if (defaultExpandedDepth === 0) return {};
|
|
3037
|
+
if (defaultExpandedDepth === Infinity) return getTreeExpandedState(treeData, "*");
|
|
3038
|
+
return getTreeExpandedState(treeData, getExpandedIds(treeData, defaultExpandedDepth + 1));
|
|
3039
|
+
}, [treeData, defaultExpandedDepth]) });
|
|
3040
|
+
const renderValue = useCallback((val, key, path) => {
|
|
3041
|
+
const custom = formatValue?.(key, val, path);
|
|
3042
|
+
if (custom !== void 0) return /* @__PURE__ */ jsx(Text, {
|
|
3043
|
+
component: "span",
|
|
3044
|
+
size,
|
|
3045
|
+
style: STYLES.string,
|
|
3046
|
+
className: "alepha-json-viewer-value",
|
|
3047
|
+
title: String(val),
|
|
3048
|
+
children: custom
|
|
3049
|
+
});
|
|
3050
|
+
const type = getValueType(val);
|
|
3051
|
+
switch (type) {
|
|
3052
|
+
case "string": return /* @__PURE__ */ jsxs(Text, {
|
|
3053
|
+
style: STYLES.string,
|
|
3054
|
+
component: "span",
|
|
3055
|
+
size,
|
|
3056
|
+
className: "alepha-json-viewer-value",
|
|
3057
|
+
title: val,
|
|
3058
|
+
children: [
|
|
3059
|
+
"\"",
|
|
3060
|
+
val,
|
|
3061
|
+
"\""
|
|
3062
|
+
]
|
|
3063
|
+
});
|
|
3064
|
+
case "number": return /* @__PURE__ */ jsx(Text, {
|
|
3065
|
+
component: "span",
|
|
3066
|
+
size,
|
|
3067
|
+
style: STYLES.number,
|
|
3068
|
+
children: val
|
|
3069
|
+
});
|
|
3070
|
+
case "boolean": return /* @__PURE__ */ jsx(Text, {
|
|
3071
|
+
component: "span",
|
|
3072
|
+
size,
|
|
3073
|
+
style: STYLES.boolean,
|
|
3074
|
+
children: String(val)
|
|
3075
|
+
});
|
|
3076
|
+
case "null":
|
|
3077
|
+
case "undefined": return /* @__PURE__ */ jsx(Text, {
|
|
3078
|
+
component: "span",
|
|
3079
|
+
size,
|
|
3080
|
+
style: STYLES.null,
|
|
3081
|
+
children: type
|
|
3082
|
+
});
|
|
3083
|
+
default: return /* @__PURE__ */ jsx(Text, {
|
|
3084
|
+
component: "span",
|
|
3085
|
+
size,
|
|
3086
|
+
children: String(val)
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
}, [
|
|
3090
|
+
formatValue,
|
|
3091
|
+
showQuotes,
|
|
3092
|
+
size
|
|
3093
|
+
]);
|
|
3094
|
+
const renderNode = useCallback(({ node, expanded, hasChildren, elementProps }) => {
|
|
3095
|
+
return /* @__PURE__ */ jsx(RowNode, {
|
|
3096
|
+
node,
|
|
3097
|
+
expanded,
|
|
3098
|
+
hasChildren,
|
|
3099
|
+
elementProps,
|
|
3100
|
+
size,
|
|
3101
|
+
config,
|
|
3102
|
+
showQuotes,
|
|
3103
|
+
showCopyButton,
|
|
3104
|
+
renderValue
|
|
3105
|
+
});
|
|
3106
|
+
}, [
|
|
3107
|
+
config,
|
|
3108
|
+
renderValue,
|
|
3109
|
+
showCopyButton,
|
|
3110
|
+
showQuotes,
|
|
3111
|
+
size
|
|
3112
|
+
]);
|
|
3113
|
+
if (treeData.length === 0) return /* @__PURE__ */ jsx(Text, {
|
|
3114
|
+
size,
|
|
3115
|
+
style: STYLES.null,
|
|
3116
|
+
children: data === null ? "null" : data === void 0 ? "undefined" : "{}"
|
|
3117
|
+
});
|
|
3118
|
+
return /* @__PURE__ */ jsx(Tree, {
|
|
3119
|
+
data: treeData,
|
|
3120
|
+
tree,
|
|
3121
|
+
levelOffset: config.levelOffset,
|
|
3122
|
+
expandOnClick: true,
|
|
3123
|
+
renderNode,
|
|
3124
|
+
styles: { root: STYLES.root }
|
|
3125
|
+
});
|
|
3126
|
+
};
|
|
3127
|
+
|
|
3128
|
+
//#endregion
|
|
3129
|
+
//#region ../../src/core/table/interfaces/types.ts
|
|
3130
|
+
const DEFAULT_MAX_VISIBLE_COLUMNS = 8;
|
|
3131
|
+
|
|
3132
|
+
//#endregion
|
|
3133
|
+
//#region ../../src/core/table/components/DataTableFilters.tsx
|
|
3134
|
+
const DataTableFilters = ({ schema, form, typeFormProps, filterVisibility }) => {
|
|
3135
|
+
const visibleSchema = useMemo(() => {
|
|
3136
|
+
const visibleKeys = Object.keys(schema.properties).filter((key) => filterVisibility[key] !== false);
|
|
3137
|
+
if (visibleKeys.length === 0) return null;
|
|
3138
|
+
const visibleProps = visibleKeys.reduce((acc, key) => {
|
|
3139
|
+
acc[key] = schema.properties[key];
|
|
3140
|
+
return acc;
|
|
3141
|
+
}, {});
|
|
3142
|
+
return t.object(visibleProps);
|
|
3143
|
+
}, [schema, filterVisibility]);
|
|
3144
|
+
if (!visibleSchema) return null;
|
|
3145
|
+
return /* @__PURE__ */ jsx(Flex, {
|
|
3146
|
+
w: "100%",
|
|
3147
|
+
p: "xs",
|
|
3148
|
+
bg: ui.colors.surface,
|
|
3149
|
+
style: { borderBottom: "1px solid var(--alepha-border)" },
|
|
3150
|
+
children: /* @__PURE__ */ jsx(TypeForm, {
|
|
3151
|
+
size: "xs",
|
|
3152
|
+
...typeFormProps,
|
|
3153
|
+
skipSubmitButton: true,
|
|
3154
|
+
fill: true,
|
|
3155
|
+
form,
|
|
3156
|
+
schema: visibleSchema,
|
|
3157
|
+
columns: {
|
|
3158
|
+
base: 1,
|
|
3159
|
+
sm: 2,
|
|
3160
|
+
md: 3,
|
|
3161
|
+
lg: 4,
|
|
3162
|
+
xl: 6
|
|
3163
|
+
}
|
|
3164
|
+
})
|
|
3165
|
+
});
|
|
3166
|
+
};
|
|
3167
|
+
|
|
3168
|
+
//#endregion
|
|
3169
|
+
//#region ../../src/core/table/components/DataTablePagination.tsx
|
|
3170
|
+
const DataTablePagination = ({ page, size, totalPages, onPageChange, onSizeChange }) => {
|
|
3171
|
+
return /* @__PURE__ */ jsxs(Flex, {
|
|
3172
|
+
align: "center",
|
|
3173
|
+
justify: "end",
|
|
3174
|
+
gap: "md",
|
|
3175
|
+
p: "xs",
|
|
3176
|
+
style: { borderTop: "1px solid var(--alepha-border)" },
|
|
3177
|
+
children: [/* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Select, {
|
|
3178
|
+
w: 96,
|
|
3179
|
+
variant: "default",
|
|
3180
|
+
value: size,
|
|
3181
|
+
onChange: (value) => {
|
|
3182
|
+
if (value) onSizeChange(Number(value));
|
|
3183
|
+
},
|
|
3184
|
+
data: [
|
|
3185
|
+
{
|
|
3186
|
+
value: "5",
|
|
3187
|
+
label: "5"
|
|
3188
|
+
},
|
|
3189
|
+
{
|
|
3190
|
+
value: "10",
|
|
3191
|
+
label: "10"
|
|
3192
|
+
},
|
|
3193
|
+
{
|
|
3194
|
+
value: "25",
|
|
3195
|
+
label: "25"
|
|
3196
|
+
},
|
|
3197
|
+
{
|
|
3198
|
+
value: "50",
|
|
3199
|
+
label: "50"
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
value: "100",
|
|
3203
|
+
label: "100"
|
|
3204
|
+
}
|
|
3205
|
+
]
|
|
3206
|
+
}) }), /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Pagination, {
|
|
3207
|
+
withEdges: true,
|
|
3208
|
+
total: totalPages,
|
|
3209
|
+
value: page,
|
|
3210
|
+
onChange: onPageChange
|
|
3211
|
+
}) })]
|
|
3212
|
+
});
|
|
3213
|
+
};
|
|
3214
|
+
|
|
3215
|
+
//#endregion
|
|
3216
|
+
//#region ../../src/core/table/components/ColumnPicker.tsx
|
|
3217
|
+
const ColumnPicker = ({ columns, visibility, onVisibilityChange }) => {
|
|
3218
|
+
const [opened, setOpened] = useState(false);
|
|
3219
|
+
const columnEntries = Object.entries(columns);
|
|
3220
|
+
const handleShowAll = () => {
|
|
3221
|
+
onVisibilityChange(columnEntries.reduce((acc, [key]) => ({
|
|
3222
|
+
...acc,
|
|
3223
|
+
[key]: true
|
|
3224
|
+
}), {}));
|
|
3225
|
+
};
|
|
3226
|
+
const handleDefault = () => {
|
|
3227
|
+
let count = 0;
|
|
3228
|
+
onVisibilityChange(columnEntries.reduce((acc, [key, col]) => {
|
|
3229
|
+
if (col.defaultHidden) acc[key] = false;
|
|
3230
|
+
else if (count < DEFAULT_MAX_VISIBLE_COLUMNS) {
|
|
3231
|
+
acc[key] = true;
|
|
3232
|
+
count++;
|
|
3233
|
+
} else acc[key] = false;
|
|
3234
|
+
return acc;
|
|
3235
|
+
}, {}));
|
|
3236
|
+
};
|
|
3237
|
+
const handleToggle = (key, checked) => {
|
|
3238
|
+
onVisibilityChange({
|
|
3239
|
+
...visibility,
|
|
3240
|
+
[key]: checked
|
|
3241
|
+
});
|
|
3242
|
+
};
|
|
3243
|
+
const visibleCount = columnEntries.filter(([key]) => visibility[key] !== false).length;
|
|
3244
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
3245
|
+
width: 280,
|
|
3246
|
+
position: "bottom-start",
|
|
3247
|
+
shadow: "md",
|
|
3248
|
+
opened,
|
|
3249
|
+
onChange: setOpened,
|
|
3250
|
+
closeOnClickOutside: true,
|
|
3251
|
+
closeOnEscape: true,
|
|
3252
|
+
transitionProps: {
|
|
3253
|
+
transition: "fade-up",
|
|
3254
|
+
duration: 200,
|
|
3255
|
+
timingFunction: "ease"
|
|
3256
|
+
},
|
|
3257
|
+
children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ActionButton, {
|
|
3258
|
+
variant: "subtle",
|
|
3259
|
+
icon: IconColumns,
|
|
3260
|
+
onClick: () => setOpened((o) => !o)
|
|
3261
|
+
}) }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
|
|
3262
|
+
bg: "transparent",
|
|
3263
|
+
p: "xs",
|
|
3264
|
+
bd: `1px solid ${ui.colors.border}`,
|
|
3265
|
+
style: { backdropFilter: "blur(20px)" },
|
|
3266
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
3267
|
+
direction: "column",
|
|
3268
|
+
gap: "xs",
|
|
3269
|
+
bg: ui.colors.surface,
|
|
3270
|
+
p: "sm",
|
|
3271
|
+
bdrs: "sm",
|
|
3272
|
+
children: [/* @__PURE__ */ jsxs(Flex, {
|
|
3273
|
+
justify: "space-between",
|
|
3274
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
3275
|
+
size: "sm",
|
|
3276
|
+
fw: 500,
|
|
3277
|
+
children: [
|
|
3278
|
+
"Columns (",
|
|
3279
|
+
visibleCount,
|
|
3280
|
+
"/",
|
|
3281
|
+
columnEntries.length,
|
|
3282
|
+
")"
|
|
3283
|
+
]
|
|
3284
|
+
}), /* @__PURE__ */ jsxs(Flex, {
|
|
3285
|
+
gap: 4,
|
|
3286
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
3287
|
+
size: "compact-xs",
|
|
3288
|
+
variant: "subtle",
|
|
3289
|
+
onClick: handleShowAll,
|
|
3290
|
+
children: "All"
|
|
3291
|
+
}), /* @__PURE__ */ jsx(ActionButton, {
|
|
3292
|
+
size: "compact-xs",
|
|
3293
|
+
variant: "subtle",
|
|
3294
|
+
onClick: handleDefault,
|
|
3295
|
+
children: "Default"
|
|
3296
|
+
})]
|
|
3297
|
+
})]
|
|
3298
|
+
}), /* @__PURE__ */ jsx(ScrollArea.Autosize, {
|
|
3299
|
+
mah: 300,
|
|
3300
|
+
children: /* @__PURE__ */ jsx(Flex, {
|
|
3301
|
+
direction: "column",
|
|
3302
|
+
gap: 4,
|
|
3303
|
+
children: columnEntries.map(([key, col]) => /* @__PURE__ */ jsx(Checkbox, {
|
|
3304
|
+
label: col.label || key,
|
|
3305
|
+
checked: visibility[key] !== false,
|
|
3306
|
+
onChange: (e) => handleToggle(key, e.currentTarget.checked),
|
|
3307
|
+
size: "sm"
|
|
3308
|
+
}, key))
|
|
3309
|
+
})
|
|
3310
|
+
})]
|
|
3311
|
+
})
|
|
3312
|
+
})]
|
|
3313
|
+
});
|
|
3314
|
+
};
|
|
3315
|
+
|
|
3316
|
+
//#endregion
|
|
3317
|
+
//#region ../../src/core/table/components/FilterPicker.tsx
|
|
3318
|
+
const getFieldLabel = (schema, key) => {
|
|
3319
|
+
const prop = schema.properties[key];
|
|
3320
|
+
if (prop && typeof prop === "object" && "title" in prop && prop.title) return prop.title;
|
|
3321
|
+
return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
3322
|
+
};
|
|
3323
|
+
const FilterPicker = ({ schema, visibility, onVisibilityChange }) => {
|
|
3324
|
+
const [opened, setOpened] = useState(false);
|
|
3325
|
+
const filterKeys = Object.keys(schema.properties);
|
|
3326
|
+
const handleShowAll = () => {
|
|
3327
|
+
onVisibilityChange(filterKeys.reduce((acc, key) => ({
|
|
3328
|
+
...acc,
|
|
3329
|
+
[key]: true
|
|
3330
|
+
}), {}));
|
|
3331
|
+
};
|
|
3332
|
+
const handleHideAll = () => {
|
|
3333
|
+
onVisibilityChange(filterKeys.reduce((acc, key) => ({
|
|
3334
|
+
...acc,
|
|
3335
|
+
[key]: false
|
|
3336
|
+
}), {}));
|
|
3337
|
+
};
|
|
3338
|
+
const handleToggle = (key, checked) => {
|
|
3339
|
+
onVisibilityChange({
|
|
3340
|
+
...visibility,
|
|
3341
|
+
[key]: checked
|
|
3342
|
+
});
|
|
3343
|
+
};
|
|
3344
|
+
const visibleCount = filterKeys.filter((key) => visibility[key]).length;
|
|
3345
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
3346
|
+
width: 280,
|
|
3347
|
+
position: "bottom-start",
|
|
3348
|
+
shadow: "md",
|
|
3349
|
+
opened,
|
|
3350
|
+
onChange: setOpened,
|
|
3351
|
+
closeOnClickOutside: true,
|
|
3352
|
+
closeOnEscape: true,
|
|
3353
|
+
transitionProps: {
|
|
3354
|
+
transition: "fade-up",
|
|
3355
|
+
duration: 200,
|
|
3356
|
+
timingFunction: "ease"
|
|
3357
|
+
},
|
|
3358
|
+
children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ActionButton, {
|
|
3359
|
+
variant: "subtle",
|
|
3360
|
+
icon: IconFilter,
|
|
3361
|
+
onClick: () => setOpened((o) => !o)
|
|
3362
|
+
}) }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
|
|
3363
|
+
bg: "transparent",
|
|
3364
|
+
p: "xs",
|
|
3365
|
+
bd: `1px solid ${ui.colors.border}`,
|
|
3366
|
+
style: { backdropFilter: "blur(20px)" },
|
|
3367
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
3368
|
+
direction: "column",
|
|
3369
|
+
gap: "xs",
|
|
3370
|
+
bg: ui.colors.surface,
|
|
3371
|
+
p: "sm",
|
|
3372
|
+
bdrs: "sm",
|
|
3373
|
+
children: [/* @__PURE__ */ jsxs(Flex, {
|
|
3374
|
+
justify: "space-between",
|
|
3375
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
3376
|
+
size: "sm",
|
|
3377
|
+
fw: 500,
|
|
3378
|
+
children: [
|
|
3379
|
+
"Filters (",
|
|
3380
|
+
visibleCount,
|
|
3381
|
+
"/",
|
|
3382
|
+
filterKeys.length,
|
|
3383
|
+
")"
|
|
3384
|
+
]
|
|
3385
|
+
}), /* @__PURE__ */ jsxs(Flex, {
|
|
3386
|
+
gap: 4,
|
|
3387
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
3388
|
+
size: "compact-xs",
|
|
3389
|
+
variant: "subtle",
|
|
3390
|
+
onClick: handleShowAll,
|
|
3391
|
+
children: "All"
|
|
3392
|
+
}), /* @__PURE__ */ jsx(ActionButton, {
|
|
3393
|
+
size: "compact-xs",
|
|
3394
|
+
variant: "subtle",
|
|
3395
|
+
onClick: handleHideAll,
|
|
3396
|
+
children: "None"
|
|
3397
|
+
})]
|
|
3398
|
+
})]
|
|
3399
|
+
}), /* @__PURE__ */ jsx(ScrollArea.Autosize, {
|
|
3400
|
+
mah: 300,
|
|
3401
|
+
children: /* @__PURE__ */ jsx(Flex, {
|
|
3402
|
+
direction: "column",
|
|
3403
|
+
gap: 4,
|
|
3404
|
+
children: filterKeys.map((key) => /* @__PURE__ */ jsx(Checkbox, {
|
|
3405
|
+
label: getFieldLabel(schema, key),
|
|
3406
|
+
checked: visibility[key] === true,
|
|
3407
|
+
onChange: (e) => handleToggle(key, e.currentTarget.checked),
|
|
3408
|
+
size: "sm"
|
|
3409
|
+
}, key))
|
|
3410
|
+
})
|
|
3411
|
+
})]
|
|
3412
|
+
})
|
|
3413
|
+
})]
|
|
3414
|
+
});
|
|
3415
|
+
};
|
|
3416
|
+
|
|
3417
|
+
//#endregion
|
|
3418
|
+
//#region ../../src/core/table/components/DataTableToolbar.tsx
|
|
3419
|
+
const escapeCsvField = (value) => {
|
|
3420
|
+
if (value.includes(",") || value.includes("\"") || value.includes("\n")) return `"${value.replace(/"/g, "\"\"")}"`;
|
|
3421
|
+
return value;
|
|
3422
|
+
};
|
|
3423
|
+
const extractText = (node) => {
|
|
3424
|
+
if (node == null || typeof node === "boolean") return "";
|
|
3425
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
3426
|
+
if (Array.isArray(node)) return node.map(extractText).join("");
|
|
3427
|
+
if (typeof node === "object" && "props" in node) return extractText(node.props.children);
|
|
3428
|
+
return "";
|
|
3429
|
+
};
|
|
3430
|
+
const DataTableToolbar = ({ columns, filters, columnVisibility, filterVisibility, onColumnVisibilityChange, onFilterVisibilityChange, actions, onRefresh, items, withExport, selectedItems = [], checkboxActions, onClearSelection }) => {
|
|
3431
|
+
const hasSelection = selectedItems.length > 0;
|
|
3432
|
+
const exportableColumns = useCallback(() => {
|
|
3433
|
+
return Object.entries(columns).filter(([key, col]) => !col.actions && columnVisibility[key] !== false);
|
|
3434
|
+
}, [columns, columnVisibility]);
|
|
3435
|
+
const buildRows = useCallback(() => {
|
|
3436
|
+
const cols = exportableColumns();
|
|
3437
|
+
return items.map((item) => cols.map(([_key, col]) => {
|
|
3438
|
+
if (!col.value) return "";
|
|
3439
|
+
return extractText(col.value(item, {}));
|
|
3440
|
+
}));
|
|
3441
|
+
}, [items, exportableColumns]);
|
|
3442
|
+
const buildCsv = useCallback(() => {
|
|
3443
|
+
const header = exportableColumns().map(([_key, col]) => escapeCsvField(col.label));
|
|
3444
|
+
const rows = buildRows().map((row) => row.map(escapeCsvField));
|
|
3445
|
+
return [header.join(","), ...rows.map((r) => r.join(","))].join("\n");
|
|
3446
|
+
}, [exportableColumns, buildRows]);
|
|
3447
|
+
const exportCsv = useCallback(() => {
|
|
3448
|
+
const csv = buildCsv();
|
|
3449
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
3450
|
+
const url = URL.createObjectURL(blob);
|
|
3451
|
+
const a = document.createElement("a");
|
|
3452
|
+
a.href = url;
|
|
3453
|
+
a.download = "export.csv";
|
|
3454
|
+
a.click();
|
|
3455
|
+
URL.revokeObjectURL(url);
|
|
3456
|
+
}, [buildCsv]);
|
|
3457
|
+
const exportClipboard = useCallback(async () => {
|
|
3458
|
+
const header = exportableColumns().map(([_key, col]) => col.label);
|
|
3459
|
+
const rows = buildRows();
|
|
3460
|
+
const text = [header.join(" "), ...rows.map((r) => r.join(" "))].join("\n");
|
|
3461
|
+
await navigator.clipboard.writeText(text);
|
|
3462
|
+
}, [exportableColumns, buildRows]);
|
|
3463
|
+
const handleCheckboxAction = async (action) => {
|
|
3464
|
+
const ctx = {
|
|
3465
|
+
selectedItems,
|
|
3466
|
+
clearSelection: onClearSelection || (() => {})
|
|
3467
|
+
};
|
|
3468
|
+
await action.onClick(ctx);
|
|
3469
|
+
};
|
|
3470
|
+
return /* @__PURE__ */ jsxs(Flex, {
|
|
3471
|
+
p: "xs",
|
|
3472
|
+
style: { borderBottom: "1px solid var(--alepha-border)" },
|
|
3473
|
+
children: [
|
|
3474
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
3475
|
+
gap: 4,
|
|
3476
|
+
align: "center",
|
|
3477
|
+
children: [
|
|
3478
|
+
filters && /* @__PURE__ */ jsx(FilterPicker, {
|
|
3479
|
+
schema: filters,
|
|
3480
|
+
visibility: filterVisibility,
|
|
3481
|
+
onVisibilityChange: onFilterVisibilityChange
|
|
3482
|
+
}),
|
|
3483
|
+
/* @__PURE__ */ jsx(ColumnPicker, {
|
|
3484
|
+
columns,
|
|
3485
|
+
visibility: columnVisibility,
|
|
3486
|
+
onVisibilityChange: onColumnVisibilityChange
|
|
3487
|
+
}),
|
|
3488
|
+
withExport && /* @__PURE__ */ jsx(ActionButton, {
|
|
3489
|
+
variant: "subtle",
|
|
3490
|
+
icon: IconDownload,
|
|
3491
|
+
menu: { items: [{
|
|
3492
|
+
label: "Export as CSV",
|
|
3493
|
+
icon: /* @__PURE__ */ jsx(IconDownload, { size: 14 }),
|
|
3494
|
+
onClick: exportCsv
|
|
3495
|
+
}, {
|
|
3496
|
+
label: "Copy to clipboard",
|
|
3497
|
+
icon: /* @__PURE__ */ jsx(IconClipboard, { size: 14 }),
|
|
3498
|
+
onClick: exportClipboard
|
|
3499
|
+
}] }
|
|
3500
|
+
}),
|
|
3501
|
+
hasSelection && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3502
|
+
/* @__PURE__ */ jsx(Divider, {
|
|
3503
|
+
orientation: "vertical",
|
|
3504
|
+
mx: "xs"
|
|
3505
|
+
}),
|
|
3506
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
3507
|
+
variant: "light",
|
|
3508
|
+
size: "lg",
|
|
3509
|
+
children: [selectedItems.length, " selected"]
|
|
3510
|
+
}),
|
|
3511
|
+
/* @__PURE__ */ jsx(ActionButton, {
|
|
3512
|
+
variant: "subtle",
|
|
3513
|
+
size: "compact-sm",
|
|
3514
|
+
icon: IconX,
|
|
3515
|
+
onClick: onClearSelection,
|
|
3516
|
+
children: "Clear"
|
|
3517
|
+
}),
|
|
3518
|
+
checkboxActions?.map((action, index) => /* @__PURE__ */ jsx(ActionButton, {
|
|
3519
|
+
variant: "light",
|
|
3520
|
+
size: "compact-sm",
|
|
3521
|
+
intent: action.intent,
|
|
3522
|
+
icon: action.icon && isComponentType(action.icon) ? action.icon : void 0,
|
|
3523
|
+
onClick: () => handleCheckboxAction(action),
|
|
3524
|
+
children: action.label
|
|
3525
|
+
}, index))
|
|
3526
|
+
] })
|
|
3527
|
+
]
|
|
3528
|
+
}),
|
|
3529
|
+
/* @__PURE__ */ jsx(Flex, { flex: 1 }),
|
|
3530
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
3531
|
+
gap: "xs",
|
|
3532
|
+
children: [actions?.map((props, index) => !isValidElement(props) ? /* @__PURE__ */ jsx(ActionButton, {
|
|
3533
|
+
...props,
|
|
3534
|
+
children: props.label
|
|
3535
|
+
}, index) : props), /* @__PURE__ */ jsx(ActionButton, {
|
|
3536
|
+
variant: "subtle",
|
|
3537
|
+
icon: IconRefresh,
|
|
3538
|
+
onClick: onRefresh
|
|
3539
|
+
})]
|
|
3540
|
+
})
|
|
3541
|
+
]
|
|
3542
|
+
});
|
|
3543
|
+
};
|
|
3544
|
+
|
|
3545
|
+
//#endregion
|
|
3546
|
+
//#region ../../src/core/table/components/useTableSelection.ts
|
|
3547
|
+
const useTableSelection = (items, getItemKey, enabled) => {
|
|
3548
|
+
const [selectedKeys, setSelectedKeys] = useState(/* @__PURE__ */ new Set());
|
|
3549
|
+
const selectedItems = useMemo(() => {
|
|
3550
|
+
if (!enabled) return [];
|
|
3551
|
+
return items.filter((item) => selectedKeys.has(getItemKey(item)));
|
|
3552
|
+
}, [
|
|
3553
|
+
items,
|
|
3554
|
+
selectedKeys,
|
|
3555
|
+
getItemKey,
|
|
3556
|
+
enabled
|
|
3557
|
+
]);
|
|
3558
|
+
const allSelected = useMemo(() => {
|
|
3559
|
+
if (items.length === 0) return false;
|
|
3560
|
+
return items.every((item) => selectedKeys.has(getItemKey(item)));
|
|
3561
|
+
}, [
|
|
3562
|
+
items,
|
|
3563
|
+
selectedKeys,
|
|
3564
|
+
getItemKey
|
|
3565
|
+
]);
|
|
3566
|
+
return {
|
|
3567
|
+
selectedItems,
|
|
3568
|
+
allSelected,
|
|
3569
|
+
someSelected: useMemo(() => {
|
|
3570
|
+
if (items.length === 0) return false;
|
|
3571
|
+
const count = items.filter((item) => selectedKeys.has(getItemKey(item))).length;
|
|
3572
|
+
return count > 0 && count < items.length;
|
|
3573
|
+
}, [
|
|
3574
|
+
items,
|
|
3575
|
+
selectedKeys,
|
|
3576
|
+
getItemKey
|
|
3577
|
+
]),
|
|
3578
|
+
toggleItem: useCallback((item) => {
|
|
3579
|
+
const key = getItemKey(item);
|
|
3580
|
+
setSelectedKeys((prev) => {
|
|
3581
|
+
const next = new Set(prev);
|
|
3582
|
+
if (next.has(key)) next.delete(key);
|
|
3583
|
+
else next.add(key);
|
|
3584
|
+
return next;
|
|
3585
|
+
});
|
|
3586
|
+
}, [getItemKey]),
|
|
3587
|
+
toggleAll: useCallback(() => {
|
|
3588
|
+
if (allSelected) setSelectedKeys((prev) => {
|
|
3589
|
+
const next = new Set(prev);
|
|
3590
|
+
for (const item of items) next.delete(getItemKey(item));
|
|
3591
|
+
return next;
|
|
3592
|
+
});
|
|
3593
|
+
else setSelectedKeys((prev) => {
|
|
3594
|
+
const next = new Set(prev);
|
|
3595
|
+
for (const item of items) next.add(getItemKey(item));
|
|
3596
|
+
return next;
|
|
3597
|
+
});
|
|
3598
|
+
}, [
|
|
3599
|
+
allSelected,
|
|
3600
|
+
items,
|
|
3601
|
+
getItemKey
|
|
3602
|
+
]),
|
|
3603
|
+
clear: useCallback(() => setSelectedKeys(/* @__PURE__ */ new Set()), []),
|
|
3604
|
+
isSelected: useCallback((item) => selectedKeys.has(getItemKey(item)), [selectedKeys, getItemKey])
|
|
3605
|
+
};
|
|
3606
|
+
};
|
|
3607
|
+
|
|
3608
|
+
//#endregion
|
|
3609
|
+
//#region ../../src/core/table/components/DataTable.tsx
|
|
3610
|
+
/**
|
|
3611
|
+
* Parse the sort string to get direction for a specific field.
|
|
3612
|
+
* Alepha convention: 'field' = ASC, '-field' = DESC
|
|
3613
|
+
*/
|
|
3614
|
+
const getSortDirection = (sortString, field) => {
|
|
3615
|
+
if (!sortString) return null;
|
|
3616
|
+
const parts = sortString.split(",").map((s) => s.trim());
|
|
3617
|
+
for (const part of parts) {
|
|
3618
|
+
if (part === field) return "asc";
|
|
3619
|
+
if (part === `-${field}`) return "desc";
|
|
3620
|
+
}
|
|
3621
|
+
return null;
|
|
3622
|
+
};
|
|
3623
|
+
/**
|
|
3624
|
+
* Toggle sort for a field in the sort string.
|
|
3625
|
+
* Cycles: null -> asc -> desc -> null
|
|
3626
|
+
*/
|
|
3627
|
+
const toggleSort = (sortString, field) => {
|
|
3628
|
+
const current = getSortDirection(sortString, field);
|
|
3629
|
+
const parts = (sortString || "").split(",").map((s) => s.trim()).filter((s) => s && s !== field && s !== `-${field}`);
|
|
3630
|
+
if (current === null) parts.unshift(field);
|
|
3631
|
+
else if (current === "asc") parts.unshift(`-${field}`);
|
|
3632
|
+
return parts.length > 0 ? parts.join(",") : void 0;
|
|
3633
|
+
};
|
|
3634
|
+
const toAriaSort = (dir) => {
|
|
3635
|
+
if (dir === "asc") return "ascending";
|
|
3636
|
+
if (dir === "desc") return "descending";
|
|
3637
|
+
return "none";
|
|
3638
|
+
};
|
|
3639
|
+
const FIT_STYLE = {
|
|
3640
|
+
width: 1,
|
|
3641
|
+
whiteSpace: "nowrap"
|
|
3642
|
+
};
|
|
3643
|
+
const DataTable = (props) => {
|
|
3644
|
+
const [items, setItems] = useState(typeof props.items === "function" ? { content: [] } : props.items);
|
|
3645
|
+
const defaultSize = props.infinityScroll ? 100 : props.defaultSize || 10;
|
|
3646
|
+
const [page, setPage] = useState(1);
|
|
3647
|
+
const [size, setSize] = useState(String(defaultSize));
|
|
3648
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
3649
|
+
const alepha = useInject(Alepha);
|
|
3650
|
+
const sentinelRef = useRef(null);
|
|
3651
|
+
const [columnVisibility, setColumnVisibility] = useState(() => {
|
|
3652
|
+
const entries = Object.entries(props.columns);
|
|
3653
|
+
let visibleCount = 0;
|
|
3654
|
+
return entries.reduce((acc, [key, col]) => {
|
|
3655
|
+
if (col.defaultHidden) acc[key] = false;
|
|
3656
|
+
else if (visibleCount < DEFAULT_MAX_VISIBLE_COLUMNS) {
|
|
3657
|
+
acc[key] = true;
|
|
3658
|
+
visibleCount++;
|
|
3659
|
+
} else acc[key] = false;
|
|
3660
|
+
return acc;
|
|
3661
|
+
}, {});
|
|
3662
|
+
});
|
|
3663
|
+
const [filterVisibility, setFilterVisibility] = useState(() => {
|
|
3664
|
+
if (!props.filters?.properties) return {};
|
|
3665
|
+
const defaults = new Set(props.defaultFilters ?? []);
|
|
3666
|
+
return Object.keys(props.filters.properties).reduce((acc, key) => ({
|
|
3667
|
+
...acc,
|
|
3668
|
+
[key]: defaults.has(key)
|
|
3669
|
+
}), {});
|
|
3670
|
+
});
|
|
3671
|
+
const visibleColumns = useMemo(() => {
|
|
3672
|
+
return Object.entries(props.columns).filter(([key]) => columnVisibility[key] !== false);
|
|
3673
|
+
}, [props.columns, columnVisibility]);
|
|
3674
|
+
const [sortString, setSortString] = useState(void 0);
|
|
3675
|
+
const handleSortClick = (columnKey, sortKey) => {
|
|
3676
|
+
const newSort = toggleSort(sortString, sortKey || columnKey);
|
|
3677
|
+
setSortString(newSort);
|
|
3678
|
+
form.input.sort.set(newSort);
|
|
3679
|
+
form.input.page.set(0);
|
|
3680
|
+
};
|
|
3681
|
+
const getItemKey = useCallback((item) => {
|
|
3682
|
+
if (props.getItemKey) return props.getItemKey(item);
|
|
3683
|
+
if ("id" in item) return String(item.id);
|
|
3684
|
+
return JSON.stringify(item);
|
|
3685
|
+
}, [props.getItemKey]);
|
|
3686
|
+
const selection = useTableSelection(items.content, getItemKey, props.withCheckbox ?? false);
|
|
3687
|
+
const panelConfig = useMemo(() => {
|
|
3688
|
+
if (!props.panel) return null;
|
|
3689
|
+
if (typeof props.panel === "function") return {
|
|
3690
|
+
render: props.panel,
|
|
3691
|
+
can: void 0
|
|
3692
|
+
};
|
|
3693
|
+
return props.panel;
|
|
3694
|
+
}, [props.panel]);
|
|
3695
|
+
const [drawerItem, setDrawerItem] = useState(null);
|
|
3696
|
+
const drawerConfig = useMemo(() => {
|
|
3697
|
+
if (!props.drawer) return null;
|
|
3698
|
+
if (typeof props.drawer === "function") return {
|
|
3699
|
+
render: props.drawer,
|
|
3700
|
+
can: void 0,
|
|
3701
|
+
props: void 0
|
|
3702
|
+
};
|
|
3703
|
+
return props.drawer;
|
|
3704
|
+
}, [props.drawer]);
|
|
3705
|
+
const [expandedKeys, setExpandedKeys] = useState(/* @__PURE__ */ new Set());
|
|
3706
|
+
const toggleExpand = useCallback((key) => {
|
|
3707
|
+
setExpandedKeys((prev) => {
|
|
3708
|
+
const next = new Set(prev);
|
|
3709
|
+
if (next.has(key)) next.delete(key);
|
|
3710
|
+
else next.add(key);
|
|
3711
|
+
return next;
|
|
3712
|
+
});
|
|
3713
|
+
}, []);
|
|
3714
|
+
const form = useForm({
|
|
3715
|
+
schema: t.object({
|
|
3716
|
+
...props.filters ? props.filters.properties : {},
|
|
3717
|
+
page: t.number({ default: 0 }),
|
|
3718
|
+
size: t.number({ default: defaultSize }),
|
|
3719
|
+
sort: t.optional(t.string())
|
|
3720
|
+
}),
|
|
3721
|
+
handler: async (values) => {
|
|
3722
|
+
if (typeof props.items === "function") {
|
|
3723
|
+
const response = await props.items(values, { items: items.content });
|
|
3724
|
+
if (props.infinityScroll && values.page > 0) setItems((prev) => ({
|
|
3725
|
+
...response,
|
|
3726
|
+
content: [...prev.content, ...response.content]
|
|
3727
|
+
}));
|
|
3728
|
+
else setItems(response);
|
|
3729
|
+
setCurrentPage(values.page);
|
|
3730
|
+
}
|
|
3731
|
+
},
|
|
3732
|
+
onReset: async () => {
|
|
3733
|
+
setPage(1);
|
|
3734
|
+
setSize(String(defaultSize));
|
|
3735
|
+
await form.submit();
|
|
3736
|
+
},
|
|
3737
|
+
onChange: async (key, value) => {
|
|
3738
|
+
if (key === "page") {
|
|
3739
|
+
setPage(value + 1);
|
|
3740
|
+
await form.submit();
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
if (key === "size") {
|
|
3744
|
+
setSize(String(value));
|
|
3745
|
+
form.input.page.set(0);
|
|
3746
|
+
return;
|
|
3747
|
+
}
|
|
3748
|
+
props.onFilterChange?.(key, value, form);
|
|
3749
|
+
}
|
|
3750
|
+
}, [items]);
|
|
3751
|
+
const dt = useInject(DateTimeProvider);
|
|
3752
|
+
useEffect(() => {
|
|
3753
|
+
if (props.submitOnInit) form.submit();
|
|
3754
|
+
if (props.submitEvery) {
|
|
3755
|
+
const it = dt.createInterval(() => {
|
|
3756
|
+
form.submit();
|
|
3757
|
+
}, props.submitEvery);
|
|
3758
|
+
return () => dt.clearInterval(it);
|
|
3759
|
+
}
|
|
3760
|
+
}, []);
|
|
3761
|
+
useEffect(() => {
|
|
3762
|
+
if (typeof props.items !== "function") setItems(props.items);
|
|
3763
|
+
}, [props.items]);
|
|
3764
|
+
useEffect(() => {
|
|
3765
|
+
if (!props.infinityScroll || typeof props.items !== "function") return;
|
|
3766
|
+
const sentinel = sentinelRef.current;
|
|
3767
|
+
if (!sentinel) return;
|
|
3768
|
+
const observer = new IntersectionObserver((entries) => {
|
|
3769
|
+
if (!entries[0].isIntersecting || form.submitting) return;
|
|
3770
|
+
const totalPages = items.page?.totalPages ?? 1;
|
|
3771
|
+
if (currentPage + 1 < totalPages) form.input.page.set(currentPage + 1);
|
|
3772
|
+
}, { rootMargin: "300px" });
|
|
3773
|
+
observer.observe(sentinel);
|
|
3774
|
+
return () => observer.disconnect();
|
|
3775
|
+
}, [
|
|
3776
|
+
props.infinityScroll,
|
|
3777
|
+
form.submitting,
|
|
3778
|
+
items.page?.totalPages,
|
|
3779
|
+
currentPage,
|
|
3780
|
+
form
|
|
3781
|
+
]);
|
|
3782
|
+
const totalColumns = visibleColumns.length + (panelConfig ? 1 : 0) + (props.withCheckbox ? 1 : 0);
|
|
3783
|
+
const checkboxHeader = props.withCheckbox ? /* @__PURE__ */ jsx(Table.Th, {
|
|
3784
|
+
style: { width: 40 },
|
|
3785
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
3786
|
+
checked: selection.allSelected,
|
|
3787
|
+
indeterminate: selection.someSelected,
|
|
3788
|
+
onChange: selection.toggleAll,
|
|
3789
|
+
"aria-label": "Select all"
|
|
3790
|
+
})
|
|
3791
|
+
}) : null;
|
|
3792
|
+
const head = visibleColumns.map(([key, col]) => {
|
|
3793
|
+
const sortField = col.sortKey || key;
|
|
3794
|
+
const sortDir = col.sortable ? getSortDirection(sortString, sortField) : null;
|
|
3795
|
+
return /* @__PURE__ */ jsx(Table.Th, {
|
|
3796
|
+
onClick: col.sortable ? () => handleSortClick(key, col.sortKey) : void 0,
|
|
3797
|
+
"aria-sort": col.sortable ? toAriaSort(sortDir) : void 0,
|
|
3798
|
+
style: {
|
|
3799
|
+
...col.fit ? FIT_STYLE : {},
|
|
3800
|
+
...col.sortable ? {
|
|
3801
|
+
cursor: "pointer",
|
|
3802
|
+
userSelect: "none"
|
|
3803
|
+
} : {}
|
|
3804
|
+
},
|
|
3805
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
3806
|
+
align: "center",
|
|
3807
|
+
gap: 4,
|
|
3808
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
3809
|
+
size: "xs",
|
|
3810
|
+
children: col.label
|
|
3811
|
+
}), col.sortable && /* @__PURE__ */ jsxs(Flex, {
|
|
3812
|
+
c: "dimmed",
|
|
3813
|
+
children: [
|
|
3814
|
+
sortDir === "asc" && /* @__PURE__ */ jsx(IconArrowUp, { size: ui.sizes.icon.sm }),
|
|
3815
|
+
sortDir === "desc" && /* @__PURE__ */ jsx(IconArrowDown, { size: ui.sizes.icon.sm }),
|
|
3816
|
+
sortDir === null && /* @__PURE__ */ jsx(IconArrowsSort, { size: ui.sizes.icon.sm })
|
|
3817
|
+
]
|
|
3818
|
+
})]
|
|
3819
|
+
})
|
|
3820
|
+
}, key);
|
|
3821
|
+
});
|
|
3822
|
+
const rows = items.content.flatMap((item, index) => {
|
|
3823
|
+
const trProps = props.tableTrProps ? props.tableTrProps(item) : {};
|
|
3824
|
+
const itemKey = getItemKey(item);
|
|
3825
|
+
const isSelected = selection.isSelected(item);
|
|
3826
|
+
const showPanel = panelConfig && (panelConfig.can ? panelConfig.can(item) : true);
|
|
3827
|
+
const isExpanded = expandedKeys.has(itemKey);
|
|
3828
|
+
const canOpenDrawer = drawerConfig && (drawerConfig.can ? drawerConfig.can(item) : true);
|
|
3829
|
+
const elements = [/* @__PURE__ */ jsxs(Table.Tr, {
|
|
3830
|
+
...trProps,
|
|
3831
|
+
style: {
|
|
3832
|
+
...canOpenDrawer ? { cursor: "pointer" } : {},
|
|
3833
|
+
...trProps.style ?? {}
|
|
3834
|
+
},
|
|
3835
|
+
onClick: (e) => {
|
|
3836
|
+
if (canOpenDrawer) setDrawerItem(item);
|
|
3837
|
+
trProps.onClick?.(e);
|
|
3838
|
+
},
|
|
3839
|
+
children: [
|
|
3840
|
+
panelConfig && /* @__PURE__ */ jsx(Table.Td, {
|
|
3841
|
+
style: {
|
|
3842
|
+
width: 36,
|
|
3843
|
+
textAlign: "center"
|
|
3844
|
+
},
|
|
3845
|
+
py: 2,
|
|
3846
|
+
px: 0,
|
|
3847
|
+
children: showPanel && /* @__PURE__ */ jsx(UnstyledButton, {
|
|
3848
|
+
onClick: (e) => {
|
|
3849
|
+
e.stopPropagation();
|
|
3850
|
+
toggleExpand(itemKey);
|
|
3851
|
+
},
|
|
3852
|
+
style: { display: "inline-flex" },
|
|
3853
|
+
children: /* @__PURE__ */ jsx(Flex, {
|
|
3854
|
+
c: "dimmed",
|
|
3855
|
+
align: "center",
|
|
3856
|
+
justify: "center",
|
|
3857
|
+
children: isExpanded ? /* @__PURE__ */ jsx(IconChevronDown, { size: ui.sizes.icon.sm }) : /* @__PURE__ */ jsx(IconChevronRight, { size: ui.sizes.icon.sm })
|
|
3858
|
+
})
|
|
3859
|
+
})
|
|
3860
|
+
}),
|
|
3861
|
+
props.withCheckbox && /* @__PURE__ */ jsx(Table.Td, {
|
|
3862
|
+
style: { width: 40 },
|
|
3863
|
+
onClick: (e) => e.stopPropagation(),
|
|
3864
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
3865
|
+
checked: isSelected,
|
|
3866
|
+
onChange: () => selection.toggleItem(item),
|
|
3867
|
+
"aria-label": "Select row"
|
|
3868
|
+
})
|
|
3869
|
+
}),
|
|
3870
|
+
visibleColumns.map(([key, col]) => {
|
|
3871
|
+
const ctx = {
|
|
3872
|
+
index,
|
|
3873
|
+
form,
|
|
3874
|
+
alepha
|
|
3875
|
+
};
|
|
3876
|
+
if (col.actions) {
|
|
3877
|
+
const rowActions = col.actions(item, ctx).filter((a) => a.visible !== false);
|
|
3878
|
+
return /* @__PURE__ */ jsx(Table.Td, {
|
|
3879
|
+
py: 2,
|
|
3880
|
+
px: 4,
|
|
3881
|
+
style: col.fit ? FIT_STYLE : void 0,
|
|
3882
|
+
onClick: (e) => e.stopPropagation(),
|
|
3883
|
+
children: /* @__PURE__ */ jsx(Flex, {
|
|
3884
|
+
gap: 4,
|
|
3885
|
+
children: rowActions.map(({ visible: _, ...actionProps }, i) => /* @__PURE__ */ jsx(ActionButton, {
|
|
3886
|
+
variant: "subtle",
|
|
3887
|
+
size: "xs",
|
|
3888
|
+
preventDefault: true,
|
|
3889
|
+
h: 20,
|
|
3890
|
+
...actionProps
|
|
3891
|
+
}, i))
|
|
3892
|
+
})
|
|
3893
|
+
}, key);
|
|
3894
|
+
}
|
|
3895
|
+
return /* @__PURE__ */ jsx(Table.Td, {
|
|
3896
|
+
py: 2,
|
|
3897
|
+
px: 4,
|
|
3898
|
+
style: col.fit ? FIT_STYLE : void 0,
|
|
3899
|
+
children: col.value?.(item, ctx)
|
|
3900
|
+
}, key);
|
|
3901
|
+
})
|
|
3902
|
+
]
|
|
3903
|
+
}, itemKey)];
|
|
3904
|
+
if (panelConfig && showPanel && isExpanded) elements.push(/* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
|
|
3905
|
+
colSpan: totalColumns,
|
|
3906
|
+
p: 0,
|
|
3907
|
+
children: panelConfig.render(item)
|
|
3908
|
+
}) }, `${itemKey}-panel`));
|
|
3909
|
+
return elements;
|
|
3910
|
+
});
|
|
3911
|
+
const filterSchema = useMemo(() => {
|
|
3912
|
+
if (!props.filters) return null;
|
|
3913
|
+
return t.omit(form.options.schema, [
|
|
3914
|
+
"page",
|
|
3915
|
+
"size",
|
|
3916
|
+
"sort"
|
|
3917
|
+
]);
|
|
3918
|
+
}, [props.filters, form.options.schema]);
|
|
3919
|
+
return /* @__PURE__ */ jsxs(Flex, {
|
|
3920
|
+
flex: 1,
|
|
3921
|
+
p: 0,
|
|
3922
|
+
bdrs: "sm",
|
|
3923
|
+
direction: "column",
|
|
3924
|
+
children: [
|
|
3925
|
+
/* @__PURE__ */ jsx(DataTableToolbar, {
|
|
3926
|
+
columns: props.columns,
|
|
3927
|
+
filters: props.filters,
|
|
3928
|
+
columnVisibility,
|
|
3929
|
+
filterVisibility,
|
|
3930
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
3931
|
+
onFilterVisibilityChange: setFilterVisibility,
|
|
3932
|
+
actions: props.actions,
|
|
3933
|
+
onRefresh: () => form.submit(),
|
|
3934
|
+
items: items.content,
|
|
3935
|
+
withExport: props.withExport,
|
|
3936
|
+
selectedItems: selection.selectedItems,
|
|
3937
|
+
checkboxActions: props.checkboxActions,
|
|
3938
|
+
onClearSelection: selection.clear
|
|
3939
|
+
}),
|
|
3940
|
+
filterSchema && props.filters && /* @__PURE__ */ jsx(DataTableFilters, {
|
|
3941
|
+
schema: filterSchema,
|
|
3942
|
+
form,
|
|
3943
|
+
typeFormProps: props.typeFormProps,
|
|
3944
|
+
filterVisibility
|
|
3945
|
+
}),
|
|
3946
|
+
/* @__PURE__ */ jsx(Flex, {
|
|
3947
|
+
className: "overflow-auto",
|
|
3948
|
+
children: /* @__PURE__ */ jsxs(Table, {
|
|
3949
|
+
"aria-label": "Data table",
|
|
3950
|
+
withColumnBorders: true,
|
|
3951
|
+
withRowBorders: true,
|
|
3952
|
+
...props.tableProps,
|
|
3953
|
+
children: [/* @__PURE__ */ jsx(Table.Thead, {
|
|
3954
|
+
style: {
|
|
3955
|
+
position: "sticky",
|
|
3956
|
+
top: 0,
|
|
3957
|
+
zIndex: 1,
|
|
3958
|
+
backgroundColor: "var(--mantine-color-body)"
|
|
3959
|
+
},
|
|
3960
|
+
children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
3961
|
+
panelConfig && /* @__PURE__ */ jsx(Table.Th, { style: { width: 36 } }),
|
|
3962
|
+
checkboxHeader,
|
|
3963
|
+
head
|
|
3964
|
+
] })
|
|
3965
|
+
}), /* @__PURE__ */ jsxs(Table.Tbody, {
|
|
3966
|
+
style: {
|
|
3967
|
+
opacity: form.submitting ? .5 : 1,
|
|
3968
|
+
transition: "opacity 150ms ease"
|
|
3969
|
+
},
|
|
3970
|
+
children: [rows, items.content.length === 0 && /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
|
|
3971
|
+
colSpan: totalColumns || 1,
|
|
3972
|
+
py: "xl",
|
|
3973
|
+
style: { textAlign: "center" },
|
|
3974
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
3975
|
+
c: "dimmed",
|
|
3976
|
+
size: "sm",
|
|
3977
|
+
children: form.submitting ? "Loading…" : "No results"
|
|
3978
|
+
})
|
|
3979
|
+
}) })]
|
|
3980
|
+
})]
|
|
3981
|
+
})
|
|
3982
|
+
}),
|
|
3983
|
+
props.infinityScroll && /* @__PURE__ */ jsx("div", { ref: sentinelRef }),
|
|
3984
|
+
!props.infinityScroll && /* @__PURE__ */ jsx(DataTablePagination, {
|
|
3985
|
+
page,
|
|
3986
|
+
size,
|
|
3987
|
+
totalPages: items.page?.totalPages ?? 1,
|
|
3988
|
+
onPageChange: (value) => {
|
|
3989
|
+
form.input.page.set(value - 1);
|
|
3990
|
+
},
|
|
3991
|
+
onSizeChange: (value) => {
|
|
3992
|
+
form.input.size.set(value);
|
|
3993
|
+
}
|
|
3994
|
+
}),
|
|
3995
|
+
drawerConfig && /* @__PURE__ */ jsx(Drawer, {
|
|
3996
|
+
opened: drawerItem !== null,
|
|
3997
|
+
onClose: () => setDrawerItem(null),
|
|
3998
|
+
position: "right",
|
|
3999
|
+
size: "xl",
|
|
4000
|
+
...drawerConfig.props,
|
|
4001
|
+
children: drawerItem && drawerConfig.render(drawerItem)
|
|
4002
|
+
})
|
|
4003
|
+
]
|
|
4004
|
+
});
|
|
4005
|
+
};
|
|
4006
|
+
|
|
4007
|
+
//#endregion
|
|
4008
|
+
//#region ../../src/core/utils/extractSchemaFields.ts
|
|
4009
|
+
/**
|
|
4010
|
+
* Extract field information from a TypeBox schema for query building.
|
|
4011
|
+
* Supports nested objects and provides field metadata for autocomplete.
|
|
4012
|
+
*/
|
|
4013
|
+
function extractSchemaFields(schema, prefix = "") {
|
|
4014
|
+
const fields = [];
|
|
4015
|
+
if (!schema || typeof schema !== "object") return fields;
|
|
4016
|
+
const properties = "properties" in schema ? schema.properties : schema;
|
|
4017
|
+
if (!properties || typeof properties !== "object") return fields;
|
|
4018
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
4019
|
+
if (typeof value !== "object" || value === null) continue;
|
|
4020
|
+
const fieldSchema = value;
|
|
4021
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
4022
|
+
const format = "format" in fieldSchema ? fieldSchema.format : void 0;
|
|
4023
|
+
let displayType = "type" in fieldSchema ? fieldSchema.type : "object";
|
|
4024
|
+
if (format === "date-time") displayType = "datetime";
|
|
4025
|
+
else if (format === "date") displayType = "date";
|
|
4026
|
+
else if (format === "time") displayType = "time";
|
|
4027
|
+
else if (format === "duration") displayType = "duration";
|
|
4028
|
+
const field = {
|
|
4029
|
+
name: key,
|
|
4030
|
+
path,
|
|
4031
|
+
type: displayType,
|
|
4032
|
+
format,
|
|
4033
|
+
description: "description" in fieldSchema ? fieldSchema.description : void 0
|
|
4034
|
+
};
|
|
4035
|
+
if ("enum" in fieldSchema && fieldSchema.enum) {
|
|
4036
|
+
field.enum = fieldSchema.enum;
|
|
4037
|
+
field.type = "enum";
|
|
4038
|
+
}
|
|
4039
|
+
if ("type" in fieldSchema && fieldSchema.type === "object" && "properties" in fieldSchema && typeof fieldSchema.properties === "object") field.nested = extractSchemaFields(fieldSchema.properties, path);
|
|
4040
|
+
fields.push(field);
|
|
4041
|
+
if (field.nested) fields.push(...field.nested);
|
|
4042
|
+
}
|
|
4043
|
+
return fields;
|
|
4044
|
+
}
|
|
4045
|
+
/**
|
|
4046
|
+
* Get operator symbol and description
|
|
4047
|
+
*/
|
|
4048
|
+
const OPERATOR_INFO = {
|
|
4049
|
+
eq: {
|
|
4050
|
+
symbol: "=",
|
|
4051
|
+
label: "equals",
|
|
4052
|
+
example: "name=John"
|
|
4053
|
+
},
|
|
4054
|
+
ne: {
|
|
4055
|
+
symbol: "!=",
|
|
4056
|
+
label: "not equals",
|
|
4057
|
+
example: "status!=archived"
|
|
4058
|
+
},
|
|
4059
|
+
gt: {
|
|
4060
|
+
symbol: ">",
|
|
4061
|
+
label: "greater than",
|
|
4062
|
+
example: "age>18"
|
|
4063
|
+
},
|
|
4064
|
+
gte: {
|
|
4065
|
+
symbol: ">=",
|
|
4066
|
+
label: "greater or equal",
|
|
4067
|
+
example: "age>=18"
|
|
4068
|
+
},
|
|
4069
|
+
lt: {
|
|
4070
|
+
symbol: "<",
|
|
4071
|
+
label: "less than",
|
|
4072
|
+
example: "age<65"
|
|
4073
|
+
},
|
|
4074
|
+
lte: {
|
|
4075
|
+
symbol: "<=",
|
|
4076
|
+
label: "less or equal",
|
|
4077
|
+
example: "age<=65"
|
|
4078
|
+
},
|
|
4079
|
+
null: {
|
|
4080
|
+
symbol: "=null",
|
|
4081
|
+
label: "is null",
|
|
4082
|
+
example: "deletedAt=null"
|
|
4083
|
+
},
|
|
4084
|
+
notNull: {
|
|
4085
|
+
symbol: "!=null",
|
|
4086
|
+
label: "is not null",
|
|
4087
|
+
example: "email!=null"
|
|
4088
|
+
},
|
|
4089
|
+
in: {
|
|
4090
|
+
symbol: "[...]",
|
|
4091
|
+
label: "in array",
|
|
4092
|
+
example: "status=[active,pending]"
|
|
4093
|
+
}
|
|
4094
|
+
};
|
|
4095
|
+
|
|
4096
|
+
//#endregion
|
|
4097
|
+
//#region ../../src/core/utils/icons.tsx
|
|
4098
|
+
/**
|
|
4099
|
+
* Get the default icon for an input based on its type, format, or name.
|
|
4100
|
+
*/
|
|
4101
|
+
const getDefaultIcon = (params) => {
|
|
4102
|
+
const { type, format, name, isEnum, isArray, size = "sm" } = params;
|
|
4103
|
+
const iconSize = ui.sizes.icon[size];
|
|
4104
|
+
if (format) switch (format) {
|
|
4105
|
+
case "email": return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
|
|
4106
|
+
case "url":
|
|
4107
|
+
case "uri": return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
|
|
4108
|
+
case "tel":
|
|
4109
|
+
case "phone": return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
|
|
4110
|
+
case "date": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
4111
|
+
case "date-time": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
4112
|
+
case "time": return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
|
|
4113
|
+
case "color": return /* @__PURE__ */ jsx(IconColorPicker, { size: iconSize });
|
|
4114
|
+
case "uuid": return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
|
|
4115
|
+
}
|
|
4116
|
+
if (name) {
|
|
4117
|
+
const nameLower = name.toLowerCase();
|
|
4118
|
+
if (nameLower.includes("password") || nameLower.includes("secret")) return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
|
|
4119
|
+
if (nameLower.includes("email") || nameLower.includes("mail")) return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
|
|
4120
|
+
if (nameLower.includes("url") || nameLower.includes("link")) return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
|
|
4121
|
+
if (nameLower.includes("phone") || nameLower.includes("tel")) return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
|
|
4122
|
+
if (nameLower.includes("color")) return /* @__PURE__ */ jsx(IconPalette, { size: iconSize });
|
|
4123
|
+
if (nameLower.includes("file") || nameLower.includes("upload")) return /* @__PURE__ */ jsx(IconFile, { size: iconSize });
|
|
4124
|
+
if (nameLower.includes("date")) return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
4125
|
+
if (nameLower.includes("time")) return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
|
|
4126
|
+
}
|
|
4127
|
+
if (isEnum || isArray) return /* @__PURE__ */ jsx(IconSelector, { size: iconSize });
|
|
4128
|
+
if (type) switch (type) {
|
|
4129
|
+
case "boolean": return /* @__PURE__ */ jsx(IconToggleLeft, { size: iconSize });
|
|
4130
|
+
case "number":
|
|
4131
|
+
case "integer": return /* @__PURE__ */ jsx(IconHash, { size: iconSize });
|
|
4132
|
+
case "array": return /* @__PURE__ */ jsx(IconList, { size: iconSize });
|
|
4133
|
+
case "string": return /* @__PURE__ */ jsx(IconLetterCase, { size: iconSize });
|
|
4134
|
+
}
|
|
4135
|
+
return /* @__PURE__ */ jsx(IconAt, { size: iconSize });
|
|
4136
|
+
};
|
|
4137
|
+
|
|
4138
|
+
//#endregion
|
|
4139
|
+
//#region ../../src/core/utils/string.ts
|
|
4140
|
+
/**
|
|
4141
|
+
* Capitalizes the first letter of a string.
|
|
4142
|
+
*
|
|
4143
|
+
* @example
|
|
4144
|
+
* capitalize("hello") // "Hello"
|
|
4145
|
+
*/
|
|
4146
|
+
const capitalize = (str) => {
|
|
4147
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
4148
|
+
};
|
|
4149
|
+
/**
|
|
4150
|
+
* Converts camelCase or snake_case to Title Case with spaces.
|
|
4151
|
+
*
|
|
4152
|
+
* @example
|
|
4153
|
+
* toTitleCase("userName") // "User Name"
|
|
4154
|
+
* toTitleCase("first_name") // "First Name"
|
|
4155
|
+
* toTitleCase("email") // "Email"
|
|
4156
|
+
*/
|
|
4157
|
+
const toTitleCase = (str) => {
|
|
4158
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
4159
|
+
};
|
|
4160
|
+
/**
|
|
4161
|
+
* Converts a path or identifier string into a pretty display name.
|
|
4162
|
+
* For paths like "/contacts/0/name", extracts just the field name "Name".
|
|
4163
|
+
* Handles camelCase and snake_case conversion to Title Case.
|
|
4164
|
+
*
|
|
4165
|
+
* @example
|
|
4166
|
+
* prettyName("/userName") // "User Name"
|
|
4167
|
+
* prettyName("/contacts/0/email") // "Email"
|
|
4168
|
+
* prettyName("/address/streetName") // "Street Name"
|
|
4169
|
+
* prettyName("first_name") // "First Name"
|
|
4170
|
+
*/
|
|
4171
|
+
const prettyName = (name) => {
|
|
4172
|
+
const segments = name.split("/").filter((s) => s && !/^\d+$/.test(s));
|
|
4173
|
+
return toTitleCase(segments[segments.length - 1] || name.replaceAll("/", ""));
|
|
4174
|
+
};
|
|
4175
|
+
|
|
4176
|
+
//#endregion
|
|
4177
|
+
//#region ../../src/core/index.ts
|
|
4178
|
+
/**
|
|
4179
|
+
* Core UI components based on Mantine UI v8.
|
|
4180
|
+
*
|
|
4181
|
+
* **Features:**
|
|
4182
|
+
* - Mantine integration with theme support
|
|
4183
|
+
* - ActionButton, BurgerButton, ClipboardButton, DarkModeButton, LanguageButton, ThemeButton
|
|
4184
|
+
* - AlertDialog, ConfirmDialog, PromptDialog
|
|
4185
|
+
* - Form controls: Control, ControlArray, ControlDate, ControlNumber, ControlObject, ControlSelect, ControlQueryBuilder
|
|
4186
|
+
* - TypeForm for automatic form generation from TypeBox schemas
|
|
4187
|
+
* - DashboardShell layout component
|
|
4188
|
+
* - AppBar with configurable elements
|
|
4189
|
+
* - Sidebar navigation with sections and menu items
|
|
4190
|
+
* - Omnibar for command palette / search
|
|
4191
|
+
* - DataTable with filtering, sorting, pagination
|
|
4192
|
+
* - Toast notifications
|
|
4193
|
+
* - Theme system with dark mode
|
|
4194
|
+
*
|
|
4195
|
+
* @module alepha.ui
|
|
4196
|
+
*/
|
|
4197
|
+
const AlephaUI = $module({
|
|
4198
|
+
name: "alepha.ui",
|
|
4199
|
+
services: [
|
|
4200
|
+
DialogService,
|
|
4201
|
+
ToastService,
|
|
4202
|
+
ThemeProvider,
|
|
4203
|
+
UiRouter
|
|
4204
|
+
],
|
|
4205
|
+
register: (alepha) => {
|
|
4206
|
+
alepha.with(AlephaReactI18n);
|
|
4207
|
+
alepha.with(AlephaReactHead);
|
|
4208
|
+
alepha.with(AlephaReactForm);
|
|
4209
|
+
alepha.with(ThemeProvider);
|
|
4210
|
+
alepha.with(DialogService);
|
|
4211
|
+
alepha.with(ToastService);
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
|
|
4215
|
+
//#endregion
|
|
4216
|
+
export { AlephaMantineProvider as _, useDialog as a, Text$1 as c, Breadcrumb as d, Heading as f, ui as g, ActionButton as h, JsonViewer as i, DashboardShell as l, ToggleSidebarButton as m, capitalize as n, TypeForm as o, Flex$1 as p, DataTable as r, Control as s, AlephaUI as t, Sidebar as u, useToast as v };
|
|
4217
|
+
//# sourceMappingURL=core-RCUw1Q-a.js.map
|