ToggleButton
Action
눌러서 켜고 끄는 상태를 전환하는 토글 버튼.
Usage
눌린 상태가 유지되는 버튼(단일은 pressed, 배타 그룹은 ToggleButton.Group). 2~5개 배타 전환 자체가 목적이면 SegmentedControl
import
import
import { ToggleButton } from '@mildang/design-system/ToggleButton';예제를 복사해 쓸 때 필요한 준비
• @mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.
• @mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.
API Reference
ToggleButton Props
Prop
Type
Default
boolean
false
ReactNode
지정 안 함
string
지정 안 함
boolean
false
ReactNode
지정 안 함
string
지정 안 함
boolean
false
"xl" | "lg" | "md" | "sm" | "xs"
md
ReactNode
지정 안 함
"text" | "icon_only"
text
string | (string & readonly string[])
지정 안 함
제어와 그룹
pressed와 onPressedChange를 사용해 단일 토글을 제어할 수 있습니다. ToggleButton.Group은 단일 또는 multiple 선택을 제공합니다.
상태와 크기
기본과 눌린 상태, 크기 5종, 비활성을 한 번에 봅니다 — 눌린 상태가 유지되는 것이 이 컴포넌트의 정체입니다.
코드
import { useState } from 'react'; import { ToggleButton } from '@mildang/design-system/ToggleButton';
export default function ToggleButtonControlledExample() { const [pressed, setPressed] = useState(false); return <ToggleButton type="text" outline pressed={pressed} onPressedChange={setPressed}>{pressed ? 'On' : 'Off'}</ToggleButton>; }
토글 그룹
단일·다중 선택 그룹을 사용합니다.
import { useState } from 'react'; import { ToggleButton } from '@mildang/design-system/ToggleButton';
export default function ToggleButtonGroupsExample() { const [single, setSingle] = useState<string[]>(['bold']); const [multiple, setMultiple] = useState<string[]>(['bold', 'italic']); const group = (value: string[], setter: (v: string[]) => void, isMultiple = false) => <ToggleButton.Group multiple={isMultiple} value={value} onValueChange={(d) => setter(d.value)}><ToggleButton value="bold" outline>Bold</ToggleButton><ToggleButton value="italic" outline>Italic</ToggleButton><ToggleButton value="underline" outline>Underline</ToggleButton></ToggleButton.Group>; return <div>{group(single, setSingle)}{group(multiple, setMultiple, true)}</div>; }
예제
Group · icon_only
import { ToggleButton } from '@mildang/design-system/ToggleButton';
import Star from '@mildang/icons/react/star-fill';
const ToggleButtonGroupIconOnlyExample = () => (
<ToggleButton.Group multiple defaultValue={['star1']}>
<ToggleButton value="star1" type="icon_only" outline>
<Star />
</ToggleButton>
<ToggleButton value="star2" type="icon_only" outline>
<Star />
</ToggleButton>
<ToggleButton value="star3" type="icon_only" outline>
<Star />
</ToggleButton>
</ToggleButton.Group>
);
export default ToggleButtonGroupIconOnlyExample;
Matrix
import { ToggleButton } from '@mildang/design-system/ToggleButton';
import { Text } from '@mildang/design-system/Text';
import Star from '@mildang/icons/react/star-fill';
import User from '@mildang/icons/react/user';
import CaretDown from '@mildang/icons/react/caret-down';
import { css } from '@mildang/styled-system/css';
/**
* Figma `_toggle_button_base` 상태 매트릭스.
* 열: default / hover / pressed / selected / selected_hover / selected_pressed / disabled
* 행: text·outline / text·no-outline / icon_only·outline / icon_only·no-outline
*
* hover/pressed 는 `storybook-addon-pseudo-states` 로 강제 표시한다.
* selected(_hover/_pressed) 는 ark-ui `defaultPressed` 로 시작 상태를 켜둔다.
*/
const STATE_ROWS = [
{ key: 'text-outline', label: 'Text · outline', type: 'text' as const, outline: true },
{ key: 'text-noOutline', label: 'Text · no-outline', type: 'text' as const, outline: false },
{ key: 'icon-outline', label: 'IconOnly · outline', type: 'icon_only' as const, outline: true },
{ key: 'icon-noOutline', label: 'IconOnly · no-outline', type: 'icon_only' as const, outline: false },
] as const;
type StateCol = {
key: string;
label: string;
pseudo?: 'hover' | 'active';
pressed?: boolean;
disabled?: boolean;
};
const STATE_COLS: readonly StateCol[] = [
{ key: 'default', label: 'Default' },
{ key: 'hover', label: 'Hover', pseudo: 'hover' },
{ key: 'pressed', label: 'Pressed', pseudo: 'active' },
{ key: 'selected', label: 'Selected', pressed: true },
{ key: 'selectedHover', label: 'Selected · Hover', pressed: true, pseudo: 'hover' },
{ key: 'selectedPressed', label: 'Selected · Pressed', pressed: true, pseudo: 'active' },
{ key: 'disabled', label: 'Disabled', disabled: true },
{ key: 'selectedDisabled', label: 'Selected · Disabled', pressed: true, disabled: true },
];
const stateCellId = (rowKey: string, colKey: string) => `tb-state-${rowKey}-${colKey}`;
// gridTemplateColumns 는 Panda 정적 추출을 위해 상수 문자열 (STATE_COLS.length = 8)
const stateMatrixWrap = css({
display: 'grid',
gridTemplateColumns: '160px repeat(8, minmax(96px, auto))',
gap: '8px 12px',
alignItems: 'center',
padding: '8px',
});
type ToggleSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
type ToggleButtonMatrixProps = { size: ToggleSize };
const STORY_DEFAULT_ARGS = { size: 'md' } as ToggleButtonMatrixProps;
const ToggleButtonMatrixExampleRender = ({ size }: ToggleButtonMatrixProps) => (
<div className={stateMatrixWrap}>
<div />
{STATE_COLS.map((col) => (
<Text key={col.key} variant="caption-lg-medium" color="neutral.text.low" textAlign="center">
{col.label}
</Text>
))}
{STATE_ROWS.flatMap((row) => [
<Text key={`${row.key}-label`} variant="caption-lg-medium">
{row.label}
</Text>,
...STATE_COLS.map((col) => {
const id = stateCellId(row.key, col.key);
if (row.type === 'icon_only') {
return (
<ToggleButton
key={col.key}
id={id}
type="icon_only"
size={size}
outline={row.outline}
defaultPressed={col.pressed}
disabled={col.disabled}
aria-label={`${row.label} · ${col.label}`}
justifySelf="center"
>
<Star />
</ToggleButton>
);
}
return (
<ToggleButton
key={col.key}
id={id}
type="text"
size={size}
outline={row.outline}
defaultPressed={col.pressed}
disabled={col.disabled}
startIcon={<User />}
endIcon={<CaretDown />}
>
Text
</ToggleButton>
);
}),
])}
</div>
);
export default function ToggleButtonMatrixExample(props: Partial<ToggleButtonMatrixProps>) {
const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ToggleButtonMatrixProps;
return ToggleButtonMatrixExampleRender(mergedProps);
}
Size matrix
import { Text } from '@mildang/design-system/Text';
import { ToggleButton } from '@mildang/design-system/ToggleButton';
import Star from '@mildang/icons/react/star-fill';
import User from '@mildang/icons/react/user';
import CaretDown from '@mildang/icons/react/caret-down';
import { css } from '@mildang/styled-system/css';
/**
* 사이즈별 매트릭스 (xs / sm / md / lg / xl).
* 각 사이즈에서 `text·outline` / `text·no-outline` / `icon_only·outline` / `icon_only·no-outline` 4가지를 함께 보여준다.
* 상태(hover/pressed 등)는 StateMatrix 에서 확인하고, 여기서는 사이즈 비교에 집중한다.
*/
const SIZE_LIST = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
const SIZE_VARIANTS = [
{ key: 'text-outline', label: 'Text · outline', type: 'text' as const, outline: true },
{ key: 'text-noOutline', label: 'Text · no-outline', type: 'text' as const, outline: false },
{ key: 'icon-outline', label: 'IconOnly · outline', type: 'icon_only' as const, outline: true },
{ key: 'icon-noOutline', label: 'IconOnly · no-outline', type: 'icon_only' as const, outline: false },
] as const;
// gridTemplateColumns 는 Panda 정적 추출을 위해 상수 문자열 (SIZE_VARIANTS.length = 4).
// max-content 로 각 컬럼이 컨텐츠(xl 크기 버튼) 폭에 hug 되게 하여 불필요한 여백을 없앤다.
const sizeMatrixWrap = css({
display: 'grid',
gridTemplateColumns: '48px repeat(4, max-content)',
gap: '8px 24px',
alignItems: 'center',
justifyItems: 'center',
padding: '8px',
});
const ToggleButtonSizeMatrixExample = () => (
<div className={sizeMatrixWrap}>
<div />
{SIZE_VARIANTS.map((v) => (
<Text key={v.key} variant="caption-lg-medium" color="neutral.text.low" textAlign="center">
{v.label}
</Text>
))}
{SIZE_LIST.flatMap((size) => [
<Text key={`${size}-label`} variant="caption-lg-medium" justifySelf="start">
{size}
</Text>,
...SIZE_VARIANTS.map((v) =>
v.type === 'icon_only' ? (
<ToggleButton
key={`${size}-${v.key}`}
type="icon_only"
size={size}
outline={v.outline}
aria-label={`${size} · ${v.label}`}
>
<Star />
</ToggleButton>
) : (
<ToggleButton
key={`${size}-${v.key}`}
type="text"
size={size}
outline={v.outline}
startIcon={<User />}
endIcon={<CaretDown />}
>
Text
</ToggleButton>
),
),
])}
</div>
);
export default ToggleButtonSizeMatrixExample;
Default
import type { ComponentProps } from 'react';
import { ToggleButton } from '@mildang/design-system/ToggleButton';
const STORY_DEFAULT_ARGS = { ...({}), ...({ type: 'text', children: 'Label' }) } as ComponentProps<typeof ToggleButton>;
export default function ToggleButtonDefaultExample(props: Partial<ComponentProps<typeof ToggleButton>> = {}) {
const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ToggleButton>;
return <ToggleButton {...mergedProps} />;
}
Pressed
import type { ComponentProps } from 'react';
import { ToggleButton } from '@mildang/design-system/ToggleButton';
const STORY_DEFAULT_ARGS = { ...({}), ...({ type: 'text', defaultPressed: true, children: 'Label' }) } as ComponentProps<typeof ToggleButton>;
export default function ToggleButtonPressedExample(props: Partial<ComponentProps<typeof ToggleButton>> = {}) {
const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ToggleButton>;
return <ToggleButton {...mergedProps} />;
}
Outline
import type { ComponentProps } from 'react';
import { ToggleButton } from '@mildang/design-system/ToggleButton';
const STORY_DEFAULT_ARGS = { ...({}), ...({ type: 'text', outline: true, children: 'Label' }) } as ComponentProps<typeof ToggleButton>;
export default function ToggleButtonOutlineExample(props: Partial<ComponentProps<typeof ToggleButton>> = {}) {
const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ToggleButton>;
return <ToggleButton {...mergedProps} />;
}
IconOnly
import type { ComponentProps } from 'react';
import Star from '@mildang/icons/react/star-fill';
import { ToggleButton } from '@mildang/design-system/ToggleButton';
const STORY_DEFAULT_ARGS = { ...({}), ...({ type: 'icon_only', children: <Star /> }) } as ComponentProps<typeof ToggleButton>;
export default function ToggleButtonIconOnlyExample(props: Partial<ComponentProps<typeof ToggleButton>> = {}) {
const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ToggleButton>;
return <ToggleButton {...mergedProps} />;
}
text · startIcon + endIcon
import type { ComponentProps } from 'react';
import User from '@mildang/icons/react/user';
import ChevronDown from '@mildang/icons/react/chevron-down';
import { ToggleButton } from '@mildang/design-system/ToggleButton';
const STORY_DEFAULT_ARGS = { ...({}), ...({
type: 'text',
outline: true,
startIcon: <User />,
endIcon: <ChevronDown />,
children: 'Label',
}) } as ComponentProps<typeof ToggleButton>;
export default function ToggleButtonWithStartEndIconExample(props: Partial<ComponentProps<typeof ToggleButton>> = {}) {
const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ToggleButton>;
return <ToggleButton {...mergedProps} />;
}