使用自定义select组件

This commit is contained in:
YI FANG
2025-12-02 09:51:02 +08:00
parent 8a2f1ef95d
commit a1b8d12c13
3 changed files with 138 additions and 48 deletions

View File

@@ -1,61 +1,65 @@
import { useState } from 'react';
import { BOOKING_DOCTORS } from '../../data/mockData';
import { Button, Input } from '../ui';
import { Button, Input, Select } from '../ui';
interface BookingModalProps {
doctor: (typeof BOOKING_DOCTORS)[number];
onClose: () => void;
}
export const BookingModal = ({ doctor, onClose }: BookingModalProps) => (
<div className='fixed inset-0 z-40 flex items-center justify-center bg-black/30'>
<div className='w-[520px] max-w-[95vw] bg-white rounded-3xl shadow-xl overflow-hidden text-sm'>
<div className='px-4 py-3 border-b flex items-center justify-between'>
<div className='font-semibold'> · {doctor.name}</div>
<button className='text-xs text-gray-500' onClick={onClose}>
</button>
</div>
<div className='px-4 py-4 bg-gray-50/60 space-y-3 text-xs text-gray-700'>
<div className='grid grid-cols-2 gap-3'>
<div>
<select className='mt-1 w-full rounded-2xl border px-3 py-1.5 bg-white outline-none text-xs'>
<option></option>
<option></option>
</select>
</div>
<div>
<Input placeholder='如:专家门诊咨询' className='mt-1' />
</div>
<div>
<select className='mt-1 w-full rounded-2xl border px-3 py-1.5 bg-white outline-none text-xs'>
<option></option>
<option></option>
</select>
</div>
<div>
<Input placeholder='例如2025-11-20 上午' className='mt-1' />
</div>
export const BookingModal = ({ doctor, onClose }: BookingModalProps) => {
const [paymentMethod, setPaymentMethod] = useState('自费');
const [isCustomized, setIsCustomized] = useState('否');
return (
<div className='fixed inset-0 z-40 flex items-center justify-center bg-black/30'>
<div className='w-[520px] max-w-[95vw] bg-white rounded-3xl shadow-xl overflow-hidden text-sm'>
<div className='px-4 py-3 border-b flex items-center justify-between'>
<div className='font-semibold'> · {doctor.name}</div>
<button className='text-xs text-gray-500' onClick={onClose}>
</button>
</div>
<div>
<textarea
className='w-full mt-1 rounded-2xl border px-3 py-2 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[72px]'
placeholder='可填写病情简要、既往史、特殊需求等信息'
/>
</div>
<div className='flex items-center justify-between text-[11px] text-gray-500'>
<span>
{doctor.name}{doctor.dept}
</span>
<Button></Button>
<div className='px-4 py-4 bg-gray-50/60 space-y-3 text-xs text-gray-700'>
<div className='grid grid-cols-2 gap-3'>
<div>
<Select
options={['自费', '单位结算']}
value={paymentMethod}
onChange={setPaymentMethod}
/>
</div>
<div>
<Input placeholder='如:专家门诊咨询' className='mt-1' />
</div>
<div>
<Select options={['否', '是']} value={isCustomized} onChange={setIsCustomized} />
</div>
<div>
<Input placeholder='例如2025-11-20 上午' className='mt-1' />
</div>
</div>
<div>
<textarea
className='w-full mt-1 rounded-2xl border px-3 py-2 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[72px]'
placeholder='可填写病情简要、既往史、特殊需求等信息'
/>
</div>
<div className='flex items-center justify-between text-[11px] text-gray-500'>
<span>
{doctor.name}{doctor.dept}
</span>
<Button></Button>
</div>
</div>
</div>
</div>
</div>
);
);
};

View File

@@ -0,0 +1,85 @@
import { useEffect, useRef, useState } from 'react';
import { cls } from '../../utils/cls';
interface SelectProps {
options: string[];
value?: string;
onChange?: (value: string) => void;
className?: string;
placeholder?: string;
}
export const Select = ({ options, value, onChange, className = '', placeholder }: SelectProps) => {
const [isOpen, setIsOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(value || options[0] || '');
const selectRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (value !== undefined) {
setSelectedValue(value);
}
}, [value]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (selectRef.current && !selectRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
const handleSelect = (option: string) => {
setSelectedValue(option);
onChange?.(option);
setIsOpen(false);
};
return (
<div ref={selectRef} className={cls('relative', className)}>
<button
type='button'
onClick={() => setIsOpen(!isOpen)}
className={cls(
'mt-1 w-full rounded-2xl border px-3 py-1.5 bg-white outline-none text-xs',
'flex items-center justify-between',
'hover:border-gray-300 focus:ring-2 focus:ring-gray-200 transition-colors',
)}
>
<span className={selectedValue ? 'text-gray-900' : 'text-gray-400'}>
{selectedValue || placeholder || '请选择'}
</span>
<span className={cls('text-gray-400 transition-transform text-[10px]', isOpen && 'rotate-180')}>
</span>
</button>
{isOpen && (
<div className='absolute z-50 w-full mt-1 bg-white border rounded-2xl shadow-lg overflow-hidden'>
{options.map((option) => (
<button
key={option}
type='button'
onClick={() => handleSelect(option)}
className={cls(
'w-full px-3 py-2 text-xs text-left transition-colors',
'hover:bg-gray-50',
selectedValue === option && 'bg-gray-100 text-gray-900 font-medium',
selectedValue !== option && 'text-gray-700',
)}
>
{option}
</button>
))}
</div>
)}
</div>
);
};

View File

@@ -3,5 +3,6 @@ export * from './Button';
export * from './Card';
export * from './InfoCard';
export * from './Input';
export * from './Select';