feat(deps): Tailwind 3 → 4 + swap tailwindcss-animate for tw-animate-css

Ran the official @tailwindcss/upgrade tool:
- tailwind.config.ts → @theme directive in globals.css
- @tailwind base/components/utilities → @import 'tailwindcss'
- postcss.config switched from tailwindcss + autoprefixer to
  @tailwindcss/postcss (autoprefixer baked in)
- focus-visible:outline-none → focus-visible:outline-hidden (the v3
  utility was a footgun — outline still showed in forced-colors mode)

Reverted the migration tool's over-zealous variant="outline" →
variant="outline-solid" rename on CVA prop values; that rename was
meant for the Tailwind `outline:` utility, not our Button/Badge
component variants.

Swapped tailwindcss-animate (v3-style JS plugin) for tw-animate-css
(v4-native @import). Same utility surface (animate-spin, animate-in,
etc.), one fewer JS plugin in the bundle.

Fixed the upgrade tool's malformed dark variant
(@custom-variant dark (&:is(class *)) — `class` was being parsed as
a tag) to canonical &:where(.dark, .dark *).

Verified: tsc 0 errors, eslint 0 errors (16 pre-existing warnings),
vitest 1315/1315, next build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 22:14:38 +02:00
parent 3147923d91
commit 0ab96d74a8
69 changed files with 561 additions and 753 deletions

View File

@@ -209,7 +209,7 @@ export function InlineStagePicker({
}}
className={cn(
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-sm font-medium',
'transition-colors hover:brightness-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
'transition-colors hover:brightness-95 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring',
STAGE_BADGE[stage],
className,
)}

View File

@@ -281,7 +281,7 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[var(--radix-popper-anchor-width)] min-w-[280px] p-0">
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[280px] p-0">
{/* shouldFilter={false}: server-side search via setClientSearch
drives the result set. Without this, cmdk's default filter
matches the user's typed text against CommandItem.value
@@ -339,7 +339,7 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[var(--radix-popper-anchor-width)] min-w-[280px] p-0">
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[280px] p-0">
<Command shouldFilter={false}>
<CommandInput placeholder="Search berths..." onValueChange={setBerthSearch} />
<CommandList>

View File

@@ -26,7 +26,7 @@ export function PipelineColumn({ stage, label, items }: PipelineColumnProps) {
return (
<div
ref={setNodeRef}
className={`flex flex-col gap-2 min-w-[220px] w-[220px] flex-shrink-0 bg-muted/40 rounded-lg p-3 transition-colors ${
className={`flex flex-col gap-2 min-w-[220px] w-[220px] shrink-0 bg-muted/40 rounded-lg p-3 transition-colors ${
isOver ? 'bg-muted/70 ring-2 ring-primary/30' : ''
}`}
>