修复角色中心组件中的类型不匹配错误,改进skinId和capeId的类型处理逻辑
This commit is contained in:
185
src/components/ui/select.tsx
Normal file
185
src/components/ui/select.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import * as React from 'react';
|
||||
|
||||
// 创建Select上下文
|
||||
const SelectContext = React.createContext<{
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
} | null>(null);
|
||||
|
||||
// Select组件接口
|
||||
export interface SelectProps {
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// SelectTrigger组件接口
|
||||
export interface SelectTriggerProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// SelectValue组件接口
|
||||
export interface SelectValueProps {
|
||||
placeholder?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
// SelectContent组件接口
|
||||
export interface SelectContentProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// SelectItem组件接口
|
||||
export interface SelectItemProps {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select组件 - 管理选择器的状态
|
||||
*/
|
||||
export const Select: React.FC<SelectProps> = ({
|
||||
defaultValue = '',
|
||||
value: valueProp,
|
||||
onValueChange,
|
||||
children,
|
||||
className = ''
|
||||
}) => {
|
||||
const [valueState, setValueState] = React.useState(defaultValue);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const value = valueProp !== undefined ? valueProp : valueState;
|
||||
const setValue = valueProp !== undefined && onValueChange
|
||||
? onValueChange
|
||||
: setValueState;
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (!target.closest(`.${className}`) && !target.closest('.select-trigger')) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}
|
||||
}, [open, className]);
|
||||
|
||||
return (
|
||||
<SelectContext.Provider value={{ value, setValue, open, setOpen }}>
|
||||
<div className={className}>{children}</div>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* SelectTrigger组件 - 选择器的触发器按钮
|
||||
*/
|
||||
export const SelectTrigger: React.FC<SelectTriggerProps> = ({ children, className = '' }) => {
|
||||
const context = React.useContext(SelectContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('SelectTrigger must be used within a Select component');
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
context.setOpen(!context.open);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={`px-4 py-2 border rounded-md flex items-center justify-between w-full select-trigger ${className}`}
|
||||
>
|
||||
{children}
|
||||
<span className="ml-2">▼</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* SelectValue组件 - 显示当前选中的值或占位符
|
||||
*/
|
||||
export const SelectValue: React.FC<SelectValueProps> = ({ placeholder, children }) => {
|
||||
const context = React.useContext(SelectContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('SelectValue must be used within a Select component');
|
||||
}
|
||||
|
||||
if (context.value && children) {
|
||||
return <span>{children}</span>;
|
||||
}
|
||||
|
||||
return placeholder ? (
|
||||
<span className="text-gray-500">{placeholder}</span>
|
||||
) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* SelectContent组件 - 包含选项的下拉菜单
|
||||
*/
|
||||
export const SelectContent: React.FC<SelectContentProps> = ({ children, className = '' }) => {
|
||||
const context = React.useContext(SelectContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('SelectContent must be used within a Select component');
|
||||
}
|
||||
|
||||
if (!context.open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`absolute mt-1 border rounded-md bg-white shadow-lg z-10 max-h-60 overflow-auto ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* SelectItem组件 - 选择器中的单个选项
|
||||
*/
|
||||
export const SelectItem: React.FC<SelectItemProps> = ({
|
||||
value,
|
||||
children,
|
||||
className = ''
|
||||
}) => {
|
||||
const context = React.useContext(SelectContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('SelectItem must be used within a Select component');
|
||||
}
|
||||
|
||||
const isSelected = context.value === value;
|
||||
|
||||
const handleClick = () => {
|
||||
context.setValue(value);
|
||||
context.setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={`block w-full text-left px-4 py-2 hover:bg-gray-100 ${isSelected ? 'bg-blue-50 text-blue-500' : ''} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
Reference in New Issue
Block a user