Featuring Design System
Legacy

CoreMultiSelectPrim

컴파운드 패턴으로 구성된 Select 컴포넌트입니다. 다중 값에 대한 선택을 지원합니다. 선택된 값은 CoreTag 컴포넌트로 표시됩니다.

Usage

기본 사용법

import { MultiSelectPrim } from '@featuring-corp/components';
import { CoreCheckbox } from '@featuring-corp/components';

<MultiSelectPrim.Root required width="240px">
  <MultiSelectPrim.Label tooltip="도움말">Label</MultiSelectPrim.Label>
  <MultiSelectPrim.Trigger>
    <MultiSelectPrim.Value placeholder="옵션을 선택하세요" />
  </MultiSelectPrim.Trigger>
  <MultiSelectPrim.HelperText>Helper Text</MultiSelectPrim.HelperText>
  <MultiSelectPrim.Portal>
    <MultiSelectPrim.Content>
      <MultiSelectPrim.Item value="option1">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 1" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
      <MultiSelectPrim.Item value="option2">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 2" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
      <MultiSelectPrim.Item value="option3" disabled>
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 3 (disabled)" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
    </MultiSelectPrim.Content>
  </MultiSelectPrim.Portal>
</MultiSelectPrim.Root>

제어된 컴포넌트 (Controlled Component)

import { useState } from 'react';
import { MultiSelectPrim } from '@featuring-corp/components';
import { CoreCheckbox } from '@featuring-corp/components';

const [selected, setSelected] = useState<string[]>(['option1', 'option2']);

const handleValueChange = (value: string) => {
  if (selected.includes(value)) {
    setSelected(selected.filter((item) => item !== value));
  } else {
    setSelected([...selected, value]);
  }
};

<MultiSelectPrim.Root 
  value={selected} 
  onValueChange={handleValueChange}
  required
  width="240px"
>
  <MultiSelectPrim.Label>Label</MultiSelectPrim.Label>
  <MultiSelectPrim.Trigger>
    <MultiSelectPrim.Value />
  </MultiSelectPrim.Trigger>
  <MultiSelectPrim.Portal>
    <MultiSelectPrim.Content>
      <MultiSelectPrim.Item value="option1">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 1" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
      <MultiSelectPrim.Item value="option2">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 2" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
    </MultiSelectPrim.Content>
  </MultiSelectPrim.Portal>
</MultiSelectPrim.Root>

valueWrap을 사용한 여러 줄 표시

import { useState } from 'react';
import { MultiSelectPrim } from '@featuring-corp/components';
import { CoreCheckbox } from '@featuring-corp/components';

const [selected, setSelected] = useState<string[]>(['option1', 'option2', 'option3', 'option4', 'option5']);

const handleValueChange = (value: string) => {
  if (selected.includes(value)) {
    setSelected(selected.filter((item) => item !== value));
  } else {
    setSelected([...selected, value]);
  }
};

<MultiSelectPrim.Root 
  value={selected} 
  onValueChange={handleValueChange}
  required
  width="300px"
>
  <MultiSelectPrim.Label>Label</MultiSelectPrim.Label>
  <MultiSelectPrim.Trigger>
    <MultiSelectPrim.Value valueWrap={true} />
  </MultiSelectPrim.Trigger>
  <MultiSelectPrim.Portal>
    <MultiSelectPrim.Content>
      <MultiSelectPrim.Item value="option1">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 1" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
      <MultiSelectPrim.Item value="option2">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 2" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
    </MultiSelectPrim.Content>
  </MultiSelectPrim.Portal>
</MultiSelectPrim.Root>

그룹과 구분선 사용

import { MultiSelectPrim } from '@featuring-corp/components';
import { CoreCheckbox } from '@featuring-corp/components';
import { CoreTooltip } from '@featuring-corp/components';
import { IconWarningFilled, IconErrorFilled } from '@featuring-corp/icons';
import { flex } from '@styles/utils';
import { vars } from '@styles/theme.css';

<MultiSelectPrim.Root required width="240px">
  <MultiSelectPrim.Label tooltip="도움말">Label</MultiSelectPrim.Label>
  <MultiSelectPrim.Trigger>
    <MultiSelectPrim.Value />
  </MultiSelectPrim.Trigger>
  <MultiSelectPrim.HelperText>Helper Text</MultiSelectPrim.HelperText>
  <MultiSelectPrim.Portal>
    <MultiSelectPrim.Content>
      <MultiSelectPrim.Group>
        <MultiSelectPrim.GroupLabel>옵션 그룹 1</MultiSelectPrim.GroupLabel>
        <MultiSelectPrim.Item value="option1">
          {(selected) => (
            <CoreCheckbox 
              checked={selected} 
              label="Option 1" 
              onChange={() => {}} 
            />
          )}
        </MultiSelectPrim.Item>
        <MultiSelectPrim.Item value="option2">
          {(selected) => (
            <CoreCheckbox 
              checked={selected} 
              label="Option 2" 
              onChange={() => {}} 
            />
          )}
        </MultiSelectPrim.Item>
      </MultiSelectPrim.Group>
      <MultiSelectPrim.Separator />
      <MultiSelectPrim.Group>
        <MultiSelectPrim.GroupLabel>옵션 그룹 2</MultiSelectPrim.GroupLabel>
        <MultiSelectPrim.Item value="option3" label="Option 3">
          {(selected) => (
            <CoreCheckbox 
              checked={selected} 
              label={
                <div className={flex({ gap: 'spacing-100' })}>
                  <CoreTooltip text="경고!">
                    <IconWarningFilled size="12px" fill={vars.semantic.color.support.warning[3]} />
                  </CoreTooltip>
                  Option 3
                </div>
              }
              onChange={() => {}} 
            />
          )}
        </MultiSelectPrim.Item>
        <MultiSelectPrim.Item value="option4" label="Option 4">
          {(selected) => (
            <CoreCheckbox 
              checked={selected} 
              label={
                <div className={flex({ gap: 'spacing-100' })}>
                  <CoreTooltip text="에러!">
                    <IconErrorFilled size="12px" fill={vars.semantic.color.support.error[3]} />
                  </CoreTooltip>
                  Option 4
                </div>
              }
              onChange={() => {}} 
            />
          )}
        </MultiSelectPrim.Item>
      </MultiSelectPrim.Group>
      <MultiSelectPrim.Separator />
      <MultiSelectPrim.Item value="option5">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 5" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
    </MultiSelectPrim.Content>
  </MultiSelectPrim.Portal>
</MultiSelectPrim.Root>

커스텀 Value 표시

import { useState } from 'react';
import { MultiSelectPrim } from '@featuring-corp/components';
import { CoreCheckbox } from '@featuring-corp/components';
import { CoreStatusBadge } from '@featuring-corp/components';
import { flex } from '@styles/utils';

const [selected, setSelected] = useState<string[]>(['option1', 'option2']);

const handleValueChange = (value: string) => {
  if (selected.includes(value)) {
    setSelected(selected.filter((item) => item !== value));
  } else {
    setSelected([...selected, value]);
  }
};

const options = {
  option1: { label: 'Option 1' },
  option2: { label: 'Option 2' },
  option3: { label: 'Option 3' },
};

<MultiSelectPrim.Root 
  value={selected} 
  onValueChange={handleValueChange}
  required
  width="240px"
>
  <MultiSelectPrim.Label tooltip="도움말">Label</MultiSelectPrim.Label>
  <MultiSelectPrim.Trigger>
    <MultiSelectPrim.Value className={flex({ gap: 'spacing-100' })}>
      {(selectedList) => 
        selectedList.map(({ value, label }) => (
          <CoreStatusBadge key={value} text={label} type="tint" />
        ))
      }
    </MultiSelectPrim.Value>
  </MultiSelectPrim.Trigger>
  <MultiSelectPrim.Portal>
    <MultiSelectPrim.Content>
      {Object.entries(options).map(([value, { label }]) => (
        <MultiSelectPrim.Item key={value} value={value}>
          {(selected) => (
            <CoreCheckbox 
              checked={selected} 
              label={label} 
              onChange={() => {}} 
            />
          )}
        </MultiSelectPrim.Item>
      ))}
    </MultiSelectPrim.Content>
  </MultiSelectPrim.Portal>
</MultiSelectPrim.Root>

label prop을 사용한 아이템

import { MultiSelectPrim } from '@featuring-corp/components';
import { CoreCheckbox } from '@featuring-corp/components';

<MultiSelectPrim.Root required width="240px">
  <MultiSelectPrim.Label>Label</MultiSelectPrim.Label>
  <MultiSelectPrim.Trigger>
    <MultiSelectPrim.Value />
  </MultiSelectPrim.Trigger>
  <MultiSelectPrim.Portal>
    <MultiSelectPrim.Content>
      <MultiSelectPrim.Item value="option1" label="Option 1">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="커스텀 표시 텍스트" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
      <MultiSelectPrim.Item value="option2" label="Option 2">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="다른 커스텀 텍스트" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
    </MultiSelectPrim.Content>
  </MultiSelectPrim.Portal>
</MultiSelectPrim.Root>

상태와 크기 설정

import { useState } from 'react';
import { MultiSelectPrim } from '@featuring-corp/components';
import { CoreCheckbox } from '@featuring-corp/components';

const [selected, setSelected] = useState<string[]>(['option1']);

const handleValueChange = (value: string) => {
  if (selected.includes(value)) {
    setSelected(selected.filter((item) => item !== value));
  } else {
    setSelected([...selected, value]);
  }
};

const getStatus = () => {
  if (selected.includes('option4')) return 'error';
  else if (selected.includes('option3')) return 'warning';
  return 'none';
};

<MultiSelectPrim.Root 
  value={selected} 
  onValueChange={handleValueChange}
  status={getStatus()}
  size="lg" 
  width="300px"
  required
  disabled={false}
  readonly={false}
>
  <MultiSelectPrim.Label tooltip="도움말">Label</MultiSelectPrim.Label>
  <MultiSelectPrim.Trigger>
    <MultiSelectPrim.Value />
  </MultiSelectPrim.Trigger>
  <MultiSelectPrim.HelperText status={getStatus()}>
    상태에 따른 메시지가 표시됩니다
  </MultiSelectPrim.HelperText>
  <MultiSelectPrim.Portal>
    <MultiSelectPrim.Content height={200}>
      <MultiSelectPrim.Item value="option1">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 1" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
      <MultiSelectPrim.Item value="option2">
        {(selected) => (
          <CoreCheckbox 
            checked={selected} 
            label="Option 2" 
            onChange={() => {}} 
          />
        )}
      </MultiSelectPrim.Item>
    </MultiSelectPrim.Content>
  </MultiSelectPrim.Portal>
</MultiSelectPrim.Root>

Props

MultiSelectPrim.Root

가장 상위 컴포넌트로, Select 전체를 감싸는 역할을 합니다.
해당 컴포넌트를 통해 내부적으로 많은 기능을 컨트롤하기 때문에, 반드시 포함되어야하는 컴포넌트입니다.
Root 컴포넌트에 주입된 status, size, width, readonly, required, disabled props는 하위 컴포넌트에 모두 전달됩니다.

Prop

Type

MultiSelectPrim.Label

Select의 라벨을 표시합니다.

Prop

Type

MultiSelectPrim.Trigger

Select의 선택된 값을 표시합니다. button element로 되어 있습니다.

Prop

Type

MultiSelectPrim.Value

선택된 값을 표시합니다. Trigger 내부에서 사용합니다.

Prop

Type

MultiSelectPrim.HelperText

Select의 헬퍼 텍스트를 표시합니다.

Prop

Type

MultiSelectPrim.Portal

Select의 옵션 목록을 렌더링할 포탈입니다.

Select.SelectPortalProps 타입을 확장한 컴포넌트입니다.

MultiSelectPrim.Content

Select의 옵션 목록을 표시합니다.

Prop

Type

MultiSelectPrim.Item

옵션 목록 내부의 아이템입니다.
children에 작성한 내용이 선택 시에도 자동으로 적용됩니다.

Prop

Type

MultiSelectPrim.Group

옵션 목록 내부의 그룹입니다.

Select.SelectGroupProps 타입을 확장한 컴포넌트입니다.

MultiSelectPrim.GroupLabel

옵션 목록 내부의 그룹 라벨입니다.

Select.SelectLabelProps 타입을 확장한 컴포넌트입니다.

MultiSelectPrim.Separator

옵션 목록 내부의 구분선입니다.

Select.SelectSeparatorProps 타입을 확장한 컴포넌트입니다.

SelectStatus

Prop

Type

SelectSize

Prop

Type

CoreTooltipProps

CoreTooltip 컴포넌트의 props를 참조하세요.

스타일

기본 스타일 속성

Label:

  • display: flex
  • justify-content: flex-start
  • align-items: center
  • margin-bottom: spacing-150 (6px)
  • typography: heading[1]
  • color: text-2 (기본), text-4 (disabled)

Label Required:

  • margin-left: spacing-50 (2px)

Label Icon:

  • margin-left: spacing-100 (4px)

Select Trigger:

  • display: flex
  • justify-content: space-between
  • gap: spacing-200 (8px)
  • padding-x: spacing-250 (10px)
  • border-radius: radius-100
  • border: 1px solid border[1]
  • background-color: background-1 (기본), background-2 (hover)
  • color: text-1 (기본), text-5 (placeholder)
  • cursor: pointer
  • width: auto (기본값)

Select Value:

  • padding: 2px 0 (sm), 4px 0 (md), 8px 0 (lg)
  • overflow: hidden (valueWrap: off)
  • flex-wrap: nowrap (valueWrap: off), wrap (valueWrap: on)

Content Container:

  • background-color: background-1
  • padding: spacing-200 (8px)
  • border-radius: radius-100
  • elevation: elevation-8
  • width: auto (기본값)
  • height: auto (기본값)
  • overflow: auto

Select Item Box:

  • position: relative

Select Item Shadow Dom:

  • position: absolute
  • top: 0
  • left: 0
  • width: 0
  • height: 0
  • overflow: hidden
  • opacity: 0

Select Item:

  • display: flex
  • gap: spacing-150 (6px)
  • border-radius: radius-100
  • min-height: 28px
  • cursor: pointer (disabled: off), not-allowed (disabled: on)

Helper Text:

  • display: flex
  • gap: spacing-100 (4px)
  • margin-top: spacing-100 (4px)
  • typography: body[1]

Group Label:

  • padding-x: spacing-200 (8px)
  • padding-y: spacing-100 (4px)
  • color: text-3
  • typography: body[1]

Separator:

  • margin-y: spacing-300 (12px)
  • margin-x: spacing-200 (8px)
  • border-top: 1px solid border-default

Size Variants (Trigger)

  • sm: typography: body[1], min-height: 28px, height: max-content
  • md: typography: body[2], min-height: 32px, height: max-content
  • lg: typography: body[2], min-height: 40px, height: max-content

Size Variants (Item)

  • sm: typography: body[1], padding-y: spacing-50 (2px)
  • md: typography: body[2], padding-y: spacing-100 (4px)
  • lg: typography: body[2], padding-y: spacing-200 (8px)

Status Variants (Trigger)

  • none: border-color: border[1]
  • warning: border-color: support-warning[3]
  • error: border-color: support-error[3]

Helper Text Status Variants

  • none: color: text-3
  • warning: color: support-warning-3
  • error: color: support-error-3

Select Item States

  • Disabled (on): background-color: toggle-disabled-bg, color: toggle-disabled-text, cursor: not-allowed
  • Disabled (off): div[data-highlighted] + &: background-color: primary[10], color: primary[100], cursor: pointer

States (상태별 토큰)

Select Trigger:

  • Default: background-color: background-1, border: 1px solid border[1], color: text-1
  • Hover: background-color: background-2, border-color: border-2
  • Focus (not readonly, not disabled): border-color: primary[50]
  • Data-state="open": border-color: primary[50]
  • Readonly: border-color: border[2], background-color: background[4], color: text[1], cursor: auto
  • Disabled: border-color: border[2], background-color: background[4], color: text[4], cursor: not-allowed
  • Placeholder: color: text[5]