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

85 lines
2.7 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 Toggle on/off switch
*
* @prop {boolean} checked - controlled value
* @prop {function} onChange - change handler (receives event OR is called with no args if `simpleChange` true)
* @prop {string} label - text label beside the toggle
* @prop {string} hint - small helper text
* @prop {string} size - 'sm' | 'md' | 'lg'
* @prop {string} variant - 'accent' | 'emerald' | 'sky'
*/
const sizeMap = {
sm: { track: 'w-8 h-4', thumb: 'w-3 h-3', translate: 'translate-x-4', offset: 'translate-x-0.5' },
md: { track: 'w-10 h-5', thumb: 'w-3.5 h-3.5', translate: 'translate-x-5', offset: 'translate-x-[3px]' },
lg: { track: 'w-12 h-6', thumb: 'w-4.5 h-4.5', translate: 'translate-x-6', offset: 'translate-x-[3px]' },
}
const variantMap = {
accent: 'bg-accent',
emerald: 'bg-emerald-500',
sky: 'bg-sky-500',
}
const Toggle = forwardRef(function Toggle(
{ checked = false, onChange, label, hint, size = 'md', variant = 'accent', id, disabled = false, className = '' },
ref,
) {
const s = sizeMap[size] ?? sizeMap.md
const vClass = variantMap[variant] ?? variantMap.accent
const inputId = id ?? (label ? `toggle-${label.toLowerCase().replace(/\s+/g, '-')}` : undefined)
return (
<label
className={[
'inline-flex items-start gap-3 cursor-pointer select-none',
disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : '',
className,
].join(' ')}
>
{/* Hidden native checkbox for a11y */}
<input
type="checkbox"
id={inputId}
ref={ref}
checked={checked}
onChange={onChange}
disabled={disabled}
className="sr-only"
role="switch"
aria-checked={checked}
/>
{/* Track */}
<span
className={[
'relative inline-flex shrink-0 rounded-full transition-colors duration-200 mt-px',
s.track,
checked ? vClass : 'bg-white/15',
'focus-within:ring-2 focus-within:ring-accent/50 focus-within:ring-offset-0',
].join(' ')}
aria-hidden="true"
>
{/* Thumb */}
<span
className={[
'absolute top-1/2 -translate-y-1/2 rounded-full bg-white shadow transition-transform duration-200',
s.thumb,
checked ? s.translate : s.offset,
].join(' ')}
/>
</span>
{(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>
)
})
export default Toggle