@evenicanpm/admin-integrate 1.8.1 → 2.0.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/documents/ProcessTask/fragments.ts +4 -0
- package/documents/ProcessTemplate/fragments.ts +30 -0
- package/documents/ProcessTemplate/list.ts +29 -0
- package/package.json +2 -2
- package/src/api/process-config/mutation/process-config-import.mutation.ts +1 -1
- package/src/api/templates/queries/get-field-values.query.ts +48 -0
- package/src/api/templates/queries/get-field-values.server.ts +9 -0
- package/src/api/templates/queries/index.ts +4 -0
- package/src/api/templates/queries/types.ts +2 -2
- package/src/api/templates/queries/validate-field-on-check.query.ts +22 -0
- package/src/api/templates/queries/validate-field-on-check.server.ts +30 -0
- package/src/api/templates/queries/validate-field-on-submit.server.ts +18 -0
- package/src/components/data-table/table-row.tsx +1 -1
- package/src/components/icons/up-down.tsx +1 -0
- package/src/components/integration-view/integration-view.tsx +10 -15
- package/src/components/integration-view/nodes/task-node.tsx +1 -1
- package/src/components/integration-view/utils/mapping.ts +10 -11
- package/src/components/list-filter/list-filter.tsx +2 -0
- package/src/components/templatesV2/inputs/FormikPageBody.tsx +191 -0
- package/src/components/templatesV2/inputs/context/FormModeContext.tsx +31 -0
- package/src/components/templatesV2/inputs/context/TemplateContext.tsx +37 -0
- package/src/components/templatesV2/inputs/field-names.ts +8 -0
- package/src/components/templatesV2/inputs/form-type.ts +48 -0
- package/src/components/templatesV2/inputs/index.tsx +33 -0
- package/src/components/templatesV2/inputs/input-type.ts +23 -0
- package/src/components/templatesV2/inputs/primitives/CheckboxInput.tsx +45 -0
- package/src/components/templatesV2/inputs/primitives/NumberInput.tsx +58 -0
- package/src/components/templatesV2/inputs/primitives/SelectorInput.tsx +111 -0
- package/src/components/templatesV2/inputs/primitives/TextInput.tsx +55 -0
- package/src/components/templatesV2/inputs/primitives/TextWithQueryInput.tsx +292 -0
- package/src/components/templatesV2/inputs/primitives/index.ts +10 -0
- package/src/components/templatesV2/inputs/renderers/index.ts +2 -0
- package/src/components/templatesV2/inputs/renderers/renderFormControl.tsx +376 -0
- package/src/components/templatesV2/inputs/renderers/renderSearchInput.tsx +129 -0
- package/src/components/templatesV2/inputs/search/MultiTextSearchInput.tsx +87 -0
- package/src/components/templatesV2/inputs/search/SearchInputWrapper.tsx +113 -0
- package/src/components/templatesV2/inputs/search/TextSearchInput.tsx +309 -0
- package/src/components/templatesV2/inputs/search/index.ts +4 -0
- package/src/components/templatesV2/inputs/search/search-query-key.ts +23 -0
- package/src/components/templatesV2/inputs/search/useFieldValuesOptions.ts +58 -0
- package/src/components/templatesV2/inputs/search/useSearchInput.ts +74 -0
- package/src/components/templatesV2/inputs/table/InputTable.tsx +259 -0
- package/src/components/templatesV2/inputs/table/index.ts +1 -0
- package/src/components/templatesV2/inputs/utils/applyTemplateConfig.ts +28 -0
- package/src/components/templatesV2/inputs/utils/arrangeInputs.ts +51 -0
- package/src/components/templatesV2/inputs/utils/index.ts +6 -0
- package/src/components/templatesV2/inputs/utils/parseQueryName.ts +20 -0
- package/src/components/templatesV2/inputs/utils/renderReadOnlyText.tsx +26 -0
- package/src/components/templatesV2/inputs/utils/selectors.ts +69 -0
- package/src/components/templatesV2/inputs/utils/toStringArray.ts +23 -0
- package/src/components/templatesV2/pageForm.tsx +151 -0
- package/src/components/{templates → templatesV2}/templates-list/templates-list-row.tsx +2 -1
- package/src/components/{templates → templatesV2}/templates-list/templates-list-table.tsx +2 -2
- package/src/components/{templates → templatesV2}/templates-list/templates-list.tsx +19 -28
- package/src/components/{templates → templatesV2}/types.tsx +12 -12
- package/src/components/templatesV2/validation/buildZodSchema.ts +216 -0
- package/src/components/templatesV2/validation/zodFormikValidate.ts +17 -0
- package/src/components/templatesV2/wizard.tsx +744 -0
- package/src/pages/integration-create/integration-create-steps.ts +8 -0
- package/src/pages/integration-create/integration-create.tsx +61 -14
- package/src/pages/integration-create/select-step.tsx +3 -3
- package/src/pages/integration-create/template-list-step.tsx +28 -3
- package/src/pages/integration-create/template-options-step.tsx +119 -0
- package/src/pages/integration-create/wizard-step.tsx +5 -4
- package/src/pages/integration-details/task-drawer/add-task.tsx +4 -4
- package/src/pages/integration-details/task-drawer/edit-task.tsx +3 -3
- package/src/components/templates/inputs/index.tsx +0 -1392
- package/src/components/templates/wizard.tsx +0 -737
- /package/src/components/{templates → templatesV2}/index.ts +0 -0
- /package/src/components/{templates → templatesV2}/template-filter.ts +0 -0
- /package/src/components/{templates → templatesV2}/templates-list/index.ts +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
type TemplateContextValue = {
|
|
4
|
+
templateId: string;
|
|
5
|
+
templateConfig?: string;
|
|
6
|
+
disabledFields?: Set<string>;
|
|
7
|
+
setFieldsDisabled?: (
|
|
8
|
+
templateId: string,
|
|
9
|
+
fieldNames: string[],
|
|
10
|
+
disabled: boolean,
|
|
11
|
+
) => void;
|
|
12
|
+
clearTemplateFields?: (templateId: string) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const TemplateContext = React.createContext<TemplateContextValue | null>(null);
|
|
16
|
+
|
|
17
|
+
export function TemplateProvider({
|
|
18
|
+
value,
|
|
19
|
+
children,
|
|
20
|
+
}: Readonly<{
|
|
21
|
+
value: TemplateContextValue;
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}>) {
|
|
24
|
+
return (
|
|
25
|
+
<TemplateContext.Provider value={value}>
|
|
26
|
+
{children}
|
|
27
|
+
</TemplateContext.Provider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useTemplateContext() {
|
|
32
|
+
const ctx = React.useContext(TemplateContext);
|
|
33
|
+
if (!ctx) {
|
|
34
|
+
throw new Error("useTemplateContext must be used inside TemplateProvider");
|
|
35
|
+
}
|
|
36
|
+
return ctx;
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reserved form/config field names used by the template wizard.
|
|
3
|
+
* Use this enum instead of string literals to avoid typos and keep a single source of truth.
|
|
4
|
+
*/
|
|
5
|
+
export enum ReservedFieldName {
|
|
6
|
+
ProcessTemplateId = "processTemplateId",
|
|
7
|
+
IsUnique = "isUnique",
|
|
8
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { TemplateInputSearchQueryKey } from "@evenicanpm/admin-integrate/api/templates/queries/types";
|
|
2
|
+
import type { FieldValue, ProcessTemplateInputSchema } from "../types";
|
|
3
|
+
import type { InputType } from "./input-type";
|
|
4
|
+
export type TableRow = Record<string, string | number | boolean | null>;
|
|
5
|
+
|
|
6
|
+
export type SetRowsFn = (rows: TableRow[]) => void;
|
|
7
|
+
|
|
8
|
+
export interface InputTableProps {
|
|
9
|
+
rows: TableRow[];
|
|
10
|
+
setRows: SetRowsFn;
|
|
11
|
+
schemas: ProcessTemplateInputSchema[];
|
|
12
|
+
tableError: string | null;
|
|
13
|
+
readOnly?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SearchInputWrapperProps = {
|
|
17
|
+
fieldName?: string;
|
|
18
|
+
inputType:
|
|
19
|
+
| InputType.MULTI_TEXT_SEARCH
|
|
20
|
+
| InputType.TEXT_SEARCH
|
|
21
|
+
| InputType.TEXT_UNIQUE_IDENTIFIER;
|
|
22
|
+
|
|
23
|
+
label: string;
|
|
24
|
+
value: string | string[];
|
|
25
|
+
setValue:
|
|
26
|
+
| ((v: string) => void)
|
|
27
|
+
| ((v: string[]) => void)
|
|
28
|
+
| ((v: string, fieldName?: string) => void);
|
|
29
|
+
|
|
30
|
+
placeholder?: string | null;
|
|
31
|
+
helpText?: string | null;
|
|
32
|
+
error?: string | null;
|
|
33
|
+
|
|
34
|
+
queryName: TemplateInputSearchQueryKey;
|
|
35
|
+
|
|
36
|
+
/** Raw query name (e.g. Templates_GetSourceEndpoints) for getFieldValues API when inputType is TEXT_SEARCH */
|
|
37
|
+
validationQuery?: string;
|
|
38
|
+
|
|
39
|
+
/** Used by TextSearchInput to apply selected template config into the form */
|
|
40
|
+
handleFieldChange?: (
|
|
41
|
+
templateId: string,
|
|
42
|
+
fieldName: string,
|
|
43
|
+
value: FieldValue,
|
|
44
|
+
) => void;
|
|
45
|
+
|
|
46
|
+
/** Call when the search input blurs so errors show on focus out */
|
|
47
|
+
onBlur?: () => void;
|
|
48
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export { FormikPageBody } from "./FormikPageBody";
|
|
4
|
+
export { ReservedFieldName } from "./field-names";
|
|
5
|
+
export type { SearchInputWrapperProps } from "./form-type";
|
|
6
|
+
export { InputType, isHiddenInputType } from "./input-type";
|
|
7
|
+
export {
|
|
8
|
+
CheckboxInput,
|
|
9
|
+
NumberInput,
|
|
10
|
+
SelectorInput,
|
|
11
|
+
TextInput,
|
|
12
|
+
} from "./primitives";
|
|
13
|
+
export { renderFormControl, renderSearchInput } from "./renderers";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
MultiTextSearchInput,
|
|
17
|
+
SearchInputWrapper,
|
|
18
|
+
TextSearchInput,
|
|
19
|
+
useSearchInput,
|
|
20
|
+
} from "./search";
|
|
21
|
+
export { TemplateSearchQueryKey } from "./search/search-query-key";
|
|
22
|
+
|
|
23
|
+
export { InputTable } from "./table";
|
|
24
|
+
export {
|
|
25
|
+
applyTemplateConfig,
|
|
26
|
+
arrangeInputs,
|
|
27
|
+
getSelectorOptionsFromInput,
|
|
28
|
+
getTableRowErrors,
|
|
29
|
+
parseQueryName,
|
|
30
|
+
renderReadOnlyText,
|
|
31
|
+
toStringArray,
|
|
32
|
+
} from "./utils";
|
|
33
|
+
export type { InputRow } from "./utils/arrangeInputs";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export enum InputType {
|
|
2
|
+
TEXT = "text",
|
|
3
|
+
NUMBER = "number",
|
|
4
|
+
CHECKBOX = "checkbox",
|
|
5
|
+
SELECTOR = "selector",
|
|
6
|
+
INPUT_TABLE = "input-table",
|
|
7
|
+
TEXT_SEARCH = "text-search",
|
|
8
|
+
MULTI_TEXT_SEARCH = "multi-text-search",
|
|
9
|
+
TEXT_UNIQUE_IDENTIFIER = "text-unique-identifier",
|
|
10
|
+
HIDDEN = "hidden",
|
|
11
|
+
HIDDEN_NUMBER = "hidden-number",
|
|
12
|
+
HIDDEN_NUMBER_OR_NULL = "hidden-number-or-null",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isHiddenInputType(
|
|
16
|
+
inputType: string | null | undefined,
|
|
17
|
+
): boolean {
|
|
18
|
+
return (
|
|
19
|
+
inputType === InputType.HIDDEN ||
|
|
20
|
+
inputType === InputType.HIDDEN_NUMBER ||
|
|
21
|
+
inputType === InputType.HIDDEN_NUMBER_OR_NULL
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { SxProps, Theme } from "@mui/material";
|
|
4
|
+
import { Box, Checkbox, FormControlLabel, FormHelperText } from "@mui/material";
|
|
5
|
+
|
|
6
|
+
export function CheckboxInput({
|
|
7
|
+
label,
|
|
8
|
+
checked,
|
|
9
|
+
setChecked,
|
|
10
|
+
helpText,
|
|
11
|
+
readOnly,
|
|
12
|
+
disabled,
|
|
13
|
+
formHelperTextSx,
|
|
14
|
+
}: Readonly<{
|
|
15
|
+
label: string;
|
|
16
|
+
checked: boolean;
|
|
17
|
+
setChecked: (v: boolean) => void;
|
|
18
|
+
helpText?: string | null;
|
|
19
|
+
readOnly?: boolean;
|
|
20
|
+
/** Disable without read-only (e.g. while validating). */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Optional sx for the helper text (e.g. color: 'success.main' or 'error.main'). */
|
|
23
|
+
formHelperTextSx?: SxProps<Theme>;
|
|
24
|
+
}>) {
|
|
25
|
+
return (
|
|
26
|
+
<Box>
|
|
27
|
+
<FormControlLabel
|
|
28
|
+
control={
|
|
29
|
+
<Checkbox
|
|
30
|
+
checked={checked}
|
|
31
|
+
onChange={(e) => setChecked(e.target.checked)}
|
|
32
|
+
/>
|
|
33
|
+
}
|
|
34
|
+
label={label}
|
|
35
|
+
disabled={disabled ?? readOnly}
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<FormHelperText
|
|
39
|
+
sx={{ mt: -1.5, mb: 1, marginLeft: 0, ...formHelperTextSx }}
|
|
40
|
+
>
|
|
41
|
+
{helpText || " "}
|
|
42
|
+
</FormHelperText>
|
|
43
|
+
</Box>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { SxProps, Theme } from "@mui/material";
|
|
4
|
+
import { TextField } from "@mui/material";
|
|
5
|
+
import { renderReadOnlyText } from "../utils/renderReadOnlyText";
|
|
6
|
+
|
|
7
|
+
export function NumberInput({
|
|
8
|
+
label,
|
|
9
|
+
placeholder,
|
|
10
|
+
value,
|
|
11
|
+
setValue,
|
|
12
|
+
error,
|
|
13
|
+
helpText,
|
|
14
|
+
readOnly,
|
|
15
|
+
disabled,
|
|
16
|
+
onBlur,
|
|
17
|
+
onFocus,
|
|
18
|
+
formHelperTextSx,
|
|
19
|
+
}: Readonly<{
|
|
20
|
+
label: string;
|
|
21
|
+
placeholder?: string | null;
|
|
22
|
+
value: number | "" | null;
|
|
23
|
+
setValue: (v: number | "") => void;
|
|
24
|
+
error?: string | null;
|
|
25
|
+
helpText?: string | null;
|
|
26
|
+
readOnly?: boolean;
|
|
27
|
+
/** Disable input without switching to read-only text (e.g. while validating). */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
onBlur?: () => void;
|
|
30
|
+
onFocus?: () => void;
|
|
31
|
+
/** Optional sx for the helper text (e.g. color: 'success.main' for validation success). */
|
|
32
|
+
formHelperTextSx?: SxProps<Theme>;
|
|
33
|
+
}>) {
|
|
34
|
+
if (readOnly) return renderReadOnlyText(label, value);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<TextField
|
|
38
|
+
type="number"
|
|
39
|
+
label={label}
|
|
40
|
+
value={value ?? ""}
|
|
41
|
+
onChange={(e) =>
|
|
42
|
+
setValue(e.target.value === "" ? "" : Number(e.target.value))
|
|
43
|
+
}
|
|
44
|
+
onBlur={onBlur}
|
|
45
|
+
onFocus={onFocus}
|
|
46
|
+
placeholder={placeholder ?? undefined}
|
|
47
|
+
fullWidth
|
|
48
|
+
error={Boolean(error)}
|
|
49
|
+
helperText={error ?? helpText ?? " "}
|
|
50
|
+
disabled={disabled}
|
|
51
|
+
slotProps={{
|
|
52
|
+
formHelperText: {
|
|
53
|
+
sx: { marginLeft: 0, ...formHelperTextSx },
|
|
54
|
+
},
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { SxProps, Theme } from "@mui/material";
|
|
4
|
+
import { Autocomplete, Box, TextField } from "@mui/material";
|
|
5
|
+
import { renderReadOnlyText } from "../utils/renderReadOnlyText";
|
|
6
|
+
|
|
7
|
+
/** Option can be a plain string (label = value) or { label, value } for API-backed options. */
|
|
8
|
+
export type SelectorOption = string | { label: string; value: string };
|
|
9
|
+
|
|
10
|
+
function getOptionLabel(opt: SelectorOption): string {
|
|
11
|
+
return typeof opt === "string" ? opt : opt.label;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getOptionValue(opt: SelectorOption): string {
|
|
15
|
+
return typeof opt === "string" ? opt : opt.value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function optionEquals(opt: SelectorOption, value: string): boolean {
|
|
19
|
+
return getOptionValue(opt) === value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function SelectorInput({
|
|
23
|
+
fieldName,
|
|
24
|
+
label,
|
|
25
|
+
value,
|
|
26
|
+
setValue,
|
|
27
|
+
helpText,
|
|
28
|
+
error,
|
|
29
|
+
options,
|
|
30
|
+
insideTable = false,
|
|
31
|
+
minWidth,
|
|
32
|
+
readOnly,
|
|
33
|
+
disabled,
|
|
34
|
+
onBlur,
|
|
35
|
+
onFocus,
|
|
36
|
+
formHelperTextSx,
|
|
37
|
+
}: Readonly<{
|
|
38
|
+
fieldName: string;
|
|
39
|
+
label: string;
|
|
40
|
+
value: string;
|
|
41
|
+
setValue: (v: string) => void;
|
|
42
|
+
helpText?: string | null;
|
|
43
|
+
error?: string | null;
|
|
44
|
+
/** String options (label = value) or { label, value } for API-backed options. */
|
|
45
|
+
options?: SelectorOption[];
|
|
46
|
+
insideTable?: boolean;
|
|
47
|
+
minWidth?: number;
|
|
48
|
+
readOnly?: boolean;
|
|
49
|
+
/** Disable input without switching to read-only text (e.g. while validating). */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
onBlur?: () => void;
|
|
52
|
+
onFocus?: () => void;
|
|
53
|
+
/** Optional sx for the helper text (e.g. color: 'success.main' for validation success). */
|
|
54
|
+
formHelperTextSx?: SxProps<Theme>;
|
|
55
|
+
}>) {
|
|
56
|
+
const opts = options ?? [];
|
|
57
|
+
const allowFreeText = opts.length === 0;
|
|
58
|
+
const id = `selector-${fieldName}`;
|
|
59
|
+
const currentValue = value ?? "";
|
|
60
|
+
const selectedOption =
|
|
61
|
+
opts.find((o) => optionEquals(o, currentValue)) ?? (currentValue || null);
|
|
62
|
+
|
|
63
|
+
if (readOnly)
|
|
64
|
+
return renderReadOnlyText(insideTable ? undefined : label, value);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Box
|
|
68
|
+
sx={{
|
|
69
|
+
minWidth: minWidth ?? (insideTable ? 220 : undefined),
|
|
70
|
+
width: "100%",
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<Autocomplete<SelectorOption, false, false, boolean>
|
|
74
|
+
id={id}
|
|
75
|
+
disablePortal={insideTable}
|
|
76
|
+
options={opts}
|
|
77
|
+
getOptionLabel={getOptionLabel}
|
|
78
|
+
isOptionEqualToValue={(opt, v) =>
|
|
79
|
+
v == null
|
|
80
|
+
? false
|
|
81
|
+
: opt === v ||
|
|
82
|
+
optionEquals(opt, typeof v === "string" ? v : getOptionValue(v))
|
|
83
|
+
}
|
|
84
|
+
freeSolo={allowFreeText}
|
|
85
|
+
value={selectedOption ?? null}
|
|
86
|
+
onChange={(_e, val) => setValue(val != null ? getOptionValue(val) : "")}
|
|
87
|
+
onInputChange={(_e, val) => setValue(val)}
|
|
88
|
+
disabled={disabled ?? readOnly}
|
|
89
|
+
renderInput={(params) => (
|
|
90
|
+
<TextField
|
|
91
|
+
{...params}
|
|
92
|
+
size={insideTable ? "small" : "medium"}
|
|
93
|
+
label={label}
|
|
94
|
+
name={fieldName}
|
|
95
|
+
error={Boolean(error)}
|
|
96
|
+
helperText={error ?? helpText ?? " "}
|
|
97
|
+
fullWidth
|
|
98
|
+
disabled={disabled ?? readOnly}
|
|
99
|
+
onBlur={onBlur}
|
|
100
|
+
onFocus={onFocus}
|
|
101
|
+
slotProps={{
|
|
102
|
+
formHelperText: {
|
|
103
|
+
sx: { marginLeft: 0, ...formHelperTextSx },
|
|
104
|
+
},
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
/>
|
|
109
|
+
</Box>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { SxProps, Theme } from "@mui/material";
|
|
4
|
+
import { TextField } from "@mui/material";
|
|
5
|
+
import { renderReadOnlyText } from "../utils/renderReadOnlyText";
|
|
6
|
+
|
|
7
|
+
export function TextInput({
|
|
8
|
+
label,
|
|
9
|
+
placeholder,
|
|
10
|
+
value,
|
|
11
|
+
setValue,
|
|
12
|
+
error,
|
|
13
|
+
helpText,
|
|
14
|
+
readOnly,
|
|
15
|
+
disabled,
|
|
16
|
+
onBlur,
|
|
17
|
+
onFocus,
|
|
18
|
+
formHelperTextSx,
|
|
19
|
+
}: Readonly<{
|
|
20
|
+
label: string;
|
|
21
|
+
placeholder?: string | null;
|
|
22
|
+
value: string;
|
|
23
|
+
setValue: (v: string) => void;
|
|
24
|
+
error?: string | null;
|
|
25
|
+
helpText?: string | null;
|
|
26
|
+
readOnly?: boolean;
|
|
27
|
+
/** Disable input without switching to read-only text (e.g. while validating). */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
onBlur?: () => void;
|
|
30
|
+
onFocus?: () => void;
|
|
31
|
+
/** Optional sx for the helper text (e.g. color: 'success.main' for validation success). */
|
|
32
|
+
formHelperTextSx?: SxProps<Theme>;
|
|
33
|
+
}>) {
|
|
34
|
+
if (readOnly) return renderReadOnlyText(label, value);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<TextField
|
|
38
|
+
label={label}
|
|
39
|
+
value={value ?? ""}
|
|
40
|
+
onChange={(e) => setValue(e.target.value)}
|
|
41
|
+
onBlur={onBlur}
|
|
42
|
+
onFocus={onFocus}
|
|
43
|
+
placeholder={placeholder ?? undefined}
|
|
44
|
+
fullWidth
|
|
45
|
+
error={Boolean(error)}
|
|
46
|
+
helperText={error ?? helpText ?? " "}
|
|
47
|
+
disabled={disabled}
|
|
48
|
+
slotProps={{
|
|
49
|
+
formHelperText: {
|
|
50
|
+
sx: { minHeight: 16, marginLeft: 0, ...formHelperTextSx },
|
|
51
|
+
},
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|