Skip to content

Commit 66bdd39

Browse files
committed
fix(ComboBox): support for legacy field * 4
1 parent 23f1c00 commit 66bdd39

File tree

3 files changed

+132
-59
lines changed

3 files changed

+132
-59
lines changed

src/components/fields/ComboBox/ComboBox.stories.tsx

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,43 @@ const TemplateForm: StoryFn<CubeComboBoxProps<any>> = (
4444
<Flow gap="2x">
4545
<Form
4646
form={form}
47-
defaultValues={{ combobox: args.allowsCustomValue ? 'Unknown' : 'red' }}
47+
defaultValues={{ combobox: args.allowsCustomValue ? 'unknown' : 'red' }}
4848
onSubmit={(data) => console.log('! submit', data)}
4949
>
50-
<ComboBox name="combobox" {...args}>
51-
<ComboBox.Item key="red">Red</ComboBox.Item>
52-
<ComboBox.Item key="orange">Orange</ComboBox.Item>
53-
<ComboBox.Item key="yellow">Yellow</ComboBox.Item>
54-
<ComboBox.Item key="green">Green</ComboBox.Item>
55-
<ComboBox.Item key="blue">Blue</ComboBox.Item>
56-
<ComboBox.Item key="purple">Purple</ComboBox.Item>
57-
<ComboBox.Item key="violet">Violet</ComboBox.Item>
50+
<ComboBox
51+
name="combobox"
52+
{...args}
53+
rules={[
54+
{
55+
required: true,
56+
},
57+
{
58+
pattern: /^[A-Za-z_][A-Za-z0-9_]*$/,
59+
message: 'Please enter valid variable name',
60+
},
61+
]}
62+
>
63+
<ComboBox.Item key="red">
64+
{args.allowsCustomValue ? 'red' : 'Red'}
65+
</ComboBox.Item>
66+
<ComboBox.Item key="orange">
67+
{args.allowsCustomValue ? 'orange' : 'Orange'}
68+
</ComboBox.Item>
69+
<ComboBox.Item key="yellow">
70+
{args.allowsCustomValue ? 'yellow' : 'Yellow'}
71+
</ComboBox.Item>
72+
<ComboBox.Item key="green">
73+
{args.allowsCustomValue ? 'green' : 'Green'}
74+
</ComboBox.Item>
75+
<ComboBox.Item key="blue">
76+
{args.allowsCustomValue ? 'blue' : 'Blue'}
77+
</ComboBox.Item>
78+
<ComboBox.Item key="purple">
79+
{args.allowsCustomValue ? 'purple' : 'Purple'}
80+
</ComboBox.Item>
81+
<ComboBox.Item key="violet">
82+
{args.allowsCustomValue ? 'violet' : 'Violet'}
83+
</ComboBox.Item>
5884
</ComboBox>
5985
</Form>
6086
<Button>Focus</Button>
@@ -71,18 +97,41 @@ const TemplateLegacyForm: StoryFn<CubeComboBoxProps<any>> = (
7197
<Flow gap="2x">
7298
<Form
7399
form={form}
74-
defaultValues={{ combobox: args.allowsCustomValue ? 'Unknown' : 'red' }}
100+
defaultValues={{ combobox: args.allowsCustomValue ? 'unknown' : 'red' }}
75101
onSubmit={(data) => console.log('! Submit', data)}
76102
>
77-
<Field name="combobox">
103+
<Field
104+
name="combobox"
105+
rules={[
106+
{
107+
required: true,
108+
pattern: /^[A-Za-z_][A-Za-z0-9_]*$/,
109+
message: 'Please enter valid variable name',
110+
},
111+
]}
112+
>
78113
<ComboBox {...args}>
79-
<ComboBox.Item key="red">Red</ComboBox.Item>
80-
<ComboBox.Item key="orange">Orange</ComboBox.Item>
81-
<ComboBox.Item key="yellow">Yellow</ComboBox.Item>
82-
<ComboBox.Item key="green">Green</ComboBox.Item>
83-
<ComboBox.Item key="blue">Blue</ComboBox.Item>
84-
<ComboBox.Item key="purple">Purple</ComboBox.Item>
85-
<ComboBox.Item key="violet">Violet</ComboBox.Item>
114+
<ComboBox.Item key="red">
115+
{args.allowsCustomValue ? 'red' : 'Red'}
116+
</ComboBox.Item>
117+
<ComboBox.Item key="orange">
118+
{args.allowsCustomValue ? 'orange' : 'Orange'}
119+
</ComboBox.Item>
120+
<ComboBox.Item key="yellow">
121+
{args.allowsCustomValue ? 'yellow' : 'Yellow'}
122+
</ComboBox.Item>
123+
<ComboBox.Item key="green">
124+
{args.allowsCustomValue ? 'green' : 'Green'}
125+
</ComboBox.Item>
126+
<ComboBox.Item key="blue">
127+
{args.allowsCustomValue ? 'blue' : 'Blue'}
128+
</ComboBox.Item>
129+
<ComboBox.Item key="purple">
130+
{args.allowsCustomValue ? 'purple' : 'Purple'}
131+
</ComboBox.Item>
132+
<ComboBox.Item key="violet">
133+
{args.allowsCustomValue ? 'violet' : 'Violet'}
134+
</ComboBox.Item>
86135
</ComboBox>
87136
</Field>
88137
</Form>

src/components/fields/ComboBox/ComboBox.tsx

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
RefObject,
77
useEffect,
88
useMemo,
9-
useState,
109
} from 'react';
1110
import {
1211
useButton,
@@ -111,7 +110,7 @@ export interface CubeComboBoxProps<T>
111110
AriaTextFieldProps {
112111
defaultSelectedKey?: string;
113112
selectedKey?: string;
114-
onSelectionChange?: (selectedKey: string) => void;
113+
onSelectionChange?: (selectedKey: string | null) => void;
115114
onInputChange?: (inputValue: string) => void;
116115
inputValue?: string;
117116
placeholder?: string;
@@ -147,22 +146,26 @@ export const ComboBox = forwardRef(function ComboBox<T extends object>(
147146
props = useFieldProps(props, {
148147
valuePropsMapper: ({ value, onChange }) => {
149148
return {
150-
selectedKey: value ?? null,
151-
onSelectionChange(val: string) {
152-
setIsEmptyStateAllowed(false);
149+
selectedKey: !props.allowsCustomValue ? value ?? null : undefined,
150+
inputValue: props.allowsCustomValue ? value ?? '' : undefined,
151+
onInputChange(val) {
152+
if (!props.allowsCustomValue) {
153+
return;
154+
}
153155

154-
if (val != null) {
155-
onChange(val);
156-
} else {
157-
onChange(inputRef?.current?.value);
156+
onChange(val);
157+
},
158+
onSelectionChange(val: string) {
159+
if (val == null && props.allowsCustomValue) {
160+
return;
158161
}
162+
163+
onChange(val);
159164
},
160165
};
161166
},
162167
});
163168

164-
const [isEmptyStateAllowed, setIsEmptyStateAllowed] = useState(false);
165-
166169
let {
167170
qa,
168171
label,
@@ -326,43 +329,55 @@ export const ComboBox = forwardRef(function ComboBox<T extends object>(
326329

327330
// If input is not full and the user presses Enter, pick the first option.
328331
let onKeyPress = useEvent((e: KeyboardEvent) => {
329-
if (e.key === 'Enter' && !props.allowsCustomValue && state.isOpen) {
330-
const option = [...state.collection][0]?.key;
332+
if (e.key === 'Enter') {
333+
if (!props.allowsCustomValue && state.isOpen) {
334+
const inputValue = inputRef?.current?.value;
331335

332-
if (option && selectedKey !== option) {
333-
props.onSelectionChange?.(option);
336+
if (inputValue === '') {
337+
state.close();
338+
props.onSelectionChange?.(null);
334339

335-
e.stopPropagation();
336-
e.preventDefault();
337-
}
338-
}
339-
});
340+
e.stopPropagation();
341+
e.preventDefault();
342+
} else {
343+
const option = [...state.collection][0]?.key;
340344

341-
let onChange = useEvent(() => {
342-
if (!inputRef?.current?.value && props.allowsCustomValue) {
343-
setIsEmptyStateAllowed(true);
344-
}
345-
});
345+
if (option && selectedKey !== option) {
346+
props.onSelectionChange?.(option);
347+
348+
e.stopPropagation();
349+
e.preventDefault();
350+
}
351+
}
352+
// If a custom value is allowed, we need to check if the input value is in the collection.
353+
} else if (props.allowsCustomValue) {
354+
const inputValue = inputRef?.current?.value;
355+
356+
const item = [...state.collection].find(
357+
(item) => item.textValue === inputValue,
358+
);
346359

347-
let onBlur = useEvent(() => {
348-
setIsEmptyStateAllowed(false);
360+
props.onSelectionChange?.(
361+
item ? item.key : inputRef?.current?.value ?? '',
362+
);
363+
364+
if (item && inputRef?.current) {
365+
inputRef.current.value = item.key;
366+
}
367+
}
368+
}
349369
});
350370

351371
useEffect(() => {
352372
inputRef.current?.addEventListener('keydown', onKeyPress, true);
353-
inputRef.current?.addEventListener('input', onChange, true);
354373

355374
return () => {
356375
inputRef.current?.removeEventListener('keydown', onKeyPress, true);
357-
inputRef.current?.removeEventListener('input', onChange, true);
358376
};
359377
}, []);
360378

361379
let allInputProps = useMemo(
362-
() =>
363-
mergeProps(inputProps, hoverProps, focusProps, {
364-
onBlur,
365-
}),
380+
() => mergeProps(inputProps, hoverProps, focusProps),
366381
[inputProps, hoverProps, focusProps],
367382
);
368383

@@ -383,11 +398,6 @@ export const ComboBox = forwardRef(function ComboBox<T extends object>(
383398
autoFocus={autoFocus}
384399
data-autofocus={autoFocus ? '' : undefined}
385400
{...allInputProps}
386-
value={
387-
isEmptyStateAllowed || allInputProps.value || !props.allowsCustomValue
388-
? allInputProps.value
389-
: selectedKey
390-
}
391401
autoComplete={autoComplete}
392402
styles={inputStyles}
393403
{...modAttrs(mods)}

src/components/form/Form/Field.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,26 @@ function getValueProps(
6565
};
6666
} else if (type === 'ComboBox') {
6767
return {
68-
selectedKey: value ?? null,
69-
onSelectionChange: onChange,
68+
selectedKey: !allowsCustomValue ? value ?? null : undefined,
69+
inputValue: allowsCustomValue ? value ?? '' : undefined,
70+
onInputChange(val) {
71+
if (!allowsCustomValue) {
72+
return;
73+
}
74+
75+
onChange(val);
76+
},
77+
onSelectionChange(val: string) {
78+
if (val == null && allowsCustomValue) {
79+
return;
80+
}
81+
82+
onChange(val);
83+
},
7084
};
7185
} else if (type === 'Select') {
7286
return {
73-
selectedKey: value != null ? value : null,
87+
selectedKey: value ?? null,
7488
onSelectionChange: onChange,
7589
};
7690
}

0 commit comments

Comments
 (0)