@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/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +317 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/components/Action.tsx +184 -0
- package/src/components/AlephaMantineProvider.tsx +49 -0
- package/src/components/Control.tsx +275 -0
- package/src/components/Omnibar.tsx +0 -0
- package/src/index.ts +26 -0
|
@@ -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
|
+
});
|