MenuButton
Action
트리거와 명령 목록을 하나로 묶는 메뉴 버튼.
Usage
한 버튼 아래에 서로 관련된 여러 행동을 제공할 때 도구 모음에서 보조 행동을 압축할 때
import
import
import { MenuButton } from '@mildang/design-system/MenuButton';예제를 복사해 쓸 때 필요한 준비
• @mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.
• @mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.
API Reference
MenuButton Props
Prop
Type
Default
ReactNode
지정 안 함
"end" | "start" | "center"
지정 안 함
string
지정 안 함
Omit<MenuContentProps, "children" | "sideOffset" | "align" | "side">
지정 안 함
boolean
지정 안 함
boolean
지정 안 함
ReactNode
지정 안 함
ReactNode
지정 안 함
(open: boolean) => void
지정 안 함
(value: string) => void
지정 안 함
boolean
지정 안 함
boolean
false
string
지정 안 함
"bottom" | "left" | "right" | "top"
지정 안 함
number
4
"xs" | "sm" | "md" | "lg" | "xl"
지정 안 함
ReactNode
지정 안 함
ReactNode
지정 안 함
"primary" | "impact" | "secondary" | "tertiary" | "quaternary"
지정 안 함
사용 가이드
권장
- 트리거 레이블만으로 메뉴의 목적을 알 수 있게 한다.
- 메뉴 항목은 짧고 구체적인 행동 문구로 쓴다.
지양
- 하나의 행동만 있는 경우 MenuButton을 사용하지 않는다.
- 접근 가능한 이름 없는 트리거를 만들지 않는다.
예제
기본 사용
기본 IconButton 트리거와 두 메뉴 그룹을 조합합니다.
import { MenuButton } from '@mildang/design-system/MenuButton';
import { IconButton } from '@mildang/design-system/IconButton';
import StarFill from '@mildang/icons/react/star-fill';
import { Menu } from '@mildang/design-system/Menu';
const MenuButtonDefaultExample = () => (
<MenuButton
trigger={
<IconButton variant="primary" aria-label="즐겨찾기 메뉴 열기">
<StarFill />
</IconButton>
}
>
<Menu.Group>
<Menu.Label>Label</Menu.Label>
<Menu.Item>Option</Menu.Item>
<Menu.Item>Option</Menu.Item>
<Menu.Item>Option</Menu.Item>
</Menu.Group>
<Menu.Separator />
<Menu.Group>
<Menu.Label>Label</Menu.Label>
<Menu.Item>Option</Menu.Item>
<Menu.Item>Option</Menu.Item>
<Menu.Item>Option</Menu.Item>
</Menu.Group>
</MenuButton>
);
export default MenuButtonDefaultExample;
Chevron이 있는 레이블 트리거
import { MenuButton } from '@mildang/design-system/MenuButton';
import ChevronDown from '@mildang/icons/react/chevron-down';
import { Menu } from '@mildang/design-system/Menu';
const MenuButtonWithChevronExample = () => (
<MenuButton label="정렬" variant="primary" endIcon={<ChevronDown />}>
<Menu.Item>이름순</Menu.Item>
<Menu.Item>날짜순</Menu.Item>
<Menu.Item>인기순</Menu.Item>
</MenuButton>
);
export default MenuButtonWithChevronExample;
커스텀 트리거
import { MenuButton } from '@mildang/design-system/MenuButton';
import { IconButton } from '@mildang/design-system/IconButton';
import MoreHoriz from '@mildang/icons/react/more-horiz';
import { Menu } from '@mildang/design-system/Menu';
const MenuButtonCustomTriggerExample = () => (
<MenuButton
trigger={
<IconButton variant="tertiary" aria-label="더보기 메뉴 열기">
<MoreHoriz />
</IconButton>
}
>
<Menu.Item>복사</Menu.Item>
<Menu.Item>수정</Menu.Item>
<Menu.Item>삭제</Menu.Item>
</MenuButton>
);
export default MenuButtonCustomTriggerExample;
그룹과 레이블
import { MenuButton } from '@mildang/design-system/MenuButton';
import { Menu } from '@mildang/design-system/Menu';
const MenuButtonWithGroupsAndLabelExample = () => (
<MenuButton label="파일" variant="primary">
<Menu.Group>
<Menu.Label>편집</Menu.Label>
<Menu.Item>복사</Menu.Item>
<Menu.Item>붙여넣기</Menu.Item>
</Menu.Group>
<Menu.Separator />
<Menu.Group>
<Menu.Label>관리</Menu.Label>
<Menu.Item description="이 폴더로 이동">이동</Menu.Item>
<Menu.Item>삭제</Menu.Item>
</Menu.Group>
</MenuButton>
);
export default MenuButtonWithGroupsAndLabelExample;
FloatingButton 트리거
import { MenuButton } from '@mildang/design-system/MenuButton';
import { FloatingButton } from '@mildang/design-system/FloatingButton';
import Add from '@mildang/icons/react/add';
import { Menu } from '@mildang/design-system/Menu';
const MenuButtonWithFloatingButtonExample = () => (
<MenuButton trigger={<FloatingButton icon={<Add />} aria-label="새 액션 메뉴 열기" />}>
<Menu.Item>글쓰기</Menu.Item>
<Menu.Item>이미지 업로드</Menu.Item>
<Menu.Separator />
<Menu.Item>파일 첨부</Menu.Item>
</MenuButton>
);
export default MenuButtonWithFloatingButtonExample;
비활성
import { MenuButton } from '@mildang/design-system/MenuButton';
import { Menu } from '@mildang/design-system/Menu';
const MenuButtonDisabledExample = () => (
<MenuButton label="비활성" variant="primary" disabled>
<Menu.Item>Option A</Menu.Item>
</MenuButton>
);
export default MenuButtonDisabledExample;
검색 가능한 메뉴
코드
import { MenuButton } from '@mildang/design-system/MenuButton';
import { Menu } from '@mildang/design-system/Menu';
import { forwardRef } from 'react';
import { cx } from '@mildang/styled-system/css';
import { menu } from '@mildang/styled-system/recipes';
import Search from '@mildang/icons/react/search';
import { useMemo, useState, type ReactNode } from 'react';
const MenuListEmptyState = (() => {
const menuStyles = menu();
/** @internal MenuList 내부 재료. 앱 코드는 MenuButton/Select/Combobox wrapper를 사용하세요. */
interface MenuListEmptyStateProps {
children: ReactNode;
className?: string;
}
/** @internal MenuList 내부 재료. 앱 코드는 MenuButton/Select/Combobox wrapper를 사용하세요. */
const MenuListEmptyState = forwardRef<HTMLDivElement, MenuListEmptyStateProps>(
({ children, className, ...rest }, ref) => {
return (
<div ref={ref} className={cx(menuStyles.emptyState, className)} {...rest}>
{children}
</div>
);
},
);
MenuListEmptyState.displayName = 'MenuListEmptyState';
return MenuListEmptyState;
})();
// SearchableStory 렌더 헬퍼 — MenuButton 은 controlled 상태를 위해 caller 가 useState 를 잡아야 하므로
// 재사용·가독성을 위해 wrapper 컴포넌트로 분리
const MenuButtonSearchableWrapper = ({
items,
children,
}: {
items: string[];
children: (filtered: string[]) => ReactNode;
}) => {
const [search, setSearch] = useState('');
const filtered = useMemo(
() => items.filter((it) => it.toLowerCase().includes(search.toLowerCase())),
[items, search],
);
return (
<MenuButton
label="메뉴 열기"
variant="secondary"
searchable
searchPlaceholder="검색"
onSearchChange={setSearch}
>
{children(filtered)}
</MenuButton>
);
};
const MenuButtonSearchableExample = () => {
const items = ['옵션 1', '옵션 2', '옵션 3', '옵션 A', '옵션 B', '옵션 C'];
return (
<MenuButtonSearchableWrapper items={items}>
{(filtered) =>
filtered.length > 0 ? (
filtered.map((label) => <Menu.Item key={label}>{label}</Menu.Item>)
) : (
<MenuListEmptyState>
<Search />
검색 결과 없음
</MenuListEmptyState>
)
}
</MenuButtonSearchableWrapper>
);
};
export default MenuButtonSearchableExample;