@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,2264 @@
|
|
|
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, Autocomplete, Badge, Button, Card, ColorInput, ColorSchemeScript, Container, Divider, Fieldset, FileInput, Flex, Grid, Image, Input, Loader, MantineProvider, Menu, MultiSelect, NumberInput, PasswordInput, Popover, SegmentedControl, Select, Slider, Switch, TagsInput, Text, TextInput, Textarea, ThemeIcon, Tooltip, UnstyledButton, useMantineTheme } from "@mantine/core";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Children, createElement, forwardRef, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
+
import { Notifications, notifications } from "@mantine/notifications";
|
|
11
|
+
import { IconAlertTriangle, IconAt, IconCalendar, IconCheck, IconChevronRight, IconClock, IconColorPicker, IconFile, IconFilter, IconGripVertical, IconHash, IconInfoCircle, IconInfoTriangle, IconKey, IconLetterCase, IconLink, IconList, IconMail, IconPalette, IconPhone, IconPlus, IconSearch, IconSelector, IconToggleLeft, IconTrash, IconX } from "@tabler/icons-react";
|
|
12
|
+
import { $page, NestedView, useActive, useRouter, useRouterState } from "alepha/react/router";
|
|
13
|
+
import { NavigationProgress, nprogress } from "@mantine/nprogress";
|
|
14
|
+
import { useAction, useAlepha, useEvents, useInject, useStore } from "alepha/react";
|
|
15
|
+
import { Spotlight } from "@mantine/spotlight";
|
|
16
|
+
import { currentUserAtom } from "alepha/security";
|
|
17
|
+
import "@mantine/hooks";
|
|
18
|
+
import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
|
|
19
|
+
import { parseQueryString } from "alepha/orm";
|
|
20
|
+
import "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/Flex.tsx
|
|
880
|
+
const Flex$1 = forwardRef((props, ref) => {
|
|
881
|
+
const { fill, center, centerX, centerY, col, ...rest } = props;
|
|
882
|
+
if (fill) rest.flex ??= 1;
|
|
883
|
+
if (col) rest.direction ??= "column";
|
|
884
|
+
if (center) {
|
|
885
|
+
rest.align ??= "center";
|
|
886
|
+
rest.justify ??= "center";
|
|
887
|
+
}
|
|
888
|
+
if (centerX) rest.justify ??= "center";
|
|
889
|
+
if (centerY) rest.align ??= "center";
|
|
890
|
+
return /* @__PURE__ */ jsx(Flex, {
|
|
891
|
+
ref,
|
|
892
|
+
...rest
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
Flex$1.displayName = "Flex";
|
|
896
|
+
|
|
897
|
+
//#endregion
|
|
898
|
+
//#region ../../src/core/components/layout/Container.tsx
|
|
899
|
+
const Container$1 = forwardRef((props, ref) => {
|
|
900
|
+
return /* @__PURE__ */ jsx(Container, {
|
|
901
|
+
ref,
|
|
902
|
+
...props
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
Container$1.displayName = "Container";
|
|
906
|
+
|
|
907
|
+
//#endregion
|
|
908
|
+
//#region ../../src/core/components/Text.tsx
|
|
909
|
+
const INTENT_COLORS = {
|
|
910
|
+
primary: "blue",
|
|
911
|
+
info: "cyan",
|
|
912
|
+
success: "green",
|
|
913
|
+
warning: "yellow",
|
|
914
|
+
danger: "red"
|
|
915
|
+
};
|
|
916
|
+
const Text$1 = forwardRef((props, ref) => {
|
|
917
|
+
const { intent, bold, italic, light, muted, small, uppercase, capitalize, center, monospace, title, ...rest } = props;
|
|
918
|
+
if (intent) rest.c ??= INTENT_COLORS[intent];
|
|
919
|
+
if (bold) rest.fw ??= 700;
|
|
920
|
+
if (light) rest.fw ??= 300;
|
|
921
|
+
if (italic) rest.fs ??= "italic";
|
|
922
|
+
if (muted) rest.c ??= "dimmed";
|
|
923
|
+
if (small) rest.size ??= "sm";
|
|
924
|
+
if (uppercase) rest.tt ??= "uppercase";
|
|
925
|
+
if (capitalize) rest.tt ??= "capitalize";
|
|
926
|
+
if (center) rest.ta ??= "center";
|
|
927
|
+
if (monospace) rest.ff ??= "monospace";
|
|
928
|
+
if (title) rest.size ??= "xl";
|
|
929
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
930
|
+
ref,
|
|
931
|
+
...rest
|
|
932
|
+
});
|
|
933
|
+
});
|
|
934
|
+
Text$1.displayName = "Text";
|
|
935
|
+
|
|
936
|
+
//#endregion
|
|
937
|
+
//#region ../../src/core/form/utils/parseInput.ts
|
|
938
|
+
const parseInput = (props, form) => {
|
|
939
|
+
const disabled = false;
|
|
940
|
+
const id = props.input.props.id;
|
|
941
|
+
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);
|
|
942
|
+
const description = props.description ?? ("description" in props.input.schema && typeof props.input.schema.description === "string" ? props.input.schema.description : void 0);
|
|
943
|
+
const error = form.error && form.error instanceof TypeBoxError ? form.error.value.message : void 0;
|
|
944
|
+
const icon = !props.icon ? getDefaultIcon({
|
|
945
|
+
type: props.input.schema && "type" in props.input.schema ? String(props.input.schema.type) : void 0,
|
|
946
|
+
format: props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0,
|
|
947
|
+
name: props.input.props.name,
|
|
948
|
+
isEnum: props.input.schema && "enum" in props.input.schema && Boolean(props.input.schema.enum),
|
|
949
|
+
isArray: props.input.schema && "type" in props.input.schema && props.input.schema.type === "array"
|
|
950
|
+
}) : isValidElement(props.icon) ? props.icon : createElement(props.icon, { size: ui.sizes.icon.md });
|
|
951
|
+
const format = props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0;
|
|
952
|
+
const required = props.input.required;
|
|
953
|
+
const schema = props.input.schema;
|
|
954
|
+
const inputProps = {
|
|
955
|
+
label,
|
|
956
|
+
description,
|
|
957
|
+
error,
|
|
958
|
+
required,
|
|
959
|
+
disabled
|
|
960
|
+
};
|
|
961
|
+
if ("minLength" in schema && typeof schema.minLength === "number") inputProps.minLength = schema.minLength;
|
|
962
|
+
if ("maxLength" in schema && typeof schema.maxLength === "number") inputProps.maxLength = schema.maxLength;
|
|
963
|
+
if ("minimum" in schema && typeof schema.minimum === "number") inputProps.minimum = schema.minimum;
|
|
964
|
+
if ("maximum" in schema && typeof schema.maximum === "number") inputProps.maximum = schema.maximum;
|
|
965
|
+
return {
|
|
966
|
+
id,
|
|
967
|
+
icon,
|
|
968
|
+
format,
|
|
969
|
+
schema: props.input.schema,
|
|
970
|
+
inputProps
|
|
971
|
+
};
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
//#endregion
|
|
975
|
+
//#region ../../src/core/form/components/ControlArray.tsx
|
|
976
|
+
/**
|
|
977
|
+
* Custom hook to sync array items with form state.
|
|
978
|
+
* Uses form events as the source of truth, eliminating dual-state issues.
|
|
979
|
+
*/
|
|
980
|
+
const useArrayItems = (input) => {
|
|
981
|
+
const alepha = useAlepha();
|
|
982
|
+
const keyCounter = useRef(0);
|
|
983
|
+
const [items, setItemsState] = useState(() => {
|
|
984
|
+
const defaultValue = input?.props?.defaultValue;
|
|
985
|
+
if (Array.isArray(defaultValue)) return defaultValue.map((value) => ({
|
|
986
|
+
key: keyCounter.current++,
|
|
987
|
+
value
|
|
988
|
+
}));
|
|
989
|
+
return [];
|
|
990
|
+
});
|
|
991
|
+
const syncFromFormValue = useCallback((formValue) => {
|
|
992
|
+
if (!Array.isArray(formValue)) {
|
|
993
|
+
setItemsState([]);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
setItemsState((prevItems) => {
|
|
997
|
+
if (prevItems.length === formValue.length) {
|
|
998
|
+
if (prevItems.every((item, i) => item.value === formValue[i])) return prevItems;
|
|
999
|
+
}
|
|
1000
|
+
keyCounter.current = 0;
|
|
1001
|
+
return formValue.map((value) => ({
|
|
1002
|
+
key: keyCounter.current++,
|
|
1003
|
+
value
|
|
1004
|
+
}));
|
|
1005
|
+
});
|
|
1006
|
+
}, []);
|
|
1007
|
+
useEffect(() => {
|
|
1008
|
+
if (!input?.form) return;
|
|
1009
|
+
const formId = input.form.id;
|
|
1010
|
+
const fieldPath = input.path;
|
|
1011
|
+
const listeners = [alepha.events.on("form:reset", (event) => {
|
|
1012
|
+
if (event.id === formId) {
|
|
1013
|
+
const defaultValue = input.props?.defaultValue;
|
|
1014
|
+
keyCounter.current = 0;
|
|
1015
|
+
if (Array.isArray(defaultValue)) setItemsState(defaultValue.map((value) => ({
|
|
1016
|
+
key: keyCounter.current++,
|
|
1017
|
+
value
|
|
1018
|
+
})));
|
|
1019
|
+
else setItemsState([]);
|
|
1020
|
+
}
|
|
1021
|
+
}), alepha.events.on("form:change", (event) => {
|
|
1022
|
+
if (event.id === formId && event.path === fieldPath) syncFromFormValue(event.value);
|
|
1023
|
+
})];
|
|
1024
|
+
return () => {
|
|
1025
|
+
for (const unsub of listeners) unsub();
|
|
1026
|
+
};
|
|
1027
|
+
}, [
|
|
1028
|
+
alepha,
|
|
1029
|
+
input,
|
|
1030
|
+
syncFromFormValue
|
|
1031
|
+
]);
|
|
1032
|
+
return {
|
|
1033
|
+
items,
|
|
1034
|
+
setItems: useCallback((newItems) => {
|
|
1035
|
+
setItemsState(newItems);
|
|
1036
|
+
input?.set(newItems.map((item) => item.value));
|
|
1037
|
+
}, [input]),
|
|
1038
|
+
nextKey: useCallback(() => keyCounter.current++, [])
|
|
1039
|
+
};
|
|
1040
|
+
};
|
|
1041
|
+
/**
|
|
1042
|
+
* Creates a proper InputField for an array item that integrates with the form system.
|
|
1043
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
1044
|
+
*/
|
|
1045
|
+
const createArrayItemInput = (parentInput, itemSchema, index, _itemKey, value, onValueChange) => {
|
|
1046
|
+
return {
|
|
1047
|
+
schema: itemSchema,
|
|
1048
|
+
path: `${parentInput.path}/${index}`,
|
|
1049
|
+
required: false,
|
|
1050
|
+
form: parentInput.form,
|
|
1051
|
+
props: {
|
|
1052
|
+
id: `${parentInput.props.id}-${index}`,
|
|
1053
|
+
name: `${parentInput.props.name}[${index}]`,
|
|
1054
|
+
defaultValue: value
|
|
1055
|
+
},
|
|
1056
|
+
set: onValueChange
|
|
1057
|
+
};
|
|
1058
|
+
};
|
|
1059
|
+
/**
|
|
1060
|
+
* Creates a proper InputField for a nested object field within an array item.
|
|
1061
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
1062
|
+
*/
|
|
1063
|
+
const createArrayItemFieldInput = (parentInput, itemSchema, fieldName, index, _itemKey, itemValue, onFieldChange) => {
|
|
1064
|
+
return {
|
|
1065
|
+
schema: itemSchema.properties[fieldName],
|
|
1066
|
+
path: `${parentInput.path}/${index}/${fieldName}`,
|
|
1067
|
+
required: itemSchema.required?.includes(fieldName) ?? false,
|
|
1068
|
+
form: parentInput.form,
|
|
1069
|
+
props: {
|
|
1070
|
+
id: `${parentInput.props.id}-${index}-${fieldName}`,
|
|
1071
|
+
name: `${parentInput.props.name}[${index}].${fieldName}`,
|
|
1072
|
+
defaultValue: itemValue?.[fieldName]
|
|
1073
|
+
},
|
|
1074
|
+
set: (value) => onFieldChange(fieldName, value)
|
|
1075
|
+
};
|
|
1076
|
+
};
|
|
1077
|
+
/**
|
|
1078
|
+
* ControlArray component for editing arrays of schema items.
|
|
1079
|
+
*
|
|
1080
|
+
* Features:
|
|
1081
|
+
* - Dynamic add/remove of items
|
|
1082
|
+
* - Supports arrays of objects with nested fields
|
|
1083
|
+
* - Supports arrays of primitives
|
|
1084
|
+
* - Grid layout for object items
|
|
1085
|
+
* - Min/max constraints
|
|
1086
|
+
* - Syncs with form state (handles external updates and resets)
|
|
1087
|
+
*
|
|
1088
|
+
* @example
|
|
1089
|
+
* ```tsx
|
|
1090
|
+
* // For a schema like:
|
|
1091
|
+
* // t.object({
|
|
1092
|
+
* // contacts: t.array(t.object({
|
|
1093
|
+
* // name: t.text(),
|
|
1094
|
+
* // email: t.text({ format: "email" }),
|
|
1095
|
+
* // }))
|
|
1096
|
+
* // })
|
|
1097
|
+
*
|
|
1098
|
+
* <ControlArray
|
|
1099
|
+
* input={form.input.contacts}
|
|
1100
|
+
* columns={2}
|
|
1101
|
+
* addLabel="Add contact"
|
|
1102
|
+
* controlProps={{
|
|
1103
|
+
* email: { text: { placeholder: "email@example.com" } }
|
|
1104
|
+
* }}
|
|
1105
|
+
* />
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
const ControlArray = (props) => {
|
|
1109
|
+
const { inputProps } = parseInput(props, {});
|
|
1110
|
+
const { items, setItems, nextKey } = useArrayItems(props.input);
|
|
1111
|
+
if (!props.input?.props) return null;
|
|
1112
|
+
const schema = props.input.schema;
|
|
1113
|
+
if (!schema || !("items" in schema)) return null;
|
|
1114
|
+
const itemSchema = schema.items;
|
|
1115
|
+
const isObjectItem = itemSchema && "properties" in itemSchema;
|
|
1116
|
+
const { min = 0, max = Number.POSITIVE_INFINITY, columns = 1 } = props;
|
|
1117
|
+
const handleAdd = () => {
|
|
1118
|
+
if (items.length >= max) return;
|
|
1119
|
+
let newValue;
|
|
1120
|
+
if (isObjectItem) {
|
|
1121
|
+
newValue = {};
|
|
1122
|
+
const objSchema = itemSchema;
|
|
1123
|
+
for (const [key, propSchema] of Object.entries(objSchema.properties)) if ("default" in propSchema) newValue[key] = propSchema.default;
|
|
1124
|
+
} else newValue = "";
|
|
1125
|
+
setItems([...items, {
|
|
1126
|
+
key: nextKey(),
|
|
1127
|
+
value: newValue
|
|
1128
|
+
}]);
|
|
1129
|
+
};
|
|
1130
|
+
const handleRemove = (index) => {
|
|
1131
|
+
if (items.length <= min) return;
|
|
1132
|
+
setItems(items.filter((_, i) => i !== index));
|
|
1133
|
+
};
|
|
1134
|
+
const handleItemChange = (index, value) => {
|
|
1135
|
+
const newItems = [...items];
|
|
1136
|
+
newItems[index] = {
|
|
1137
|
+
...newItems[index],
|
|
1138
|
+
value
|
|
1139
|
+
};
|
|
1140
|
+
setItems(newItems);
|
|
1141
|
+
};
|
|
1142
|
+
const handleFieldChange = (index, field, value) => {
|
|
1143
|
+
const newItems = [...items];
|
|
1144
|
+
newItems[index] = {
|
|
1145
|
+
...newItems[index],
|
|
1146
|
+
value: {
|
|
1147
|
+
...newItems[index].value,
|
|
1148
|
+
[field]: value
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
setItems(newItems);
|
|
1152
|
+
};
|
|
1153
|
+
const colSpan = 12 / columns;
|
|
1154
|
+
const objectItemSchema = isObjectItem ? itemSchema : null;
|
|
1155
|
+
const fieldNames = objectItemSchema ? Object.keys(objectItemSchema.properties) : [];
|
|
1156
|
+
const renderItems = () => /* @__PURE__ */ jsxs(Flex, {
|
|
1157
|
+
direction: "column",
|
|
1158
|
+
gap: "sm",
|
|
1159
|
+
children: [items.map((item, index) => /* @__PURE__ */ jsxs(Flex, {
|
|
1160
|
+
gap: "sm",
|
|
1161
|
+
align: "flex-start",
|
|
1162
|
+
p: "xs",
|
|
1163
|
+
bg: ui.colors.surface,
|
|
1164
|
+
style: { borderRadius: "var(--mantine-radius-sm)" },
|
|
1165
|
+
children: [
|
|
1166
|
+
props.sortable && /* @__PURE__ */ jsx(ActionIcon, {
|
|
1167
|
+
variant: "subtle",
|
|
1168
|
+
color: "gray",
|
|
1169
|
+
style: { cursor: "grab" },
|
|
1170
|
+
children: /* @__PURE__ */ jsx(IconGripVertical, { size: 16 })
|
|
1171
|
+
}),
|
|
1172
|
+
objectItemSchema ? /* @__PURE__ */ jsx(Grid, {
|
|
1173
|
+
style: { flex: 1 },
|
|
1174
|
+
gutter: "sm",
|
|
1175
|
+
children: fieldNames.map((fieldName) => {
|
|
1176
|
+
const fieldControlProps = props.controlProps?.[fieldName] ?? {};
|
|
1177
|
+
const fieldInput = createArrayItemFieldInput(props.input, objectItemSchema, fieldName, index, item.key, item.value, (field, value) => handleFieldChange(index, field, value));
|
|
1178
|
+
return /* @__PURE__ */ jsx(Grid.Col, {
|
|
1179
|
+
span: colSpan,
|
|
1180
|
+
children: /* @__PURE__ */ jsx(Control, {
|
|
1181
|
+
input: fieldInput,
|
|
1182
|
+
...fieldControlProps
|
|
1183
|
+
})
|
|
1184
|
+
}, fieldName);
|
|
1185
|
+
})
|
|
1186
|
+
}) : /* @__PURE__ */ jsx(Flex, {
|
|
1187
|
+
style: { flex: 1 },
|
|
1188
|
+
children: /* @__PURE__ */ jsx(Control, {
|
|
1189
|
+
input: createArrayItemInput(props.input, itemSchema, index, item.key, item.value, (value) => handleItemChange(index, value)),
|
|
1190
|
+
...props.itemControlProps
|
|
1191
|
+
})
|
|
1192
|
+
}),
|
|
1193
|
+
/* @__PURE__ */ jsx(ActionIcon, {
|
|
1194
|
+
variant: "subtle",
|
|
1195
|
+
color: "red",
|
|
1196
|
+
onClick: () => handleRemove(index),
|
|
1197
|
+
disabled: items.length <= min,
|
|
1198
|
+
children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
|
|
1199
|
+
})
|
|
1200
|
+
]
|
|
1201
|
+
}, item.key)), /* @__PURE__ */ jsxs(UnstyledButton, {
|
|
1202
|
+
onClick: handleAdd,
|
|
1203
|
+
disabled: items.length >= max,
|
|
1204
|
+
style: {
|
|
1205
|
+
display: "flex",
|
|
1206
|
+
alignItems: "center",
|
|
1207
|
+
justifyContent: "center",
|
|
1208
|
+
gap: 6,
|
|
1209
|
+
padding: "8px 12px",
|
|
1210
|
+
borderRadius: "var(--mantine-radius-sm)",
|
|
1211
|
+
border: "1px dashed var(--mantine-color-dimmed)",
|
|
1212
|
+
color: "var(--mantine-color-dimmed)",
|
|
1213
|
+
fontSize: "var(--mantine-font-size-sm)",
|
|
1214
|
+
cursor: items.length >= max ? "not-allowed" : "pointer",
|
|
1215
|
+
opacity: items.length >= max ? .5 : 1,
|
|
1216
|
+
transition: "all 150ms ease"
|
|
1217
|
+
},
|
|
1218
|
+
onMouseEnter: (e) => {
|
|
1219
|
+
if (items.length < max) {
|
|
1220
|
+
e.currentTarget.style.borderColor = "var(--mantine-color-blue-filled)";
|
|
1221
|
+
e.currentTarget.style.color = "var(--mantine-color-blue-filled)";
|
|
1222
|
+
e.currentTarget.style.background = "var(--mantine-color-blue-light)";
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
onMouseLeave: (e) => {
|
|
1226
|
+
e.currentTarget.style.borderColor = "var(--mantine-color-dimmed)";
|
|
1227
|
+
e.currentTarget.style.color = "var(--mantine-color-dimmed)";
|
|
1228
|
+
e.currentTarget.style.background = "transparent";
|
|
1229
|
+
},
|
|
1230
|
+
children: [/* @__PURE__ */ jsx(IconPlus, { size: 14 }), props.addLabel ?? "Add"]
|
|
1231
|
+
})]
|
|
1232
|
+
});
|
|
1233
|
+
if (props.variant === "plain") return /* @__PURE__ */ jsxs(Flex, {
|
|
1234
|
+
direction: "column",
|
|
1235
|
+
gap: "xs",
|
|
1236
|
+
children: [
|
|
1237
|
+
inputProps.label && /* @__PURE__ */ jsx(Text, {
|
|
1238
|
+
size: "sm",
|
|
1239
|
+
fw: 500,
|
|
1240
|
+
children: inputProps.label
|
|
1241
|
+
}),
|
|
1242
|
+
inputProps.description && /* @__PURE__ */ jsx(Text, {
|
|
1243
|
+
size: "sm",
|
|
1244
|
+
c: "dimmed",
|
|
1245
|
+
children: inputProps.description
|
|
1246
|
+
}),
|
|
1247
|
+
renderItems(),
|
|
1248
|
+
inputProps.error && /* @__PURE__ */ jsx(Text, {
|
|
1249
|
+
size: "sm",
|
|
1250
|
+
c: "red",
|
|
1251
|
+
children: inputProps.error
|
|
1252
|
+
})
|
|
1253
|
+
]
|
|
1254
|
+
});
|
|
1255
|
+
return /* @__PURE__ */ jsx(Fieldset, {
|
|
1256
|
+
legend: inputProps.label,
|
|
1257
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
1258
|
+
direction: "column",
|
|
1259
|
+
gap: "xs",
|
|
1260
|
+
children: [
|
|
1261
|
+
inputProps.description && /* @__PURE__ */ jsx(Text, {
|
|
1262
|
+
size: "sm",
|
|
1263
|
+
c: "dimmed",
|
|
1264
|
+
children: inputProps.description
|
|
1265
|
+
}),
|
|
1266
|
+
renderItems(),
|
|
1267
|
+
inputProps.error && /* @__PURE__ */ jsx(Text, {
|
|
1268
|
+
size: "sm",
|
|
1269
|
+
c: "red",
|
|
1270
|
+
children: inputProps.error
|
|
1271
|
+
})
|
|
1272
|
+
]
|
|
1273
|
+
})
|
|
1274
|
+
});
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
//#endregion
|
|
1278
|
+
//#region ../../src/core/form/components/ControlDate.tsx
|
|
1279
|
+
/**
|
|
1280
|
+
* ControlDate component for handling date, datetime, and time inputs.
|
|
1281
|
+
*
|
|
1282
|
+
* Features:
|
|
1283
|
+
* - DateInput for date format
|
|
1284
|
+
* - DateTimePicker for date-time format
|
|
1285
|
+
* - TimeInput for time format
|
|
1286
|
+
*
|
|
1287
|
+
* Automatically detects date formats from schema and renders appropriate picker.
|
|
1288
|
+
*/
|
|
1289
|
+
const ControlDate = (props) => {
|
|
1290
|
+
const { inputProps, id, icon, format } = parseInput(props, useFormState(props.input));
|
|
1291
|
+
if (!props.input?.props) return null;
|
|
1292
|
+
if (props.datetime || format === "date-time") {
|
|
1293
|
+
const dateTimePickerProps = typeof props.datetime === "object" ? props.datetime : {};
|
|
1294
|
+
return /* @__PURE__ */ jsx(DateTimePicker, {
|
|
1295
|
+
...inputProps,
|
|
1296
|
+
id,
|
|
1297
|
+
leftSection: icon,
|
|
1298
|
+
defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
|
|
1299
|
+
onChange: (value) => {
|
|
1300
|
+
props.input.set(value ? new Date(value).toISOString() : void 0);
|
|
1301
|
+
},
|
|
1302
|
+
...dateTimePickerProps
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
if (props.date || format === "date") {
|
|
1306
|
+
const dateInputProps = typeof props.date === "object" ? props.date : {};
|
|
1307
|
+
return /* @__PURE__ */ jsx(DateInput, {
|
|
1308
|
+
...inputProps,
|
|
1309
|
+
id,
|
|
1310
|
+
leftSection: icon,
|
|
1311
|
+
defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
|
|
1312
|
+
onChange: (value) => {
|
|
1313
|
+
props.input.set(value ? new Date(value).toISOString().slice(0, 10) : void 0);
|
|
1314
|
+
},
|
|
1315
|
+
...dateInputProps
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
if (props.time || format === "time") {
|
|
1319
|
+
const timeInputProps = typeof props.time === "object" ? props.time : {};
|
|
1320
|
+
return /* @__PURE__ */ jsx(TimeInput, {
|
|
1321
|
+
...inputProps,
|
|
1322
|
+
id,
|
|
1323
|
+
leftSection: icon,
|
|
1324
|
+
defaultValue: props.input.props.defaultValue,
|
|
1325
|
+
onChange: (event) => {
|
|
1326
|
+
props.input.set(event.currentTarget.value);
|
|
1327
|
+
},
|
|
1328
|
+
...timeInputProps
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
return null;
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
//#endregion
|
|
1335
|
+
//#region ../../src/core/form/components/ControlNumber.tsx
|
|
1336
|
+
/**
|
|
1337
|
+
*
|
|
1338
|
+
*/
|
|
1339
|
+
const ControlNumber = (props) => {
|
|
1340
|
+
const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
|
|
1341
|
+
const ref = useRef(null);
|
|
1342
|
+
const [value, setValue] = useState(props.input.props.defaultValue);
|
|
1343
|
+
useEvents({ "form:reset": (event) => {
|
|
1344
|
+
if (event.id === props.input?.form.id && ref.current) setValue(props.input.props.defaultValue);
|
|
1345
|
+
} }, [props.input]);
|
|
1346
|
+
if (!props.input?.props) return null;
|
|
1347
|
+
const { type, ...inputPropsWithoutType } = props.input.props;
|
|
1348
|
+
if (props.sliderProps) {
|
|
1349
|
+
const min = props.sliderProps.min ?? inputProps.minimum ?? 0;
|
|
1350
|
+
const max = props.sliderProps.max ?? inputProps.maximum ?? 100;
|
|
1351
|
+
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
1352
|
+
...inputProps,
|
|
1353
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1354
|
+
style: {
|
|
1355
|
+
height: 32,
|
|
1356
|
+
padding: 8
|
|
1357
|
+
},
|
|
1358
|
+
children: /* @__PURE__ */ jsx(Slider, {
|
|
1359
|
+
...inputProps,
|
|
1360
|
+
ref,
|
|
1361
|
+
id,
|
|
1362
|
+
...inputPropsWithoutType,
|
|
1363
|
+
...props.sliderProps,
|
|
1364
|
+
value,
|
|
1365
|
+
min,
|
|
1366
|
+
max,
|
|
1367
|
+
label: () => value,
|
|
1368
|
+
onChange: (val) => {
|
|
1369
|
+
setValue(val);
|
|
1370
|
+
props.input.set(val);
|
|
1371
|
+
}
|
|
1372
|
+
})
|
|
1373
|
+
})
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
return /* @__PURE__ */ jsx(NumberInput, {
|
|
1377
|
+
...inputProps,
|
|
1378
|
+
ref,
|
|
1379
|
+
id,
|
|
1380
|
+
leftSection: icon,
|
|
1381
|
+
...inputPropsWithoutType,
|
|
1382
|
+
...props.numberInputProps,
|
|
1383
|
+
value: value ?? "",
|
|
1384
|
+
onChange: (val) => {
|
|
1385
|
+
const newValue = val !== null ? Number(val) : void 0;
|
|
1386
|
+
setValue(newValue);
|
|
1387
|
+
props.input.set(newValue);
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
//#endregion
|
|
1393
|
+
//#region ../../src/core/form/components/ControlObject.tsx
|
|
1394
|
+
/**
|
|
1395
|
+
* ControlObject component for editing nested object schemas.
|
|
1396
|
+
*
|
|
1397
|
+
* Features:
|
|
1398
|
+
* - Renders all properties of an object schema
|
|
1399
|
+
* - Supports grid layout with configurable columns
|
|
1400
|
+
* - Per-field customization via controlProps
|
|
1401
|
+
* - Recursive support for deeply nested objects
|
|
1402
|
+
*
|
|
1403
|
+
* The form system provides nested InputFields under the `.items` property.
|
|
1404
|
+
* For example: form.input.address.items.street
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* ```tsx
|
|
1408
|
+
* // For a schema like:
|
|
1409
|
+
* // t.object({
|
|
1410
|
+
* // address: t.object({
|
|
1411
|
+
* // street: t.text(),
|
|
1412
|
+
* // city: t.text(),
|
|
1413
|
+
* // zip: t.text(),
|
|
1414
|
+
* // })
|
|
1415
|
+
* // })
|
|
1416
|
+
*
|
|
1417
|
+
* <ControlObject
|
|
1418
|
+
* input={form.input.address}
|
|
1419
|
+
* columns={2}
|
|
1420
|
+
* controlProps={{
|
|
1421
|
+
* zip: { text: { maxLength: 10 } }
|
|
1422
|
+
* }}
|
|
1423
|
+
* />
|
|
1424
|
+
* ```
|
|
1425
|
+
*/
|
|
1426
|
+
const ControlObject = (props) => {
|
|
1427
|
+
const { inputProps } = parseInput(props, {});
|
|
1428
|
+
if (!props.input?.props) return null;
|
|
1429
|
+
const schema = props.input.schema;
|
|
1430
|
+
if (!schema?.properties) return null;
|
|
1431
|
+
const fieldNames = Object.keys(schema.properties);
|
|
1432
|
+
const colSpan = 12 / (props.columns ?? 1);
|
|
1433
|
+
const nestedItems = props.input.items;
|
|
1434
|
+
const renderFields = () => /* @__PURE__ */ jsx(Grid, { children: fieldNames.map((fieldName) => {
|
|
1435
|
+
const fieldControlProps = props.controlProps?.[fieldName] ?? {};
|
|
1436
|
+
const field = nestedItems?.[fieldName];
|
|
1437
|
+
if (!field) return null;
|
|
1438
|
+
return /* @__PURE__ */ jsx(Grid.Col, {
|
|
1439
|
+
span: colSpan,
|
|
1440
|
+
children: /* @__PURE__ */ jsx(Control, {
|
|
1441
|
+
input: field,
|
|
1442
|
+
...fieldControlProps
|
|
1443
|
+
})
|
|
1444
|
+
}, fieldName);
|
|
1445
|
+
}) });
|
|
1446
|
+
if (props.variant === "plain") return renderFields();
|
|
1447
|
+
return /* @__PURE__ */ jsx(Fieldset, {
|
|
1448
|
+
legend: inputProps.label,
|
|
1449
|
+
children: /* @__PURE__ */ jsxs(Flex, {
|
|
1450
|
+
direction: "column",
|
|
1451
|
+
gap: "xs",
|
|
1452
|
+
children: [
|
|
1453
|
+
inputProps.description && /* @__PURE__ */ jsx(Text, {
|
|
1454
|
+
size: "sm",
|
|
1455
|
+
c: "dimmed",
|
|
1456
|
+
children: inputProps.description
|
|
1457
|
+
}),
|
|
1458
|
+
renderFields(),
|
|
1459
|
+
inputProps.error && /* @__PURE__ */ jsx(Text, {
|
|
1460
|
+
size: "sm",
|
|
1461
|
+
c: "red",
|
|
1462
|
+
children: inputProps.error
|
|
1463
|
+
})
|
|
1464
|
+
]
|
|
1465
|
+
})
|
|
1466
|
+
});
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region ../../src/core/form/components/ControlQueryBuilder.tsx
|
|
1471
|
+
/**
|
|
1472
|
+
* Query builder with text input and help popover.
|
|
1473
|
+
* Generates query strings for parseQueryString syntax.
|
|
1474
|
+
*/
|
|
1475
|
+
const ControlQueryBuilder = ({ schema, value = "", onChange, placeholder = "Enter query or click for assistance...", ...textInputProps }) => {
|
|
1476
|
+
const [helpOpened, setHelpOpened] = useState(false);
|
|
1477
|
+
const [textValue, setTextValue] = useState(value);
|
|
1478
|
+
const inputRef = useRef(null);
|
|
1479
|
+
const fields = schema ? extractSchemaFields(schema) : [];
|
|
1480
|
+
const [error, setError] = useState(null);
|
|
1481
|
+
const isValid = (value) => {
|
|
1482
|
+
try {
|
|
1483
|
+
parseQueryString(value.trim());
|
|
1484
|
+
} catch (e) {
|
|
1485
|
+
setError(e.message);
|
|
1486
|
+
return false;
|
|
1487
|
+
}
|
|
1488
|
+
setError(null);
|
|
1489
|
+
return true;
|
|
1490
|
+
};
|
|
1491
|
+
const handleTextChange = (newValue) => {
|
|
1492
|
+
setTextValue(newValue);
|
|
1493
|
+
if (isValid(newValue)) onChange?.(newValue);
|
|
1494
|
+
};
|
|
1495
|
+
const handleClear = () => {
|
|
1496
|
+
setTextValue("");
|
|
1497
|
+
onChange?.("");
|
|
1498
|
+
isValid("");
|
|
1499
|
+
};
|
|
1500
|
+
const handleInsert = (text) => {
|
|
1501
|
+
const newValue = textValue ? `${textValue}${text} ` : `${text} `;
|
|
1502
|
+
setTextValue(newValue);
|
|
1503
|
+
if (isValid(newValue)) onChange?.(newValue);
|
|
1504
|
+
setTimeout(() => {
|
|
1505
|
+
inputRef.current?.focus();
|
|
1506
|
+
const length = inputRef.current?.value.length || 0;
|
|
1507
|
+
inputRef.current?.setSelectionRange(length, length);
|
|
1508
|
+
}, 0);
|
|
1509
|
+
};
|
|
1510
|
+
useEvents({ "form:change": (event) => {
|
|
1511
|
+
if (event.id === inputRef.current?.form?.id) {
|
|
1512
|
+
if (event.path === textInputProps["data-path"]) setTextValue(event.value ?? "");
|
|
1513
|
+
}
|
|
1514
|
+
} }, []);
|
|
1515
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
1516
|
+
width: 800,
|
|
1517
|
+
position: "bottom-start",
|
|
1518
|
+
shadow: "md",
|
|
1519
|
+
opened: helpOpened,
|
|
1520
|
+
onChange: setHelpOpened,
|
|
1521
|
+
closeOnClickOutside: true,
|
|
1522
|
+
closeOnEscape: true,
|
|
1523
|
+
transitionProps: {
|
|
1524
|
+
transition: "fade-up",
|
|
1525
|
+
duration: 200,
|
|
1526
|
+
timingFunction: "ease"
|
|
1527
|
+
},
|
|
1528
|
+
children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(TextInput, {
|
|
1529
|
+
ref: inputRef,
|
|
1530
|
+
placeholder,
|
|
1531
|
+
value: textValue,
|
|
1532
|
+
onChange: (e) => handleTextChange(e.currentTarget.value),
|
|
1533
|
+
onFocus: () => setHelpOpened(true),
|
|
1534
|
+
leftSection: error ? /* @__PURE__ */ jsx(IconInfoTriangle, { size: 16 }) : /* @__PURE__ */ jsx(IconFilter, { size: 16 }),
|
|
1535
|
+
rightSection: textValue && /* @__PURE__ */ jsx(ActionIcon, {
|
|
1536
|
+
size: "sm",
|
|
1537
|
+
variant: "subtle",
|
|
1538
|
+
color: "gray",
|
|
1539
|
+
onClick: handleClear,
|
|
1540
|
+
children: /* @__PURE__ */ jsx(IconX, { size: 14 })
|
|
1541
|
+
}),
|
|
1542
|
+
...textInputProps
|
|
1543
|
+
}) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
|
|
1544
|
+
bg: "transparent",
|
|
1545
|
+
p: "xs",
|
|
1546
|
+
bd: `1px solid ${ui.colors.border}`,
|
|
1547
|
+
style: { backdropFilter: "blur(20px)" },
|
|
1548
|
+
children: /* @__PURE__ */ jsx(QueryHelp, {
|
|
1549
|
+
fields,
|
|
1550
|
+
onInsert: handleInsert
|
|
1551
|
+
})
|
|
1552
|
+
})]
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
function QueryHelp({ fields, onInsert }) {
|
|
1556
|
+
return /* @__PURE__ */ jsxs(Flex, {
|
|
1557
|
+
gap: "md",
|
|
1558
|
+
align: "flex-start",
|
|
1559
|
+
wrap: "nowrap",
|
|
1560
|
+
bg: ui.colors.surface,
|
|
1561
|
+
p: "sm",
|
|
1562
|
+
bdrs: "sm",
|
|
1563
|
+
children: [
|
|
1564
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
1565
|
+
direction: "column",
|
|
1566
|
+
gap: "md",
|
|
1567
|
+
style: { flex: 1 },
|
|
1568
|
+
children: [
|
|
1569
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
1570
|
+
direction: "column",
|
|
1571
|
+
gap: "xs",
|
|
1572
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1573
|
+
size: "sm",
|
|
1574
|
+
fw: 600,
|
|
1575
|
+
children: "Operators"
|
|
1576
|
+
}), /* @__PURE__ */ jsx(Flex, {
|
|
1577
|
+
direction: "column",
|
|
1578
|
+
gap: 4,
|
|
1579
|
+
children: Object.entries(OPERATOR_INFO).map(([key, info]) => /* @__PURE__ */ jsxs(Flex, {
|
|
1580
|
+
gap: "xs",
|
|
1581
|
+
wrap: "nowrap",
|
|
1582
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
1583
|
+
px: "xs",
|
|
1584
|
+
size: "xs",
|
|
1585
|
+
h: 24,
|
|
1586
|
+
variant: "default",
|
|
1587
|
+
justify: "center",
|
|
1588
|
+
miw: 48,
|
|
1589
|
+
onClick: () => onInsert(info.symbol),
|
|
1590
|
+
children: info.symbol
|
|
1591
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1592
|
+
size: "xs",
|
|
1593
|
+
c: "dimmed",
|
|
1594
|
+
style: { flex: 1 },
|
|
1595
|
+
children: info.label
|
|
1596
|
+
})]
|
|
1597
|
+
}, key))
|
|
1598
|
+
})]
|
|
1599
|
+
}),
|
|
1600
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
1601
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
1602
|
+
direction: "column",
|
|
1603
|
+
gap: "xs",
|
|
1604
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1605
|
+
size: "sm",
|
|
1606
|
+
fw: 600,
|
|
1607
|
+
children: "Logic"
|
|
1608
|
+
}), /* @__PURE__ */ jsxs(Flex, {
|
|
1609
|
+
direction: "column",
|
|
1610
|
+
gap: 4,
|
|
1611
|
+
children: [/* @__PURE__ */ jsxs(Flex, {
|
|
1612
|
+
gap: "xs",
|
|
1613
|
+
wrap: "nowrap",
|
|
1614
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
1615
|
+
px: "xs",
|
|
1616
|
+
size: "xs",
|
|
1617
|
+
h: 24,
|
|
1618
|
+
variant: "default",
|
|
1619
|
+
justify: "center",
|
|
1620
|
+
miw: 48,
|
|
1621
|
+
onClick: () => onInsert("&"),
|
|
1622
|
+
children: "&"
|
|
1623
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1624
|
+
size: "xs",
|
|
1625
|
+
c: "dimmed",
|
|
1626
|
+
children: "AND"
|
|
1627
|
+
})]
|
|
1628
|
+
}), /* @__PURE__ */ jsxs(Flex, {
|
|
1629
|
+
gap: "xs",
|
|
1630
|
+
wrap: "nowrap",
|
|
1631
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
1632
|
+
px: "xs",
|
|
1633
|
+
size: "xs",
|
|
1634
|
+
h: 24,
|
|
1635
|
+
variant: "default",
|
|
1636
|
+
justify: "center",
|
|
1637
|
+
miw: 48,
|
|
1638
|
+
onClick: () => onInsert("|"),
|
|
1639
|
+
children: "|"
|
|
1640
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1641
|
+
size: "xs",
|
|
1642
|
+
c: "dimmed",
|
|
1643
|
+
children: "OR"
|
|
1644
|
+
})]
|
|
1645
|
+
})]
|
|
1646
|
+
})]
|
|
1647
|
+
})
|
|
1648
|
+
]
|
|
1649
|
+
}),
|
|
1650
|
+
fields.length > 0 && /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }),
|
|
1651
|
+
fields.length > 0 && /* @__PURE__ */ jsxs(Flex, {
|
|
1652
|
+
direction: "column",
|
|
1653
|
+
gap: "xs",
|
|
1654
|
+
style: { flex: 2 },
|
|
1655
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1656
|
+
size: "sm",
|
|
1657
|
+
fw: 600,
|
|
1658
|
+
children: "Fields"
|
|
1659
|
+
}), /* @__PURE__ */ jsx(Flex, {
|
|
1660
|
+
direction: "column",
|
|
1661
|
+
gap: 4,
|
|
1662
|
+
style: {
|
|
1663
|
+
maxHeight: 300,
|
|
1664
|
+
overflowY: "auto"
|
|
1665
|
+
},
|
|
1666
|
+
children: fields.map((field) => /* @__PURE__ */ jsxs(Flex, {
|
|
1667
|
+
gap: "xs",
|
|
1668
|
+
wrap: "nowrap",
|
|
1669
|
+
align: "flex-start",
|
|
1670
|
+
children: [
|
|
1671
|
+
/* @__PURE__ */ jsx(ActionButton, {
|
|
1672
|
+
px: "xs",
|
|
1673
|
+
size: "xs",
|
|
1674
|
+
h: 24,
|
|
1675
|
+
variant: "default",
|
|
1676
|
+
justify: "end",
|
|
1677
|
+
miw: 120,
|
|
1678
|
+
onClick: () => onInsert(field.path),
|
|
1679
|
+
children: field.path
|
|
1680
|
+
}),
|
|
1681
|
+
/* @__PURE__ */ jsxs(Flex, {
|
|
1682
|
+
mt: 3,
|
|
1683
|
+
direction: "column",
|
|
1684
|
+
gap: 2,
|
|
1685
|
+
style: {
|
|
1686
|
+
flex: 1,
|
|
1687
|
+
minWidth: 0
|
|
1688
|
+
},
|
|
1689
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1690
|
+
size: "xs",
|
|
1691
|
+
c: "dimmed",
|
|
1692
|
+
lineClamp: 1,
|
|
1693
|
+
children: field.description || field.type
|
|
1694
|
+
}), field.enum && /* @__PURE__ */ jsx(Flex, {
|
|
1695
|
+
gap: 0,
|
|
1696
|
+
wrap: "wrap",
|
|
1697
|
+
children: field.enum.map((enumValue) => /* @__PURE__ */ jsx(ActionButton, {
|
|
1698
|
+
px: "xs",
|
|
1699
|
+
size: "xs",
|
|
1700
|
+
h: 24,
|
|
1701
|
+
onClick: () => onInsert(enumValue),
|
|
1702
|
+
children: enumValue
|
|
1703
|
+
}, enumValue))
|
|
1704
|
+
})]
|
|
1705
|
+
}),
|
|
1706
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
1707
|
+
size: "xs",
|
|
1708
|
+
variant: "light",
|
|
1709
|
+
style: { flexShrink: 0 },
|
|
1710
|
+
children: field.type
|
|
1711
|
+
})
|
|
1712
|
+
]
|
|
1713
|
+
}, field.path))
|
|
1714
|
+
})]
|
|
1715
|
+
})
|
|
1716
|
+
]
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
//#endregion
|
|
1721
|
+
//#region ../../src/core/form/components/ControlSelect.tsx
|
|
1722
|
+
/**
|
|
1723
|
+
* ControlSelect component for handling Select, MultiSelect, and TagsInput.
|
|
1724
|
+
*
|
|
1725
|
+
* Features:
|
|
1726
|
+
* - Basic Select with enum support
|
|
1727
|
+
* - MultiSelect for array of enums
|
|
1728
|
+
* - TagsInput for array of strings (no enum)
|
|
1729
|
+
* - Future: Lazy loading
|
|
1730
|
+
* - Future: Searchable/filterable options
|
|
1731
|
+
* - Future: Custom option rendering
|
|
1732
|
+
*
|
|
1733
|
+
* Automatically detects enum values and array types from schema.
|
|
1734
|
+
*/
|
|
1735
|
+
const ControlSelect = (props) => {
|
|
1736
|
+
const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
|
|
1737
|
+
const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
|
|
1738
|
+
let itemsEnum;
|
|
1739
|
+
if (isArray && "items" in props.input.schema && props.input.schema.items) {
|
|
1740
|
+
const items = props.input.schema.items;
|
|
1741
|
+
if ("enum" in items && Array.isArray(items.enum)) itemsEnum = items.enum;
|
|
1742
|
+
}
|
|
1743
|
+
const enumValues = props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum : [];
|
|
1744
|
+
const [data, setData] = useState([]);
|
|
1745
|
+
useEffect(() => {
|
|
1746
|
+
if (!props.input?.props) return;
|
|
1747
|
+
if (props.loader) props.loader().then(setData);
|
|
1748
|
+
else setData(enumValues);
|
|
1749
|
+
}, [props.input, props.loader]);
|
|
1750
|
+
if (!props.input?.props) return null;
|
|
1751
|
+
if (props.segmented) {
|
|
1752
|
+
const segmentedControlProps = typeof props.segmented === "object" ? props.segmented : {};
|
|
1753
|
+
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
1754
|
+
...inputProps,
|
|
1755
|
+
children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
1756
|
+
disabled: inputProps.disabled,
|
|
1757
|
+
defaultValue: String(props.input.props.defaultValue),
|
|
1758
|
+
...segmentedControlProps,
|
|
1759
|
+
onChange: (value) => {
|
|
1760
|
+
props.input.set(value);
|
|
1761
|
+
},
|
|
1762
|
+
data: data.slice(0, 10)
|
|
1763
|
+
}) })
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
if (props.autocomplete) {
|
|
1767
|
+
const autocompleteProps = typeof props.autocomplete === "object" ? props.autocomplete : {};
|
|
1768
|
+
return /* @__PURE__ */ jsx(Autocomplete, {
|
|
1769
|
+
...inputProps,
|
|
1770
|
+
size: props.size,
|
|
1771
|
+
id,
|
|
1772
|
+
leftSection: icon,
|
|
1773
|
+
data,
|
|
1774
|
+
...props.input.props,
|
|
1775
|
+
...autocompleteProps
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
if (isArray && !itemsEnum || props.tags) {
|
|
1779
|
+
const tagsInputProps = typeof props.tags === "object" ? props.tags : {};
|
|
1780
|
+
return /* @__PURE__ */ jsx(TagsInput, {
|
|
1781
|
+
...inputProps,
|
|
1782
|
+
size: props.size,
|
|
1783
|
+
id,
|
|
1784
|
+
leftSection: icon,
|
|
1785
|
+
defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
|
|
1786
|
+
onChange: (value) => {
|
|
1787
|
+
props.input.set(value);
|
|
1788
|
+
},
|
|
1789
|
+
...tagsInputProps
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
if (isArray && itemsEnum || props.multi) {
|
|
1793
|
+
const data = itemsEnum?.map((value) => ({
|
|
1794
|
+
value,
|
|
1795
|
+
label: value
|
|
1796
|
+
})) || [];
|
|
1797
|
+
const multiSelectProps = typeof props.multi === "object" ? props.multi : {};
|
|
1798
|
+
return /* @__PURE__ */ jsx(MultiSelect, {
|
|
1799
|
+
...inputProps,
|
|
1800
|
+
size: props.size,
|
|
1801
|
+
id,
|
|
1802
|
+
leftSection: icon,
|
|
1803
|
+
data,
|
|
1804
|
+
defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
|
|
1805
|
+
onChange: (value) => {
|
|
1806
|
+
props.input.set(value);
|
|
1807
|
+
},
|
|
1808
|
+
...multiSelectProps
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
const selectProps = typeof props.select === "object" ? props.select : {};
|
|
1812
|
+
return /* @__PURE__ */ jsx(Select, {
|
|
1813
|
+
...inputProps,
|
|
1814
|
+
size: props.size,
|
|
1815
|
+
id,
|
|
1816
|
+
leftSection: icon,
|
|
1817
|
+
rightSection: null,
|
|
1818
|
+
data,
|
|
1819
|
+
...props.input.props,
|
|
1820
|
+
...selectProps
|
|
1821
|
+
});
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
//#endregion
|
|
1825
|
+
//#region ../../src/core/form/components/Control.tsx
|
|
1826
|
+
/**
|
|
1827
|
+
* Generic form control that renders the appropriate input based on the schema and props.
|
|
1828
|
+
*
|
|
1829
|
+
* Supports:
|
|
1830
|
+
* - TextInput (with format detection: email, url, tel)
|
|
1831
|
+
* - Textarea
|
|
1832
|
+
* - NumberInput (for number/integer types)
|
|
1833
|
+
* - FileInput
|
|
1834
|
+
* - ColorInput (for color format)
|
|
1835
|
+
* - Select (for enum types)
|
|
1836
|
+
* - Autocomplete
|
|
1837
|
+
* - PasswordInput
|
|
1838
|
+
* - Switch (for boolean types)
|
|
1839
|
+
* - SegmentedControl (for enum types)
|
|
1840
|
+
* - DateInput (for date format)
|
|
1841
|
+
* - DateTimePicker (for date-time format)
|
|
1842
|
+
* - TimeInput (for time format)
|
|
1843
|
+
* - QueryBuilder (for building type-safe queries with autocomplete)
|
|
1844
|
+
* - ControlObject (for nested object schemas)
|
|
1845
|
+
* - ControlArray (for arrays of objects)
|
|
1846
|
+
* - Custom component
|
|
1847
|
+
*
|
|
1848
|
+
* Automatically handles labels, descriptions, error messages, required state, and default icons.
|
|
1849
|
+
*/
|
|
1850
|
+
const Control = (_props) => {
|
|
1851
|
+
const form = useFormState(_props.input, ["error"]);
|
|
1852
|
+
if (!_props.input?.props) return null;
|
|
1853
|
+
const { inputProps, id, icon, format, schema } = parseInput(_props, form);
|
|
1854
|
+
const props = {
|
|
1855
|
+
..._props,
|
|
1856
|
+
...schema.$control
|
|
1857
|
+
};
|
|
1858
|
+
if (props.query) return /* @__PURE__ */ jsx(ControlQueryBuilder, {
|
|
1859
|
+
...props.input.props,
|
|
1860
|
+
...inputProps,
|
|
1861
|
+
schema: props.query,
|
|
1862
|
+
value: props.input.props.value,
|
|
1863
|
+
onChange: (value) => {
|
|
1864
|
+
props.input.set(value);
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
if (props.custom) {
|
|
1868
|
+
const Custom = props.custom;
|
|
1869
|
+
return /* @__PURE__ */ jsx(Input.Wrapper, {
|
|
1870
|
+
...inputProps,
|
|
1871
|
+
children: /* @__PURE__ */ jsx(Flex, {
|
|
1872
|
+
flex: 1,
|
|
1873
|
+
mt: "calc(var(--mantine-spacing-xs) / 2)",
|
|
1874
|
+
children: /* @__PURE__ */ jsx(Custom, {
|
|
1875
|
+
defaultValue: props.input.props.defaultValue,
|
|
1876
|
+
onChange: (value) => {
|
|
1877
|
+
props.input.set(value);
|
|
1878
|
+
}
|
|
1879
|
+
})
|
|
1880
|
+
})
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
const isObject = props.input.schema && "type" in props.input.schema && props.input.schema.type === "object" && "properties" in props.input.schema;
|
|
1884
|
+
if (props.object || isObject) {
|
|
1885
|
+
const controlObjectProps = typeof props.object === "object" ? props.object : {};
|
|
1886
|
+
return /* @__PURE__ */ jsx(ControlObject, {
|
|
1887
|
+
input: props.input,
|
|
1888
|
+
title: props.title,
|
|
1889
|
+
description: props.description,
|
|
1890
|
+
...controlObjectProps
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
|
|
1894
|
+
const isArrayOfObjects = isArray && "items" in props.input.schema && props.input.schema.items && typeof props.input.schema.items === "object" && "properties" in props.input.schema.items;
|
|
1895
|
+
if (props.array || isArrayOfObjects) {
|
|
1896
|
+
const controlArrayProps = typeof props.array === "object" ? props.array : {};
|
|
1897
|
+
return /* @__PURE__ */ jsx(ControlArray, {
|
|
1898
|
+
input: props.input,
|
|
1899
|
+
title: props.title,
|
|
1900
|
+
description: props.description,
|
|
1901
|
+
...controlArrayProps
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
if (props.number || props.input.schema && "type" in props.input.schema && (props.input.schema.type === "number" || props.input.schema.type === "integer")) {
|
|
1905
|
+
const controlNumberProps = typeof props.number === "object" ? props.number : {};
|
|
1906
|
+
if (props.slider) controlNumberProps.sliderProps ??= {};
|
|
1907
|
+
return /* @__PURE__ */ jsx(ControlNumber, {
|
|
1908
|
+
size: props.size,
|
|
1909
|
+
input: props.input,
|
|
1910
|
+
title: props.title,
|
|
1911
|
+
description: props.description,
|
|
1912
|
+
icon,
|
|
1913
|
+
...controlNumberProps
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
if (props.file) {
|
|
1917
|
+
const fileInputProps = typeof props.file === "object" ? props.file : {};
|
|
1918
|
+
return /* @__PURE__ */ jsx(FileInput, {
|
|
1919
|
+
...inputProps,
|
|
1920
|
+
size: props.size,
|
|
1921
|
+
id,
|
|
1922
|
+
leftSection: icon,
|
|
1923
|
+
onChange: (file) => {
|
|
1924
|
+
props.input.set(file);
|
|
1925
|
+
},
|
|
1926
|
+
...fileInputProps
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
if (props.color || format === "color") {
|
|
1930
|
+
const colorInputProps = typeof props.color === "object" ? props.color : {};
|
|
1931
|
+
return /* @__PURE__ */ jsx(ColorInput, {
|
|
1932
|
+
...inputProps,
|
|
1933
|
+
size: props.size,
|
|
1934
|
+
id,
|
|
1935
|
+
leftSection: icon,
|
|
1936
|
+
...props.input.props,
|
|
1937
|
+
...colorInputProps
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
if (props.input.schema && "enum" in props.input.schema && props.input.schema.enum || isArray && !isArrayOfObjects || props.select) {
|
|
1941
|
+
const opts = typeof props.select === "object" ? props.select : {};
|
|
1942
|
+
if (props.segmented) opts.segmented ??= {};
|
|
1943
|
+
return /* @__PURE__ */ jsx(ControlSelect, {
|
|
1944
|
+
size: props.size,
|
|
1945
|
+
input: props.input,
|
|
1946
|
+
title: props.title,
|
|
1947
|
+
description: props.description,
|
|
1948
|
+
icon,
|
|
1949
|
+
...opts
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
if (props.input.schema && "type" in props.input.schema && props.input.schema.type === "boolean") {
|
|
1953
|
+
if (props.switch) {
|
|
1954
|
+
const switchProps = typeof props.switch === "object" ? props.switch : {};
|
|
1955
|
+
return /* @__PURE__ */ jsx(Switch, {
|
|
1956
|
+
...inputProps,
|
|
1957
|
+
size: props.size,
|
|
1958
|
+
id,
|
|
1959
|
+
color: "blue",
|
|
1960
|
+
defaultChecked: props.input.props.defaultValue,
|
|
1961
|
+
onChange: (event) => {
|
|
1962
|
+
props.input.set(event.currentTarget.checked);
|
|
1963
|
+
},
|
|
1964
|
+
...switchProps
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
const opts = {
|
|
1968
|
+
input: props.input,
|
|
1969
|
+
select: { data: [{
|
|
1970
|
+
value: "true",
|
|
1971
|
+
label: "Yes"
|
|
1972
|
+
}, {
|
|
1973
|
+
value: "false",
|
|
1974
|
+
label: "No"
|
|
1975
|
+
}] }
|
|
1976
|
+
};
|
|
1977
|
+
return /* @__PURE__ */ jsx(ControlSelect, {
|
|
1978
|
+
size: props.size,
|
|
1979
|
+
title: props.title,
|
|
1980
|
+
description: props.description,
|
|
1981
|
+
icon,
|
|
1982
|
+
...opts
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
if (props.password || props.input.props.name?.includes("password")) {
|
|
1986
|
+
const passwordInputProps = typeof props.password === "object" ? props.password : {};
|
|
1987
|
+
return /* @__PURE__ */ jsx(PasswordInput, {
|
|
1988
|
+
...inputProps,
|
|
1989
|
+
size: props.size,
|
|
1990
|
+
id,
|
|
1991
|
+
leftSection: icon,
|
|
1992
|
+
...props.input.props,
|
|
1993
|
+
...passwordInputProps
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
if (props.area) {
|
|
1997
|
+
const textAreaProps = typeof props.area === "object" ? props.area : {};
|
|
1998
|
+
return /* @__PURE__ */ jsx(Textarea, {
|
|
1999
|
+
...inputProps,
|
|
2000
|
+
size: props.size,
|
|
2001
|
+
id,
|
|
2002
|
+
leftSection: icon,
|
|
2003
|
+
...props.input.props,
|
|
2004
|
+
...textAreaProps
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
if (props.date || props.datetime || props.time || format === "date" || format === "date-time" || format === "time") return /* @__PURE__ */ jsx(ControlDate, {
|
|
2008
|
+
size: props.size,
|
|
2009
|
+
input: props.input,
|
|
2010
|
+
title: props.title,
|
|
2011
|
+
description: props.description,
|
|
2012
|
+
icon,
|
|
2013
|
+
date: props.date,
|
|
2014
|
+
datetime: props.datetime,
|
|
2015
|
+
time: props.time
|
|
2016
|
+
});
|
|
2017
|
+
const textInputProps = typeof props.text === "object" ? props.text : {};
|
|
2018
|
+
const getInputType = () => {
|
|
2019
|
+
switch (format) {
|
|
2020
|
+
case "email": return "email";
|
|
2021
|
+
case "url":
|
|
2022
|
+
case "uri": return "url";
|
|
2023
|
+
case "tel":
|
|
2024
|
+
case "phone": return "tel";
|
|
2025
|
+
default: return;
|
|
2026
|
+
}
|
|
2027
|
+
};
|
|
2028
|
+
return /* @__PURE__ */ jsx(TextInput, {
|
|
2029
|
+
...inputProps,
|
|
2030
|
+
size: props.size,
|
|
2031
|
+
id,
|
|
2032
|
+
leftSection: icon,
|
|
2033
|
+
type: getInputType(),
|
|
2034
|
+
...props.input.props,
|
|
2035
|
+
...textInputProps,
|
|
2036
|
+
inputWrapperOrder: [
|
|
2037
|
+
"label",
|
|
2038
|
+
"input",
|
|
2039
|
+
"description",
|
|
2040
|
+
"error"
|
|
2041
|
+
]
|
|
2042
|
+
});
|
|
2043
|
+
};
|
|
2044
|
+
|
|
2045
|
+
//#endregion
|
|
2046
|
+
//#region ../../src/core/helpers/renderIcon.tsx
|
|
2047
|
+
const renderIcon = (icon, size) => {
|
|
2048
|
+
if (!icon) return null;
|
|
2049
|
+
if (isValidElement(icon)) return icon;
|
|
2050
|
+
if (isComponentType(icon)) return /* @__PURE__ */ jsx(icon, { size: size ?? ui.sizes.icon.md });
|
|
2051
|
+
return icon;
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
//#endregion
|
|
2055
|
+
//#region ../../src/core/utils/extractSchemaFields.ts
|
|
2056
|
+
/**
|
|
2057
|
+
* Extract field information from a TypeBox schema for query building.
|
|
2058
|
+
* Supports nested objects and provides field metadata for autocomplete.
|
|
2059
|
+
*/
|
|
2060
|
+
function extractSchemaFields(schema, prefix = "") {
|
|
2061
|
+
const fields = [];
|
|
2062
|
+
if (!schema || typeof schema !== "object") return fields;
|
|
2063
|
+
const properties = "properties" in schema ? schema.properties : schema;
|
|
2064
|
+
if (!properties || typeof properties !== "object") return fields;
|
|
2065
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
2066
|
+
if (typeof value !== "object" || value === null) continue;
|
|
2067
|
+
const fieldSchema = value;
|
|
2068
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2069
|
+
const format = "format" in fieldSchema ? fieldSchema.format : void 0;
|
|
2070
|
+
let displayType = "type" in fieldSchema ? fieldSchema.type : "object";
|
|
2071
|
+
if (format === "date-time") displayType = "datetime";
|
|
2072
|
+
else if (format === "date") displayType = "date";
|
|
2073
|
+
else if (format === "time") displayType = "time";
|
|
2074
|
+
else if (format === "duration") displayType = "duration";
|
|
2075
|
+
const field = {
|
|
2076
|
+
name: key,
|
|
2077
|
+
path,
|
|
2078
|
+
type: displayType,
|
|
2079
|
+
format,
|
|
2080
|
+
description: "description" in fieldSchema ? fieldSchema.description : void 0
|
|
2081
|
+
};
|
|
2082
|
+
if ("enum" in fieldSchema && fieldSchema.enum) {
|
|
2083
|
+
field.enum = fieldSchema.enum;
|
|
2084
|
+
field.type = "enum";
|
|
2085
|
+
}
|
|
2086
|
+
if ("type" in fieldSchema && fieldSchema.type === "object" && "properties" in fieldSchema && typeof fieldSchema.properties === "object") field.nested = extractSchemaFields(fieldSchema.properties, path);
|
|
2087
|
+
fields.push(field);
|
|
2088
|
+
if (field.nested) fields.push(...field.nested);
|
|
2089
|
+
}
|
|
2090
|
+
return fields;
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Get operator symbol and description
|
|
2094
|
+
*/
|
|
2095
|
+
const OPERATOR_INFO = {
|
|
2096
|
+
eq: {
|
|
2097
|
+
symbol: "=",
|
|
2098
|
+
label: "equals",
|
|
2099
|
+
example: "name=John"
|
|
2100
|
+
},
|
|
2101
|
+
ne: {
|
|
2102
|
+
symbol: "!=",
|
|
2103
|
+
label: "not equals",
|
|
2104
|
+
example: "status!=archived"
|
|
2105
|
+
},
|
|
2106
|
+
gt: {
|
|
2107
|
+
symbol: ">",
|
|
2108
|
+
label: "greater than",
|
|
2109
|
+
example: "age>18"
|
|
2110
|
+
},
|
|
2111
|
+
gte: {
|
|
2112
|
+
symbol: ">=",
|
|
2113
|
+
label: "greater or equal",
|
|
2114
|
+
example: "age>=18"
|
|
2115
|
+
},
|
|
2116
|
+
lt: {
|
|
2117
|
+
symbol: "<",
|
|
2118
|
+
label: "less than",
|
|
2119
|
+
example: "age<65"
|
|
2120
|
+
},
|
|
2121
|
+
lte: {
|
|
2122
|
+
symbol: "<=",
|
|
2123
|
+
label: "less or equal",
|
|
2124
|
+
example: "age<=65"
|
|
2125
|
+
},
|
|
2126
|
+
null: {
|
|
2127
|
+
symbol: "=null",
|
|
2128
|
+
label: "is null",
|
|
2129
|
+
example: "deletedAt=null"
|
|
2130
|
+
},
|
|
2131
|
+
notNull: {
|
|
2132
|
+
symbol: "!=null",
|
|
2133
|
+
label: "is not null",
|
|
2134
|
+
example: "email!=null"
|
|
2135
|
+
},
|
|
2136
|
+
in: {
|
|
2137
|
+
symbol: "[...]",
|
|
2138
|
+
label: "in array",
|
|
2139
|
+
example: "status=[active,pending]"
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
|
|
2143
|
+
//#endregion
|
|
2144
|
+
//#region ../../src/core/utils/icons.tsx
|
|
2145
|
+
/**
|
|
2146
|
+
* Get the default icon for an input based on its type, format, or name.
|
|
2147
|
+
*/
|
|
2148
|
+
const getDefaultIcon = (params) => {
|
|
2149
|
+
const { type, format, name, isEnum, isArray, size = "sm" } = params;
|
|
2150
|
+
const iconSize = ui.sizes.icon[size];
|
|
2151
|
+
if (format) switch (format) {
|
|
2152
|
+
case "email": return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
|
|
2153
|
+
case "url":
|
|
2154
|
+
case "uri": return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
|
|
2155
|
+
case "tel":
|
|
2156
|
+
case "phone": return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
|
|
2157
|
+
case "date": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
2158
|
+
case "date-time": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
2159
|
+
case "time": return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
|
|
2160
|
+
case "color": return /* @__PURE__ */ jsx(IconColorPicker, { size: iconSize });
|
|
2161
|
+
case "uuid": return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
|
|
2162
|
+
}
|
|
2163
|
+
if (name) {
|
|
2164
|
+
const nameLower = name.toLowerCase();
|
|
2165
|
+
if (nameLower.includes("password") || nameLower.includes("secret")) return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
|
|
2166
|
+
if (nameLower.includes("email") || nameLower.includes("mail")) return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
|
|
2167
|
+
if (nameLower.includes("url") || nameLower.includes("link")) return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
|
|
2168
|
+
if (nameLower.includes("phone") || nameLower.includes("tel")) return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
|
|
2169
|
+
if (nameLower.includes("color")) return /* @__PURE__ */ jsx(IconPalette, { size: iconSize });
|
|
2170
|
+
if (nameLower.includes("file") || nameLower.includes("upload")) return /* @__PURE__ */ jsx(IconFile, { size: iconSize });
|
|
2171
|
+
if (nameLower.includes("date")) return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
|
|
2172
|
+
if (nameLower.includes("time")) return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
|
|
2173
|
+
}
|
|
2174
|
+
if (isEnum || isArray) return /* @__PURE__ */ jsx(IconSelector, { size: iconSize });
|
|
2175
|
+
if (type) switch (type) {
|
|
2176
|
+
case "boolean": return /* @__PURE__ */ jsx(IconToggleLeft, { size: iconSize });
|
|
2177
|
+
case "number":
|
|
2178
|
+
case "integer": return /* @__PURE__ */ jsx(IconHash, { size: iconSize });
|
|
2179
|
+
case "array": return /* @__PURE__ */ jsx(IconList, { size: iconSize });
|
|
2180
|
+
case "string": return /* @__PURE__ */ jsx(IconLetterCase, { size: iconSize });
|
|
2181
|
+
}
|
|
2182
|
+
return /* @__PURE__ */ jsx(IconAt, { size: iconSize });
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
//#endregion
|
|
2186
|
+
//#region ../../src/core/utils/string.ts
|
|
2187
|
+
/**
|
|
2188
|
+
* Capitalizes the first letter of a string.
|
|
2189
|
+
*
|
|
2190
|
+
* @example
|
|
2191
|
+
* capitalize("hello") // "Hello"
|
|
2192
|
+
*/
|
|
2193
|
+
const capitalize = (str) => {
|
|
2194
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2195
|
+
};
|
|
2196
|
+
/**
|
|
2197
|
+
* Converts camelCase or snake_case to Title Case with spaces.
|
|
2198
|
+
*
|
|
2199
|
+
* @example
|
|
2200
|
+
* toTitleCase("userName") // "User Name"
|
|
2201
|
+
* toTitleCase("first_name") // "First Name"
|
|
2202
|
+
* toTitleCase("email") // "Email"
|
|
2203
|
+
*/
|
|
2204
|
+
const toTitleCase = (str) => {
|
|
2205
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2206
|
+
};
|
|
2207
|
+
/**
|
|
2208
|
+
* Converts a path or identifier string into a pretty display name.
|
|
2209
|
+
* For paths like "/contacts/0/name", extracts just the field name "Name".
|
|
2210
|
+
* Handles camelCase and snake_case conversion to Title Case.
|
|
2211
|
+
*
|
|
2212
|
+
* @example
|
|
2213
|
+
* prettyName("/userName") // "User Name"
|
|
2214
|
+
* prettyName("/contacts/0/email") // "Email"
|
|
2215
|
+
* prettyName("/address/streetName") // "Street Name"
|
|
2216
|
+
* prettyName("first_name") // "First Name"
|
|
2217
|
+
*/
|
|
2218
|
+
const prettyName = (name) => {
|
|
2219
|
+
const segments = name.split("/").filter((s) => s && !/^\d+$/.test(s));
|
|
2220
|
+
return toTitleCase(segments[segments.length - 1] || name.replaceAll("/", ""));
|
|
2221
|
+
};
|
|
2222
|
+
|
|
2223
|
+
//#endregion
|
|
2224
|
+
//#region ../../src/core/index.ts
|
|
2225
|
+
/**
|
|
2226
|
+
* Core UI components based on Mantine UI v8.
|
|
2227
|
+
*
|
|
2228
|
+
* **Features:**
|
|
2229
|
+
* - Mantine integration with theme support
|
|
2230
|
+
* - ActionButton, BurgerButton, ClipboardButton, DarkModeButton, LanguageButton, ThemeButton
|
|
2231
|
+
* - AlertDialog, ConfirmDialog, PromptDialog
|
|
2232
|
+
* - Form controls: Control, ControlArray, ControlDate, ControlNumber, ControlObject, ControlSelect, ControlQueryBuilder
|
|
2233
|
+
* - TypeForm for automatic form generation from TypeBox schemas
|
|
2234
|
+
* - DashboardShell layout component
|
|
2235
|
+
* - AppBar with configurable elements
|
|
2236
|
+
* - Sidebar navigation with sections and menu items
|
|
2237
|
+
* - Omnibar for command palette / search
|
|
2238
|
+
* - DataTable with filtering, sorting, pagination
|
|
2239
|
+
* - Toast notifications
|
|
2240
|
+
* - Theme system with dark mode
|
|
2241
|
+
*
|
|
2242
|
+
* @module alepha.ui
|
|
2243
|
+
*/
|
|
2244
|
+
const AlephaUI = $module({
|
|
2245
|
+
name: "alepha.ui",
|
|
2246
|
+
services: [
|
|
2247
|
+
DialogService,
|
|
2248
|
+
ToastService,
|
|
2249
|
+
ThemeProvider,
|
|
2250
|
+
UiRouter
|
|
2251
|
+
],
|
|
2252
|
+
register: (alepha) => {
|
|
2253
|
+
alepha.with(AlephaReactI18n);
|
|
2254
|
+
alepha.with(AlephaReactHead);
|
|
2255
|
+
alepha.with(AlephaReactForm);
|
|
2256
|
+
alepha.with(ThemeProvider);
|
|
2257
|
+
alepha.with(DialogService);
|
|
2258
|
+
alepha.with(ToastService);
|
|
2259
|
+
}
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2262
|
+
//#endregion
|
|
2263
|
+
export { ui as a, ActionButton as i, capitalize as n, AlephaMantineProvider as o, Control as r, AlephaUI as t };
|
|
2264
|
+
//# sourceMappingURL=core-niW0sFLv.js.map
|