Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -0,0 +1,84 @@
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