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);
|
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) ───────────────────────── */
|
/* ─── 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
|
label?: string
|
||||||
error?: string
|
error?: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
options: { label: string; value: string }[]
|
options: { label: string; value: string }[]
|
||||||
placeholder?: 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>(
|
export default function Select({
|
||||||
({ label, error, required, options, placeholder, className = '', ...props }, ref) => {
|
label,
|
||||||
return (
|
error,
|
||||||
<div className="space-y-1.5">
|
required,
|
||||||
{label && (
|
options,
|
||||||
<label className="block text-sm font-medium text-gray-300">
|
placeholder,
|
||||||
{label}
|
value,
|
||||||
{required && <span className="text-red-400 ml-1">*</span>}
|
defaultValue,
|
||||||
</label>
|
onChange,
|
||||||
)}
|
disabled,
|
||||||
<select
|
className = '',
|
||||||
ref={ref}
|
name,
|
||||||
className={`w-full px-4 py-2 glass-input text-gray-100 rounded-lg
|
id,
|
||||||
focus:ring-2 focus:ring-cyan-400/40 focus:border-transparent
|
}: SelectProps) {
|
||||||
focus:bg-white/[0.06] focus:shadow-[0_0_20px_rgba(6,182,212,0.08)]
|
const isControlled = value !== undefined
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 ${
|
const [internalValue, setInternalValue] = useState(defaultValue ?? '')
|
||||||
error ? 'border-red-500 focus:ring-red-500' : ''
|
const [open, setOpen] = useState(false)
|
||||||
} ${className}`}
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{placeholder && (
|
|
||||||
<option value="" className="text-gray-500">
|
|
||||||
{placeholder}
|
|
||||||
</option>
|
|
||||||
)}
|
|
||||||
{options.map(opt => (
|
|
||||||
<option key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{error && <p className="text-xs text-red-400">{error}</p>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Select.displayName = 'Select'
|
const currentValue = isControlled ? value : internalValue
|
||||||
export default Select
|
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 ${className}`}>
|
||||||
|
{label && (
|
||||||
|
<label htmlFor={id} className="block text-sm font-medium text-gray-300">
|
||||||
|
{label}
|
||||||
|
{required && <span className="text-red-400 ml-1">*</span>}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 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 && (
|
||||||
|
<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}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{options.map(opt => (
|
||||||
|
<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}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <p className="text-xs text-red-400">{error}</p>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user