@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.
@@ -0,0 +1,275 @@
1
+ import { TypeBoxError } from "@alepha/core";
2
+ import { type InputField, useFormState } from "@alepha/react-form";
3
+ import {
4
+ Autocomplete,
5
+ type AutocompleteProps,
6
+ Flex,
7
+ Input,
8
+ PasswordInput,
9
+ type PasswordInputProps,
10
+ SegmentedControl,
11
+ type SegmentedControlProps,
12
+ Select,
13
+ type SelectProps,
14
+ Switch,
15
+ type SwitchProps,
16
+ Textarea,
17
+ type TextareaProps,
18
+ TextInput,
19
+ type TextInputProps,
20
+ } from "@mantine/core";
21
+ import type { ComponentType, ReactNode } from "react";
22
+
23
+ export interface ControlProps {
24
+ input: InputField;
25
+
26
+ title?: string;
27
+ description?: string;
28
+
29
+ icon?: ReactNode;
30
+
31
+ text?: TextInputProps;
32
+ area?: boolean | TextareaProps;
33
+ select?: boolean | SelectProps;
34
+ autocomplete?: boolean | AutocompleteProps;
35
+ password?: boolean | PasswordInputProps;
36
+ switch?: boolean | SwitchProps;
37
+ segmented?: boolean | Partial<SegmentedControlProps>;
38
+
39
+ custom?: ComponentType<CustomControlProps>;
40
+ }
41
+
42
+ /**
43
+ * Generic form control that renders the appropriate input based on the schema and props.
44
+ *
45
+ * Supports:
46
+ * - TextInput
47
+ * - Textarea
48
+ * - Select (for enum types)
49
+ * - Autocomplete
50
+ * - PasswordInput
51
+ * - Switch (for boolean types)
52
+ * - SegmentedControl (for enum types)
53
+ * - Custom component
54
+ *
55
+ * Automatically handles labels, descriptions, error messages, and required state.
56
+ */
57
+ const Control = (props: ControlProps) => {
58
+ const form = useFormState(props.input);
59
+ if (!props.input?.props) {
60
+ return null;
61
+ }
62
+
63
+ // shared props
64
+
65
+ const disabled = false; // form.loading;
66
+ const id = props.input.props.id;
67
+ const label =
68
+ props.title ??
69
+ ("title" in props.input.schema &&
70
+ typeof props.input.schema.title === "string"
71
+ ? props.input.schema.title
72
+ : undefined) ??
73
+ prettyName(props.input.path);
74
+ const description =
75
+ props.description ??
76
+ ("description" in props.input.schema &&
77
+ typeof props.input.schema.description === "string"
78
+ ? props.input.schema.description
79
+ : undefined);
80
+ const error =
81
+ form.error && form.error instanceof TypeBoxError
82
+ ? form.error.value.message
83
+ : undefined;
84
+ const icon = props.icon;
85
+ const required = props.input.required;
86
+
87
+ const inputProps = {
88
+ label,
89
+ description,
90
+ error,
91
+ required,
92
+ disabled,
93
+ };
94
+
95
+ // -------------------------------------------------------------------------------------------------------------------
96
+
97
+ if (props.custom) {
98
+ const Custom = props.custom;
99
+ return (
100
+ <Input.Wrapper {...inputProps}>
101
+ <Flex flex={1} mt={"calc(var(--mantine-spacing-xs) / 2)"}>
102
+ <Custom
103
+ defaultValue={props.input.props.defaultValue}
104
+ onChange={(value) => {
105
+ props.input.set(value);
106
+ }}
107
+ />
108
+ </Flex>
109
+ </Input.Wrapper>
110
+ );
111
+ }
112
+
113
+ // region <SegmentedControl/>
114
+ if (props.segmented) {
115
+ const segmentedControlProps: Partial<SegmentedControlProps> =
116
+ typeof props.segmented === "object" ? props.segmented : {};
117
+ const data =
118
+ segmentedControlProps.data ??
119
+ (props.input.schema &&
120
+ "enum" in props.input.schema &&
121
+ Array.isArray(props.input.schema.enum)
122
+ ? props.input.schema.enum?.map((value: string) => ({
123
+ value,
124
+ label: value,
125
+ }))
126
+ : []);
127
+ return (
128
+ <Input.Wrapper {...inputProps}>
129
+ <Flex mt={"calc(var(--mantine-spacing-xs) / 2)"}>
130
+ <SegmentedControl
131
+ disabled={disabled}
132
+ defaultValue={String(props.input.props.defaultValue)}
133
+ {...segmentedControlProps}
134
+ onChange={(value) => {
135
+ props.input.set(value);
136
+ }}
137
+ data={data}
138
+ />
139
+ </Flex>
140
+ </Input.Wrapper>
141
+ );
142
+ }
143
+ // endregion
144
+
145
+ // region <Autocomplete/>
146
+ if (props.autocomplete) {
147
+ const autocompleteProps =
148
+ typeof props.autocomplete === "object" ? props.autocomplete : {};
149
+
150
+ return (
151
+ <Autocomplete
152
+ {...inputProps}
153
+ id={id}
154
+ leftSection={icon}
155
+ {...props.input.props}
156
+ {...autocompleteProps}
157
+ />
158
+ );
159
+ }
160
+ // endregion
161
+
162
+ // region <Select/>
163
+ if (
164
+ (props.input.schema &&
165
+ "enum" in props.input.schema &&
166
+ props.input.schema.enum) ||
167
+ props.select
168
+ ) {
169
+ const data =
170
+ props.input.schema &&
171
+ "enum" in props.input.schema &&
172
+ Array.isArray(props.input.schema.enum)
173
+ ? props.input.schema.enum?.map((value: string) => ({
174
+ value,
175
+ label: value,
176
+ }))
177
+ : [];
178
+
179
+ const selectProps = typeof props.select === "object" ? props.select : {};
180
+
181
+ return (
182
+ <Select
183
+ {...inputProps}
184
+ id={id}
185
+ leftSection={icon}
186
+ data={data}
187
+ {...props.input.props}
188
+ {...selectProps}
189
+ />
190
+ );
191
+ }
192
+ // endregion
193
+
194
+ // region <Switch/>
195
+
196
+ if (
197
+ (props.input.schema &&
198
+ "type" in props.input.schema &&
199
+ props.input.schema.type === "boolean") ||
200
+ props.switch
201
+ ) {
202
+ const switchProps = typeof props.switch === "object" ? props.switch : {};
203
+
204
+ return (
205
+ <Switch
206
+ {...inputProps}
207
+ id={id}
208
+ color={"blue"}
209
+ defaultChecked={props.input.props.defaultValue}
210
+ {...props.input.props}
211
+ {...switchProps}
212
+ />
213
+ );
214
+ }
215
+ // endregion
216
+
217
+ // region <PasswordInput/>
218
+ if (props.password) {
219
+ const passwordInputProps =
220
+ typeof props.password === "object" ? props.password : {};
221
+ return (
222
+ <PasswordInput
223
+ {...inputProps}
224
+ id={id}
225
+ leftSection={icon}
226
+ {...props.input.props}
227
+ {...passwordInputProps}
228
+ />
229
+ );
230
+ }
231
+ //endregion
232
+
233
+ //region <Textarea/>
234
+ if (props.area) {
235
+ const textAreaProps = typeof props.area === "object" ? props.area : {};
236
+ return (
237
+ <Textarea
238
+ {...inputProps}
239
+ id={id}
240
+ leftSection={icon}
241
+ {...props.input.props}
242
+ {...textAreaProps}
243
+ />
244
+ );
245
+ }
246
+ //endregion
247
+
248
+ // region <TextInput/>
249
+ const textInputProps = typeof props.text === "object" ? props.text : {};
250
+ return (
251
+ <TextInput
252
+ {...inputProps}
253
+ id={id}
254
+ leftSection={icon}
255
+ {...props.input.props}
256
+ {...textInputProps}
257
+ />
258
+ );
259
+ //endregion
260
+ };
261
+
262
+ export default Control;
263
+
264
+ const prettyName = (name: string) => {
265
+ return capitalize(name.replaceAll("/", ""));
266
+ };
267
+
268
+ const capitalize = (str: string) => {
269
+ return str.charAt(0).toUpperCase() + str.slice(1);
270
+ };
271
+
272
+ export type CustomControlProps = {
273
+ defaultValue: any;
274
+ onChange: (value: any) => void;
275
+ };
File without changes
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { $module } from "@alepha/core";
2
+ import { AlephaReact } from "@alepha/react";
3
+
4
+ // ---------------------------------------------------------------------------------------------------------------------
5
+ import "@mantine/core/styles.css";
6
+ import "@mantine/nprogress/styles.css";
7
+ import "@mantine/spotlight/styles.css";
8
+ import "@mantine/notifications/styles.css";
9
+
10
+ // ---------------------------------------------------------------------------------------------------------------------
11
+
12
+ export { default as Action } from "./components/Action";
13
+ export { default as AlephaMantineProvider } from "./components/AlephaMantineProvider.tsx";
14
+ export { default as Control } from "./components/Control";
15
+
16
+ // ---------------------------------------------------------------------------------------------------------------------
17
+
18
+ /**
19
+ *
20
+ *
21
+ * @module alepha.ui
22
+ */
23
+ export const AlephaUI = $module({
24
+ name: "alepha.ui",
25
+ services: [AlephaReact],
26
+ });