Fix form validation: red outline only on submit, add required asterisks
- Remove instant invalid:ring/border from Input component (was showing red outline on empty required fields before any interaction) - Add CSS rule: form[data-submitted] input:invalid shows red border - Add global submit listener in main.tsx that sets data-submitted on forms - Add required prop to Labels missing asterisks: PersonForm (First Name), LocationForm (Location Name), CalendarForm (Name), LockScreen (Username, Password, Confirm Password) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4207a62ad8
commit
f5265a589e
@ -204,7 +204,7 @@ export default function LockScreen() {
|
|||||||
|
|
||||||
<form onSubmit={handleCredentialSubmit} className="space-y-4">
|
<form onSubmit={handleCredentialSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="username">Username</Label>
|
<Label htmlFor="username" required>Username</Label>
|
||||||
<Input
|
<Input
|
||||||
id="username"
|
id="username"
|
||||||
type="text"
|
type="text"
|
||||||
@ -218,7 +218,7 @@ export default function LockScreen() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="password">Password</Label>
|
<Label htmlFor="password" required>Password</Label>
|
||||||
<Input
|
<Input
|
||||||
id="password"
|
id="password"
|
||||||
type="password"
|
type="password"
|
||||||
@ -232,7 +232,7 @@ export default function LockScreen() {
|
|||||||
|
|
||||||
{isSetup && (
|
{isSetup && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="confirm-password">Confirm Password</Label>
|
<Label htmlFor="confirm-password" required>Confirm Password</Label>
|
||||||
<Input
|
<Input
|
||||||
id="confirm-password"
|
id="confirm-password"
|
||||||
type="password"
|
type="password"
|
||||||
|
|||||||
@ -89,7 +89,7 @@ export default function CalendarForm({ calendar, onClose }: CalendarFormProps) {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="cal-name">Name</Label>
|
<Label htmlFor="cal-name" required>Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="cal-name"
|
id="cal-name"
|
||||||
value={name}
|
value={name}
|
||||||
|
|||||||
@ -110,7 +110,7 @@ export default function LocationForm({ location, categories, onClose }: Location
|
|||||||
<div className="px-6 py-5 space-y-4 flex-1">
|
<div className="px-6 py-5 space-y-4 flex-1">
|
||||||
{/* Location Name */}
|
{/* Location Name */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="loc-name">Location Name</Label>
|
<Label htmlFor="loc-name" required>Location Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="loc-name"
|
id="loc-name"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
|
|||||||
@ -132,7 +132,7 @@ export default function PersonForm({ person, categories, onClose }: PersonFormPr
|
|||||||
{/* Row 2: First + Last name */}
|
{/* Row 2: First + Last name */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="first_name">First Name</Label>
|
<Label htmlFor="first_name" required>First Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="first_name"
|
id="first_name"
|
||||||
value={formData.first_name}
|
value={formData.first_name}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 invalid:ring-red-500 invalid:border-red-500',
|
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@ -193,6 +193,14 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Form validation — red outline only after submit attempt ── */
|
||||||
|
form[data-submitted] input:invalid,
|
||||||
|
form[data-submitted] select:invalid,
|
||||||
|
form[data-submitted] textarea:invalid {
|
||||||
|
border-color: hsl(0 62.8% 50%);
|
||||||
|
box-shadow: 0 0 0 2px hsl(0 62.8% 50% / 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Ambient background animations ── */
|
/* ── Ambient background animations ── */
|
||||||
|
|
||||||
@keyframes drift-1 {
|
@keyframes drift-1 {
|
||||||
|
|||||||
@ -6,6 +6,14 @@ import { Toaster } from 'sonner';
|
|||||||
import App from './App';
|
import App from './App';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
// Mark forms as submitted so CSS validation outlines only appear after a submit attempt.
|
||||||
|
// The attribute is cleared naturally when Sheet/Dialog forms unmount and remount.
|
||||||
|
document.addEventListener('submit', (e) => {
|
||||||
|
if (e.target instanceof HTMLFormElement) {
|
||||||
|
e.target.setAttribute('data-submitted', '');
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user