mirror of
https://github.com/rmoren97/mc-manager.git
synced 2026-03-28 17:26:47 -07:00
sadded by commitizen
This commit is contained in:
parent
7edef35d7f
commit
39766a0994
@ -59,20 +59,6 @@ body {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
select.glass-input {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
color-scheme: dark;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
select option {
|
||||
background-color: #0d0d1a;
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
/* ─── Glass Shine (top border gradient) ───────────────────────── */
|
||||
|
||||
|
||||
@ -1,49 +1,124 @@
|
||||
import { forwardRef } from 'react'
|
||||
'use client'
|
||||
|
||||
interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
|
||||
interface SelectProps {
|
||||
label?: string
|
||||
error?: string
|
||||
required?: boolean
|
||||
options: { label: string; value: string }[]
|
||||
placeholder?: string
|
||||
value?: string
|
||||
defaultValue?: string
|
||||
onChange?: (e: { target: { value: string; name?: string } }) => void
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
name?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
||||
({ label, error, required, options, placeholder, className = '', ...props }, ref) => {
|
||||
export default function Select({
|
||||
label,
|
||||
error,
|
||||
required,
|
||||
options,
|
||||
placeholder,
|
||||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
disabled,
|
||||
className = '',
|
||||
name,
|
||||
id,
|
||||
}: SelectProps) {
|
||||
const isControlled = value !== undefined
|
||||
const [internalValue, setInternalValue] = useState(defaultValue ?? '')
|
||||
const [open, setOpen] = useState(false)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const currentValue = isControlled ? value : internalValue
|
||||
const selected = options.find(o => o.value === currentValue)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
const handleSelect = (optValue: string) => {
|
||||
if (!isControlled) setInternalValue(optValue)
|
||||
onChange?.({ target: { value: optValue, name } })
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className={`space-y-1.5 ${className}`}>
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-gray-300">
|
||||
<label htmlFor={id} className="block text-sm font-medium text-gray-300">
|
||||
{label}
|
||||
{required && <span className="text-red-400 ml-1">*</span>}
|
||||
</label>
|
||||
)}
|
||||
<select
|
||||
ref={ref}
|
||||
className={`w-full px-4 py-2 glass-input text-gray-100 rounded-lg
|
||||
focus:ring-2 focus:ring-cyan-400/40 focus:border-transparent
|
||||
focus:bg-white/[0.06] focus:shadow-[0_0_20px_rgba(6,182,212,0.08)]
|
||||
disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 ${
|
||||
error ? 'border-red-500 focus:ring-red-500' : ''
|
||||
} ${className}`}
|
||||
{...props}
|
||||
|
||||
{/* Hidden input for FormData compatibility */}
|
||||
{name && <input type="hidden" name={name} value={currentValue} />}
|
||||
|
||||
<div ref={ref} className="relative">
|
||||
<button
|
||||
id={id}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => !disabled && setOpen(prev => !prev)}
|
||||
className={`w-full px-4 py-2 glass-input text-left flex items-center justify-between rounded-lg
|
||||
focus:outline-none focus:ring-2 focus:ring-cyan-400/40
|
||||
disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200
|
||||
${error ? 'border-red-500' : ''}
|
||||
`}
|
||||
>
|
||||
<span className={selected ? 'text-gray-100 text-sm' : 'text-gray-500 text-sm'}>
|
||||
{selected ? selected.label : (placeholder ?? 'Select...')}
|
||||
</span>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className={`text-gray-400 shrink-0 ml-2 transition-transform duration-200 ${open ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="absolute z-50 w-full mt-1 glass rounded-lg overflow-hidden shadow-xl shadow-black/50 border border-white/[0.1]">
|
||||
{placeholder && (
|
||||
<option value="" className="text-gray-500">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelect('')}
|
||||
className="w-full px-4 py-2.5 text-left text-sm text-gray-500 hover:bg-white/[0.06] transition-colors"
|
||||
>
|
||||
{placeholder}
|
||||
</option>
|
||||
</button>
|
||||
)}
|
||||
{options.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
onClick={() => handleSelect(opt.value)}
|
||||
className={`w-full px-4 py-2.5 text-left text-sm transition-colors
|
||||
${opt.value === currentValue
|
||||
? 'bg-cyan-500/20 text-cyan-400'
|
||||
: 'text-gray-200 hover:bg-white/[0.06]'
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</option>
|
||||
</button>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-400">{error}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Select.displayName = 'Select'
|
||||
export default Select
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user