Skip to content

Commit e38de06

Browse files
committed
feat: double enter fix to chip input and style improvements
1 parent 94e8b1b commit e38de06

File tree

1 file changed

+65
-21
lines changed

1 file changed

+65
-21
lines changed

src/components/chip-input.tsx

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,50 +40,94 @@ export function ChipInput(props: ChipInputProps) {
4040
'disabled',
4141
]);
4242

43-
const [inputValue, setInputValue] = createSignal('');
43+
const [inputRef, setInputRef] = createSignal<HTMLInputElement>();
4444

4545
function onKeyDown(event: KeyboardEvent) {
46-
if (event.key === 'Enter' && inputValue() !== '') {
46+
// Get value directly from input element.
47+
const inputValue = inputRef()?.value;
48+
49+
// Add a chip when enter or comma is pressed.
50+
if (event.key === 'Enter' || event.key === ',') {
4751
event.preventDefault();
48-
addChip(inputValue());
49-
setInputValue('');
52+
53+
const trimmed = inputValue?.trim();
54+
55+
if (trimmed) {
56+
addChip(trimmed);
57+
inputRef()!.value = '';
58+
return;
59+
}
60+
}
61+
62+
// Remove the last chip when backspace is pressed.
63+
if (event.key === 'Backspace' && !inputValue && props.value?.length) {
64+
removeChip(props.value[props.value.length - 1]!);
5065
}
5166
}
5267

5368
function addChip(chip: string) {
54-
props.onChange?.([...(props.value ?? []), chip]);
69+
// Prevent adding duplicate chips.
70+
if (!props.value?.includes(chip)) {
71+
props.onChange?.([...(props.value ?? []), chip]);
72+
}
5573
}
5674

5775
function removeChip(chip: string) {
5876
props.onChange?.(props.value?.filter(t => t !== chip) ?? []);
5977
}
6078

79+
// Handle paste to add multiple chips at once.
80+
function onPaste(event: ClipboardEvent) {
81+
event.preventDefault();
82+
83+
const pasteData = event.clipboardData?.getData('text');
84+
const pastedChips = (pasteData?.split(/,|\n/) ?? [])
85+
.map(item => item.trim())
86+
.filter(Boolean);
87+
88+
const newChips = [...(props.value ?? [])];
89+
pastedChips.forEach(chip => {
90+
if (!newChips.includes(chip)) {
91+
newChips.push(chip);
92+
}
93+
});
94+
95+
props.onChange?.(newChips);
96+
}
97+
6198
return (
62-
<div {...rest}>
63-
<TextInput
64-
disabled={props.disabled}
65-
onBlur={() => props.onBlur?.()}
66-
onKeyDown={onKeyDown}
67-
onChange={setInputValue}
68-
placeholder={props.placeholder}
69-
value={inputValue()}
70-
/>
71-
72-
<Show when={props.value?.length}>
73-
<div class="flex flex-wrap gap-2 mt-2">
99+
<div {...rest} class="w-full">
100+
<div
101+
onClick={() => inputRef()?.focus()}
102+
class="flex flex-wrap items-center gap-2 p-2 border border-gray-300 rounded-md focus-within:ring-2 focus-within:ring-primary-500 focus-within:border-primary-500 min-h-12"
103+
>
104+
<Show when={props.value?.length}>
74105
{props.value?.map(chip => (
75-
<Badge variant="secondary">
106+
<Badge
107+
variant="secondary"
108+
class="flex items-center gap-1 px-2 py-1"
109+
>
76110
{chip}
77111
{!props.disabled && (
78112
<IconX
79113
onClick={() => removeChip(chip)}
80-
class="ml-1 h-3 w-3 cursor-pointer"
114+
class="h-3 w-3 cursor-pointer"
81115
/>
82116
)}
83117
</Badge>
84118
))}
85-
</div>
86-
</Show>
119+
</Show>
120+
121+
<TextInput
122+
ref={setInputRef}
123+
disabled={props.disabled}
124+
onBlur={() => props.onBlur?.()}
125+
onKeyDown={onKeyDown}
126+
onPaste={onPaste}
127+
placeholder={props.value?.length ? '' : props.placeholder}
128+
class="flex-grow !border-0 !ring-0 !shadow-none !p-0 min-w-20"
129+
/>
130+
</div>
87131
</div>
88132
);
89133
}

0 commit comments

Comments
 (0)