feat: Nova UI component library + Studio dropdown/picker polish
- Add Nova UI library: Button, TextInput, Textarea, FormField, Select, NovaSelect, Checkbox, Radio/RadioGroup, Toggle, DatePicker, DateRangePicker, Modal + barrel index.js - Replace all native <select> in Studio with NovaSelect (StudioFilters, StudioToolbar, BulkActionsBar) including frosted-glass portal and category group headers - Replace native checkboxes in StudioGridCard, StudioTable, UploadSidebar, UploadWizard, Upload/Index with custom Checkbox component - Add nova-scrollbar CSS utility (thin 4px, semi-transparent) - Fix portal position drift: use viewport-relative coords (no scrollY offset) for NovaSelect, DatePicker and DateRangePicker - Close portals on external scroll instead of remeasuring - Improve hover highlight visibility in NovaSelect (bg-white/[0.13]) - Move search icon to right side in NovaSelect dropdown - Reduce Studio layout top spacing (py-6 -> pt-4 pb-8) - Add StudioCheckbox and SquareCheckbox backward-compat shims - Add sync.sh rsync deploy script
This commit is contained in:
84
resources/js/components/ui/Toggle.jsx
Normal file
84
resources/js/components/ui/Toggle.jsx
Normal 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
|
||||
Reference in New Issue
Block a user