Files
SkinbaseNova/resources/js/components/ui/Select.jsx

95 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { forwardRef } from 'react'
/**
* Nova Select styled native <select>
*
* Accepts the same options API as a plain <select>:
* - Pass children (<option>, <optgroup>) directly, OR
* - Pass `options` array of { value, label } and optional `placeholder`
*
* @prop {Array} options - [{ value, label }] optional shorthand
* @prop {string} placeholder - adds a blank first option when using `options`
* @prop {string} label - field label
* @prop {string} error - validation error
* @prop {string} hint - helper text
* @prop {boolean} required - asterisk on label
* @prop {string} size - 'sm' | 'md' | 'lg'
*/
const Select = forwardRef(function Select(
{ label, error, hint, required, options, placeholder, size = 'md', id, className = '', children, style, ...rest },
ref,
) {
const inputId = id ?? (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined)
const sizeClass = {
sm: 'py-1.5 text-xs',
md: 'py-2.5 text-sm',
lg: 'py-3 text-base',
}[size] ?? 'py-2.5 text-sm'
const inputClass = [
'block w-full rounded-xl border bg-white/[0.06] text-white',
'pl-3.5 pr-9',
'appearance-none cursor-pointer',
'bg-no-repeat bg-right',
'transition-all duration-150',
'focus:outline-none focus:ring-2 focus:ring-offset-0',
error
? 'border-red-500/60 focus:border-red-500/70 focus:ring-red-500/40'
: 'border-white/12 hover:border-white/20 focus:border-accent/50 focus:ring-accent/40',
'disabled:opacity-50 disabled:cursor-not-allowed',
sizeClass,
className,
].join(' ')
return (
<div className="flex flex-col gap-1.5">
{label && (
<label htmlFor={inputId} className="text-sm font-medium text-white/85">
{label}
{required && <span className="text-red-400 ml-1">*</span>}
</label>
)}
<div className="relative">
<select
id={inputId}
ref={ref}
className={inputClass}
aria-invalid={!!error}
style={{
appearance: 'none',
WebkitAppearance: 'none',
MozAppearance: 'none',
backgroundImage: 'none',
...style,
}}
{...rest}
>
{placeholder && <option value="" className="bg-nova-900">{placeholder}</option>}
{options
? options.map((o) => (
<option key={o.value} value={o.value} className="bg-nova-900 text-white">
{o.label}
</option>
))
: children}
</select>
{/* Custom chevron */}
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-slate-500">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M2 4l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</div>
{error && <p role="alert" className="text-xs text-red-400">{error}</p>}
{!error && hint && <p className="text-xs text-slate-500">{hint}</p>}
</div>
)
})
export default Select