@alepha/ui 0.10.7 → 0.11.0
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/{AlephaMantineProvider-WfiC2EH6.js → AlephaMantineProvider-DDbIijPF.js} +5 -4
- package/dist/AlephaMantineProvider-DDbIijPF.js.map +1 -0
- package/dist/AlephaMantineProvider-pOu8hOzK.js +3 -0
- package/dist/index.d.ts +365 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +904 -111
- package/dist/index.js.map +1 -1
- package/package.json +21 -17
- package/src/components/Action.tsx +179 -18
- package/src/components/Control.tsx +15 -154
- package/src/components/ControlDate.tsx +2 -10
- package/src/components/ControlSelect.tsx +71 -9
- package/src/components/DarkModeButton.tsx +9 -2
- package/src/components/DataTable.css +199 -0
- package/src/components/DataTable.tsx +724 -0
- package/src/components/Omnibar.tsx +2 -1
- package/src/components/Sidebar.css +217 -0
- package/src/components/Sidebar.tsx +255 -0
- package/src/components/TypeForm.tsx +13 -13
- package/src/components/dialogs/AlertDialog.tsx +13 -0
- package/src/components/dialogs/ConfirmDialog.tsx +21 -0
- package/src/components/dialogs/PromptDialog.tsx +52 -0
- package/src/hooks/useDialog.ts +15 -0
- package/src/index.ts +30 -2
- package/src/services/DialogService.tsx +207 -0
- package/src/utils/parseInput.ts +125 -0
- package/dist/AlephaMantineProvider-EemOtraW.js +0 -3
- package/dist/AlephaMantineProvider-WfiC2EH6.js.map +0 -1
|
@@ -6,13 +6,93 @@ import {
|
|
|
6
6
|
useRouter,
|
|
7
7
|
} from "@alepha/react";
|
|
8
8
|
import { type FormModel, useFormState } from "@alepha/react-form";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Button,
|
|
11
|
+
type ButtonProps,
|
|
12
|
+
Flex,
|
|
13
|
+
Menu,
|
|
14
|
+
Tooltip,
|
|
15
|
+
type TooltipProps,
|
|
16
|
+
} from "@mantine/core";
|
|
17
|
+
import { IconChevronRight } from "@tabler/icons-react";
|
|
10
18
|
import { type ReactNode, useState } from "react";
|
|
11
19
|
|
|
20
|
+
export interface ActionMenuItem {
|
|
21
|
+
/**
|
|
22
|
+
* Menu item type
|
|
23
|
+
*/
|
|
24
|
+
type?: "item" | "divider" | "label";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Label text for the menu item
|
|
28
|
+
*/
|
|
29
|
+
label?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Icon element to display before the label
|
|
33
|
+
*/
|
|
34
|
+
icon?: ReactNode;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Click handler for menu items
|
|
38
|
+
*/
|
|
39
|
+
onClick?: () => void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Color for the menu item (e.g., "red" for danger actions)
|
|
43
|
+
*/
|
|
44
|
+
color?: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Nested submenu items
|
|
48
|
+
*/
|
|
49
|
+
children?: ActionMenuItem[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ActionMenuConfig {
|
|
53
|
+
/**
|
|
54
|
+
* Array of menu items to display
|
|
55
|
+
*/
|
|
56
|
+
items: ActionMenuItem[];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Menu position relative to the button
|
|
60
|
+
*/
|
|
61
|
+
position?:
|
|
62
|
+
| "bottom"
|
|
63
|
+
| "bottom-start"
|
|
64
|
+
| "bottom-end"
|
|
65
|
+
| "top"
|
|
66
|
+
| "top-start"
|
|
67
|
+
| "top-end"
|
|
68
|
+
| "left"
|
|
69
|
+
| "right";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Menu width
|
|
73
|
+
*/
|
|
74
|
+
width?: number | string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Menu shadow
|
|
78
|
+
*/
|
|
79
|
+
shadow?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
80
|
+
}
|
|
81
|
+
|
|
12
82
|
export interface ActionCommonProps extends ButtonProps {
|
|
13
83
|
children?: ReactNode;
|
|
14
84
|
textVisibleFrom?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
15
|
-
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Tooltip to display on hover. Can be a string for simple tooltips
|
|
88
|
+
* or a TooltipProps object for advanced configuration.
|
|
89
|
+
*/
|
|
90
|
+
tooltip?: string | TooltipProps;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Menu configuration. When provided, the action will display a dropdown menu.
|
|
94
|
+
*/
|
|
95
|
+
menu?: ActionMenuConfig;
|
|
16
96
|
|
|
17
97
|
/**
|
|
18
98
|
* If set, a confirmation dialog will be shown before performing the action.
|
|
@@ -28,25 +108,78 @@ export type ActionProps = ActionCommonProps &
|
|
|
28
108
|
|
|
29
109
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
30
110
|
|
|
111
|
+
// Helper function to render menu items recursively
|
|
112
|
+
const renderMenuItem = (item: ActionMenuItem, index: number): ReactNode => {
|
|
113
|
+
// Render divider
|
|
114
|
+
if (item.type === "divider") {
|
|
115
|
+
return <Menu.Divider key={index} />;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Render label
|
|
119
|
+
if (item.type === "label") {
|
|
120
|
+
return <Menu.Label key={index}>{item.label}</Menu.Label>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Render submenu if has children
|
|
124
|
+
if (item.children && item.children.length > 0) {
|
|
125
|
+
return (
|
|
126
|
+
<Menu key={index} trigger="hover" position="right-start" offset={2}>
|
|
127
|
+
<Menu.Target>
|
|
128
|
+
<Menu.Item
|
|
129
|
+
leftSection={item.icon}
|
|
130
|
+
rightSection={<IconChevronRight size={14} />}
|
|
131
|
+
>
|
|
132
|
+
{item.label}
|
|
133
|
+
</Menu.Item>
|
|
134
|
+
</Menu.Target>
|
|
135
|
+
<Menu.Dropdown>
|
|
136
|
+
{item.children.map((child, childIndex) =>
|
|
137
|
+
renderMenuItem(child, childIndex),
|
|
138
|
+
)}
|
|
139
|
+
</Menu.Dropdown>
|
|
140
|
+
</Menu>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Render regular menu item
|
|
145
|
+
return (
|
|
146
|
+
<Menu.Item
|
|
147
|
+
key={index}
|
|
148
|
+
leftSection={item.icon}
|
|
149
|
+
onClick={item.onClick}
|
|
150
|
+
color={item.color}
|
|
151
|
+
>
|
|
152
|
+
{item.label}
|
|
153
|
+
</Menu.Item>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
31
157
|
const Action = (_props: ActionProps) => {
|
|
32
158
|
const props = { variant: "subtle", ..._props };
|
|
159
|
+
const { tooltip, menu, ...restProps } = props;
|
|
33
160
|
|
|
34
161
|
if (props.leftSection && !props.children) {
|
|
35
|
-
|
|
36
|
-
|
|
162
|
+
restProps.className ??= "mantine-Action-iconOnly";
|
|
163
|
+
restProps.p ??= "xs";
|
|
37
164
|
}
|
|
38
165
|
|
|
39
166
|
if (props.textVisibleFrom) {
|
|
40
|
-
const { children, textVisibleFrom, leftSection, ...rest } =
|
|
167
|
+
const { children, textVisibleFrom, leftSection, ...rest } = restProps;
|
|
41
168
|
return (
|
|
42
169
|
<>
|
|
43
170
|
<Flex w={"100%"} visibleFrom={textVisibleFrom}>
|
|
44
|
-
<Action
|
|
171
|
+
<Action
|
|
172
|
+
flex={1}
|
|
173
|
+
{...rest}
|
|
174
|
+
leftSection={leftSection}
|
|
175
|
+
tooltip={tooltip}
|
|
176
|
+
menu={menu}
|
|
177
|
+
>
|
|
45
178
|
{children}
|
|
46
179
|
</Action>
|
|
47
180
|
</Flex>
|
|
48
181
|
<Flex w={"100%"} hiddenFrom={textVisibleFrom}>
|
|
49
|
-
<Action px={"xs"} {...rest}>
|
|
182
|
+
<Action px={"xs"} {...rest} tooltip={tooltip} menu={menu}>
|
|
50
183
|
{leftSection}
|
|
51
184
|
</Action>
|
|
52
185
|
</Flex>
|
|
@@ -55,34 +188,62 @@ const Action = (_props: ActionProps) => {
|
|
|
55
188
|
}
|
|
56
189
|
|
|
57
190
|
const renderAction = () => {
|
|
58
|
-
if ("href" in
|
|
191
|
+
if ("href" in restProps && restProps.href) {
|
|
59
192
|
return (
|
|
60
|
-
<ActionHref {...
|
|
61
|
-
{
|
|
193
|
+
<ActionHref {...restProps} href={restProps.href}>
|
|
194
|
+
{restProps.children}
|
|
62
195
|
</ActionHref>
|
|
63
196
|
);
|
|
64
197
|
}
|
|
65
198
|
|
|
66
|
-
if ("onClick" in
|
|
199
|
+
if ("onClick" in restProps && restProps.onClick) {
|
|
67
200
|
return (
|
|
68
|
-
<ActionClick {...
|
|
69
|
-
{
|
|
201
|
+
<ActionClick {...restProps} onClick={restProps.onClick}>
|
|
202
|
+
{restProps.children}
|
|
70
203
|
</ActionClick>
|
|
71
204
|
);
|
|
72
205
|
}
|
|
73
206
|
|
|
74
|
-
if ("form" in
|
|
207
|
+
if ("form" in restProps && restProps.form) {
|
|
75
208
|
return (
|
|
76
|
-
<ActionSubmit {...
|
|
77
|
-
{
|
|
209
|
+
<ActionSubmit {...restProps} form={restProps.form}>
|
|
210
|
+
{restProps.children}
|
|
78
211
|
</ActionSubmit>
|
|
79
212
|
);
|
|
80
213
|
}
|
|
81
214
|
|
|
82
|
-
return <Button {...(
|
|
215
|
+
return <Button {...(restProps as any)}>{restProps.children}</Button>;
|
|
83
216
|
};
|
|
84
217
|
|
|
85
|
-
|
|
218
|
+
let actionElement = renderAction();
|
|
219
|
+
|
|
220
|
+
// Wrap with Menu if provided
|
|
221
|
+
if (menu) {
|
|
222
|
+
actionElement = (
|
|
223
|
+
<Menu
|
|
224
|
+
position={menu.position || "bottom-start"}
|
|
225
|
+
width={menu.width || 200}
|
|
226
|
+
shadow={menu.shadow || "md"}
|
|
227
|
+
>
|
|
228
|
+
<Menu.Target>{actionElement}</Menu.Target>
|
|
229
|
+
<Menu.Dropdown>
|
|
230
|
+
{menu.items.map((item, index) => renderMenuItem(item, index))}
|
|
231
|
+
</Menu.Dropdown>
|
|
232
|
+
</Menu>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Wrap with Tooltip if provided
|
|
237
|
+
if (tooltip) {
|
|
238
|
+
const tooltipProps: TooltipProps =
|
|
239
|
+
typeof tooltip === "string"
|
|
240
|
+
? { label: tooltip, children: actionElement }
|
|
241
|
+
: { ...tooltip, children: actionElement };
|
|
242
|
+
|
|
243
|
+
return <Tooltip {...tooltipProps} />;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return actionElement;
|
|
86
247
|
};
|
|
87
248
|
|
|
88
249
|
export default Action;
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useFormState } from "@alepha/react-form";
|
|
2
2
|
import {
|
|
3
|
-
type InputField,
|
|
4
|
-
type UseFormStateReturn,
|
|
5
|
-
useFormState,
|
|
6
|
-
} from "@alepha/react-form";
|
|
7
|
-
import {
|
|
8
|
-
Autocomplete,
|
|
9
|
-
type AutocompleteProps,
|
|
10
3
|
ColorInput,
|
|
11
4
|
type ColorInputProps,
|
|
12
5
|
FileInput,
|
|
@@ -17,9 +10,6 @@ import {
|
|
|
17
10
|
type NumberInputProps,
|
|
18
11
|
PasswordInput,
|
|
19
12
|
type PasswordInputProps,
|
|
20
|
-
SegmentedControl,
|
|
21
|
-
type SegmentedControlProps,
|
|
22
|
-
type SelectProps,
|
|
23
13
|
Switch,
|
|
24
14
|
type SwitchProps,
|
|
25
15
|
Textarea,
|
|
@@ -32,20 +22,17 @@ import type {
|
|
|
32
22
|
DateTimePickerProps,
|
|
33
23
|
TimeInputProps,
|
|
34
24
|
} from "@mantine/dates";
|
|
35
|
-
import type { ComponentType
|
|
36
|
-
import {
|
|
37
|
-
import { prettyName } from "../utils/string.ts";
|
|
25
|
+
import type { ComponentType } from "react";
|
|
26
|
+
import { type GenericControlProps, parseInput } from "../utils/parseInput.ts";
|
|
38
27
|
import ControlDate from "./ControlDate";
|
|
39
|
-
import ControlSelect from "./ControlSelect";
|
|
28
|
+
import ControlSelect, { type ControlSelectProps } from "./ControlSelect";
|
|
40
29
|
|
|
41
30
|
export interface ControlProps extends GenericControlProps {
|
|
42
31
|
text?: TextInputProps;
|
|
43
32
|
area?: boolean | TextareaProps;
|
|
44
|
-
select?: boolean |
|
|
45
|
-
autocomplete?: boolean | AutocompleteProps;
|
|
33
|
+
select?: boolean | Partial<ControlSelectProps>;
|
|
46
34
|
password?: boolean | PasswordInputProps;
|
|
47
35
|
switch?: boolean | SwitchProps;
|
|
48
|
-
segmented?: boolean | Partial<SegmentedControlProps>;
|
|
49
36
|
number?: boolean | NumberInputProps;
|
|
50
37
|
file?: boolean | FileInputProps;
|
|
51
38
|
color?: boolean | ColorInputProps;
|
|
@@ -76,20 +63,17 @@ export interface ControlProps extends GenericControlProps {
|
|
|
76
63
|
*
|
|
77
64
|
* Automatically handles labels, descriptions, error messages, required state, and default icons.
|
|
78
65
|
*/
|
|
79
|
-
const Control = (
|
|
80
|
-
const form = useFormState(
|
|
81
|
-
const { inputProps, id, icon } = parseInput(
|
|
82
|
-
if (!
|
|
66
|
+
const Control = (_props: ControlProps) => {
|
|
67
|
+
const form = useFormState(_props.input, ["error"]);
|
|
68
|
+
const { inputProps, id, icon, format, schema } = parseInput(_props, form);
|
|
69
|
+
if (!_props.input?.props) {
|
|
83
70
|
return null;
|
|
84
71
|
}
|
|
85
72
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
typeof props.input.schema.format === "string"
|
|
91
|
-
? props.input.schema.format
|
|
92
|
-
: undefined;
|
|
73
|
+
const props = {
|
|
74
|
+
..._props,
|
|
75
|
+
...schema.$control,
|
|
76
|
+
};
|
|
93
77
|
|
|
94
78
|
//region <Custom/>
|
|
95
79
|
if (props.custom) {
|
|
@@ -164,55 +148,6 @@ const Control = (props: ControlProps) => {
|
|
|
164
148
|
}
|
|
165
149
|
//endregion
|
|
166
150
|
|
|
167
|
-
//region <SegmentedControl/>
|
|
168
|
-
if (props.segmented) {
|
|
169
|
-
const segmentedControlProps: Partial<SegmentedControlProps> =
|
|
170
|
-
typeof props.segmented === "object" ? props.segmented : {};
|
|
171
|
-
const data =
|
|
172
|
-
segmentedControlProps.data ??
|
|
173
|
-
(props.input.schema &&
|
|
174
|
-
"enum" in props.input.schema &&
|
|
175
|
-
Array.isArray(props.input.schema.enum)
|
|
176
|
-
? props.input.schema.enum?.map((value: string) => ({
|
|
177
|
-
value,
|
|
178
|
-
label: value,
|
|
179
|
-
}))
|
|
180
|
-
: []);
|
|
181
|
-
return (
|
|
182
|
-
<Input.Wrapper {...inputProps}>
|
|
183
|
-
<Flex mt={"calc(var(--mantine-spacing-xs) / 2)"}>
|
|
184
|
-
<SegmentedControl
|
|
185
|
-
disabled={inputProps.disabled}
|
|
186
|
-
defaultValue={String(props.input.props.defaultValue)}
|
|
187
|
-
{...segmentedControlProps}
|
|
188
|
-
onChange={(value) => {
|
|
189
|
-
props.input.set(value);
|
|
190
|
-
}}
|
|
191
|
-
data={data}
|
|
192
|
-
/>
|
|
193
|
-
</Flex>
|
|
194
|
-
</Input.Wrapper>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
//endregion
|
|
198
|
-
|
|
199
|
-
//region <Autocomplete/>
|
|
200
|
-
if (props.autocomplete) {
|
|
201
|
-
const autocompleteProps =
|
|
202
|
-
typeof props.autocomplete === "object" ? props.autocomplete : {};
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<Autocomplete
|
|
206
|
-
{...inputProps}
|
|
207
|
-
id={id}
|
|
208
|
-
leftSection={icon}
|
|
209
|
-
{...props.input.props}
|
|
210
|
-
{...autocompleteProps}
|
|
211
|
-
/>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
//endregion
|
|
215
|
-
|
|
216
151
|
//region <ControlSelect/>
|
|
217
152
|
// Handle: single enum, array of enum, array of strings, or explicit select/multi/tags props
|
|
218
153
|
const isEnum =
|
|
@@ -225,13 +160,14 @@ const Control = (props: ControlProps) => {
|
|
|
225
160
|
props.input.schema.type === "array";
|
|
226
161
|
|
|
227
162
|
if (isEnum || isArray || props.select) {
|
|
163
|
+
const opts = typeof props.select === "object" ? props.select : {};
|
|
228
164
|
return (
|
|
229
165
|
<ControlSelect
|
|
230
166
|
input={props.input}
|
|
231
167
|
title={props.title}
|
|
232
168
|
description={props.description}
|
|
233
169
|
icon={icon}
|
|
234
|
-
|
|
170
|
+
{...opts}
|
|
235
171
|
/>
|
|
236
172
|
);
|
|
237
173
|
}
|
|
@@ -348,81 +284,6 @@ const Control = (props: ControlProps) => {
|
|
|
348
284
|
|
|
349
285
|
export default Control;
|
|
350
286
|
|
|
351
|
-
// =============================================================================
|
|
352
|
-
// Helper Types and Functions
|
|
353
|
-
// =============================================================================
|
|
354
|
-
|
|
355
|
-
export interface GenericControlProps {
|
|
356
|
-
input: InputField;
|
|
357
|
-
title?: string;
|
|
358
|
-
description?: string;
|
|
359
|
-
icon?: ReactNode;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export const parseInput = (
|
|
363
|
-
props: GenericControlProps,
|
|
364
|
-
form: UseFormStateReturn<TObject>,
|
|
365
|
-
) => {
|
|
366
|
-
const disabled = false; // form.loading;
|
|
367
|
-
const id = props.input.props.id;
|
|
368
|
-
const label =
|
|
369
|
-
props.title ??
|
|
370
|
-
("title" in props.input.schema &&
|
|
371
|
-
typeof props.input.schema.title === "string"
|
|
372
|
-
? props.input.schema.title
|
|
373
|
-
: undefined) ??
|
|
374
|
-
prettyName(props.input.path);
|
|
375
|
-
const description =
|
|
376
|
-
props.description ??
|
|
377
|
-
("description" in props.input.schema &&
|
|
378
|
-
typeof props.input.schema.description === "string"
|
|
379
|
-
? props.input.schema.description
|
|
380
|
-
: undefined);
|
|
381
|
-
const error =
|
|
382
|
-
form.error && form.error instanceof TypeBoxError
|
|
383
|
-
? form.error.value.message
|
|
384
|
-
: undefined;
|
|
385
|
-
|
|
386
|
-
// Auto-generate icon if not provided
|
|
387
|
-
const icon =
|
|
388
|
-
props.icon ??
|
|
389
|
-
getDefaultIcon({
|
|
390
|
-
type:
|
|
391
|
-
props.input.schema && "type" in props.input.schema
|
|
392
|
-
? String(props.input.schema.type)
|
|
393
|
-
: undefined,
|
|
394
|
-
format:
|
|
395
|
-
props.input.schema &&
|
|
396
|
-
"format" in props.input.schema &&
|
|
397
|
-
typeof props.input.schema.format === "string"
|
|
398
|
-
? props.input.schema.format
|
|
399
|
-
: undefined,
|
|
400
|
-
name: props.input.props.name,
|
|
401
|
-
isEnum:
|
|
402
|
-
props.input.schema &&
|
|
403
|
-
"enum" in props.input.schema &&
|
|
404
|
-
Boolean(props.input.schema.enum),
|
|
405
|
-
isArray:
|
|
406
|
-
props.input.schema &&
|
|
407
|
-
"type" in props.input.schema &&
|
|
408
|
-
props.input.schema.type === "array",
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
const required = props.input.required;
|
|
412
|
-
|
|
413
|
-
return {
|
|
414
|
-
id,
|
|
415
|
-
icon,
|
|
416
|
-
inputProps: {
|
|
417
|
-
label,
|
|
418
|
-
description,
|
|
419
|
-
error,
|
|
420
|
-
required,
|
|
421
|
-
disabled,
|
|
422
|
-
},
|
|
423
|
-
};
|
|
424
|
-
};
|
|
425
|
-
|
|
426
287
|
export type CustomControlProps = {
|
|
427
288
|
defaultValue: any;
|
|
428
289
|
onChange: (value: any) => void;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
TimeInput,
|
|
8
8
|
type TimeInputProps,
|
|
9
9
|
} from "@mantine/dates";
|
|
10
|
-
import { type GenericControlProps, parseInput } from "
|
|
10
|
+
import { type GenericControlProps, parseInput } from "../utils/parseInput.ts";
|
|
11
11
|
|
|
12
12
|
export interface ControlDateProps extends GenericControlProps {
|
|
13
13
|
date?: boolean | DateInputProps;
|
|
@@ -27,19 +27,11 @@ export interface ControlDateProps extends GenericControlProps {
|
|
|
27
27
|
*/
|
|
28
28
|
const ControlDate = (props: ControlDateProps) => {
|
|
29
29
|
const form = useFormState(props.input);
|
|
30
|
-
const { inputProps, id, icon } = parseInput(props, form);
|
|
30
|
+
const { inputProps, id, icon, format } = parseInput(props, form);
|
|
31
31
|
if (!props.input?.props) {
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// Detect format from schema
|
|
36
|
-
const format =
|
|
37
|
-
props.input.schema &&
|
|
38
|
-
"format" in props.input.schema &&
|
|
39
|
-
typeof props.input.schema.format === "string"
|
|
40
|
-
? props.input.schema.format
|
|
41
|
-
: undefined;
|
|
42
|
-
|
|
43
35
|
// region <DateTimePicker/>
|
|
44
36
|
if (props.datetime || format === "date-time") {
|
|
45
37
|
const dateTimePickerProps =
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
import { useFormState } from "@alepha/react-form";
|
|
2
2
|
import {
|
|
3
|
+
Autocomplete,
|
|
4
|
+
type AutocompleteProps,
|
|
5
|
+
Flex,
|
|
6
|
+
Input,
|
|
3
7
|
MultiSelect,
|
|
4
8
|
type MultiSelectProps,
|
|
9
|
+
SegmentedControl,
|
|
10
|
+
type SegmentedControlProps,
|
|
5
11
|
Select,
|
|
6
12
|
type SelectProps,
|
|
7
13
|
TagsInput,
|
|
8
14
|
type TagsInputProps,
|
|
9
15
|
} from "@mantine/core";
|
|
10
|
-
import {
|
|
16
|
+
import { useEffect, useState } from "react";
|
|
17
|
+
import { type GenericControlProps, parseInput } from "../utils/parseInput.ts";
|
|
18
|
+
|
|
19
|
+
export type SelectValueLabel =
|
|
20
|
+
| string
|
|
21
|
+
| { value: string; label: string; icon?: string };
|
|
11
22
|
|
|
12
23
|
export interface ControlSelectProps extends GenericControlProps {
|
|
13
24
|
select?: boolean | SelectProps;
|
|
14
25
|
multi?: boolean | MultiSelectProps;
|
|
15
26
|
tags?: boolean | TagsInputProps;
|
|
27
|
+
autocomplete?: boolean | AutocompleteProps;
|
|
28
|
+
segmented?: boolean | Partial<SegmentedControlProps>;
|
|
29
|
+
|
|
30
|
+
loader?: () => Promise<SelectValueLabel[]>;
|
|
16
31
|
}
|
|
17
32
|
|
|
18
33
|
/**
|
|
@@ -31,9 +46,6 @@ export interface ControlSelectProps extends GenericControlProps {
|
|
|
31
46
|
const ControlSelect = (props: ControlSelectProps) => {
|
|
32
47
|
const form = useFormState(props.input);
|
|
33
48
|
const { inputProps, id, icon } = parseInput(props, form);
|
|
34
|
-
if (!props.input?.props) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
49
|
|
|
38
50
|
// Detect if schema is an array type
|
|
39
51
|
const isArray =
|
|
@@ -58,6 +70,61 @@ const ControlSelect = (props: ControlSelectProps) => {
|
|
|
58
70
|
? props.input.schema.enum
|
|
59
71
|
: [];
|
|
60
72
|
|
|
73
|
+
const [data, setData] = useState<SelectValueLabel[]>([]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!props.input?.props) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (props.loader) {
|
|
81
|
+
props.loader().then(setData);
|
|
82
|
+
} else {
|
|
83
|
+
setData(enumValues);
|
|
84
|
+
}
|
|
85
|
+
}, [props.input, props.loader]);
|
|
86
|
+
|
|
87
|
+
if (!props.input?.props) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (props.segmented) {
|
|
92
|
+
const segmentedControlProps: Partial<SegmentedControlProps> =
|
|
93
|
+
typeof props.segmented === "object" ? props.segmented : {};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Input.Wrapper {...inputProps}>
|
|
97
|
+
<Flex mt={"calc(var(--mantine-spacing-xs) / 2)"}>
|
|
98
|
+
<SegmentedControl
|
|
99
|
+
disabled={inputProps.disabled}
|
|
100
|
+
defaultValue={String(props.input.props.defaultValue)}
|
|
101
|
+
{...segmentedControlProps}
|
|
102
|
+
onChange={(value) => {
|
|
103
|
+
props.input.set(value);
|
|
104
|
+
}}
|
|
105
|
+
data={data.slice(0, 10)}
|
|
106
|
+
/>
|
|
107
|
+
</Flex>
|
|
108
|
+
</Input.Wrapper>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (props.autocomplete) {
|
|
113
|
+
const autocompleteProps =
|
|
114
|
+
typeof props.autocomplete === "object" ? props.autocomplete : {};
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Autocomplete
|
|
118
|
+
{...inputProps}
|
|
119
|
+
id={id}
|
|
120
|
+
leftSection={icon}
|
|
121
|
+
data={data}
|
|
122
|
+
{...props.input.props}
|
|
123
|
+
{...autocompleteProps}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
61
128
|
// region <TagsInput/> - for array of strings without enum
|
|
62
129
|
if ((isArray && !itemsEnum) || props.tags) {
|
|
63
130
|
const tagsInputProps = typeof props.tags === "object" ? props.tags : {};
|
|
@@ -111,11 +178,6 @@ const ControlSelect = (props: ControlSelectProps) => {
|
|
|
111
178
|
// endregion
|
|
112
179
|
|
|
113
180
|
// region <Select/> - for single enum value
|
|
114
|
-
const data = enumValues.map((value: string) => ({
|
|
115
|
-
value,
|
|
116
|
-
label: value,
|
|
117
|
-
}));
|
|
118
|
-
|
|
119
181
|
const selectProps = typeof props.select === "object" ? props.select : {};
|
|
120
182
|
|
|
121
183
|
return (
|
|
@@ -24,10 +24,11 @@ const DarkModeButton = (props: DarkModeButtonProps) => {
|
|
|
24
24
|
const { setColorScheme } = useMantineColorScheme();
|
|
25
25
|
const computedColorScheme = useComputedColorScheme("light");
|
|
26
26
|
const [colorScheme, setColorScheme2] = useState("default");
|
|
27
|
+
const mode = props.mode ?? "minimal";
|
|
28
|
+
|
|
27
29
|
useEffect(() => {
|
|
28
30
|
setColorScheme2(computedColorScheme);
|
|
29
31
|
}, [computedColorScheme]);
|
|
30
|
-
const mode = props.mode ?? "minimal";
|
|
31
32
|
|
|
32
33
|
const toggleColorScheme = () => {
|
|
33
34
|
setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
|
|
@@ -67,7 +68,13 @@ const DarkModeButton = (props: DarkModeButtonProps) => {
|
|
|
67
68
|
size={props.size ?? "lg"}
|
|
68
69
|
aria-label="Toggle color scheme"
|
|
69
70
|
>
|
|
70
|
-
{colorScheme === "dark" ?
|
|
71
|
+
{colorScheme === "dark" ? (
|
|
72
|
+
<IconSun size={20} />
|
|
73
|
+
) : colorScheme === "light" ? (
|
|
74
|
+
<IconMoon size={20} />
|
|
75
|
+
) : (
|
|
76
|
+
<Flex h={20} />
|
|
77
|
+
)}
|
|
71
78
|
</ActionIcon>
|
|
72
79
|
);
|
|
73
80
|
};
|