InputTags
Entering and editing tags
Here's an example where tags have a limited width and can be edited by clicking on them.
tsx
import { Flex } from '@semcore/ui/base-components';
import Ellipsis from '@semcore/ui/ellipsis';
import InputTags from '@semcore/ui/input-tags';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const Demo = () => {
const inputValueRef = React.useRef<HTMLInputElement>(null);
const [tags, setTags] = React.useState([
'TikTok',
'Facebook',
'LinkedIn',
'Instagram',
'Social media with a very long name',
]);
const [value, setValue] = React.useState('');
const handleAppendTags = (newTags: string[]) => {
setTags((tags) => [...tags, ...newTags]);
setValue('');
};
const handleRemoveTag = () => {
if (tags.length === 0) return;
setTags(tags.slice(0, -1));
setValue(`${tags.slice(-1)[0]} ${value}`);
};
const handleCloseTag = (idx: number) => (e: React.SyntheticEvent) => {
e.stopPropagation();
setTags((tags) => tags.filter((_, tagIdx) => idx !== tagIdx));
};
const handleTagKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
if (e.code === 'Enter' || e.code === 'Space') {
handleEditTag(e);
}
return false;
};
const handleEditTag = (
e: React.SyntheticEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
) => {
const { dataset } = e.currentTarget;
let allTags = [...tags];
if (value) {
allTags = [...allTags, value];
}
setTags(allTags.filter((tag, ind) => ind !== Number(dataset.id)));
if (!e.defaultPrevented && dataset.id !== undefined) {
setValue(tags[Number(dataset.id)]);
inputValueRef.current?.focus();
}
return false;
};
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const value = e.target instanceof HTMLInputElement ? e.target.value : null;
if (e.key === 'Enter' && value) {
handleAppendTags([value]);
return false;
}
};
return (
<Flex direction='column'>
<Text tag='label' size={300} htmlFor='add-new-social-media'>
Social media
</Text>
<InputTags mt={2} size='l' onAppend={handleAppendTags} onRemove={handleRemoveTag}>
{tags.map((tag, idx) => (
<InputTags.Tag
key={tag}
tag={InputTags.Tag}
theme='primary'
data-id={idx}
onClick={handleEditTag}
onKeyDown={handleTagKeyDown}
active={false}
editable
>
<InputTags.Tag.Text>
<Ellipsis wMax={100}>{tag}</Ellipsis>
</InputTags.Tag.Text>
<InputTags.Tag.Close onClick={handleCloseTag(idx)} />
</InputTags.Tag>
))}
<InputTags.Value
value={value}
onChange={setValue}
onKeyDown={handleInputKeyDown}
ref={inputValueRef}
id='add-new-social-media'
placeholder='Add social media'
/>
</InputTags>
</Flex>
);
};
export default Demo;
Wrapping email in tag
In this example, emails are wrapped in tags without any width limitation.
tsx
import { Flex } from '@semcore/ui/base-components';
import InputTags from '@semcore/ui/input-tags';
import type { InputTagsTagProps } from '@semcore/ui/input-tags';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const isValidEmail = (value: string) => /.+@.+\..+/i.test(value.toLowerCase());
const defaultTags = ['bob@email.com', 'alice@domain.net', 'mary@website.com', 'steve@company.com'];
type ExampleInputTagsProps = InputTagsTagProps;
const Demo = (props: ExampleInputTagsProps) => {
const [tags, setTags] = React.useState(defaultTags);
const [value, setValue] = React.useState('');
const changeState = (tags?: string[], value?: string) => {
if (tags !== undefined) {
setTags(tags);
}
if (value !== undefined) {
setValue(() => value);
}
};
const handleAppendTags = (newTags: string[]) => {
setTags((tags) => [...tags, ...newTags]);
setValue(() => '');
};
const handleRemoveTag = () => {
changeState(tags.slice(0, -1), tags.slice(-1)[0]);
};
const handleChange = (value: string) => {
changeState(undefined, value);
};
const handleCloseTag = (e: React.SyntheticEvent<HTMLElement>) => {
const { dataset } = e.currentTarget;
changeState(
tags.filter((tag, ind) => ind !== Number(dataset.id)),
undefined,
);
};
return (
<Flex direction='column'>
<Text tag='label' size={300} htmlFor='email'>
Participants
</Text>
<InputTags mt={2} onAppend={handleAppendTags} onRemove={handleRemoveTag}>
{tags.map((tag, idx) => (
<InputTags.Tag
key={idx}
size={props.size}
theme={props.theme}
disabled={props.disabled}
editable={props.editable}
color={isValidEmail(tag) ? 'green-500' : 'red-500'}
>
<InputTags.Tag.Text>{tag}</InputTags.Tag.Text>
<InputTags.Tag.Close data-id={idx} onClick={handleCloseTag} />
</InputTags.Tag>
))}
<InputTags.Value
id='email'
placeholder='Add email'
type='email'
autoComplete='email'
value={value}
onChange={handleChange}
/>
</InputTags>
</Flex>
);
};
export const defaultPropsEmail: ExampleInputTagsProps = {
size: 'l',
theme: 'primary',
disabled: false,
editable: undefined,
};
Demo.defaultProps = defaultPropsEmail;
export default Demo;
Select for tag filtering
In this example, selected options are wrapped in tags within the input field.
tsx
import { Flex, ScreenReaderOnly } from '@semcore/ui/base-components';
import InputTags from '@semcore/ui/input-tags';
import Select from '@semcore/ui/select';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const tagsSelect = ['LinkedIn', 'Facebook', 'TikTok', 'Instagram'];
const Demo = () => {
const selectTriggerRef = React.useRef<HTMLInputElement>(null);
const [tags, setTags] = React.useState<string[]>([]);
const [valueInput, setValueInput] = React.useState('');
const [visible, setVisible] = React.useState(false);
function onRemoveLastTag() {
if (tags.length) {
setValueInput(tags[tags.length - 1]);
setTags(tags.slice(0, -1));
}
}
function onRemoveTag(index: number, e: React.SyntheticEvent<HTMLElement>) {
e.stopPropagation();
const newTags = tags.filter((tag, i) => i !== index);
setTags(tags.filter((tag, i) => i !== index));
if (newTags.length === index) {
selectTriggerRef.current?.focus();
}
}
function onChangeValue(value: string) {
setValueInput(value);
setVisible(true);
}
function onChange(value: string[]) {
setTags(value);
setValueInput('');
}
function onBlurValue() {
setValueInput('');
}
const tagsFilter = tagsSelect.filter((tag) => {
return tag.toLowerCase().includes(valueInput.toLowerCase()) && !tags.includes(tag);
});
return (
<Flex direction='column'>
<Text tag='label' size={300} htmlFor='secondary-social-medias'>
Social media
</Text>
<Select
interaction='focus'
size='l'
visible={visible && tags.length < 4}
onVisibleChange={(visible) => setVisible(visible)}
multiselect={true}
value={tags}
onChange={onChange}
>
<Select.Trigger
tag={InputTags}
mt={2}
w={300}
size='l'
onRemove={onRemoveLastTag}
delimiters={[]}
>
{tags.map((tag, i) => (
<InputTags.Tag key={i} theme='primary'>
<InputTags.Tag.Text>{tag}</InputTags.Tag.Text>
<InputTags.Tag.Close onClick={(e) => onRemoveTag(i, e)} />
</InputTags.Tag>
))}
<InputTags.Value
ref={selectTriggerRef}
value={valueInput}
onChange={onChangeValue}
id='secondary-social-medias'
placeholder='Select social media'
onBlur={onBlurValue}
aria-describedby={valueInput ? 'search-result' : undefined}
/>
</Select.Trigger>
<Select.Menu>
{tagsFilter.map((tag, i) => (
<Select.Option value={tag} key={i}>
{tag}
</Select.Option>
))}
{tagsFilter.length !== 0 && valueInput !== '' && (
<ScreenReaderOnly id='search-result' aria-hidden='true'>
{tagsFilter.length}
{' '}
result
{tagsFilter.length > 1 && 's'}
{' '}
found
</ScreenReaderOnly>
)}
{tagsFilter.length === 0 && valueInput !== '' && (
<Text
tag='div'
id='search-result'
key='Nothing'
p='6px 8px'
size={200}
use='secondary'
>
Nothing found
</Text>
)}
</Select.Menu>
</Select>
</Flex>
);
};
export default Demo;
Last updated: