mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-29 18:50:08 +03:00
F OpenNebula/one#5637: Stepper configurable by prev. choices (#2663)
This commit is contained in:
parent
00e5419209
commit
76653e2142
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { TextField, Chip, Autocomplete } from '@mui/material'
|
||||
@ -34,6 +34,7 @@ const AutocompleteController = memo(
|
||||
values = [],
|
||||
fieldProps: { separators, ...fieldProps } = {},
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const {
|
||||
field: { value: renderValue, onBlur, onChange },
|
||||
@ -44,22 +45,30 @@ const AutocompleteController = memo(
|
||||
? renderValue ?? []
|
||||
: values.find(({ value }) => value === renderValue) ?? null
|
||||
|
||||
const handleChange = useCallback(
|
||||
(_, newValue) => {
|
||||
const newValueToChange = multiple
|
||||
? newValue?.map((value) =>
|
||||
['string', 'number'].includes(typeof value)
|
||||
? value
|
||||
: { text: value, value }
|
||||
)
|
||||
: newValue?.value
|
||||
|
||||
onChange(newValueToChange ?? '')
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(newValueToChange ?? '')
|
||||
}
|
||||
},
|
||||
[onChange, onConditionChange, multiple]
|
||||
)
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
color="secondary"
|
||||
onBlur={onBlur}
|
||||
onChange={(_, newValue) => {
|
||||
const newValueToChange = multiple
|
||||
? newValue?.map((value) =>
|
||||
['string', 'number'].includes(typeof value)
|
||||
? value
|
||||
: { text: value, value }
|
||||
)
|
||||
: newValue?.value
|
||||
|
||||
return onChange(newValueToChange ?? '')
|
||||
}}
|
||||
onChange={handleChange}
|
||||
options={values}
|
||||
value={selected}
|
||||
multiple={multiple}
|
||||
@ -126,6 +135,7 @@ AutocompleteController.propTypes = {
|
||||
values: PropTypes.arrayOf(PropTypes.object),
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
AutocompleteController.displayName = 'AutocompleteController'
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
@ -44,18 +44,30 @@ const CheckboxController = memo(
|
||||
tooltip,
|
||||
fieldProps = {},
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const {
|
||||
field: { value = false, onChange },
|
||||
fieldState: { error },
|
||||
} = useController({ name, control })
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e) => {
|
||||
const condition = e.target.checked
|
||||
onChange(condition)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(condition)
|
||||
}
|
||||
},
|
||||
[onChange, onConditionChange]
|
||||
)
|
||||
|
||||
return (
|
||||
<FormControl error={Boolean(error)} margin="dense">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
onChange={handleChange}
|
||||
name={name}
|
||||
readOnly={readOnly}
|
||||
checked={Boolean(value)}
|
||||
@ -90,6 +102,7 @@ CheckboxController.propTypes = {
|
||||
tooltip: PropTypes.any,
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
CheckboxController.displayName = 'CheckboxController'
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { ErrorHelper } from 'client/components/FormControl'
|
||||
@ -22,7 +22,12 @@ import { generateKey } from 'client/utils'
|
||||
import InputCode from 'client/components/FormControl/InputCode'
|
||||
|
||||
const DockerfileController = memo(
|
||||
({ control, cy = `input-${generateKey()}`, name = '' }) => {
|
||||
({
|
||||
control,
|
||||
cy = `input-${generateKey()}`,
|
||||
name = '',
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const {
|
||||
getValues,
|
||||
setValue,
|
||||
@ -38,15 +43,23 @@ const DockerfileController = memo(
|
||||
setInternalError(messageError)
|
||||
}, [messageError])
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value) => {
|
||||
setValue(name, value)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(value)
|
||||
}
|
||||
},
|
||||
[setValue, onConditionChange, name]
|
||||
)
|
||||
|
||||
return (
|
||||
<div data-cy={cy}>
|
||||
<InputCode
|
||||
mode="dockerfile"
|
||||
height="600px"
|
||||
value={getValues(name)}
|
||||
onChange={(value) => {
|
||||
setValue(name, value)
|
||||
}}
|
||||
onChange={handleChange}
|
||||
onFocus={(e) => {
|
||||
setInternalError()
|
||||
}}
|
||||
@ -62,6 +75,7 @@ DockerfileController.propTypes = {
|
||||
control: PropTypes.object,
|
||||
cy: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
DockerfileController.displayName = 'DockerfileController'
|
||||
|
@ -50,6 +50,7 @@ const FileController = memo(
|
||||
transform,
|
||||
fieldProps = {},
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const { setValue, setError, clearErrors, watch } = useFormContext()
|
||||
|
||||
@ -108,6 +109,10 @@ const FileController = memo(
|
||||
const parsedValue = transform ? await transform(file) : file
|
||||
setValue(name, parsedValue)
|
||||
handleDelayState()
|
||||
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(parsedValue)
|
||||
}
|
||||
} catch (err) {
|
||||
setValue(name, undefined)
|
||||
handleDelayState(err?.message ?? err)
|
||||
@ -165,6 +170,7 @@ FileController.propTypes = {
|
||||
transform: PropTypes.func,
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
FileController.displayName = 'FileController'
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useMemo, useEffect } from 'react'
|
||||
import { memo, useMemo, useEffect, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { TextField } from '@mui/material'
|
||||
@ -37,6 +37,7 @@ const SelectController = memo(
|
||||
dependencies,
|
||||
fieldProps = {},
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const watch = useWatch({
|
||||
name: dependencies,
|
||||
@ -85,27 +86,38 @@ const SelectController = memo(
|
||||
onChange(ensuredWatcherValue ?? defaultValue)
|
||||
}, [watch, watcher, dependencies])
|
||||
|
||||
const handleChange = useCallback(
|
||||
(evt) => {
|
||||
if (!multiple) {
|
||||
onChange(evt)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(evt)
|
||||
}
|
||||
} else {
|
||||
const {
|
||||
target: { options },
|
||||
} = evt
|
||||
const newValue = []
|
||||
|
||||
for (const option of options) {
|
||||
option.selected && newValue.push(option.value)
|
||||
}
|
||||
|
||||
onChange(newValue)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
[onChange, onConditionChange, multiple]
|
||||
)
|
||||
|
||||
return (
|
||||
<TextField
|
||||
{...inputProps}
|
||||
inputRef={ref}
|
||||
value={optionSelected}
|
||||
onChange={
|
||||
!multiple
|
||||
? onChange
|
||||
: (evt) => {
|
||||
const {
|
||||
target: { options },
|
||||
} = evt
|
||||
const newValue = []
|
||||
|
||||
for (const option of options) {
|
||||
option.selected && newValue.push(option.value)
|
||||
}
|
||||
|
||||
onChange(newValue)
|
||||
}
|
||||
}
|
||||
onChange={handleChange}
|
||||
select
|
||||
fullWidth
|
||||
disabled={readOnly}
|
||||
@ -157,6 +169,7 @@ SelectController.propTypes = {
|
||||
]),
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
SelectController.displayName = 'SelectController'
|
||||
|
@ -34,6 +34,7 @@ const SliderController = memo(
|
||||
dependencies,
|
||||
fieldProps = {},
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const watch = useWatch({
|
||||
name: dependencies,
|
||||
@ -66,6 +67,18 @@ const SliderController = memo(
|
||||
const sliderId = `${cy}-slider`
|
||||
const inputId = `${cy}-input`
|
||||
|
||||
const handleChange = useCallback(
|
||||
(_, newValue) => {
|
||||
if (!readOnly) {
|
||||
onChange(newValue)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
[onChange, onConditionChange, readOnly]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
@ -82,7 +95,7 @@ const SliderController = memo(
|
||||
valueLabelDisplay="auto"
|
||||
disabled={readOnly}
|
||||
data-cy={sliderId}
|
||||
onChange={(_, val) => onChange(val)}
|
||||
onChange={handleChange}
|
||||
{...fieldProps}
|
||||
/>
|
||||
<TextField
|
||||
@ -134,6 +147,7 @@ SliderController.propTypes = {
|
||||
]),
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
SliderController.displayName = 'SliderController'
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
@ -44,19 +44,31 @@ const SwitchController = memo(
|
||||
tooltip,
|
||||
fieldProps = {},
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const {
|
||||
field: { value = false, onChange },
|
||||
fieldState: { error },
|
||||
} = useController({ name, control })
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e) => {
|
||||
const condition = e.target.checked
|
||||
onChange(condition)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(condition)
|
||||
}
|
||||
},
|
||||
[onChange, onConditionChange]
|
||||
)
|
||||
|
||||
return (
|
||||
<FormControl error={Boolean(error)} margin="dense">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
readOnly={readOnly}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
onChange={handleChange}
|
||||
name={name}
|
||||
checked={Boolean(value)}
|
||||
color="secondary"
|
||||
@ -90,6 +102,7 @@ SwitchController.propTypes = {
|
||||
tooltip: PropTypes.any,
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
SwitchController.displayName = 'SwitchController'
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext, useController } from 'react-hook-form'
|
||||
|
||||
@ -43,6 +43,7 @@ const TableController = memo(
|
||||
singleSelect = true,
|
||||
getRowId = defaultGetRowId,
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
fieldProps: { initialState, ...fieldProps } = {},
|
||||
}) => {
|
||||
const { clearErrors } = useFormContext()
|
||||
@ -61,6 +62,30 @@ const TableController = memo(
|
||||
setInitialRows({})
|
||||
}, [Table])
|
||||
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(rows) => {
|
||||
if (readOnly) return
|
||||
|
||||
const rowValues = rows?.map(({ original }) => getRowId(original))
|
||||
|
||||
onChange(singleSelect ? rowValues?.[0] : rowValues)
|
||||
clearErrors(name)
|
||||
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(singleSelect ? rowValues?.[0] : rowValues)
|
||||
}
|
||||
},
|
||||
[
|
||||
onChange,
|
||||
clearErrors,
|
||||
name,
|
||||
onConditionChange,
|
||||
readOnly,
|
||||
getRowId,
|
||||
singleSelect,
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Legend title={label} tooltip={tooltip} />
|
||||
@ -75,14 +100,7 @@ const TableController = memo(
|
||||
singleSelect={singleSelect}
|
||||
getRowId={getRowId}
|
||||
initialState={{ ...initialState, selectedRowIds: initialRows }}
|
||||
onSelectedRowsChange={(rows) => {
|
||||
if (readOnly) return
|
||||
|
||||
const rowValues = rows?.map(({ original }) => getRowId(original))
|
||||
|
||||
onChange(singleSelect ? rowValues?.[0] : rowValues)
|
||||
clearErrors(name)
|
||||
}}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
{...fieldProps}
|
||||
/>
|
||||
</>
|
||||
@ -106,6 +124,7 @@ TableController.propTypes = {
|
||||
tooltip: PropTypes.any,
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
TableController.displayName = 'TableController'
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useEffect } from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { TextField } from '@mui/material'
|
||||
@ -36,6 +36,7 @@ const TextController = memo(
|
||||
dependencies,
|
||||
fieldProps = {},
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const watch = useWatch({
|
||||
name: dependencies,
|
||||
@ -55,13 +56,24 @@ const TextController = memo(
|
||||
watcherValue !== undefined && onChange(watcherValue)
|
||||
}, [watch, watcher, dependencies])
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e) => {
|
||||
const condition = e.target.value
|
||||
onChange(condition)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(condition)
|
||||
}
|
||||
},
|
||||
[onChange, onConditionChange]
|
||||
)
|
||||
|
||||
return (
|
||||
<TextField
|
||||
{...inputProps}
|
||||
fullWidth
|
||||
inputRef={ref}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onChange={handleChange}
|
||||
multiline={multiline}
|
||||
rows={3}
|
||||
type={type}
|
||||
@ -111,6 +123,7 @@ TextController.propTypes = {
|
||||
]),
|
||||
fieldProps: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
TextController.displayName = 'TextController'
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useEffect } from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
@ -50,6 +50,7 @@ const ToggleController = memo(
|
||||
fieldProps = {},
|
||||
notNull = false,
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
}) => {
|
||||
const {
|
||||
field: { ref, value: optionSelected, onChange },
|
||||
@ -62,6 +63,17 @@ const ToggleController = memo(
|
||||
!exists && onChange()
|
||||
}
|
||||
}, [])
|
||||
const handleChange = useCallback(
|
||||
(_, newValues) => {
|
||||
if (!readOnly && (!notNull || newValues)) {
|
||||
onChange(newValues)
|
||||
if (typeof onConditionChange === 'function') {
|
||||
onConditionChange(newValues)
|
||||
}
|
||||
}
|
||||
},
|
||||
[onChange, onConditionChange, readOnly, notNull]
|
||||
)
|
||||
|
||||
return (
|
||||
<FormControl fullWidth margin="dense">
|
||||
@ -75,9 +87,7 @@ const ToggleController = memo(
|
||||
fullWidth
|
||||
ref={ref}
|
||||
id={cy}
|
||||
onChange={(_, newValues) =>
|
||||
!readOnly && (!notNull || newValues) && onChange(newValues)
|
||||
}
|
||||
onChange={handleChange}
|
||||
value={optionSelected}
|
||||
exclusive={!multiple}
|
||||
data-cy={cy}
|
||||
@ -115,6 +125,7 @@ ToggleController.propTypes = {
|
||||
fieldProps: PropTypes.object,
|
||||
notNull: PropTypes.bool,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
ToggleController.displayName = 'ToggleController'
|
||||
|
@ -13,7 +13,15 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useState, useMemo, useCallback, useEffect, ReactElement } from 'react'
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useMemo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
ReactElement,
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { BaseSchema } from 'yup'
|
||||
@ -68,6 +76,18 @@ DefaultFormStepper.propTypes = {
|
||||
resolver: PropTypes.func,
|
||||
}
|
||||
|
||||
const DisableStepContext = createContext(() => {})
|
||||
|
||||
/**
|
||||
* Hook that can be used to enable/disable steps in the stepper dialog.
|
||||
*
|
||||
* @returns {Function} A function that is currently provided by the DisableStepContext.
|
||||
* The function takes a stepId or an array of stepIds and a condition to disable or enable the steps.
|
||||
* @example
|
||||
* const disableStep = useDisableStep();
|
||||
* disableStep('step1', true); // This will disable 'step1'
|
||||
*/
|
||||
export const useDisableStep = () => useContext(DisableStepContext)
|
||||
/**
|
||||
* Represents a form with one or more steps.
|
||||
* Finally, it submit the result.
|
||||
@ -78,7 +98,7 @@ DefaultFormStepper.propTypes = {
|
||||
* @param {Function} props.onSubmit - Submit function
|
||||
* @returns {ReactElement} Stepper form component
|
||||
*/
|
||||
const FormStepper = ({ steps = [], schema, onSubmit }) => {
|
||||
const FormStepper = ({ steps: initialSteps = [], schema, onSubmit }) => {
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.only('xs'))
|
||||
const {
|
||||
watch,
|
||||
@ -87,6 +107,31 @@ const FormStepper = ({ steps = [], schema, onSubmit }) => {
|
||||
setError,
|
||||
} = useFormContext()
|
||||
const { isLoading } = useGeneral()
|
||||
const [steps, setSteps] = useState(initialSteps)
|
||||
const [disabledSteps, setDisabledSteps] = useState({})
|
||||
|
||||
const disableStep = useCallback((stepIds, shouldDisable) => {
|
||||
const ids = Array.isArray(stepIds) ? stepIds : [stepIds]
|
||||
|
||||
setDisabledSteps((prev) => {
|
||||
let newDisabledSteps = { ...prev }
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
ids.forEach((stepId) => {
|
||||
newDisabledSteps = shouldDisable
|
||||
? { ...newDisabledSteps, [stepId]: true }
|
||||
: (({ [stepId]: _, ...rest }) => rest)(newDisabledSteps)
|
||||
})
|
||||
|
||||
return newDisabledSteps
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// filter out disabled steps
|
||||
const enabledSteps = initialSteps.filter((step) => !disabledSteps[step.id])
|
||||
setSteps(enabledSteps)
|
||||
}, [disabledSteps, initialSteps])
|
||||
|
||||
const [formData, setFormData] = useState(() => watch())
|
||||
const [activeStep, setActiveStep] = useState(FIRST_STEP)
|
||||
@ -183,45 +228,49 @@ const FormStepper = ({ steps = [], schema, onSubmit }) => {
|
||||
)
|
||||
|
||||
const { id: stepId, content: Content } = useMemo(
|
||||
() => steps[activeStep],
|
||||
() => steps[activeStep] || { id: null, content: null },
|
||||
[formData, activeStep]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* STEPPER */}
|
||||
{useMemo(
|
||||
() =>
|
||||
isMobile ? (
|
||||
<CustomMobileStepper
|
||||
steps={steps}
|
||||
totalSteps={totalSteps}
|
||||
activeStep={activeStep}
|
||||
lastStep={lastStep}
|
||||
disabledBack={disabledBack}
|
||||
isSubmitting={isLoading}
|
||||
handleNext={handleNext}
|
||||
handleBack={handleBack}
|
||||
errors={errors}
|
||||
/>
|
||||
) : (
|
||||
<CustomStepper
|
||||
steps={steps}
|
||||
activeStep={activeStep}
|
||||
lastStep={lastStep}
|
||||
disabledBack={disabledBack}
|
||||
isSubmitting={isLoading}
|
||||
handleStep={handleStep}
|
||||
handleNext={handleNext}
|
||||
handleBack={handleBack}
|
||||
errors={errors}
|
||||
/>
|
||||
),
|
||||
[isLoading, isMobile, activeStep, errors[stepId]]
|
||||
)}
|
||||
{/* FORM CONTENT */}
|
||||
{Content && <Content data={formData[stepId]} setFormData={setFormData} />}
|
||||
</>
|
||||
<DisableStepContext.Provider value={disableStep}>
|
||||
<>
|
||||
{/* STEPPER */}
|
||||
{useMemo(
|
||||
() =>
|
||||
isMobile ? (
|
||||
<CustomMobileStepper
|
||||
steps={steps}
|
||||
totalSteps={totalSteps}
|
||||
activeStep={activeStep}
|
||||
lastStep={lastStep}
|
||||
disabledBack={disabledBack}
|
||||
isSubmitting={isLoading}
|
||||
handleNext={handleNext}
|
||||
handleBack={handleBack}
|
||||
errors={errors}
|
||||
/>
|
||||
) : (
|
||||
<CustomStepper
|
||||
steps={steps}
|
||||
activeStep={activeStep}
|
||||
lastStep={lastStep}
|
||||
disabledBack={disabledBack}
|
||||
isSubmitting={isLoading}
|
||||
handleStep={handleStep}
|
||||
handleNext={handleNext}
|
||||
handleBack={handleBack}
|
||||
errors={errors}
|
||||
/>
|
||||
),
|
||||
[isLoading, isMobile, activeStep, errors[stepId], steps]
|
||||
)}
|
||||
{/* FORM CONTENT */}
|
||||
{Content && (
|
||||
<Content data={formData[stepId]} setFormData={setFormData} />
|
||||
)}
|
||||
</>
|
||||
</DisableStepContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import * as FC from 'client/components/FormControl'
|
||||
import Legend from 'client/components/Forms/Legend'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { Field } from 'client/utils'
|
||||
import { useDisableStep } from 'client/components/FormStepper'
|
||||
|
||||
const NOT_DEPEND_ATTRIBUTES = [
|
||||
'watcher',
|
||||
@ -145,79 +146,98 @@ FormWithSchema.propTypes = {
|
||||
rootProps: PropTypes.object,
|
||||
}
|
||||
|
||||
const FieldComponent = memo(({ id, cy, dependOf, ...attributes }) => {
|
||||
const formContext = useFormContext()
|
||||
const FieldComponent = memo(
|
||||
({ id, cy, dependOf, stepControl, ...attributes }) => {
|
||||
const formContext = useFormContext()
|
||||
const disableSteps = useDisableStep()
|
||||
|
||||
const addIdToName = useCallback(
|
||||
(n) => {
|
||||
// removes character '$' and returns
|
||||
if (n.startsWith('$')) return n.slice(1)
|
||||
const addIdToName = useCallback(
|
||||
(n) => {
|
||||
// removes character '$' and returns
|
||||
if (n.startsWith('$')) return n.slice(1)
|
||||
|
||||
// concat form ID if exists
|
||||
return id ? `${id}.${n}` : n
|
||||
},
|
||||
[id]
|
||||
)
|
||||
|
||||
const nameOfDependField = useMemo(() => {
|
||||
if (!dependOf) return null
|
||||
|
||||
return Array.isArray(dependOf)
|
||||
? dependOf.map(addIdToName)
|
||||
: addIdToName(dependOf)
|
||||
}, [dependOf, addIdToName])
|
||||
|
||||
const valueOfDependField = useWatch({
|
||||
name: nameOfDependField,
|
||||
disabled: dependOf === undefined,
|
||||
defaultValue: Array.isArray(dependOf) ? [] : undefined,
|
||||
})
|
||||
|
||||
const { name, type, htmlType, grid, ...fieldProps } = Object.entries(
|
||||
attributes
|
||||
).reduce((field, attribute) => {
|
||||
const [attrKey, value] = attribute
|
||||
const isNotDependAttribute = NOT_DEPEND_ATTRIBUTES.includes(attrKey)
|
||||
|
||||
const finalValue =
|
||||
typeof value === 'function' &&
|
||||
!isNotDependAttribute &&
|
||||
!isValidElement(value())
|
||||
? value(valueOfDependField, formContext)
|
||||
: value
|
||||
|
||||
return { ...field, [attrKey]: finalValue }
|
||||
}, {})
|
||||
|
||||
const dataCy = useMemo(() => `${cy}-${name ?? ''}`.replaceAll('.', '-'), [cy])
|
||||
const inputName = useMemo(() => addIdToName(name), [addIdToName, name])
|
||||
const isHidden = useMemo(() => htmlType === INPUT_TYPES.HIDDEN, [htmlType])
|
||||
const key = useMemo(
|
||||
() =>
|
||||
fieldProps?.values
|
||||
? `${name}-${JSON.stringify(fieldProps.values)}`
|
||||
: undefined,
|
||||
[fieldProps]
|
||||
)
|
||||
|
||||
if (isHidden) return null
|
||||
|
||||
return (
|
||||
INPUT_CONTROLLER[type] && (
|
||||
<Grid item xs={12} md={6} {...grid}>
|
||||
{createElement(INPUT_CONTROLLER[type], {
|
||||
key,
|
||||
control: formContext.control,
|
||||
cy: dataCy,
|
||||
dependencies: nameOfDependField,
|
||||
name: inputName,
|
||||
type: htmlType === false ? undefined : htmlType,
|
||||
...fieldProps,
|
||||
})}
|
||||
</Grid>
|
||||
// concat form ID if exists
|
||||
return id ? `${id}.${n}` : n
|
||||
},
|
||||
[id]
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
const nameOfDependField = useMemo(() => {
|
||||
if (!dependOf) return null
|
||||
|
||||
return Array.isArray(dependOf)
|
||||
? dependOf.map(addIdToName)
|
||||
: addIdToName(dependOf)
|
||||
}, [dependOf, addIdToName])
|
||||
|
||||
const valueOfDependField = useWatch({
|
||||
name: nameOfDependField,
|
||||
disabled: dependOf === undefined,
|
||||
defaultValue: Array.isArray(dependOf) ? [] : undefined,
|
||||
})
|
||||
|
||||
const handleConditionChange = useCallback(
|
||||
(value) => {
|
||||
if (stepControl?.condition) {
|
||||
if (stepControl.condition(value)) {
|
||||
disableSteps && disableSteps(stepControl.steps, true)
|
||||
} else {
|
||||
disableSteps && disableSteps(stepControl.steps, false)
|
||||
}
|
||||
}
|
||||
},
|
||||
[stepControl, disableSteps]
|
||||
)
|
||||
|
||||
const { name, type, htmlType, grid, condition, ...fieldProps } =
|
||||
Object.entries(attributes).reduce((field, attribute) => {
|
||||
const [attrKey, value] = attribute
|
||||
const isNotDependAttribute = NOT_DEPEND_ATTRIBUTES.includes(attrKey)
|
||||
|
||||
const finalValue =
|
||||
typeof value === 'function' &&
|
||||
!isNotDependAttribute &&
|
||||
!isValidElement(value())
|
||||
? value(valueOfDependField, formContext)
|
||||
: value
|
||||
|
||||
return { ...field, [attrKey]: finalValue }
|
||||
}, {})
|
||||
|
||||
const dataCy = useMemo(
|
||||
() => `${cy}-${name ?? ''}`.replaceAll('.', '-'),
|
||||
[cy]
|
||||
)
|
||||
const inputName = useMemo(() => addIdToName(name), [addIdToName, name])
|
||||
const isHidden = useMemo(() => htmlType === INPUT_TYPES.HIDDEN, [htmlType])
|
||||
const key = useMemo(
|
||||
() =>
|
||||
fieldProps?.values
|
||||
? `${name}-${JSON.stringify(fieldProps.values)}`
|
||||
: undefined,
|
||||
[fieldProps]
|
||||
)
|
||||
|
||||
if (isHidden) return null
|
||||
|
||||
return (
|
||||
INPUT_CONTROLLER[type] && (
|
||||
<Grid item xs={12} md={6} {...grid}>
|
||||
{createElement(INPUT_CONTROLLER[type], {
|
||||
key,
|
||||
control: formContext.control,
|
||||
cy: dataCy,
|
||||
dependencies: nameOfDependField,
|
||||
name: inputName,
|
||||
type: htmlType === false ? undefined : htmlType,
|
||||
onConditionChange: handleConditionChange,
|
||||
...fieldProps,
|
||||
})}
|
||||
</Grid>
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
FieldComponent.propTypes = {
|
||||
id: PropTypes.string,
|
||||
@ -226,6 +246,10 @@ FieldComponent.propTypes = {
|
||||
PropTypes.string,
|
||||
PropTypes.arrayOf(PropTypes.string),
|
||||
]),
|
||||
stepControl: PropTypes.shape({
|
||||
condition: PropTypes.func,
|
||||
steps: PropTypes.arrayOf(PropTypes.string),
|
||||
}),
|
||||
}
|
||||
|
||||
FieldComponent.displayName = 'FieldComponent'
|
||||
|
Loading…
x
Reference in New Issue
Block a user