Files
SkinbaseNova/.deploy/artwork-evolution-release/resources/js/components/ui/Radio.jsx
2026-04-18 17:02:56 +02:00

94 lines
3.0 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 Radio single choice radio button.
*
* Usage (as a group):
* {options.map(o => (
* <Radio key={o.value} value={o.value} checked={val===o.value} onChange={setVal} label={o.label} />
* ))}
*
* Or use RadioGroup for a pre-built grouped set.
*/
export const Radio = forwardRef(function Radio(
{ label, hint, size = 18, id, className = '', ...rest },
ref,
) {
const dim = typeof size === 'number' ? `${size}px` : size
const inputId = id ?? (label ? `radio-${label.toLowerCase().replace(/\s+/g, '-')}` : undefined)
return (
<label className="inline-flex items-start gap-2.5 cursor-pointer select-none">
<input
type="radio"
id={inputId}
ref={ref}
className={[
'shrink-0 cursor-pointer',
'border border-white/25 bg-white/8',
'text-accent checked:bg-accent checked:border-accent',
'transition-colors duration-150',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 focus-visible:ring-offset-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
className,
].join(' ')}
style={{
width: dim,
height: dim,
minWidth: dim,
minHeight: dim,
aspectRatio: '1 / 1',
marginTop: label ? '1px' : undefined,
}}
{...rest}
/>
{(label || hint) && (
<span className="flex flex-col gap-0.5">
{label && <span className="text-sm text-white/90 leading-snug">{label}</span>}
{hint && <span className="text-xs text-slate-500">{hint}</span>}
</span>
)}
</label>
)
})
/**
* RadioGroup renders a set of Radio buttons from an options array.
*
* @prop {Array} options - [{ value, label, hint? }]
* @prop {string} value - currently selected value
* @prop {function} onChange - called with new value string
* @prop {string} name - unique name for radio group (required)
* @prop {string} label - group label
* @prop {string} error - validation error
* @prop {'vertical'|'horizontal'} direction
*/
export function RadioGroup({ options = [], value, onChange, name, label, error, direction = 'vertical', className = '' }) {
return (
<fieldset className={`flex flex-col gap-1.5 ${className}`}>
{label && (
<legend className="text-sm font-medium text-white/85 mb-1">{label}</legend>
)}
<div className={`flex gap-3 ${direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col'}`}>
{options.map((opt) => (
<Radio
key={opt.value}
name={name}
value={opt.value}
checked={value === opt.value}
onChange={() => onChange(opt.value)}
label={opt.label}
hint={opt.hint}
/>
))}
</div>
{error && <p role="alert" className="text-xs text-red-400">{error}</p>}
</fieldset>
)
}
export default Radio