/* ============================================================================ * 서명전에 — TDS Menu 컴포넌트 (드롭다운) * · 6 개 서브: * Menu.Dropdown : 드롭다운 패널 (자식들을 감싸는 컨테이너) * Menu.Header : 패널 안 작은 라벨 * Menu.DropdownItem : left / right 슬롯이 있는 메뉴 항목 * Menu.DropdownIcon : 슬롯에 들어가는 아이콘 헬퍼 (URL or IOSIcon) * Menu.DropdownCheckItem : DropdownItem + 자동 체크박스 * Menu.Trigger : 자식을 감싸 클릭하면 드롭다운을 띄워 줘요. * * · MenuContext 로 DropdownItem 의 onClick 후 자동 close. CheckItem 은 닫지 않음 * (스펙대로 — 다중 선택을 위해). * · Trigger 는 controlled (open + onOpenChange) / uncontrolled (defaultOpen). * · 외부 클릭 + ESC 로 닫혀요. document mousedown / keydown 리스너. * · placement 12 종 — CSS 에서 absolute 위치를 결정. * · 패널만 standalone 으로도 쓸 수 있어요 (예: 직접 위치 잡고 싶을 때). * ========================================================================== */ const __MENU_PLACEMENTS = { "top-start": 1, "top-center": 1, "top-end": 1, "bottom-start": 1, "bottom-center": 1, "bottom-end": 1, "left-start": 1, "left-center": 1, "left-end": 1, "right-start": 1, "right-center": 1, "right-end": 1, }; // MenuContext: DropdownItem 의 자동 close 를 위해 close 핸들러를 내려보내요. const __MenuContext = React.createContext({ close: null }); // ---------- Dropdown (panel) ---------- function MenuDropdown({ children, className = "", style, id, ...rest }) { const cls = ["tds-menu-dropdown", className].filter(Boolean).join(" "); return ( ); } // ---------- Header ---------- function MenuHeader({ children, className = "", style, ...rest }) { const cls = ["tds-menu-dropdown__header", className].filter(Boolean).join(" "); return (
{children}
); } // ---------- DropdownItem ---------- function MenuDropdownItem({ children, left, right, onClick, disabled = false, className = "", style, closeOnClick = true, // 내부 옵션 — DropdownCheckItem 에서 false 로 넘김 ...rest }) { const ctx = React.useContext(__MenuContext); const handleClick = function (e) { if (disabled) return; if (typeof onClick === "function") onClick(e); if (closeOnClick && ctx && typeof ctx.close === "function") ctx.close(); }; const cls = ["tds-menu-dropdown__item", className].filter(Boolean).join(" "); return ( ); } // ---------- DropdownIcon ---------- // 슬롯에 들어가는 아이콘 헬퍼. // · name 이 URL/경로면 로 렌더 (mono 면 mask 트릭으로 currentColor 칠하기) // · 그 외엔 IOSIcon 으로 위임 (있을 때만) function MenuDropdownIcon({ name, mono, size, className = "", style, ...rest }) { if (!name) return null; const isUrl = /^(https?:|\/|\.{1,2}\/)/i.test(name) || /\.(svg|png|jpe?g|gif|webp)(?:\?|#|$)/i.test(name); // mono 추론: 파일명에 -mono 들어 있으면 자동 mono 로 봐요. const inferredMono = typeof mono === "boolean" ? mono : isUrl && /-mono(?:\.[^/?#]+)?(?:\?[^#]*)?(?:#.*)?$/i.test(name); const cls = [ "tds-menu-dropdown__icon", inferredMono ? "tds-menu-dropdown__icon--mono" : "", className, ].filter(Boolean).join(" "); const px = typeof size === "number" ? `${size}px` : (size || undefined); const mergedStyle = px ? Object.assign({ width: px, height: px }, style) : style; if (isUrl) { if (inferredMono) { // mask + currentColor 트릭 const maskStyle = Object.assign( { WebkitMaskImage: `url("${name}")`, maskImage: `url("${name}")`, }, mergedStyle ); return ( ); } return ( ); } // IOSIcon 위임 if (window.IOSIcon) { return ( ); } // 폴백 — 빈 박스 return