Skip to content

Commit deb3dba

Browse files
authored
FE: bugfix/wizard form validation (#3526)
* add the parameter to trigger() call for focusing first failed input * refactor Select component to React.forwardRef for focusing purposes
1 parent acfe7a4 commit deb3dba

File tree

3 files changed

+88
-78
lines changed

3 files changed

+88
-78
lines changed

kafka-ui-react-app/src/components/common/Select/ControlledSelect.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const ControlledSelect: React.FC<ControlledSelectProps> = ({
4545
options={options}
4646
placeholder={placeholder}
4747
disabled={disabled}
48+
ref={field.ref}
4849
/>
4950
);
5051
}}

kafka-ui-react-app/src/components/common/Select/Select.tsx

Lines changed: 86 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -27,90 +27,99 @@ export interface SelectOption {
2727
isLive?: boolean;
2828
}
2929

30-
const Select: React.FC<SelectProps> = ({
31-
options = [],
32-
value,
33-
defaultValue,
34-
selectSize = 'L',
35-
placeholder = '',
36-
isLive,
37-
disabled = false,
38-
onChange,
39-
isThemeMode,
40-
...props
41-
}) => {
42-
const [selectedOption, setSelectedOption] = useState(value);
43-
const [showOptions, setShowOptions] = useState(false);
30+
const Select = React.forwardRef<HTMLUListElement, SelectProps>(
31+
(
32+
{
33+
options = [],
34+
value,
35+
defaultValue,
36+
selectSize = 'L',
37+
placeholder = '',
38+
isLive,
39+
disabled = false,
40+
onChange,
41+
isThemeMode,
42+
...props
43+
},
44+
ref
45+
) => {
46+
const [selectedOption, setSelectedOption] = useState(value);
47+
const [showOptions, setShowOptions] = useState(false);
4448

45-
const showOptionsHandler = () => {
46-
if (!disabled) setShowOptions(!showOptions);
47-
};
49+
const showOptionsHandler = () => {
50+
if (!disabled) setShowOptions(!showOptions);
51+
};
4852

49-
const selectContainerRef = useRef(null);
50-
const clickOutsideHandler = () => setShowOptions(false);
51-
useClickOutside(selectContainerRef, clickOutsideHandler);
53+
const selectContainerRef = useRef(null);
54+
const clickOutsideHandler = () => setShowOptions(false);
55+
useClickOutside(selectContainerRef, clickOutsideHandler);
5256

53-
const updateSelectedOption = (option: SelectOption) => {
54-
if (!option.disabled) {
55-
setSelectedOption(option.value);
57+
const updateSelectedOption = (option: SelectOption) => {
58+
if (!option.disabled) {
59+
setSelectedOption(option.value);
5660

57-
if (onChange) {
58-
onChange(option.value);
61+
if (onChange) {
62+
onChange(option.value);
63+
}
64+
65+
setShowOptions(false);
5966
}
67+
};
6068

61-
setShowOptions(false);
62-
}
63-
};
69+
React.useEffect(() => {
70+
setSelectedOption(value);
71+
}, [isLive, value]);
6472

65-
React.useEffect(() => {
66-
setSelectedOption(value);
67-
}, [isLive, value]);
73+
return (
74+
<div ref={selectContainerRef}>
75+
<S.Select
76+
role="listbox"
77+
selectSize={selectSize}
78+
isLive={isLive}
79+
disabled={disabled}
80+
onClick={showOptionsHandler}
81+
onKeyDown={showOptionsHandler}
82+
isThemeMode={isThemeMode}
83+
ref={ref}
84+
tabIndex={0}
85+
{...props}
86+
>
87+
<S.SelectedOptionWrapper>
88+
{isLive && <LiveIcon />}
89+
<S.SelectedOption
90+
role="option"
91+
tabIndex={0}
92+
isThemeMode={isThemeMode}
93+
>
94+
{options.find(
95+
(option) => option.value === (defaultValue || selectedOption)
96+
)?.label || placeholder}
97+
</S.SelectedOption>
98+
</S.SelectedOptionWrapper>
99+
{showOptions && (
100+
<S.OptionList>
101+
{options?.map((option) => (
102+
<S.Option
103+
value={option.value}
104+
key={option.value}
105+
disabled={option.disabled}
106+
onClick={() => updateSelectedOption(option)}
107+
tabIndex={0}
108+
role="option"
109+
>
110+
{option.isLive && <LiveIcon />}
111+
{option.label}
112+
</S.Option>
113+
))}
114+
</S.OptionList>
115+
)}
116+
<DropdownArrowIcon isOpen={showOptions} />
117+
</S.Select>
118+
</div>
119+
);
120+
}
121+
);
68122

69-
return (
70-
<div ref={selectContainerRef}>
71-
<S.Select
72-
role="listbox"
73-
selectSize={selectSize}
74-
isLive={isLive}
75-
disabled={disabled}
76-
onClick={showOptionsHandler}
77-
onKeyDown={showOptionsHandler}
78-
isThemeMode={isThemeMode}
79-
{...props}
80-
>
81-
<S.SelectedOptionWrapper>
82-
{isLive && <LiveIcon />}
83-
<S.SelectedOption
84-
role="option"
85-
tabIndex={0}
86-
isThemeMode={isThemeMode}
87-
>
88-
{options.find(
89-
(option) => option.value === (defaultValue || selectedOption)
90-
)?.label || placeholder}
91-
</S.SelectedOption>
92-
</S.SelectedOptionWrapper>
93-
{showOptions && (
94-
<S.OptionList>
95-
{options?.map((option) => (
96-
<S.Option
97-
value={option.value}
98-
key={option.value}
99-
disabled={option.disabled}
100-
onClick={() => updateSelectedOption(option)}
101-
tabIndex={0}
102-
role="option"
103-
>
104-
{option.isLive && <LiveIcon />}
105-
{option.label}
106-
</S.Option>
107-
))}
108-
</S.OptionList>
109-
)}
110-
<DropdownArrowIcon isOpen={showOptions} />
111-
</S.Select>
112-
</div>
113-
);
114-
};
123+
Select.displayName = 'Select';
115124

116125
export default Select;

kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const ClusterConfigForm: React.FC<ClusterConfigFormProps> = ({
7575
const onReset = () => methods.reset();
7676

7777
const onValidate = async () => {
78-
await trigger();
78+
await trigger(undefined, { shouldFocus: true });
7979
if (!methods.formState.isValid) return;
8080
disableForm();
8181
const data = methods.getValues();

0 commit comments

Comments
 (0)