@ -16,7 +16,7 @@
* @date 2026 - 01 - 02
* /
import React , { JSX , ReactNode , useState , useEffect } from 'react' ;
import React , { JSX , ReactNode , useState , useEffect , useCallback } from 'react' ;
import { Check , X , CheckCircle , AlertCircle , Info , AlertTriangle , Eye , EyeOff } from 'lucide-react' ;
// =========================================================
@ -206,37 +206,53 @@ export const Select: React.FC<SelectProps> = ({
const displayText = selectedOption ? . label || placeholder ;
/ * *
* 面 板 开 关 切 换 函 数
* @description
* 包 含 复 杂 的 边 界 计 算 逻 辑 : 当 开 启 时 , 通 过 getBoundingClientRect ( ) 获 取 按 钮 的 几 何 矩 阵 。
* 计 算 并 更 新 下 拉 菜 单 的 位 置
* /
const handleToggle = ( ) = > {
if ( disabled ) return ;
if ( ! isOpen && buttonRef . current ) {
/ * * * 策 略 : 对 于 M i n i m a l 变 体 , 尝 试 向 上 追 溯 带 边 框 的 物 理 容 器 ,
* 以 确 保 下 拉 菜 单 的 对 齐 视 觉 一 致 性 。
* /
let targetElement : HTMLElement | null = buttonRef . current ;
if ( variant === 'minimal' ) {
let parent = buttonRef . current . parentElement ;
while ( parent && parent !== document . body ) {
const style = window . getComputedStyle ( parent ) ;
if ( style . borderWidth && parseFloat ( style . borderWidth ) > 0 ) {
targetElement = parent ;
break ;
}
parent = parent . parentElement ;
const updatePosition = useCallback ( ( ) = > {
if ( ! isOpen || ! buttonRef . current ) return ;
let targetElement : HTMLElement | null = buttonRef . current ;
// Minimal 变体向上查找边框容器, Default 变体直接基于按钮定位
if ( variant === 'minimal' ) {
let parent = buttonRef . current . parentElement ;
while ( parent && parent !== document . body ) {
const style = window . getComputedStyle ( parent ) ;
if ( style . borderWidth && parseFloat ( style . borderWidth ) > 0 ) {
targetElement = parent ;
break ;
}
parent = parent . parentElement ;
}
}
// 物理坐标重算
const rect = targetElement ! . getBoundingClientRect ( ) ;
setMenuPosition ( {
top : rect.bottom + 4 ,
left : rect.left ,
width : rect.width
} ) ;
const rect = targetElement ! . getBoundingClientRect ( ) ;
setMenuPosition ( {
top : rect.bottom + 4 ,
left : rect.left ,
width : rect.width
} ) ;
} , [ isOpen , variant ] ) ;
/ * *
* 监 听 滚 动 和 调 整 大 小 事 件 , 实 时 更 新 位 置
* /
useEffect ( ( ) = > {
if ( isOpen ) {
updatePosition ( ) ;
window . addEventListener ( 'scroll' , updatePosition , true ) ;
window . addEventListener ( 'resize' , updatePosition ) ;
}
return ( ) = > {
window . removeEventListener ( 'scroll' , updatePosition , true ) ;
window . removeEventListener ( 'resize' , updatePosition ) ;
} ;
} , [ isOpen , updatePosition ] ) ;
/ * *
* 面 板 开 关 切 换 函 数
* /
const handleToggle = ( ) = > {
if ( disabled ) return ;
setIsOpen ( ! isOpen ) ;
} ;
@ -282,7 +298,7 @@ export const Select: React.FC<SelectProps> = ({
< / svg >
< / button >
{ /* 下拉面板:采用 Fixed 布局及 Z-Index 策略确保不被层级覆盖 */}
{ /* 下拉面板:采用 Fixed 布局及 动态位置更新策略,确保不被遮挡且紧贴触发元素 */}
{ isOpen && (
< div
className = "fixed bg-white border border-gray-200 rounded-lg shadow-md overflow-hidden animate-in fade-in zoom-in-95 duration-150"
@ -294,7 +310,7 @@ export const Select: React.FC<SelectProps> = ({
} }
>
{ /* 内部滚动区:最大高度限制为 5 个标准列表项的高度 */ }
< div className = "overflow-y- auto py-1" style = { { maxHeight : 'calc(5 * 44px)' } } >
< div className = "overflow-y- scroll py-1" style = { { maxHeight : 'calc(5 * 44px)' } } >
{ options . map ( ( option ) = > (
< div
key = { option . value }