PillsInput

PillsInput

커스텀 태그 인풋과 멀티 셀렉트를 만들기 위한 기본 컴포넌트예요. 자체적으로 로직을 포함하지 않고 주어진 children을 렌더링만 하는 유틸리티 컴포넌트예요.

출처: 문서

본문

사용법 (Usage)

PillsInput은 커스텀 태그 인풋, 멀티 셀렉트 및 유사한 컴포넌트를 만드는 데 사용하는 유틸리티 컴포넌트예요. 자체적으로 로직은 포함하지 않고 주어진 children을 렌더링만 해요. 보통 PillsInput은 Pill 컴포넌트와 함께 사용돼요.

import { PillsInput, Pill } from '@mantine/core';

function Demo() {
  return (
    <PillsInput label="PillsInput">
      <Pill.Group>
        <Pill>React</Pill>
        <Pill>Vue</Pill>
        <Pill>Svelte</Pill>
        <PillsInput.Field placeholder="Enter items" />
      </Pill.Group>
    </PillsInput>
  );
}

로딩 상태 (Loading state)

loading prop을 설정하면 로딩 인디케이터가 표시돼요. 기본적으로 로더는 인풋 오른쪽에 표시돼요. loadingPosition prop을 'left' 또는 'right'로 바꿔 위치를 변경할 수 있어요. API 호출, 검색, 검증 같은 비동기 작업에 유용해요.

import { PillsInput, Pill } from '@mantine/core';

function Demo() {
  return (
    <PillsInput label="PillsInput" loading loadingPosition="right">
      <Pill.Group>
        <Pill>React</Pill>
        <Pill>Vue</Pill>
        <PillsInput.Field placeholder="Enter items" />
      </Pill.Group>
    </PillsInput>
  );
}

인풋 props (Input props)

PillsInput 컴포넌트는 Input 및 Input.Wrapper 컴포넌트 기능과 모든 div 요소 prop을 지원해요. PillsInput 문서에는 컴포넌트가 지원하는 모든 기능이 포함되지는 않아요. 사용 가능한 모든 기능은 Input 문서를 참고해요.

import { PillsInput, Pill } from '@mantine/core';

function Demo() {
  return (
    <PillsInput label="PillsInput label" description="Input description">
      <Pill.Group>
        <Pill>React</Pill>
        <Pill>Vue</Pill>
        <Pill>Svelte</Pill>
        <PillsInput.Field placeholder="Enter items" />
      </Pill.Group>
    </PillsInput>
  );
}

검색 가능한 셀렉트 예시 (Searchable select example)

PillsInput을 Combobox와 결합해 검색 가능한 멀티 셀렉트를 만들 수 있어요.

import { useState } from 'react';
import { PillsInput, Pill, Combobox, CheckIcon, Group, useCombobox } from '@mantine/core';

const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];

function Demo() {
  const combobox = useCombobox({
    onDropdownClose: () => combobox.resetSelectedOption(),
    onDropdownOpen: () => combobox.updateSelectedOptionIndex('active'),
  });

  const [search, setSearch] = useState('');
  const [value, setValue] = useState<string[]>([]);

  const handleValueSelect = (val: string) =>
    setValue((current) =>
      current.includes(val) ? current.filter((v) => v !== val) : [...current, val]
    );

  const handleValueRemove = (val: string) =>
    setValue((current) => current.filter((v) => v !== val));

  const values = value.map((item) => (
    <Pill key={item} withRemoveButton onRemove={() => handleValueRemove(item)}>
      {item}
    </Pill>
  ));

  const options = groceries
    .filter((item) => item.toLowerCase().includes(search.trim().toLowerCase()))
    .map((item) => (
      <Combobox.Option value={item} key={item} active={value.includes(item)}>
        <Group gap="sm">
          {value.includes(item) ? <CheckIcon size={12} /> : null}
          <span>{item}</span>
        </Group>
      </Combobox.Option>
    ));

  return (
    <Combobox store={combobox} onOptionSubmit={handleValueSelect}>
      <Combobox.Dropdown>
        <Combobox.Options>
          <PillsInput onClick={() => combobox.openDropdown()}>
            <Pill.Group>
              {values}
              <Combobox.EventsTarget>
                <PillsInput.Field
                  onFocus={() => combobox.openDropdown()}
                  onBlur={() => combobox.closeDropdown()}
                  value={search}
                  placeholder="Search values"
                  onChange={(event) => {
                    combobox.updateSelectedOptionIndex();
                    setSearch(event.currentTarget.value);
                  }}
                  onKeyDown={(event) => {
                    if (event.key === 'Backspace' && search.length === 0 && value.length > 0) {
                      event.preventDefault();
                      handleValueRemove(value[value.length - 1]);
                    }
                  }}
                />
              </Combobox.EventsTarget>
            </Pill.Group>
          </PillsInput>
          {options.length > 0 ? options : <Combobox.Empty>Nothing found...</Combobox.Empty>}
        </Combobox.Options>
      </Combobox.Dropdown>
    </Combobox>
  );
}

접근성 (Accessibility)

label prop 없이 PillsInput을 사용하면 스크린 리더가 제대로 알리지 못해요.

PillsInput.Field 컴포넌트에 aria-label을 설정하면 인풋을 접근 가능하게 만들 수 있어요. 이 경우 라벨은 보이지 않지만 스크린 리더가 알려줘요.

import { PillsInput } from '@mantine/core';

// Accessible input – it has aria-label
function Demo() {
  return (
    <PillsInput>
      <Pill.Group>
        <PillsInput.Field aria-label="Your tags" placeholder="Enter items" />
      </Pill.Group>
    </PillsInput>
  );
}

label prop이 설정되어 있으면 인풋은 접근 가능하며 aria-label을 설정할 필요가 없어요.

import { PillsInput } from '@mantine/core';

// Accessible input – it has associated label element
function Demo() {
  return (
    <PillsInput label="Your tags">
      <Pill.Group>
        <PillsInput.Field placeholder="Enter items" />
      </Pill.Group>
    </PillsInput>
  );
}

더 알아보기 (Learn more)