zyc ffe92f7b15 Initial commit: 即梦视频生成平台
- web/: React + Vite + TypeScript 前端
- backend/: Django + DRF + SimpleJWT 后端
- prototype/: HTML 设计原型
- docs/: PRD 和设计评审文档
- test: 单元测试 + E2E 极限测试
2026-03-13 09:59:33 +08:00

61 lines
1.7 KiB
TypeScript

import { useState, useRef, useEffect, type ReactNode } from 'react';
import styles from './Dropdown.module.css';
interface DropdownItem {
label: string;
value: string;
icon?: ReactNode;
}
interface DropdownProps {
items: DropdownItem[];
value: string;
onSelect: (value: string) => void;
trigger: ReactNode;
minWidth?: number;
}
export function Dropdown({ items, value, onSelect, trigger, minWidth = 150 }: DropdownProps) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClick(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener('mousedown', handleClick);
return () => document.removeEventListener('mousedown', handleClick);
}, []);
return (
<div className={styles.wrapper} ref={ref}>
<div onClick={() => setOpen(!open)}>
{trigger}
</div>
<div
className={`${styles.menu} ${open ? styles.open : ''}`}
style={{ minWidth }}
>
{items.map((item) => (
<div
key={item.value}
className={`${styles.item} ${value === item.value ? styles.selected : ''}`}
onClick={() => {
onSelect(item.value);
setOpen(false);
}}
>
{item.icon && <span className={styles.itemIcon}>{item.icon}</span>}
<span>{item.label}</span>
<svg className={styles.check} width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
))}
</div>
</div>
);
}