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:
2026-03-01 10:41:43 +01:00
parent e3ca845a6d
commit a875203482
26 changed files with 2087 additions and 132 deletions

View File

@@ -0,0 +1,40 @@
import React from 'react'
/**
* Nova FormField thin wrapper that pairs a label with any input-like child,
* plus optional hint and error text. Use this for custom controls (NovaSelect,
* Toggle, etc.) that don't carry their own label.
*
* @prop {string} label - visible label text
* @prop {boolean} required - shows red asterisk
* @prop {string} error - validation error message
* @prop {string} hint - helper text shown below control
* @prop {string} htmlFor - id of the labelled element
*/
export default function FormField({ label, required, error, hint, htmlFor, children, className = '' }) {
return (
<div className={`flex flex-col gap-1.5 ${className}`}>
{label && (
<label
htmlFor={htmlFor}
className="text-sm font-medium text-white/85 select-none"
>
{label}
{required && <span className="text-red-400 ml-1">*</span>}
</label>
)}
{children}
{error && (
<p role="alert" className="text-xs text-red-400">
{error}
</p>
)}
{!error && hint && (
<p className="text-xs text-slate-500">{hint}</p>
)}
</div>
)
}