@alepha/ui 0.10.4

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/index.js ADDED
@@ -0,0 +1,317 @@
1
+ import { $module, TypeBoxError } from "@alepha/core";
2
+ import { AlephaReact, NestedView, useActive, useAlepha, useRouter, useRouterEvents } from "@alepha/react";
3
+ import "@mantine/core/styles.css";
4
+ import "@mantine/nprogress/styles.css";
5
+ import "@mantine/spotlight/styles.css";
6
+ import "@mantine/notifications/styles.css";
7
+ import { useFormState } from "@alepha/react-form";
8
+ import { Autocomplete, Button, ColorSchemeScript, Flex, Input, MantineProvider, PasswordInput, SegmentedControl, Select, Switch, TextInput, Textarea } from "@mantine/core";
9
+ import { useState } from "react";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ import { ModalsProvider } from "@mantine/modals";
12
+ import { Notifications } from "@mantine/notifications";
13
+ import { NavigationProgress, nprogress } from "@mantine/nprogress";
14
+
15
+ //#region src/components/Action.tsx
16
+ const Action = (_props) => {
17
+ const props = {
18
+ variant: "subtle",
19
+ ..._props
20
+ };
21
+ if (props.leftSection && !props.children) {
22
+ props.className ??= "mantine-Action-iconOnly";
23
+ props.p ??= "xs";
24
+ }
25
+ if (props.textVisibleFrom) {
26
+ const { children, textVisibleFrom, leftSection,...rest } = props;
27
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex, {
28
+ w: "100%",
29
+ visibleFrom: textVisibleFrom,
30
+ children: /* @__PURE__ */ jsx(Action, {
31
+ flex: 1,
32
+ ...rest,
33
+ leftSection,
34
+ children
35
+ })
36
+ }), /* @__PURE__ */ jsx(Flex, {
37
+ w: "100%",
38
+ hiddenFrom: textVisibleFrom,
39
+ children: /* @__PURE__ */ jsx(Action, {
40
+ px: "xs",
41
+ ...rest,
42
+ children: leftSection
43
+ })
44
+ })] });
45
+ }
46
+ const renderAction = () => {
47
+ if ("href" in props && props.href) return /* @__PURE__ */ jsx(ActionHref, {
48
+ ...props,
49
+ href: props.href,
50
+ children: props.children
51
+ });
52
+ if ("onClick" in props && props.onClick) return /* @__PURE__ */ jsx(ActionClick, {
53
+ ...props,
54
+ onClick: props.onClick,
55
+ children: props.children
56
+ });
57
+ if ("form" in props && props.form) return /* @__PURE__ */ jsx(ActionSubmit, {
58
+ ...props,
59
+ form: props.form,
60
+ children: props.children
61
+ });
62
+ return /* @__PURE__ */ jsx(Button, {
63
+ ...props,
64
+ children: props.children
65
+ });
66
+ };
67
+ return renderAction();
68
+ };
69
+ /**
70
+ * Action button that submits a form with loading and disabled state handling.
71
+ */
72
+ const ActionSubmit = (props) => {
73
+ const { form,...buttonProps } = props;
74
+ const state = useFormState(form);
75
+ return /* @__PURE__ */ jsx(Button, {
76
+ ...buttonProps,
77
+ loading: state.loading,
78
+ disabled: state.loading || !state.dirty,
79
+ type: "submit",
80
+ children: props.children
81
+ });
82
+ };
83
+ /**
84
+ * Basic action button that handles click events with loading and error handling.
85
+ */
86
+ const ActionClick = (props) => {
87
+ const [pending, setPending] = useState(false);
88
+ const alepha = useAlepha();
89
+ const onClick = async (e) => {
90
+ setPending(true);
91
+ try {
92
+ await props.onClick(e);
93
+ } catch (e$1) {
94
+ console.error(e$1);
95
+ await alepha.events.emit("form:submit:error", {
96
+ id: "action",
97
+ error: e$1
98
+ });
99
+ } finally {
100
+ setPending(false);
101
+ }
102
+ };
103
+ return /* @__PURE__ */ jsx(Button, {
104
+ ...props,
105
+ disabled: pending || props.disabled,
106
+ loading: pending,
107
+ onClick,
108
+ children: props.children
109
+ });
110
+ };
111
+ /**
112
+ * Action for navigation with active state support.
113
+ */
114
+ const ActionHref = (props) => {
115
+ const { active: options, routerGoOptions,...buttonProps } = props;
116
+ const router = useRouter();
117
+ const { isPending, isActive } = useActive(options ? {
118
+ href: props.href,
119
+ ...options
120
+ } : { href: props.href });
121
+ const anchorProps = router.anchor(props.href, routerGoOptions);
122
+ return /* @__PURE__ */ jsx(Button, {
123
+ component: "a",
124
+ loading: isPending,
125
+ ...anchorProps,
126
+ ...buttonProps,
127
+ variant: isActive && options !== false ? "filled" : "subtle",
128
+ children: props.children
129
+ });
130
+ };
131
+
132
+ //#endregion
133
+ //#region src/components/AlephaMantineProvider.tsx
134
+ const AlephaMantineProvider = (props) => {
135
+ useRouterEvents({
136
+ onBegin: () => {
137
+ nprogress.start();
138
+ },
139
+ onEnd: () => {
140
+ nprogress.complete();
141
+ }
142
+ });
143
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ColorSchemeScript, {
144
+ defaultColorScheme: props.mantine?.defaultColorScheme,
145
+ ...props.colorSchemeScript
146
+ }), /* @__PURE__ */ jsxs(MantineProvider, {
147
+ ...props.mantine,
148
+ children: [
149
+ /* @__PURE__ */ jsx(Notifications, { ...props.notifications }),
150
+ /* @__PURE__ */ jsx(NavigationProgress, { ...props.navigationProgress }),
151
+ /* @__PURE__ */ jsx(ModalsProvider, {
152
+ ...props.modals,
153
+ children: props.children ?? /* @__PURE__ */ jsx(NestedView, {})
154
+ })
155
+ ]
156
+ })] });
157
+ };
158
+
159
+ //#endregion
160
+ //#region src/components/Control.tsx
161
+ /**
162
+ * Generic form control that renders the appropriate input based on the schema and props.
163
+ *
164
+ * Supports:
165
+ * - TextInput
166
+ * - Textarea
167
+ * - Select (for enum types)
168
+ * - Autocomplete
169
+ * - PasswordInput
170
+ * - Switch (for boolean types)
171
+ * - SegmentedControl (for enum types)
172
+ * - Custom component
173
+ *
174
+ * Automatically handles labels, descriptions, error messages, and required state.
175
+ */
176
+ const Control = (props) => {
177
+ const form = useFormState(props.input);
178
+ if (!props.input?.props) return null;
179
+ const disabled = false;
180
+ const id = props.input.props.id;
181
+ 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);
182
+ const description = props.description ?? ("description" in props.input.schema && typeof props.input.schema.description === "string" ? props.input.schema.description : void 0);
183
+ const error = form.error && form.error instanceof TypeBoxError ? form.error.value.message : void 0;
184
+ const icon = props.icon;
185
+ const required = props.input.required;
186
+ const inputProps = {
187
+ label,
188
+ description,
189
+ error,
190
+ required,
191
+ disabled
192
+ };
193
+ if (props.custom) {
194
+ const Custom = props.custom;
195
+ return /* @__PURE__ */ jsx(Input.Wrapper, {
196
+ ...inputProps,
197
+ children: /* @__PURE__ */ jsx(Flex, {
198
+ flex: 1,
199
+ mt: "calc(var(--mantine-spacing-xs) / 2)",
200
+ children: /* @__PURE__ */ jsx(Custom, {
201
+ defaultValue: props.input.props.defaultValue,
202
+ onChange: (value) => {
203
+ props.input.set(value);
204
+ }
205
+ })
206
+ })
207
+ });
208
+ }
209
+ if (props.segmented) {
210
+ const segmentedControlProps = typeof props.segmented === "object" ? props.segmented : {};
211
+ const data = segmentedControlProps.data ?? (props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum?.map((value) => ({
212
+ value,
213
+ label: value
214
+ })) : []);
215
+ return /* @__PURE__ */ jsx(Input.Wrapper, {
216
+ ...inputProps,
217
+ children: /* @__PURE__ */ jsx(Flex, {
218
+ mt: "calc(var(--mantine-spacing-xs) / 2)",
219
+ children: /* @__PURE__ */ jsx(SegmentedControl, {
220
+ disabled,
221
+ defaultValue: String(props.input.props.defaultValue),
222
+ ...segmentedControlProps,
223
+ onChange: (value) => {
224
+ props.input.set(value);
225
+ },
226
+ data
227
+ })
228
+ })
229
+ });
230
+ }
231
+ if (props.autocomplete) {
232
+ const autocompleteProps = typeof props.autocomplete === "object" ? props.autocomplete : {};
233
+ return /* @__PURE__ */ jsx(Autocomplete, {
234
+ ...inputProps,
235
+ id,
236
+ leftSection: icon,
237
+ ...props.input.props,
238
+ ...autocompleteProps
239
+ });
240
+ }
241
+ if (props.input.schema && "enum" in props.input.schema && props.input.schema.enum || props.select) {
242
+ const data = props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum?.map((value) => ({
243
+ value,
244
+ label: value
245
+ })) : [];
246
+ const selectProps = typeof props.select === "object" ? props.select : {};
247
+ return /* @__PURE__ */ jsx(Select, {
248
+ ...inputProps,
249
+ id,
250
+ leftSection: icon,
251
+ data,
252
+ ...props.input.props,
253
+ ...selectProps
254
+ });
255
+ }
256
+ if (props.input.schema && "type" in props.input.schema && props.input.schema.type === "boolean" || props.switch) {
257
+ const switchProps = typeof props.switch === "object" ? props.switch : {};
258
+ return /* @__PURE__ */ jsx(Switch, {
259
+ ...inputProps,
260
+ id,
261
+ color: "blue",
262
+ defaultChecked: props.input.props.defaultValue,
263
+ ...props.input.props,
264
+ ...switchProps
265
+ });
266
+ }
267
+ if (props.password) {
268
+ const passwordInputProps = typeof props.password === "object" ? props.password : {};
269
+ return /* @__PURE__ */ jsx(PasswordInput, {
270
+ ...inputProps,
271
+ id,
272
+ leftSection: icon,
273
+ ...props.input.props,
274
+ ...passwordInputProps
275
+ });
276
+ }
277
+ if (props.area) {
278
+ const textAreaProps = typeof props.area === "object" ? props.area : {};
279
+ return /* @__PURE__ */ jsx(Textarea, {
280
+ ...inputProps,
281
+ id,
282
+ leftSection: icon,
283
+ ...props.input.props,
284
+ ...textAreaProps
285
+ });
286
+ }
287
+ const textInputProps = typeof props.text === "object" ? props.text : {};
288
+ return /* @__PURE__ */ jsx(TextInput, {
289
+ ...inputProps,
290
+ id,
291
+ leftSection: icon,
292
+ ...props.input.props,
293
+ ...textInputProps
294
+ });
295
+ };
296
+ const prettyName = (name) => {
297
+ return capitalize(name.replaceAll("/", ""));
298
+ };
299
+ const capitalize = (str) => {
300
+ return str.charAt(0).toUpperCase() + str.slice(1);
301
+ };
302
+
303
+ //#endregion
304
+ //#region src/index.ts
305
+ /**
306
+ *
307
+ *
308
+ * @module alepha.ui
309
+ */
310
+ const AlephaUI = $module({
311
+ name: "alepha.ui",
312
+ services: [AlephaReact]
313
+ });
314
+
315
+ //#endregion
316
+ export { Action, AlephaMantineProvider, AlephaUI, Control };
317
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["e","segmentedControlProps: Partial<SegmentedControlProps>"],"sources":["../src/components/Action.tsx","../src/components/AlephaMantineProvider.tsx","../src/components/Control.tsx","../src/index.ts"],"sourcesContent":["import {\n\ttype RouterGoOptions,\n\ttype UseActiveOptions,\n\tuseActive,\n\tuseAlepha,\n\tuseRouter,\n} from \"@alepha/react\";\nimport { type FormModel, useFormState } from \"@alepha/react-form\";\nimport { Button, type ButtonProps, Flex } from \"@mantine/core\";\nimport { type ReactNode, useState } from \"react\";\n\nexport interface ActionCommonProps extends ButtonProps {\n\tchildren?: ReactNode;\n\ttextVisibleFrom?: \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\t// TODO\n\n\t/**\n\t * If set, a confirmation dialog will be shown before performing the action.\n\t * If `true`, a default title and message will be used.\n\t * If a string, it will be used as the message with a default title.\n\t * If an object, it can contain `title` and `message` properties to customize the dialog.\n\t */\n\tconfirm?: boolean | string | { title?: string; message: string };\n}\n\nexport type ActionProps = ActionCommonProps &\n\t(ActiveHrefProps | ActionClickProps | ActionSubmitProps | {});\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst Action = (_props: ActionProps) => {\n\tconst props = { variant: \"subtle\", ..._props };\n\n\tif (props.leftSection && !props.children) {\n\t\tprops.className ??= \"mantine-Action-iconOnly\";\n\t\tprops.p ??= \"xs\";\n\t}\n\n\tif (props.textVisibleFrom) {\n\t\tconst { children, textVisibleFrom, leftSection, ...rest } = props;\n\t\treturn (\n\t\t\t<>\n\t\t\t\t<Flex w={\"100%\"} visibleFrom={textVisibleFrom}>\n\t\t\t\t\t<Action flex={1} {...rest} leftSection={leftSection}>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</Action>\n\t\t\t\t</Flex>\n\t\t\t\t<Flex w={\"100%\"} hiddenFrom={textVisibleFrom}>\n\t\t\t\t\t<Action px={\"xs\"} {...rest}>\n\t\t\t\t\t\t{leftSection}\n\t\t\t\t\t</Action>\n\t\t\t\t</Flex>\n\t\t\t</>\n\t\t);\n\t}\n\n\tconst renderAction = () => {\n\t\tif (\"href\" in props && props.href) {\n\t\t\treturn (\n\t\t\t\t<ActionHref {...props} href={props.href}>\n\t\t\t\t\t{props.children}\n\t\t\t\t</ActionHref>\n\t\t\t);\n\t\t}\n\n\t\tif (\"onClick\" in props && props.onClick) {\n\t\t\treturn (\n\t\t\t\t<ActionClick {...props} onClick={props.onClick}>\n\t\t\t\t\t{props.children}\n\t\t\t\t</ActionClick>\n\t\t\t);\n\t\t}\n\n\t\tif (\"form\" in props && props.form) {\n\t\t\treturn (\n\t\t\t\t<ActionSubmit {...props} form={props.form}>\n\t\t\t\t\t{props.children}\n\t\t\t\t</ActionSubmit>\n\t\t\t);\n\t\t}\n\n\t\treturn <Button {...(props as any)}>{props.children}</Button>;\n\t};\n\n\treturn renderAction();\n};\n\nexport default Action;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface ActionSubmitProps extends ButtonProps {\n\tform: FormModel<any>;\n}\n\n/**\n * Action button that submits a form with loading and disabled state handling.\n */\nconst ActionSubmit = (props: ActionSubmitProps) => {\n\tconst { form, ...buttonProps } = props;\n\tconst state = useFormState(form);\n\treturn (\n\t\t<Button\n\t\t\t{...buttonProps}\n\t\t\tloading={state.loading}\n\t\t\tdisabled={state.loading || !state.dirty}\n\t\t\ttype={\"submit\"}\n\t\t>\n\t\t\t{props.children}\n\t\t</Button>\n\t);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface ActionClickProps extends ButtonProps {\n\tonClick: (e: any) => any;\n}\n\n/**\n * Basic action button that handles click events with loading and error handling.\n */\nconst ActionClick = (props: ActionClickProps) => {\n\tconst [pending, setPending] = useState(false);\n\tconst alepha = useAlepha();\n\n\tconst onClick = async (e: any) => {\n\t\tsetPending(true);\n\t\ttry {\n\t\t\tawait props.onClick(e);\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t\tawait alepha.events.emit(\"form:submit:error\", {\n\t\t\t\tid: \"action\",\n\t\t\t\terror: e as Error,\n\t\t\t});\n\t\t} finally {\n\t\t\tsetPending(false);\n\t\t}\n\t};\n\n\treturn (\n\t\t<Button\n\t\t\t{...props}\n\t\t\tdisabled={pending || props.disabled}\n\t\t\tloading={pending}\n\t\t\tonClick={onClick}\n\t\t>\n\t\t\t{props.children}\n\t\t</Button>\n\t);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface ActiveHrefProps extends ButtonProps {\n\thref: string;\n\tactive?: Partial<UseActiveOptions> | false;\n\trouterGoOptions?: RouterGoOptions;\n}\n\n/**\n * Action for navigation with active state support.\n */\nconst ActionHref = (props: ActiveHrefProps) => {\n\tconst { active: options, routerGoOptions, ...buttonProps } = props;\n\tconst router = useRouter();\n\tconst { isPending, isActive } = useActive(\n\t\toptions ? { href: props.href, ...options } : { href: props.href },\n\t);\n\tconst anchorProps = router.anchor(props.href, routerGoOptions);\n\n\treturn (\n\t\t<Button\n\t\t\tcomponent={\"a\"}\n\t\t\tloading={isPending}\n\t\t\t{...anchorProps}\n\t\t\t{...buttonProps}\n\t\t\tvariant={isActive && options !== false ? \"filled\" : \"subtle\"}\n\t\t>\n\t\t\t{props.children}\n\t\t</Button>\n\t);\n};\n","import { NestedView, useRouterEvents } from \"@alepha/react\";\nimport type {\n\tColorSchemeScriptProps,\n\tMantineProviderProps,\n} from \"@mantine/core\";\nimport { ColorSchemeScript, MantineProvider } from \"@mantine/core\";\nimport { ModalsProvider, type ModalsProviderProps } from \"@mantine/modals\";\nimport { Notifications, type NotificationsProps } from \"@mantine/notifications\";\nimport type { NavigationProgressProps } from \"@mantine/nprogress\";\nimport { NavigationProgress, nprogress } from \"@mantine/nprogress\";\nimport type { ReactNode } from \"react\";\n\nexport interface AlephaMantineProviderProps {\n\tchildren?: ReactNode;\n\tmantine?: MantineProviderProps;\n\tcolorSchemeScript?: ColorSchemeScriptProps;\n\tnavigationProgress?: NavigationProgressProps;\n\tnotifications?: NotificationsProps;\n\tmodals?: ModalsProviderProps;\n}\n\nconst AlephaMantineProvider = (props: AlephaMantineProviderProps) => {\n\tuseRouterEvents({\n\t\tonBegin: () => {\n\t\t\tnprogress.start();\n\t\t},\n\t\tonEnd: () => {\n\t\t\tnprogress.complete();\n\t\t},\n\t});\n\n\treturn (\n\t\t<>\n\t\t\t<ColorSchemeScript\n\t\t\t\tdefaultColorScheme={props.mantine?.defaultColorScheme}\n\t\t\t\t{...props.colorSchemeScript}\n\t\t\t/>\n\t\t\t<MantineProvider {...props.mantine}>\n\t\t\t\t<Notifications {...props.notifications} />\n\t\t\t\t<NavigationProgress {...props.navigationProgress} />\n\t\t\t\t<ModalsProvider {...props.modals}>\n\t\t\t\t\t{props.children ?? <NestedView />}\n\t\t\t\t</ModalsProvider>\n\t\t\t</MantineProvider>\n\t\t</>\n\t);\n};\n\nexport default AlephaMantineProvider;\n","import { TypeBoxError } from \"@alepha/core\";\nimport { type InputField, useFormState } from \"@alepha/react-form\";\nimport {\n\tAutocomplete,\n\ttype AutocompleteProps,\n\tFlex,\n\tInput,\n\tPasswordInput,\n\ttype PasswordInputProps,\n\tSegmentedControl,\n\ttype SegmentedControlProps,\n\tSelect,\n\ttype SelectProps,\n\tSwitch,\n\ttype SwitchProps,\n\tTextarea,\n\ttype TextareaProps,\n\tTextInput,\n\ttype TextInputProps,\n} from \"@mantine/core\";\nimport type { ComponentType, ReactNode } from \"react\";\n\nexport interface ControlProps {\n\tinput: InputField;\n\n\ttitle?: string;\n\tdescription?: string;\n\n\ticon?: ReactNode;\n\n\ttext?: TextInputProps;\n\tarea?: boolean | TextareaProps;\n\tselect?: boolean | SelectProps;\n\tautocomplete?: boolean | AutocompleteProps;\n\tpassword?: boolean | PasswordInputProps;\n\tswitch?: boolean | SwitchProps;\n\tsegmented?: boolean | Partial<SegmentedControlProps>;\n\n\tcustom?: ComponentType<CustomControlProps>;\n}\n\n/**\n * Generic form control that renders the appropriate input based on the schema and props.\n *\n * Supports:\n * - TextInput\n * - Textarea\n * - Select (for enum types)\n * - Autocomplete\n * - PasswordInput\n * - Switch (for boolean types)\n * - SegmentedControl (for enum types)\n * - Custom component\n *\n * Automatically handles labels, descriptions, error messages, and required state.\n */\nconst Control = (props: ControlProps) => {\n\tconst form = useFormState(props.input);\n\tif (!props.input?.props) {\n\t\treturn null;\n\t}\n\n\t// shared props\n\n\tconst disabled = false; // form.loading;\n\tconst id = props.input.props.id;\n\tconst label =\n\t\tprops.title ??\n\t\t(\"title\" in props.input.schema &&\n\t\ttypeof props.input.schema.title === \"string\"\n\t\t\t? props.input.schema.title\n\t\t\t: undefined) ??\n\t\tprettyName(props.input.path);\n\tconst description =\n\t\tprops.description ??\n\t\t(\"description\" in props.input.schema &&\n\t\ttypeof props.input.schema.description === \"string\"\n\t\t\t? props.input.schema.description\n\t\t\t: undefined);\n\tconst error =\n\t\tform.error && form.error instanceof TypeBoxError\n\t\t\t? form.error.value.message\n\t\t\t: undefined;\n\tconst icon = props.icon;\n\tconst required = props.input.required;\n\n\tconst inputProps = {\n\t\tlabel,\n\t\tdescription,\n\t\terror,\n\t\trequired,\n\t\tdisabled,\n\t};\n\n\t// -------------------------------------------------------------------------------------------------------------------\n\n\tif (props.custom) {\n\t\tconst Custom = props.custom;\n\t\treturn (\n\t\t\t<Input.Wrapper {...inputProps}>\n\t\t\t\t<Flex flex={1} mt={\"calc(var(--mantine-spacing-xs) / 2)\"}>\n\t\t\t\t\t<Custom\n\t\t\t\t\t\tdefaultValue={props.input.props.defaultValue}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tprops.input.set(value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</Flex>\n\t\t\t</Input.Wrapper>\n\t\t);\n\t}\n\n\t// region <SegmentedControl/>\n\tif (props.segmented) {\n\t\tconst segmentedControlProps: Partial<SegmentedControlProps> =\n\t\t\ttypeof props.segmented === \"object\" ? props.segmented : {};\n\t\tconst data =\n\t\t\tsegmentedControlProps.data ??\n\t\t\t(props.input.schema &&\n\t\t\t\"enum\" in props.input.schema &&\n\t\t\tArray.isArray(props.input.schema.enum)\n\t\t\t\t? props.input.schema.enum?.map((value: string) => ({\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: value,\n\t\t\t\t\t}))\n\t\t\t\t: []);\n\t\treturn (\n\t\t\t<Input.Wrapper {...inputProps}>\n\t\t\t\t<Flex mt={\"calc(var(--mantine-spacing-xs) / 2)\"}>\n\t\t\t\t\t<SegmentedControl\n\t\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\t\tdefaultValue={String(props.input.props.defaultValue)}\n\t\t\t\t\t\t{...segmentedControlProps}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tprops.input.set(value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tdata={data}\n\t\t\t\t\t/>\n\t\t\t\t</Flex>\n\t\t\t</Input.Wrapper>\n\t\t);\n\t}\n\t// endregion\n\n\t// region <Autocomplete/>\n\tif (props.autocomplete) {\n\t\tconst autocompleteProps =\n\t\t\ttypeof props.autocomplete === \"object\" ? props.autocomplete : {};\n\n\t\treturn (\n\t\t\t<Autocomplete\n\t\t\t\t{...inputProps}\n\t\t\t\tid={id}\n\t\t\t\tleftSection={icon}\n\t\t\t\t{...props.input.props}\n\t\t\t\t{...autocompleteProps}\n\t\t\t/>\n\t\t);\n\t}\n\t// endregion\n\n\t// region <Select/>\n\tif (\n\t\t(props.input.schema &&\n\t\t\t\"enum\" in props.input.schema &&\n\t\t\tprops.input.schema.enum) ||\n\t\tprops.select\n\t) {\n\t\tconst data =\n\t\t\tprops.input.schema &&\n\t\t\t\"enum\" in props.input.schema &&\n\t\t\tArray.isArray(props.input.schema.enum)\n\t\t\t\t? props.input.schema.enum?.map((value: string) => ({\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: value,\n\t\t\t\t\t}))\n\t\t\t\t: [];\n\n\t\tconst selectProps = typeof props.select === \"object\" ? props.select : {};\n\n\t\treturn (\n\t\t\t<Select\n\t\t\t\t{...inputProps}\n\t\t\t\tid={id}\n\t\t\t\tleftSection={icon}\n\t\t\t\tdata={data}\n\t\t\t\t{...props.input.props}\n\t\t\t\t{...selectProps}\n\t\t\t/>\n\t\t);\n\t}\n\t// endregion\n\n\t// region <Switch/>\n\n\tif (\n\t\t(props.input.schema &&\n\t\t\t\"type\" in props.input.schema &&\n\t\t\tprops.input.schema.type === \"boolean\") ||\n\t\tprops.switch\n\t) {\n\t\tconst switchProps = typeof props.switch === \"object\" ? props.switch : {};\n\n\t\treturn (\n\t\t\t<Switch\n\t\t\t\t{...inputProps}\n\t\t\t\tid={id}\n\t\t\t\tcolor={\"blue\"}\n\t\t\t\tdefaultChecked={props.input.props.defaultValue}\n\t\t\t\t{...props.input.props}\n\t\t\t\t{...switchProps}\n\t\t\t/>\n\t\t);\n\t}\n\t// endregion\n\n\t// region <PasswordInput/>\n\tif (props.password) {\n\t\tconst passwordInputProps =\n\t\t\ttypeof props.password === \"object\" ? props.password : {};\n\t\treturn (\n\t\t\t<PasswordInput\n\t\t\t\t{...inputProps}\n\t\t\t\tid={id}\n\t\t\t\tleftSection={icon}\n\t\t\t\t{...props.input.props}\n\t\t\t\t{...passwordInputProps}\n\t\t\t/>\n\t\t);\n\t}\n\t//endregion\n\n\t//region <Textarea/>\n\tif (props.area) {\n\t\tconst textAreaProps = typeof props.area === \"object\" ? props.area : {};\n\t\treturn (\n\t\t\t<Textarea\n\t\t\t\t{...inputProps}\n\t\t\t\tid={id}\n\t\t\t\tleftSection={icon}\n\t\t\t\t{...props.input.props}\n\t\t\t\t{...textAreaProps}\n\t\t\t/>\n\t\t);\n\t}\n\t//endregion\n\n\t// region <TextInput/>\n\tconst textInputProps = typeof props.text === \"object\" ? props.text : {};\n\treturn (\n\t\t<TextInput\n\t\t\t{...inputProps}\n\t\t\tid={id}\n\t\t\tleftSection={icon}\n\t\t\t{...props.input.props}\n\t\t\t{...textInputProps}\n\t\t/>\n\t);\n\t//endregion\n};\n\nexport default Control;\n\nconst prettyName = (name: string) => {\n\treturn capitalize(name.replaceAll(\"/\", \"\"));\n};\n\nconst capitalize = (str: string) => {\n\treturn str.charAt(0).toUpperCase() + str.slice(1);\n};\n\nexport type CustomControlProps = {\n\tdefaultValue: any;\n\tonChange: (value: any) => void;\n};\n","import { $module } from \"@alepha/core\";\nimport { AlephaReact } from \"@alepha/react\";\n\n// ---------------------------------------------------------------------------------------------------------------------\nimport \"@mantine/core/styles.css\";\nimport \"@mantine/nprogress/styles.css\";\nimport \"@mantine/spotlight/styles.css\";\nimport \"@mantine/notifications/styles.css\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport { default as Action } from \"./components/Action\";\nexport { default as AlephaMantineProvider } from \"./components/AlephaMantineProvider.tsx\";\nexport { default as Control } from \"./components/Control\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n *\n *\n * @module alepha.ui\n */\nexport const AlephaUI = $module({\n\tname: \"alepha.ui\",\n\tservices: [AlephaReact],\n});\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@alepha/ui",
3
+ "keywords": [
4
+ "alepha",
5
+ "ui",
6
+ "mantine"
7
+ ],
8
+ "author": "Feunard",
9
+ "version": "0.10.4",
10
+ "type": "module",
11
+ "engines": {
12
+ "node": ">=22.0.0"
13
+ },
14
+ "license": "MIT",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "dependencies": {
22
+ "@alepha/core": "0.10.4",
23
+ "@alepha/react": "0.10.4",
24
+ "@alepha/react-form": "0.10.4",
25
+ "@mantine/core": "^8.3.3",
26
+ "@mantine/hooks": "^8.3.3",
27
+ "@mantine/modals": "^8.3.3",
28
+ "@mantine/notifications": "^8.3.3",
29
+ "@mantine/nprogress": "^8.3.3",
30
+ "@mantine/spotlight": "^8.3.3"
31
+ },
32
+ "devDependencies": {
33
+ "@biomejs/biome": "^2.2.5",
34
+ "react": "^19.2.0",
35
+ "react-dom": "^19.2.0",
36
+ "tsdown": "^0.15.6",
37
+ "typescript": "^5.9.3",
38
+ "vitest": "^3.2.4"
39
+ },
40
+ "peerDependencies": {
41
+ "react": "*",
42
+ "react-dom": "*"
43
+ },
44
+ "scripts": {
45
+ "test": "vitest run",
46
+ "lint": "biome check --write --unsafe",
47
+ "build": "tsdown -c ../../tsdown.config.ts",
48
+ "check": "tsc"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/feunard/alepha.git"
53
+ },
54
+ "homepage": "https://github.com/feunard/alepha",
55
+ "module": "./dist/index.js",
56
+ "exports": {
57
+ ".": {
58
+ "types": "./dist/index.d.ts",
59
+ "import": "./dist/index.js"
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,184 @@
1
+ import {
2
+ type RouterGoOptions,
3
+ type UseActiveOptions,
4
+ useActive,
5
+ useAlepha,
6
+ useRouter,
7
+ } from "@alepha/react";
8
+ import { type FormModel, useFormState } from "@alepha/react-form";
9
+ import { Button, type ButtonProps, Flex } from "@mantine/core";
10
+ import { type ReactNode, useState } from "react";
11
+
12
+ export interface ActionCommonProps extends ButtonProps {
13
+ children?: ReactNode;
14
+ textVisibleFrom?: "xs" | "sm" | "md" | "lg" | "xl";
15
+ // TODO
16
+
17
+ /**
18
+ * If set, a confirmation dialog will be shown before performing the action.
19
+ * If `true`, a default title and message will be used.
20
+ * If a string, it will be used as the message with a default title.
21
+ * If an object, it can contain `title` and `message` properties to customize the dialog.
22
+ */
23
+ confirm?: boolean | string | { title?: string; message: string };
24
+ }
25
+
26
+ export type ActionProps = ActionCommonProps &
27
+ (ActiveHrefProps | ActionClickProps | ActionSubmitProps | {});
28
+
29
+ // ---------------------------------------------------------------------------------------------------------------------
30
+
31
+ const Action = (_props: ActionProps) => {
32
+ const props = { variant: "subtle", ..._props };
33
+
34
+ if (props.leftSection && !props.children) {
35
+ props.className ??= "mantine-Action-iconOnly";
36
+ props.p ??= "xs";
37
+ }
38
+
39
+ if (props.textVisibleFrom) {
40
+ const { children, textVisibleFrom, leftSection, ...rest } = props;
41
+ return (
42
+ <>
43
+ <Flex w={"100%"} visibleFrom={textVisibleFrom}>
44
+ <Action flex={1} {...rest} leftSection={leftSection}>
45
+ {children}
46
+ </Action>
47
+ </Flex>
48
+ <Flex w={"100%"} hiddenFrom={textVisibleFrom}>
49
+ <Action px={"xs"} {...rest}>
50
+ {leftSection}
51
+ </Action>
52
+ </Flex>
53
+ </>
54
+ );
55
+ }
56
+
57
+ const renderAction = () => {
58
+ if ("href" in props && props.href) {
59
+ return (
60
+ <ActionHref {...props} href={props.href}>
61
+ {props.children}
62
+ </ActionHref>
63
+ );
64
+ }
65
+
66
+ if ("onClick" in props && props.onClick) {
67
+ return (
68
+ <ActionClick {...props} onClick={props.onClick}>
69
+ {props.children}
70
+ </ActionClick>
71
+ );
72
+ }
73
+
74
+ if ("form" in props && props.form) {
75
+ return (
76
+ <ActionSubmit {...props} form={props.form}>
77
+ {props.children}
78
+ </ActionSubmit>
79
+ );
80
+ }
81
+
82
+ return <Button {...(props as any)}>{props.children}</Button>;
83
+ };
84
+
85
+ return renderAction();
86
+ };
87
+
88
+ export default Action;
89
+
90
+ // ---------------------------------------------------------------------------------------------------------------------
91
+
92
+ export interface ActionSubmitProps extends ButtonProps {
93
+ form: FormModel<any>;
94
+ }
95
+
96
+ /**
97
+ * Action button that submits a form with loading and disabled state handling.
98
+ */
99
+ const ActionSubmit = (props: ActionSubmitProps) => {
100
+ const { form, ...buttonProps } = props;
101
+ const state = useFormState(form);
102
+ return (
103
+ <Button
104
+ {...buttonProps}
105
+ loading={state.loading}
106
+ disabled={state.loading || !state.dirty}
107
+ type={"submit"}
108
+ >
109
+ {props.children}
110
+ </Button>
111
+ );
112
+ };
113
+
114
+ // ---------------------------------------------------------------------------------------------------------------------
115
+
116
+ export interface ActionClickProps extends ButtonProps {
117
+ onClick: (e: any) => any;
118
+ }
119
+
120
+ /**
121
+ * Basic action button that handles click events with loading and error handling.
122
+ */
123
+ const ActionClick = (props: ActionClickProps) => {
124
+ const [pending, setPending] = useState(false);
125
+ const alepha = useAlepha();
126
+
127
+ const onClick = async (e: any) => {
128
+ setPending(true);
129
+ try {
130
+ await props.onClick(e);
131
+ } catch (e) {
132
+ console.error(e);
133
+ await alepha.events.emit("form:submit:error", {
134
+ id: "action",
135
+ error: e as Error,
136
+ });
137
+ } finally {
138
+ setPending(false);
139
+ }
140
+ };
141
+
142
+ return (
143
+ <Button
144
+ {...props}
145
+ disabled={pending || props.disabled}
146
+ loading={pending}
147
+ onClick={onClick}
148
+ >
149
+ {props.children}
150
+ </Button>
151
+ );
152
+ };
153
+
154
+ // ---------------------------------------------------------------------------------------------------------------------
155
+
156
+ export interface ActiveHrefProps extends ButtonProps {
157
+ href: string;
158
+ active?: Partial<UseActiveOptions> | false;
159
+ routerGoOptions?: RouterGoOptions;
160
+ }
161
+
162
+ /**
163
+ * Action for navigation with active state support.
164
+ */
165
+ const ActionHref = (props: ActiveHrefProps) => {
166
+ const { active: options, routerGoOptions, ...buttonProps } = props;
167
+ const router = useRouter();
168
+ const { isPending, isActive } = useActive(
169
+ options ? { href: props.href, ...options } : { href: props.href },
170
+ );
171
+ const anchorProps = router.anchor(props.href, routerGoOptions);
172
+
173
+ return (
174
+ <Button
175
+ component={"a"}
176
+ loading={isPending}
177
+ {...anchorProps}
178
+ {...buttonProps}
179
+ variant={isActive && options !== false ? "filled" : "subtle"}
180
+ >
181
+ {props.children}
182
+ </Button>
183
+ );
184
+ };
@@ -0,0 +1,49 @@
1
+ import { NestedView, useRouterEvents } from "@alepha/react";
2
+ import type {
3
+ ColorSchemeScriptProps,
4
+ MantineProviderProps,
5
+ } from "@mantine/core";
6
+ import { ColorSchemeScript, MantineProvider } from "@mantine/core";
7
+ import { ModalsProvider, type ModalsProviderProps } from "@mantine/modals";
8
+ import { Notifications, type NotificationsProps } from "@mantine/notifications";
9
+ import type { NavigationProgressProps } from "@mantine/nprogress";
10
+ import { NavigationProgress, nprogress } from "@mantine/nprogress";
11
+ import type { ReactNode } from "react";
12
+
13
+ export interface AlephaMantineProviderProps {
14
+ children?: ReactNode;
15
+ mantine?: MantineProviderProps;
16
+ colorSchemeScript?: ColorSchemeScriptProps;
17
+ navigationProgress?: NavigationProgressProps;
18
+ notifications?: NotificationsProps;
19
+ modals?: ModalsProviderProps;
20
+ }
21
+
22
+ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
23
+ useRouterEvents({
24
+ onBegin: () => {
25
+ nprogress.start();
26
+ },
27
+ onEnd: () => {
28
+ nprogress.complete();
29
+ },
30
+ });
31
+
32
+ return (
33
+ <>
34
+ <ColorSchemeScript
35
+ defaultColorScheme={props.mantine?.defaultColorScheme}
36
+ {...props.colorSchemeScript}
37
+ />
38
+ <MantineProvider {...props.mantine}>
39
+ <Notifications {...props.notifications} />
40
+ <NavigationProgress {...props.navigationProgress} />
41
+ <ModalsProvider {...props.modals}>
42
+ {props.children ?? <NestedView />}
43
+ </ModalsProvider>
44
+ </MantineProvider>
45
+ </>
46
+ );
47
+ };
48
+
49
+ export default AlephaMantineProvider;