refactor: Align form validation UX across management dialogs
This commit is contained in:
85
src/components/auth/AuthBackground.tsx
Normal file
85
src/components/auth/AuthBackground.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const BOKEH_ORBS = [
|
||||
'top-[20%] left-[10%] size-[min(18vw,140px)] opacity-[0.14]',
|
||||
'top-[-18%] right-[-20%] size-[min(42vw,380px)] opacity-[0.1]',
|
||||
'bottom-[-17%] left-[-15%] size-[min(34vw,300px)] opacity-[0.12]',
|
||||
] as const;
|
||||
|
||||
const PARTICLES = [
|
||||
'top-[30%] left-[45%] opacity-15',
|
||||
'top-[43%] right-[20%] opacity-20',
|
||||
'top-[57%] left-[10%] opacity-20',
|
||||
'bottom-[28%] left-[45%] opacity-20',
|
||||
'bottom-[14%] right-[20%] opacity-20',
|
||||
] as const;
|
||||
|
||||
type AuthBackgroundProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function AuthBackground({ className }: AuthBackgroundProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative hidden h-screen w-1/2 shrink-0 overflow-hidden md:block',
|
||||
className,
|
||||
)}
|
||||
aria-hidden
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(155deg, color-mix(in oklch, var(--primary) 92%, white 8%) 0%, var(--primary) 32%, color-mix(in oklch, var(--primary) 72%, black 28%) 66%, color-mix(in oklch, var(--primary) 48%, black 52%) 100%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
'radial-gradient(circle at 50% 43%, color-mix(in oklch, var(--primary) 78%, white 22% / 34%), transparent 42%), radial-gradient(circle at 18% 8%, color-mix(in oklch, var(--primary) 70%, white 30% / 26%), transparent 36%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<svg
|
||||
className="pointer-events-none absolute inset-0 size-full text-primary-foreground/8"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
>
|
||||
{Array.from({ length: 14 }, (_, index) => (
|
||||
<circle
|
||||
key={index}
|
||||
cx="50"
|
||||
cy="45"
|
||||
r={8 + index * 5.2}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="0.08"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
|
||||
{BOKEH_ORBS.map((position, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
'absolute rounded-full bg-primary-foreground/16',
|
||||
position,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{PARTICLES.map((position, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
'absolute size-1.5 rounded-full bg-primary-foreground/35',
|
||||
position,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export function FilterSelector({
|
||||
onValueChange={onProjectChange}
|
||||
disabled={isLoading || projects.length === 0}
|
||||
>
|
||||
<SelectTrigger className="h-10 text-sm w-full bg-background border-border/60 shadow-xs">
|
||||
<SelectTrigger className="h-10 text-sm w-full bg-background border-border/60">
|
||||
<SelectValue placeholder="Select project" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -81,7 +81,7 @@ export function FilterSelector({
|
||||
onValueChange={onPackageChange}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger className="h-10 text-sm w-full bg-background border-border/60 shadow-xs">
|
||||
<SelectTrigger className="h-10 text-sm w-full bg-background border-border/60">
|
||||
<SelectValue placeholder="All packages" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -112,7 +112,7 @@ export function FilterSelector({
|
||||
onValueChange={onChainageChange}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger className="h-10 text-sm w-full bg-background border-border/60 shadow-xs">
|
||||
<SelectTrigger className="h-10 text-sm w-full bg-background border-border/60">
|
||||
<SelectValue placeholder="All segments" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -88,14 +88,14 @@ export function MultiSelectPopover({
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button type="button" variant="outline" size="sm">
|
||||
<Button type="button" variant="outline" size="sm" className="shadow-none">
|
||||
{icon}
|
||||
{label}
|
||||
<Badge variant="secondary">{values.length}</Badge>
|
||||
<ChevronDown />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align={align} className="w-60 p-0">
|
||||
<PopoverContent align={align} className="w-60 p-0 shadow-none">
|
||||
<div className="flex items-center gap-2 border-b p-3">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -137,7 +137,7 @@ function ComboboxContent({
|
||||
data-slot="combobox-content"
|
||||
data-chips={!!anchor}
|
||||
className={cn(
|
||||
'group/combobox-content relative max-h-96 w-[var(--anchor-width)] max-w-[var(--available-width)] min-w-[calc(var(--anchor-width)+1.75rem)] origin-[var(--transform-origin)] overflow-hidden rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-[var(--anchor-width)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
|
||||
'group/combobox-content relative max-h-96 w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+1.75rem)] origin-(--transform-origin) overflow-hidden rounded-md bg-popover text-popover-foreground ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -254,7 +254,7 @@ function ComboboxChips({
|
||||
<ComboboxPrimitive.Chips
|
||||
data-slot="combobox-chips"
|
||||
className={cn(
|
||||
'flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:border-ring has-aria-invalid:border-destructive has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive',
|
||||
'flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm transition-[color,box-shadow] focus-within:border-ring has-aria-invalid:border-destructive has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
data-slot="input-group"
|
||||
role="group"
|
||||
className={cn(
|
||||
'group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30',
|
||||
'group/input-group relative flex w-full items-center rounded-md border border-input transition-[color,box-shadow] outline-none dark:bg-input/30',
|
||||
'h-9 min-w-0 has-[>textarea]:h-auto',
|
||||
|
||||
// Variants based on alignment.
|
||||
|
||||
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
'h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
|
||||
'h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
|
||||
'focus-visible:border-ring',
|
||||
'aria-invalid:border-destructive',
|
||||
className,
|
||||
|
||||
@@ -37,7 +37,7 @@ function SelectTrigger({
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"flex h-9 w-full min-w-0 items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||
"flex h-9 w-full min-w-0 items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-1 text-sm transition-[color,box-shadow] outline-none focus-visible:border-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -62,7 +62,7 @@ function SelectContent({
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
'relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
className,
|
||||
|
||||
Reference in New Issue
Block a user