Skip to content

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 React from 'react';
import InputTags from 'intergalactic/input-tags';
import Tooltip from 'intergalactic/tooltip';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';

const Demo = () => {
  const inputValueRef = React.useRef<HTMLInputElement>();
  const [tags, setTags] = React.useState(['vk', 'fk', 'twitter', 'instagram']);
  const [value, setValue] = React.useState('');

  const handleAppendTags = (newTags) => {
    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 = (e) => {
    e.preventDefault();
  };

  const handleTagKeyDown = (e) => {
    if (e.code === 'Enter' || e.code === 'Space') {
      handleEditTag(e);
    }
    return false;
  };

  const handleEditTag = (e) => {
    const { dataset } = e.currentTarget;
    let allTags = [...tags];
    if (value) {
      allTags = [...allTags, value];
    }
    setTags(allTags.filter((tag, ind) => ind !== Number(dataset.id)));
    if (!e.defaultPrevented) {
      setValue(tags[dataset.id]);
      inputValueRef.current?.focus();
    }
    return false;
  };

  const handleInputKeyDown = (e) => {
    const { value } = e.target;
    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) => (
          <Tooltip key={idx}>
            <Tooltip.Trigger
              tag={InputTags.Tag}
              theme='primary'
              editable
              data-id={idx}
              onClick={handleEditTag}
              onKeyDown={handleTagKeyDown}
            >
              <InputTags.Tag.Text tabIndex={0}>{tag}</InputTags.Tag.Text>
              <InputTags.Tag.Close onClick={handleCloseTag} />
            </Tooltip.Trigger>
            <Tooltip.Popper>tag</Tooltip.Popper>
          </Tooltip>
        ))}
        <InputTags.Value
          value={value}
          onChange={setValue}
          onKeyDown={handleInputKeyDown}
          ref={inputValueRef}
          id='add-new-social-media'
        />
      </InputTags>
    </Flex>
  );
};

export default Demo;
import React from 'react';
import InputTags from 'intergalactic/input-tags';
import Tooltip from 'intergalactic/tooltip';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';

const Demo = () => {
  const inputValueRef = React.useRef<HTMLInputElement>();
  const [tags, setTags] = React.useState(['vk', 'fk', 'twitter', 'instagram']);
  const [value, setValue] = React.useState('');

  const handleAppendTags = (newTags) => {
    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 = (e) => {
    e.preventDefault();
  };

  const handleTagKeyDown = (e) => {
    if (e.code === 'Enter' || e.code === 'Space') {
      handleEditTag(e);
    }
    return false;
  };

  const handleEditTag = (e) => {
    const { dataset } = e.currentTarget;
    let allTags = [...tags];
    if (value) {
      allTags = [...allTags, value];
    }
    setTags(allTags.filter((tag, ind) => ind !== Number(dataset.id)));
    if (!e.defaultPrevented) {
      setValue(tags[dataset.id]);
      inputValueRef.current?.focus();
    }
    return false;
  };

  const handleInputKeyDown = (e) => {
    const { value } = e.target;
    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) => (
          <Tooltip key={idx}>
            <Tooltip.Trigger
              tag={InputTags.Tag}
              theme='primary'
              editable
              data-id={idx}
              onClick={handleEditTag}
              onKeyDown={handleTagKeyDown}
            >
              <InputTags.Tag.Text tabIndex={0}>{tag}</InputTags.Tag.Text>
              <InputTags.Tag.Close onClick={handleCloseTag} />
            </Tooltip.Trigger>
            <Tooltip.Popper>tag</Tooltip.Popper>
          </Tooltip>
        ))}
        <InputTags.Value
          value={value}
          onChange={setValue}
          onKeyDown={handleInputKeyDown}
          ref={inputValueRef}
          id='add-new-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 React from 'react';
import InputTags from 'intergalactic/input-tags';
import Select from 'intergalactic/select';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';

const isValidEmail = (value) => /.+@.+\..+/i.test(value.toLowerCase());

const defaultTags = ['bob@vk.com', 'wolf@instagram.dot', 'fekla@fk.com', 'tuz@twitter.net'];

const Demo = () => {
  const [tags, setTags] = React.useState(defaultTags);
  const [value, setValue] = React.useState('');

  const changeState = (tags, value) => {
    if (tags !== undefined) {
      setTags(tags);
    }
    if (value !== undefined) {
      setValue(() => value);
    }
  };

  const handleAppendTags = (newTags) => {
    setTags((tags) => [...tags, ...newTags]);
    setValue(() => '');
  };

  const handleRemoveTag = () => {
    changeState(tags.slice(0, -1), tags.slice(-1)[0]);
  };

  const handleChange = (value) => {
    changeState(undefined, value);
  };

  const handleCloseTag = (e) => {
    const { dataset } = e.currentTarget;
    changeState(
      tags.filter((tag, ind) => ind !== Number(dataset.id)),
      undefined,
    );
  };

  const handleSelect = (value) => {
    changeState([...tags, value], '');
  };

  return (
    <Flex direction='column'>
      <Text tag='label' size={300} htmlFor='add-email'>
        Participants
      </Text>
      <Select interaction='focus' onChange={handleSelect}>
        <Select.Trigger
          tag={InputTags}
          mt={2}
          size='l'
          onAppend={handleAppendTags}
          onRemove={handleRemoveTag}
        >
          {tags.map((tag, idx) => (
            <InputTags.Tag
              key={idx}
              theme='primary'
              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='add-email'
            placeholder='bob@company.com, johndoe@domain.com'
            value={value}
            onChange={handleChange}
          />
        </Select.Trigger>
        {value && (
          <Select.Menu>
            <Select.Option value={value}>{value}</Select.Option>
          </Select.Menu>
        )}
      </Select>
    </Flex>
  );
};

export default Demo;
import React from 'react';
import InputTags from 'intergalactic/input-tags';
import Select from 'intergalactic/select';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';

const isValidEmail = (value) => /.+@.+\..+/i.test(value.toLowerCase());

const defaultTags = ['bob@vk.com', 'wolf@instagram.dot', 'fekla@fk.com', 'tuz@twitter.net'];

const Demo = () => {
  const [tags, setTags] = React.useState(defaultTags);
  const [value, setValue] = React.useState('');

  const changeState = (tags, value) => {
    if (tags !== undefined) {
      setTags(tags);
    }
    if (value !== undefined) {
      setValue(() => value);
    }
  };

  const handleAppendTags = (newTags) => {
    setTags((tags) => [...tags, ...newTags]);
    setValue(() => '');
  };

  const handleRemoveTag = () => {
    changeState(tags.slice(0, -1), tags.slice(-1)[0]);
  };

  const handleChange = (value) => {
    changeState(undefined, value);
  };

  const handleCloseTag = (e) => {
    const { dataset } = e.currentTarget;
    changeState(
      tags.filter((tag, ind) => ind !== Number(dataset.id)),
      undefined,
    );
  };

  const handleSelect = (value) => {
    changeState([...tags, value], '');
  };

  return (
    <Flex direction='column'>
      <Text tag='label' size={300} htmlFor='add-email'>
        Participants
      </Text>
      <Select interaction='focus' onChange={handleSelect}>
        <Select.Trigger
          tag={InputTags}
          mt={2}
          size='l'
          onAppend={handleAppendTags}
          onRemove={handleRemoveTag}
        >
          {tags.map((tag, idx) => (
            <InputTags.Tag
              key={idx}
              theme='primary'
              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='add-email'
            placeholder='bob@company.com, johndoe@domain.com'
            value={value}
            onChange={handleChange}
          />
        </Select.Trigger>
        {value && (
          <Select.Menu>
            <Select.Option value={value}>{value}</Select.Option>
          </Select.Menu>
        )}
      </Select>
    </Flex>
  );
};

export default Demo;

Input with DropdownMenu for tag filtering

In this example, selected options from the dropdown menu are wrapped in tags within the input field.

tsx
import React from 'react';
import InputTags from 'intergalactic/input-tags';
import DropdownMenu from 'intergalactic/dropdown-menu';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';

const tagsSelect = ['vk', 'fk', 'twitter', 'instagram'];

const Demo = () => {
  const [tags, setTags] = React.useState([]);
  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) {
    setTags(tags.filter((tag, i) => i !== index));
  }

  function onChangeValue(value) {
    setValueInput(value);
    setVisible(true);
  }

  function onSelectTag(value) {
    setTags(tags.concat(value));
    setValueInput('');
  }

  const tagsFilter = tagsSelect.filter((tag) => tag.includes(valueInput));

  return (
    <Flex direction='column'>
      <Text tag='label' size={300} htmlFor='secondary-social-medias'>
        Social media
      </Text>
      <DropdownMenu
        interaction='focus'
        size='l'
        visible={visible}
        onVisibleChange={(visible) => setVisible(visible)}
      >
        <DropdownMenu.Trigger tag={InputTags} mt={2} w={200} size='l' onRemove={onRemoveLastTag}>
          {tags.map((tag, i) => (
            <InputTags.Tag key={i} theme='primary'>
              <InputTags.Tag.Text>{tag}</InputTags.Tag.Text>
              <InputTags.Tag.Close onClick={onRemoveTag.bind(this, i)} />
            </InputTags.Tag>
          ))}
          <InputTags.Value
            value={valueInput}
            onChange={onChangeValue}
            id='secondary-social-medias'
          />
        </DropdownMenu.Trigger>
        <DropdownMenu.Menu>
          {tagsFilter.map((tag, i) => (
            <DropdownMenu.Item key={i} onClick={() => onSelectTag(tag)}>
              {tag}
            </DropdownMenu.Item>
          ))}
          {!tagsFilter.length && <DropdownMenu.ItemHint>Not found</DropdownMenu.ItemHint>}
        </DropdownMenu.Menu>
      </DropdownMenu>
    </Flex>
  );
};

export default Demo;
import React from 'react';
import InputTags from 'intergalactic/input-tags';
import DropdownMenu from 'intergalactic/dropdown-menu';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';

const tagsSelect = ['vk', 'fk', 'twitter', 'instagram'];

const Demo = () => {
  const [tags, setTags] = React.useState([]);
  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) {
    setTags(tags.filter((tag, i) => i !== index));
  }

  function onChangeValue(value) {
    setValueInput(value);
    setVisible(true);
  }

  function onSelectTag(value) {
    setTags(tags.concat(value));
    setValueInput('');
  }

  const tagsFilter = tagsSelect.filter((tag) => tag.includes(valueInput));

  return (
    <Flex direction='column'>
      <Text tag='label' size={300} htmlFor='secondary-social-medias'>
        Social media
      </Text>
      <DropdownMenu
        interaction='focus'
        size='l'
        visible={visible}
        onVisibleChange={(visible) => setVisible(visible)}
      >
        <DropdownMenu.Trigger tag={InputTags} mt={2} w={200} size='l' onRemove={onRemoveLastTag}>
          {tags.map((tag, i) => (
            <InputTags.Tag key={i} theme='primary'>
              <InputTags.Tag.Text>{tag}</InputTags.Tag.Text>
              <InputTags.Tag.Close onClick={onRemoveTag.bind(this, i)} />
            </InputTags.Tag>
          ))}
          <InputTags.Value
            value={valueInput}
            onChange={onChangeValue}
            id='secondary-social-medias'
          />
        </DropdownMenu.Trigger>
        <DropdownMenu.Menu>
          {tagsFilter.map((tag, i) => (
            <DropdownMenu.Item key={i} onClick={() => onSelectTag(tag)}>
              {tag}
            </DropdownMenu.Item>
          ))}
          {!tagsFilter.length && <DropdownMenu.ItemHint>Not found</DropdownMenu.ItemHint>}
        </DropdownMenu.Menu>
      </DropdownMenu>
    </Flex>
  );
};

export default Demo;

Released under the MIT License.

Released under the MIT License.