Skip to content

Commit dbb6f35

Browse files
authored
feat(TextInputMapper): support various input types (#622)
1 parent 5f841e2 commit dbb6f35

File tree

7 files changed

+126
-13
lines changed

7 files changed

+126
-13
lines changed

.changeset/odd-cycles-attend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
Show placeholder in TextInput or TextArea with type password.

.changeset/popular-taxis-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
Add support for `autocomplete` attribute in TextInput.

.changeset/shiny-icons-leave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': minor
3+
---
4+
5+
Add support for Combobox, TextArea and Password fields in TextInputMapper.

src/components/fields/ComboBox/ComboBox.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ export interface CubeComboBoxProps<T>
107107
>,
108108
AriaComboBoxProps<T>,
109109
AriaTextFieldProps {
110+
defaultSelectedKey?: string;
111+
selectedKey?: string;
112+
onSelectionChange?: (selectedKey: string) => void;
113+
onInputChange?: (inputValue: string) => void;
114+
inputValue?: string;
115+
placeholder?: string;
110116
icon?: ReactElement;
111117
multiLine?: boolean;
112118
autoComplete?: string;

src/components/fields/TextInput/TextInputBase.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ export interface CubeTextInputBaseProps
202202
resize?: Styles['resize'];
203203
/** The size of the input */
204204
size?: 'small' | 'default' | 'large' | (string & {});
205+
autocomplete?: string;
205206
}
206207

207208
function _TextInputBase(props: CubeTextInputBaseProps, ref) {
@@ -240,6 +241,7 @@ function _TextInputBase(props: CubeTextInputBaseProps, ref) {
240241
tooltip,
241242
rows = 1,
242243
size,
244+
autocomplete,
243245
icon,
244246
maxLength,
245247
minLength,
@@ -327,12 +329,13 @@ function _TextInputBase(props: CubeTextInputBaseProps, ref) {
327329
);
328330

329331
const hasTextSecurity = multiLine && type === 'password';
330-
const textSecurityStyles = hasTextSecurity
331-
? {
332-
fontFamily: 'text-security-disc',
333-
WebkitTextSecurity: 'disc',
334-
}
335-
: {};
332+
const textSecurityStyles =
333+
hasTextSecurity && inputProps.value?.length
334+
? {
335+
fontFamily: 'text-security-disc',
336+
WebkitTextSecurity: 'disc',
337+
}
338+
: {};
336339

337340
const textField = (
338341
<InputWrapperElement
@@ -352,6 +355,7 @@ function _TextInputBase(props: CubeTextInputBaseProps, ref) {
352355
style={textSecurityStyles}
353356
autoFocus={autoFocus}
354357
data-size={size}
358+
autocomplete={autocomplete}
355359
styles={inputStyles}
356360
disabled={!!isDisabled}
357361
maxLength={maxLength}

src/components/fields/TextInputMapper/TextInputMapper.stories.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,31 @@ WithinForm.args = {};
8484

8585
export const WithinFormInputSync = FormTemplateSync.bind({});
8686
WithinFormInputSync.args = {};
87+
88+
export const WithComboBoxKeyAndValue = Template.bind({});
89+
WithComboBoxKeyAndValue.args = {
90+
value: { 'Option 1': 'Option 2' },
91+
keyProps: {
92+
inputType: 'combobox',
93+
menuTrigger: 'focus',
94+
options: ['Option 1', 'Option 2', 'Option 3'],
95+
},
96+
valueProps: {
97+
inputType: 'combobox',
98+
options: ['Option 1', 'Option 2', 'Option 3'],
99+
},
100+
};
101+
WithComboBoxKeyAndValue.play = WithValueAndNewMapping.play;
102+
103+
export const WithPassword = Template.bind({});
104+
WithPassword.args = {
105+
value: { 'Key 1': 'Hidden' },
106+
keyProps: { autocomplete: 'off' },
107+
valueProps: { inputType: 'password', autocomplete: 'off' },
108+
};
109+
110+
export const WithTextArea = Template.bind({});
111+
WithTextArea.args = {
112+
value: { 'Key 1': 'Line 1\nLine 2' },
113+
valueProps: { inputType: 'textarea' },
114+
};

src/components/fields/TextInputMapper/TextInputMapper.tsx

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ import {
1111

1212
import { useEvent } from '../../../_internal/hooks';
1313
import { FieldBaseProps } from '../../../shared';
14-
import { mergeProps, useCombinedRefs } from '../../../utils/react/index';
14+
import { mergeProps, useCombinedRefs } from '../../../utils/react';
1515
import { useFieldProps, useFormProps, wrapWithField } from '../../form';
1616
import { CloseIcon, PlusIcon } from '../../../icons';
1717
import { Button } from '../../actions';
1818
import { Block } from '../../Block';
1919
import { Flow } from '../../layout/Flow';
2020
import { Grid } from '../../layout/Grid';
2121
import { Space } from '../../layout/Space';
22+
import { ComboBox } from '../ComboBox';
23+
import { PasswordInput } from '../PasswordInput/PasswordInput';
24+
import { TextArea } from '../TextArea/index';
2225
import { TextInput } from '../TextInput';
2326

2427
type Mapping = {
@@ -32,9 +35,11 @@ export interface CubeTextInputMapperProps extends FieldBaseProps {
3235
isDisabled?: boolean;
3336
value?: Record<string, string>;
3437
onChange?: (value: Record<string, string> | undefined) => void;
38+
KeyComponent?: ComponentType<CubeTextInputMapperInputProps>;
3539
ValueComponent?: ComponentType<CubeTextInputMapperInputProps>;
3640
keyProps?: Partial<CubeTextInputMapperInputProps>;
3741
valueProps?: Partial<CubeTextInputMapperInputProps>;
42+
allowsCustomValue?: boolean;
3843
}
3944

4045
// Rewrites upper level field component styles
@@ -89,6 +94,7 @@ function TextInputMapper(
8994
onChange,
9095
keyProps,
9196
valueProps,
97+
KeyComponent,
9298
ValueComponent,
9399
} = props;
94100

@@ -174,6 +180,7 @@ function TextInputMapper(
174180
);
175181
}, [mappings]);
176182

183+
KeyComponent = KeyComponent ?? TextInputMapperInput;
177184
ValueComponent = ValueComponent ?? TextInputMapperInput;
178185

179186
const onKeyChange = useEvent((id: number, value: string) => {
@@ -218,11 +225,11 @@ function TextInputMapper(
218225
columns="minmax(0, 1fr) minmax(0, 1fr) min-content"
219226
gap="1x"
220227
>
221-
<TextInputMapperInput
228+
<KeyComponent
222229
autoFocus={index === mappings.length - 1}
223230
id={id}
224231
isDisabled={isDisabled}
225-
type="name"
232+
fieldType="key"
226233
value={key}
227234
placeholder="Key"
228235
onChange={onKeyChange}
@@ -231,7 +238,7 @@ function TextInputMapper(
231238
/>
232239
<ValueComponent
233240
id={id}
234-
type="value"
241+
fieldType="value"
235242
isDisabled={!key || isDisabled}
236243
value={value}
237244
placeholder="Value"
@@ -278,17 +285,29 @@ function TextInputMapper(
278285

279286
export interface CubeTextInputMapperInputProps {
280287
id: number;
281-
type: 'name' | 'value';
288+
fieldType: 'key' | 'value';
289+
inputType?: 'input' | 'combobox' | 'password' | 'textarea';
282290
value?: string;
291+
options?: string[];
283292
placeholder?: string;
284293
onChange?: (id: number, newValue: string) => void;
285294
onSubmit?: (id: number) => void;
286295
isDisabled?: boolean;
287296
autoFocus?: boolean;
297+
allowsCustomValue?: boolean;
288298
}
289299

290300
function TextInputMapperInput(props: CubeTextInputMapperInputProps) {
291-
const { id, type, value, placeholder, ...rest } = props;
301+
const {
302+
id,
303+
fieldType,
304+
inputType = 'input',
305+
options,
306+
value,
307+
placeholder,
308+
allowsCustomValue,
309+
...rest
310+
} = props;
292311

293312
const onChange = useEvent((newValue: string) => {
294313
props.onChange?.(id, newValue);
@@ -298,10 +317,51 @@ function TextInputMapperInput(props: CubeTextInputMapperInputProps) {
298317
props.onSubmit?.(id);
299318
});
300319

320+
const onSelectionChange = useEvent((newValue: string) => {
321+
if (!allowsCustomValue && !options?.includes(newValue)) {
322+
props.onChange?.(id, '');
323+
} else {
324+
props.onChange?.(id, newValue);
325+
}
326+
327+
props.onSubmit?.(id);
328+
});
329+
330+
const Component = {
331+
input: TextInput,
332+
textarea: TextArea,
333+
password: PasswordInput,
334+
}[inputType];
335+
336+
if (inputType === 'combobox') {
337+
return (
338+
<ComboBox
339+
qa="AddMapping"
340+
data-type={fieldType}
341+
width="auto"
342+
{...mergeProps(rest, PROPS_GRID_HACK)}
343+
id={undefined}
344+
inputValue={value}
345+
selectedKey={value}
346+
labelPosition="top"
347+
aria-label={placeholder}
348+
placeholder={placeholder}
349+
onInputChange={onChange}
350+
onSelectionChange={onSelectionChange}
351+
>
352+
{(options ?? []).map((option) => (
353+
<ComboBox.Item key={option}>{option}</ComboBox.Item>
354+
))}
355+
</ComboBox>
356+
);
357+
}
358+
301359
return (
302-
<TextInput
360+
<Component
303361
qa="AddMapping"
362+
data-type={fieldType}
304363
width="auto"
364+
rows={1}
305365
{...mergeProps(rest, PROPS_GRID_HACK)}
306366
id={undefined}
307367
value={value}

0 commit comments

Comments
 (0)