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

@@ -104,9 +104,9 @@
"sonner": "^2.0.7",
"svgo": "^4.0.1",
"tailwind-merge": "^3.6.0",
"tailwindcss-animate": "^1.0.7",
"tesseract.js": "^7.0.0",
"ts-pattern": "^5.9.0",
"tw-animate-css": "^1.4.0",
"unpdf": "^1.6.2",
"vaul": "^1.1.2",
"web-vitals": "^5.2.0",
@@ -119,6 +119,7 @@
"@hookform/devtools": "^4.4.0",
"@next/bundle-analyzer": "^16.2.6",
"@playwright/test": "^1.60.0",
"@tailwindcss/postcss": "^4.3.0",
"@total-typescript/ts-reset": "^0.6.1",
"@types/archiver": "^7.0.0",
"@types/iso-3166-2": "^1.0.4",
@@ -129,7 +130,6 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/coverage-v8": "^4.1.6",
"autoprefixer": "^10.5.0",
"dotenv": "^17.4.2",
"drizzle-kit": "^0.31.10",
"drizzle-zod": "^0.8.3",
@@ -142,7 +142,7 @@
"postcss": "^8.5.14",
"prettier": "^3.8.3",
"react-grab": "^0.1.34",
"tailwindcss": "^3.4.19",
"tailwindcss": "^4.3.0",
"tsx": "^4.21.0",
"typescript": "^6.0.3",
"vitest": "^4.1.6"

696
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': {},
},
};

View File

@@ -200,7 +200,7 @@ export default function DocumensoSettingsPage() {
<ul className="space-y-1.5">
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>
@@ -211,7 +211,7 @@ export default function DocumensoSettingsPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>
@@ -222,7 +222,7 @@ export default function DocumensoSettingsPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>
@@ -233,7 +233,7 @@ export default function DocumensoSettingsPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>
@@ -245,7 +245,7 @@ export default function DocumensoSettingsPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>
@@ -260,7 +260,7 @@ export default function DocumensoSettingsPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>
@@ -271,7 +271,7 @@ export default function DocumensoSettingsPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>
@@ -283,7 +283,7 @@ export default function DocumensoSettingsPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2
className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-600"
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
aria-hidden="true"
/>
<span>

View File

@@ -179,7 +179,7 @@ export default function ErrorEventDetailPage() {
<KV label="Name" value={event.errorName ?? '—'} mono />
<div>
<p className="text-xs text-muted-foreground">Message</p>
<p className="mt-0.5 font-mono whitespace-pre-wrap break-words">
<p className="mt-0.5 font-mono whitespace-pre-wrap wrap-break-word">
{event.errorMessage ?? '—'}
</p>
</div>
@@ -195,7 +195,7 @@ export default function ErrorEventDetailPage() {
<Copy className="mr-1.5 h-3 w-3" /> Copy
</Button>
</div>
<pre className="mt-1 max-h-96 overflow-auto rounded bg-muted p-2 text-xs font-mono whitespace-pre-wrap break-words">
<pre className="mt-1 max-h-96 overflow-auto rounded bg-muted p-2 text-xs font-mono whitespace-pre-wrap wrap-break-word">
{event.errorStack}
</pre>
</div>
@@ -211,7 +211,7 @@ export default function ErrorEventDetailPage() {
</CardTitle>
</CardHeader>
<CardContent>
<pre className="max-h-64 overflow-auto rounded bg-muted p-2 text-xs font-mono whitespace-pre-wrap break-words">
<pre className="max-h-64 overflow-auto rounded bg-muted p-2 text-xs font-mono whitespace-pre-wrap wrap-break-word">
{event.requestBodyExcerpt}
</pre>
</CardContent>

View File

@@ -165,7 +165,7 @@ export default function InvoicesPage() {
{/* Delete confirmation */}
{deleteTarget && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center">
<div className="fixed inset-0 bg-background/80 backdrop-blur-xs z-50 flex items-center justify-center">
<div className="bg-background border rounded-lg shadow-lg p-6 max-w-sm w-full space-y-4">
<h3 className="font-semibold">Delete Invoice?</h3>
<p className="text-sm text-muted-foreground">

View File

@@ -50,7 +50,7 @@ export default async function PortalDocumentsPage() {
{documents.map((doc) => (
<div key={doc.id} className="bg-white rounded-lg border p-5">
<div className="flex items-start gap-4">
<FileText className="h-5 w-5 text-gray-400 mt-0.5 flex-shrink-0" />
<FileText className="h-5 w-5 text-gray-400 mt-0.5 shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div className="flex-1 min-w-0">
@@ -59,7 +59,7 @@ export default async function PortalDocumentsPage() {
{DOC_TYPE_LABELS[doc.documentType] ?? doc.documentType}
</p>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<div className="flex items-center gap-2 shrink-0">
<Badge variant={STATUS_COLORS[doc.status] ?? 'default'}>
{doc.status.replace(/_/g, ' ')}
</Badge>

View File

@@ -71,7 +71,7 @@ export default async function PortalInvoicesPage() {
)}
</div>
</div>
<div className="text-right flex-shrink-0">
<div className="text-right shrink-0">
<p className="text-lg font-semibold text-gray-900">
{formatCurrency(invoice.total, invoice.currency)}
</p>

View File

@@ -40,7 +40,7 @@ export default async function PortalMyYachtsPage() {
{yachts.map((y) => (
<div key={y.id} className="bg-white rounded-lg border p-5">
<div className="flex items-start gap-4">
<Sailboat className="h-5 w-5 text-gray-400 mt-0.5 flex-shrink-0" />
<Sailboat className="h-5 w-5 text-gray-400 mt-0.5 shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div className="flex-1 min-w-0">

View File

@@ -65,7 +65,7 @@ export default async function ScannerLayout({
return (
<QueryProvider>
<PortProvider ports={[port]} defaultPortId={port.id}>
<div className="min-h-[100dvh] bg-background">{children}</div>
<div className="min-h-dvh bg-background">{children}</div>
</PortProvider>
</QueryProvider>
);

View File

@@ -1,6 +1,183 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-brand-50: #d8e5f4;
--color-brand-100: #b1cbe9;
--color-brand-200: #89b0de;
--color-brand-300: #6196d3;
--color-brand-400: #3a7bc8;
--color-brand-500: #2f6ab5;
--color-brand-600: #255a9e;
--color-brand-700: #1c4a87;
--color-brand: #3a7bc8;
--color-brand-dark: #1e2844;
--color-navy-50: #cdcfd6;
--color-navy-100: #9ea1af;
--color-navy-200: #71768a;
--color-navy-300: #474e66;
--color-navy-400: #1e2844;
--color-navy-500: #171f35;
--color-navy-600: #101625;
--color-navy: #1e2844;
--color-sage: #dae3c1;
--color-sage-light: #edf1e2;
--color-sage-dark: #b8c49e;
--color-mint: #add5b3;
--color-mint-light: #d6ead9;
--color-mint-dark: #7dba85;
--color-teal: #83aab1;
--color-teal-light: #b1cdd2;
--color-teal-dark: #5a8a92;
--color-purple: #685aa3;
--color-purple-light: #a49ac6;
--color-purple-dark: #4d4280;
--color-success: #2d8a4e;
--color-success-bg: #e8f5e9;
--color-success-border: #a5d6a7;
--color-warning: #e6a817;
--color-warning-bg: #fff8e1;
--color-warning-border: #ffe082;
--color-error: #d32f2f;
--color-error-bg: #ffebee;
--color-error-border: #ef9a9a;
--color-sidebar: #1e2844;
--color-sidebar-text: #cdcfd6;
--color-sidebar-hover: #171f35;
--color-sidebar-active: #3a7bc8;
--color-sidebar-divider: #474e66;
--font-sans: Inter, system-ui, -apple-system, Arial, sans-serif;
--font-mono: JetBrains Mono, ui-monospace, monospace;
--font-serif: Georgia, Times New Roman, serif;
--shadow-xs: 0 1px 2px 0 rgb(15 23 42 / 0.04);
--shadow-sm: 0 2px 4px -1px rgb(15 23 42 / 0.06);
--shadow: 0 1px 3px rgba(30, 40, 68, 0.1), 0 1px 2px rgba(30, 40, 68, 0.06);
--shadow-md: 0 4px 12px -2px rgb(15 23 42 / 0.08);
--shadow-lg: 0 12px 32px -8px rgb(15 23 42 / 0.12);
--shadow-xl: 0 20px 25px rgba(30, 40, 68, 0.1), 0 8px 10px rgba(30, 40, 68, 0.04);
--shadow-glow: 0 0 0 4px rgb(58 123 200 / 0.12);
--radius-sm: 0.375rem;
--radius: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.625rem;
--radius-xl: 0.875rem;
--background-image-gradient-brand: linear-gradient(135deg, #3a7bc8 0%, #2f6ab5 100%);
--background-image-gradient-brand-soft: linear-gradient(135deg, #d8e5f4 0%, #ffffff 100%);
--background-image-gradient-success: linear-gradient(135deg, #e8f5e9 0%, #ffffff 100%);
--background-image-gradient-warning: linear-gradient(135deg, #fef3c7 0%, #ffffff 100%);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--width-sidebar: 256px;
--width-sidebar-collapsed: 64px;
--transition-duration-sidebar: 200ms;
--transition-duration-fast: 150ms;
--transition-duration-base: 200ms;
--transition-duration-slow: 300ms;
--spacing-safe: env(safe-area-inset-bottom);
--spacing-safe-top: env(safe-area-inset-top);
--spacing-safe-bottom: env(safe-area-inset-bottom);
--spacing-safe-left: env(safe-area-inset-left);
--spacing-safe-right: env(safe-area-inset-right);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-badge-pop: badge-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes badge-pop {
0% {
transform: scale(0.5);
opacity: 0;
}
60% {
transform: scale(1.18);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 1;
}
}
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@layer base {
:root {

View File

@@ -165,7 +165,7 @@ export function TemplateForm({ open, onOpenChange, template, onSuccess }: Templa
validateJson(e.target.value);
}}
rows={18}
className="w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs shadow-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs shadow-sm focus:outline-hidden focus:ring-2 focus:ring-ring"
spellCheck={false}
/>
{jsonError && <p className="text-xs text-destructive">{jsonError}</p>}

View File

@@ -34,7 +34,7 @@ export function ServiceHealthCard({ service }: ServiceHealthCardProps) {
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-2">
<span
className={cn('h-2.5 w-2.5 rounded-full flex-shrink-0', config.dot)}
className={cn('h-2.5 w-2.5 rounded-full shrink-0', config.dot)}
aria-hidden="true"
/>
<span className="font-medium text-sm truncate">{service.name}</span>

View File

@@ -268,7 +268,7 @@ export function BerthDetailHeader({ berth }: BerthDetailHeaderProps) {
keeping the header lean. */}
<div
className={cn(
'inline-flex h-12 min-w-[3.25rem] items-center justify-center rounded-2xl px-3 text-lg font-bold tracking-tight text-white shadow-sm',
'inline-flex h-12 min-w-13 items-center justify-center rounded-2xl px-3 text-lg font-bold tracking-tight text-white shadow-sm',
mooringLetterDot(berth.mooringNumber) ?? 'bg-slate-400',
)}
title={berth.area ? `${berth.area} Dock` : undefined}
@@ -368,10 +368,7 @@ function InterestLinkPicker({
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-[var(--radix-popper-anchor-width)] min-w-[320px] p-0"
align="start"
>
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[320px] p-0" align="start">
<Command>
<CommandInput placeholder="Search prospects…" />
<CommandList>

View File

@@ -642,7 +642,7 @@ function EntityMultiPicker({
<div className="flex flex-wrap gap-1.5">
{selectedIds.map((id) => (
<Badge key={id} variant="secondary" className="gap-1 pr-1">
<span className="max-w-[14rem] truncate">{labelById.get(id) ?? id.slice(0, 8)}</span>
<span className="max-w-56 truncate">{labelById.get(id) ?? id.slice(0, 8)}</span>
<button
type="button"
className="rounded-full p-0.5 hover:bg-muted-foreground/20"
@@ -672,10 +672,7 @@ function EntityMultiPicker({
<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"
align="start"
>
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[280px] p-0" align="start">
<Command shouldFilter={false}>
<CommandInput placeholder="Search…" onValueChange={setSearch} />
<CommandList>

View File

@@ -149,7 +149,7 @@ export function DateRangePicker({ value, onChange, className }: DateRangePickerP
empty result, and not understand why. */
max={draftTo && draftTo < today ? draftTo : today}
onChange={(e) => setDraftFrom(e.target.value)}
className="w-auto max-w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm outline-none focus:border-brand/60 focus:ring-2 focus:ring-brand/15"
className="w-auto max-w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm outline-hidden focus:border-brand/60 focus:ring-2 focus:ring-brand/15"
/>
</label>
<label className="block text-xs">
@@ -160,7 +160,7 @@ export function DateRangePicker({ value, onChange, className }: DateRangePickerP
min={draftFrom || undefined}
max={today}
onChange={(e) => setDraftTo(e.target.value)}
className="w-auto max-w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm outline-none focus:border-brand/60 focus:ring-2 focus:ring-brand/15"
className="w-auto max-w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm outline-hidden focus:border-brand/60 focus:ring-2 focus:ring-brand/15"
/>
</label>
<div className="flex items-center justify-end gap-2 pt-1">

View File

@@ -432,7 +432,7 @@ function PreviewRow({
<dt className="w-32 shrink-0 text-xs text-muted-foreground">{label}</dt>
<dd
className={cn(
'flex-1 break-words inline-flex items-center gap-2',
'flex-1 wrap-break-word inline-flex items-center gap-2',
missing
? 'text-rose-700 font-medium'
: value

View File

@@ -83,7 +83,7 @@ export function SigningProgress({ documentId, signers }: SigningProgressProps) {
)}
</div>
</div>
{idx < sorted.length - 1 && <div className="mb-6 h-0.5 w-8 flex-shrink-0 bg-border" />}
{idx < sorted.length - 1 && <div className="mb-6 h-0.5 w-8 shrink-0 bg-border" />}
</div>
))}
</div>

View File

@@ -131,7 +131,7 @@ export function EmailDraftButton({
<p
contentEditable
suppressContentEditableWarning
className="mt-1 text-sm font-medium border rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ring"
className="mt-1 text-sm font-medium border rounded-md px-3 py-2 focus:outline-hidden focus:ring-2 focus:ring-ring"
>
{draft.subject}
</p>
@@ -145,7 +145,7 @@ export function EmailDraftButton({
<pre
contentEditable
suppressContentEditableWarning
className="mt-1 text-sm whitespace-pre-wrap font-sans border rounded-md px-3 py-2 min-h-[300px] focus:outline-none focus:ring-2 focus:ring-ring"
className="mt-1 text-sm whitespace-pre-wrap font-sans border rounded-md px-3 py-2 min-h-[300px] focus:outline-hidden focus:ring-2 focus:ring-ring"
>
{draft.body}
</pre>

View File

@@ -393,7 +393,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
{noReceipt && (
<div className="flex gap-2 rounded-md border border-amber-300 bg-amber-50 p-2 text-xs text-amber-900 dark:border-amber-900 dark:bg-amber-950/40 dark:text-amber-200">
<AlertTriangle className="h-4 w-4 flex-shrink-0" />
<AlertTriangle className="h-4 w-4 shrink-0" />
<span>
Expenses without a receipt may not be reimbursed by the parent company. The PDF
export will flag this expense.

View File

@@ -76,7 +76,7 @@ export function TripLabelCombobox({
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[var(--radix-popper-anchor-width)] min-w-[260px] p-0">
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[260px] p-0">
<Command shouldFilter={true}>
<CommandInput
placeholder="Type a trip name…"

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' : ''
}`}
>

View File

@@ -38,7 +38,7 @@ export function MobileTopbar() {
<header
className={cn(
'fixed top-0 inset-x-0 z-40',
'bg-gradient-to-b from-[#1e2844] to-[#171f35]',
'bg-linear-to-b from-[#1e2844] to-[#171f35]',
'shadow-[0_4px_18px_-6px_rgba(15,23,42,0.45)]',
'h-[calc(56px+env(safe-area-inset-top))] pt-safe-top',
'flex items-center gap-2 px-3',

View File

@@ -376,7 +376,7 @@ function SidebarContent({
<button
type="button"
aria-label="Open user menu"
className="rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#3a7bc8] focus-visible:ring-offset-2 focus-visible:ring-offset-white"
className="rounded-full focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[#3a7bc8] focus-visible:ring-offset-2 focus-visible:ring-offset-white"
>
<Avatar className="w-8 h-8 cursor-pointer">
<AvatarImage src={undefined} />
@@ -396,7 +396,7 @@ function SidebarContent({
<button
type="button"
aria-label="Open user menu"
className="flex w-full items-center gap-3 rounded-md p-1.5 text-left transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#3a7bc8] focus-visible:ring-offset-2 focus-visible:ring-offset-white"
className="flex w-full items-center gap-3 rounded-md p-1.5 text-left transition-colors hover:bg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[#3a7bc8] focus-visible:ring-offset-2 focus-visible:ring-offset-white"
>
<Avatar className="w-8 h-8 shrink-0 shadow-sm ring-2 ring-slate-200">
<AvatarImage src={undefined} />

View File

@@ -36,7 +36,7 @@ export function NotificationItem({ notification, onMarkRead }: NotificationItemP
className="w-full text-left flex items-start gap-3 px-4 py-3 hover:bg-muted/50 transition-colors"
>
{/* Unread indicator */}
<div className="mt-1.5 flex-shrink-0">
<div className="mt-1.5 shrink-0">
{!notification.isRead ? (
<span className="block h-2 w-2 rounded-full bg-blue-500" />
) : (

View File

@@ -493,7 +493,7 @@ export function ScanShell() {
// the home indicator on iPhone 14/15 in standalone PWA mode
// (viewportFit:cover + statusBarStyle:default exposes the safe-
// area inset, but the original `py-6` ignored it).
className="mx-auto flex min-h-[100dvh] w-full max-w-xl flex-col gap-4 px-4 py-6 pb-[max(1.5rem,env(safe-area-inset-bottom))] sm:py-10"
className="mx-auto flex min-h-dvh w-full max-w-xl flex-col gap-4 px-4 py-6 pb-[max(1.5rem,env(safe-area-inset-bottom))] sm:py-10"
>
{/* Brand header - logo centered, page title underneath. Establishes
the standalone identity (this is the PWA home for the scanner). */}

View File

@@ -291,7 +291,7 @@ export function CommandSearch() {
// Wrapper border swap is the focus indicator; suppress the
// global *:focus-visible ring that would otherwise paint a
// rectangular box clashing with the rounded wrapper.
className="h-9 flex-1 min-w-0 bg-transparent text-sm outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-muted-foreground"
className="h-9 flex-1 min-w-0 bg-transparent text-sm outline-hidden ring-0 focus:outline-hidden focus:ring-0 focus-visible:outline-hidden focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-muted-foreground"
/>
{isFetching && query.length >= 2 && (
<span

View File

@@ -35,10 +35,7 @@ export function HighlightMatch({
if (i % 2 === 1) {
// The capture group lands on odd indices.
return (
<mark
key={i}
className="bg-brand/15 text-foreground rounded-[2px] px-[1px] font-medium"
>
<mark key={i} className="bg-brand/15 text-foreground rounded-[2px] px-px font-medium">
{part}
</mark>
);

View File

@@ -198,7 +198,7 @@ export function MobileSearchOverlay({ open, onOpenChange }: MobileSearchOverlayP
repositionInputs={false}
>
<VaulDrawer.Portal>
<VaulDrawer.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur-sm" />
<VaulDrawer.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur-xs" />
<VaulDrawer.Content
// Anchor by top + explicit height (not bottom: 0). iOS treats
// `bottom: 0` on position:fixed inconsistently when the
@@ -252,7 +252,7 @@ export function MobileSearchOverlay({ open, onOpenChange }: MobileSearchOverlayP
autoCorrect="off"
spellCheck={false}
className={cn(
'ml-2 h-full w-full min-w-0 bg-transparent text-base outline-none',
'ml-2 h-full w-full min-w-0 bg-transparent text-base outline-hidden',
'placeholder:text-muted-foreground',
)}
/>
@@ -283,7 +283,7 @@ export function MobileSearchOverlay({ open, onOpenChange }: MobileSearchOverlayP
matter the phone width. "All" is sticky-left so it's always
one tap away when the user is deep in a bucket. */}
<div className="border-b pb-3">
<div className="flex gap-1.5 overflow-x-auto px-4 [-webkit-overflow-scrolling:touch] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
<div className="flex gap-1.5 overflow-x-auto px-4 [-webkit-overflow-scrolling:touch] scrollbar-none [&::-webkit-scrollbar]:hidden">
<BucketChip
label="All"
active={activeBucket === 'all'}

View File

@@ -14,7 +14,7 @@ export function ActionRow({ children, className }: { children: ReactNode; classN
'flex gap-2',
'overflow-x-auto snap-x snap-mandatory scroll-smooth -mx-3 px-3 sm:mx-0 sm:px-0',
'sm:flex-wrap sm:overflow-visible',
'[&>*]:snap-start [&>*]:shrink-0 sm:[&>*]:snap-none',
'*:snap-start *:shrink-0 sm:*:snap-none',
className,
)}
>

View File

@@ -32,7 +32,7 @@ export function BrandedAuthShell({ children, branding }: BrandedAuthShellProps)
const altText = branding?.appName || 'Port Nimara';
// fixed inset-0 anchors the auth surface to the viewport directly —
// iOS Safari ignores overflow-hidden on inner divs for body-level
// scrolling, so a regular `h-[100dvh] overflow-hidden` wrapper doesn't
// scrolling, so a regular `h-dvh overflow-hidden` wrapper doesn't
// stop the rubber-band bounce. Pinning to the viewport via position
// fixed does. The fixed-position shell then uses flex to center the
// card within the visible area.

View File

@@ -122,10 +122,7 @@ export function CountryCombobox({
<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"
align="start"
>
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[280px] p-0" align="start">
<Command>
<CommandInput placeholder="Search country or code…" />
<CommandList>

View File

@@ -408,7 +408,7 @@ export function DataTable<TData>({
const next = e.target.value === 'all' ? 1000 : Number(e.target.value);
onPaginationChange?.(1, next);
}}
className="h-8 rounded-md border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
className="h-8 rounded-md border border-input bg-background px-2 text-sm focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
>
<option value="25">25</option>
<option value="50">50</option>

View File

@@ -42,7 +42,7 @@ export function DetailPageShell({
return (
<div className={cn('flex flex-col min-h-full', className)}>
{/* Desktop-only sticky header - mobile topbar covers this on small viewports. */}
<div className="hidden sm:block sticky top-0 z-10 bg-background/95 backdrop-blur border-b border-border px-4 py-3 sm:px-6">
<div className="hidden sm:block sticky top-0 z-10 bg-background/95 backdrop-blur-sm border-b border-border px-4 py-3 sm:px-6">
<div className="flex items-center gap-3 min-w-0">
<h2 className="truncate text-lg font-semibold text-foreground">{entityName}</h2>
{status ? <div className="ml-auto shrink-0">{status}</div> : null}
@@ -65,7 +65,7 @@ export function DetailPageShell({
className={cn(
'sm:hidden',
'fixed inset-x-0 bottom-[calc(56px+env(safe-area-inset-bottom))] z-30',
'border-t border-border bg-background/95 backdrop-blur px-4 py-3',
'border-t border-border bg-background/95 backdrop-blur-sm px-4 py-3',
'flex items-center gap-2',
)}
>

View File

@@ -119,7 +119,7 @@ export function EntityActivityFeed({ endpoint, emptyText = 'No activity yet.' }:
const actor = row.actor?.name || row.actor?.email || 'System';
return (
<li key={row.id} className="relative">
<span className="absolute -left-[31px] top-1 h-2.5 w-2.5 rounded-full bg-primary/70 ring-2 ring-background" />
<span className="absolute left-[-31px] top-1 h-2.5 w-2.5 rounded-full bg-primary/70 ring-2 ring-background" />
<div className="text-sm">
<span className="font-medium">{summarize(row)}</span>
<span className="text-muted-foreground"> · {actor}</span>

View File

@@ -89,7 +89,7 @@ export function InlineCountryField({
data-testid={testId}
className={cn(
'group inline-flex items-center gap-1.5 rounded px-1 -mx-1 py-0.5 text-left text-sm',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring',
disabled && 'cursor-not-allowed opacity-60 hover:bg-transparent',
className,
)}

View File

@@ -293,7 +293,7 @@ function ReadButton({
className={cn(
'group rounded px-1 -mx-1 py-0.5 text-left text-sm',
multiline ? 'flex w-full items-start gap-1.5' : 'inline-flex items-center gap-1.5',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring',
// Select-kind buttons get a faint border so they read as a
// distinct interactive element rather than text-with-an-icon.
kind === 'select' && 'border border-border bg-background hover:bg-accent',

View File

@@ -137,7 +137,7 @@ export function InlinePhoneField({
data-testid={testId}
className={cn(
'group inline-flex items-center gap-1.5 rounded px-1 -mx-1 py-0.5 text-left text-sm',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring',
disabled && 'cursor-not-allowed opacity-60 hover:bg-transparent',
className,
)}

View File

@@ -86,7 +86,7 @@ export function InlineTimezoneField({
data-testid={testId}
className={cn(
'group inline-flex items-center gap-1.5 rounded px-1 -mx-1 py-0.5 text-left text-sm',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring',
disabled && 'cursor-not-allowed opacity-60 hover:bg-transparent',
className,
)}

View File

@@ -45,7 +45,7 @@ export function ListCard({
const innerClassName = cn(
'block p-3',
accentClassName && 'pl-4',
'rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'rounded-lg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
);
return (

View File

@@ -48,7 +48,7 @@ export function ResponsiveTabs({ tabs, value, onValueChange }: ResponsiveTabsPro
slides under the wrapper. */}
<div
ref={listRef}
className="overflow-x-auto -mx-2 px-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
className="overflow-x-auto -mx-2 px-2 scrollbar-none [&::-webkit-scrollbar]:hidden"
>
<TabsList className="inline-flex w-max">
{tabs.map((tab) => (

View File

@@ -77,7 +77,7 @@ export function TagPicker({
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start">
<PopoverContent className="w-(--radix-popover-trigger-width) p-0" align="start">
<Command>
<CommandInput placeholder="Search tags..." />
<CommandList>

View File

@@ -97,10 +97,7 @@ export function TimezoneCombobox({
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-[var(--radix-popper-anchor-width)] min-w-[360px] p-0"
align="start"
>
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[360px] p-0" align="start">
<Command>
<CommandInput placeholder="Search timezones…" />
<CommandList>

View File

@@ -4,7 +4,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {

View File

@@ -17,7 +17,7 @@ const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWi
<ol
ref={ref}
className={cn(
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
'flex flex-wrap items-center gap-1.5 wrap-break-word text-sm text-muted-foreground sm:gap-2.5',
className,
)}
{...props}

View File

@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {

View File

@@ -25,9 +25,9 @@ function Calendar({
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
'bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
'bg-background group/calendar p-3 [--cell-size:2rem] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent',
String.raw`[.rdp-button\_next>svg]:**:rtl:rotate-180`,
String.raw`[.rdp-button\_previous>svg]:**:rtl:rotate-180`,
className,
)}
captionLayout={captionLayout}
@@ -45,20 +45,20 @@ function Calendar({
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
'h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50',
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
'h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50',
defaultClassNames.button_next,
),
month_caption: cn(
'flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]',
'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)',
defaultClassNames.month_caption,
),
dropdowns: cn(
'flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium',
'flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium',
defaultClassNames.dropdowns,
),
dropdown_root: cn(
@@ -79,7 +79,7 @@ function Calendar({
defaultClassNames.weekday,
),
week: cn('mt-2 flex w-full', defaultClassNames.week),
week_number_header: cn('w-[--cell-size] select-none', defaultClassNames.week_number_header),
week_number_header: cn('w-(--cell-size) select-none', defaultClassNames.week_number_header),
week_number: cn(
'text-muted-foreground select-none text-[0.8rem]',
defaultClassNames.week_number,
@@ -126,7 +126,7 @@ function Calendar({
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-[--cell-size] items-center justify-center text-center">
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
@@ -168,7 +168,7 @@ function CalendarDayButton({
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70',
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-(--cell-size) flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70',
defaultClassNames.day,
className,
)}

View File

@@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
'grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className,
)}
{...props}

View File

@@ -27,7 +27,7 @@ const CommandDialog = ({ children, ...props }: DialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
<Command className="**:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 **:[[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
@@ -44,7 +44,7 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
className={cn(
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
@@ -92,7 +92,7 @@ const CommandGroup = React.forwardRef<
<CommandPrimitive.Group
ref={ref}
className={cn(
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
'overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground',
className,
)}
{...props}
@@ -120,7 +120,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
className,
)}
{...props}

View File

@@ -48,7 +48,7 @@ const DialogContent = React.forwardRef<
// Long forms get an internal scroll cap so the close button + footer
// stay reachable without scrolling the whole page.
'fixed top-0 right-0 bottom-0 left-0 z-50 grid w-full gap-4 border-0 bg-background p-4 shadow-lg duration-200 sm:p-6',
'max-h-[100dvh] overflow-y-auto sm:max-h-[calc(100dvh-2rem)]',
'max-h-dvh overflow-y-auto sm:max-h-[calc(100dvh-2rem)]',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'sm:top-[50%] sm:right-auto sm:bottom-auto sm:left-[50%] sm:max-w-lg sm:translate-x-[-50%] sm:translate-y-[-50%] sm:border sm:rounded-lg',
'sm:data-[state=closed]:zoom-out-95 sm:data-[state=open]:zoom-in-95 sm:data-[state=closed]:slide-out-to-left-1/2 sm:data-[state=closed]:slide-out-to-top-[48%] sm:data-[state=open]:slide-in-from-left-1/2 sm:data-[state=open]:slide-in-from-top-[48%]',
@@ -57,7 +57,7 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>

View File

@@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className,
)}
@@ -46,7 +46,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-[--radix-dropdown-menu-content-transform-origin]',
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-(--radix-dropdown-menu-content-transform-origin)',
className,
)}
{...props}
@@ -63,8 +63,8 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-[--radix-dropdown-menu-content-transform-origin]',
'z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-(--radix-dropdown-menu-content-transform-origin)',
className,
)}
{...props}
@@ -82,7 +82,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
inset && 'pl-8',
className,
)}
@@ -98,7 +98,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
className,
)}
checked={checked}
@@ -121,7 +121,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
className,
)}
{...props}

View File

@@ -15,7 +15,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
type={type}
inputMode={resolvedInputMode}
className={cn(
'flex h-11 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
'flex h-11 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}

View File

@@ -35,7 +35,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent',
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent',
);
const NavigationMenuTrigger = React.forwardRef<
@@ -49,7 +49,7 @@ const NavigationMenuTrigger = React.forwardRef<
>
{children}{' '}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
className="relative top-px ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
@@ -80,7 +80,7 @@ const NavigationMenuViewport = React.forwardRef<
<div className={cn('absolute left-0 top-full flex justify-center')}>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]',
'origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-(--radix-navigation-menu-viewport-width)',
className,
)}
ref={ref}
@@ -97,7 +97,7 @@ const NavigationMenuIndicator = React.forwardRef<
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
'top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
className,
)}
{...props}

View File

@@ -25,7 +25,7 @@ const PopoverContent = React.forwardRef<
// on narrow viewports the calc() ceiling kicks in.
collisionPadding={collisionPadding}
className={cn(
'z-50 w-[min(calc(100vw-2rem),18rem)] rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-[--radix-popover-content-transform-origin]',
'z-50 w-[min(calc(100vw-2rem),18rem)] rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-(--radix-popover-content-transform-origin)',
className,
)}
{...props}

View File

@@ -22,7 +22,7 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}

View File

@@ -32,8 +32,8 @@ const ScrollBar = React.forwardRef<
orientation={orientation}
className={cn(
'flex touch-none select-none transition-colors',
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-px',
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-px',
className,
)}
{...props}

View File

@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-placeholder:text-muted-foreground focus:outline-hidden focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className,
)}
{...props}
@@ -68,7 +68,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-[--radix-select-content-transform-origin]',
'relative z-50 max-h-(--radix-select-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-(--radix-select-content-transform-origin)',
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,
@@ -81,7 +81,7 @@ const SelectContent = React.forwardRef<
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)',
)}
>
{children}
@@ -111,7 +111,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
className,
)}
{...props}

View File

@@ -15,7 +15,7 @@ const Separator = React.forwardRef<
orientation={orientation}
className={cn(
'shrink-0 bg-border',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
className,
)}
{...props}

View File

@@ -61,7 +61,7 @@ const SheetContent = React.forwardRef<
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>

View File

@@ -17,7 +17,7 @@ const Slider = React.forwardRef<
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;

View File

@@ -11,7 +11,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
className,
)}
{...props}

View File

@@ -37,7 +37,7 @@ const TableFooter = React.forwardRef<
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
className={cn('border-t bg-muted/50 font-medium last:[&>tr]:border-b-0', className)}
{...props}
/>
));
@@ -64,7 +64,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
'h-10 px-2 text-left align-middle font-medium text-muted-foreground has-[[role=checkbox]]:pr-0 *:[[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
@@ -79,7 +79,7 @@ const TableCell = React.forwardRef<
<td
ref={ref}
className={cn(
'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
'p-2 align-middle has-[[role=checkbox]]:pr-0 *:[[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}

View File

@@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
className,
)}
{...props}
@@ -44,7 +44,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'mt-2 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
{...props}

View File

@@ -7,7 +7,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<'tex
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
'flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}

View File

@@ -20,7 +20,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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 origin-[--radix-tooltip-content-transform-origin]',
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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 origin-(--radix-tooltip-content-transform-origin)',
className,
)}
{...props}

View File

@@ -62,7 +62,7 @@ export type Align = 'left' | 'right' | 'center';
export interface TableColumn<Row> {
header: string;
/** flex-grow weight (default 1) — controls column width proportions. */
/** grow weight (default 1) — controls column width proportions. */
flex?: number;
align?: Align;
render: (row: Row, rowIndex: number) => ReactNode;

View File

@@ -1,187 +0,0 @@
import type { Config } from 'tailwindcss';
import tailwindcssAnimate from 'tailwindcss-animate';
export default {
darkMode: ['class', 'class'],
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
brand: {
'50': '#d8e5f4',
'100': '#b1cbe9',
'200': '#89b0de',
'300': '#6196d3',
'400': '#3a7bc8',
'500': '#2f6ab5',
'600': '#255a9e',
'700': '#1c4a87',
DEFAULT: '#3a7bc8',
dark: '#1e2844',
},
navy: {
'50': '#cdcfd6',
'100': '#9ea1af',
'200': '#71768a',
'300': '#474e66',
'400': '#1e2844',
'500': '#171f35',
'600': '#101625',
DEFAULT: '#1e2844',
},
sage: {
DEFAULT: '#dae3c1',
light: '#edf1e2',
dark: '#b8c49e',
},
mint: {
DEFAULT: '#add5b3',
light: '#d6ead9',
dark: '#7dba85',
},
teal: {
DEFAULT: '#83aab1',
light: '#b1cdd2',
dark: '#5a8a92',
},
purple: {
DEFAULT: '#685aa3',
light: '#a49ac6',
dark: '#4d4280',
},
success: {
DEFAULT: '#2d8a4e',
bg: '#e8f5e9',
border: '#a5d6a7',
},
warning: {
DEFAULT: '#e6a817',
bg: '#fff8e1',
border: '#ffe082',
},
error: {
DEFAULT: '#d32f2f',
bg: '#ffebee',
border: '#ef9a9a',
},
sidebar: {
DEFAULT: '#1e2844',
text: '#cdcfd6',
hover: '#171f35',
active: '#3a7bc8',
divider: '#474e66',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'Arial', 'sans-serif'],
mono: ['JetBrains Mono', 'ui-monospace', 'monospace'],
serif: ['Georgia', 'Times New Roman', 'serif'],
},
boxShadow: {
xs: '0 1px 2px 0 rgb(15 23 42 / 0.04)',
sm: '0 2px 4px -1px rgb(15 23 42 / 0.06)',
DEFAULT: '0 1px 3px rgba(30, 40, 68, 0.10), 0 1px 2px rgba(30, 40, 68, 0.06)',
md: '0 4px 12px -2px rgb(15 23 42 / 0.08)',
lg: '0 12px 32px -8px rgb(15 23 42 / 0.12)',
xl: '0 20px 25px rgba(30, 40, 68, 0.10), 0 8px 10px rgba(30, 40, 68, 0.04)',
glow: '0 0 0 4px rgb(58 123 200 / 0.12)',
},
borderRadius: {
sm: '0.375rem',
DEFAULT: '0.375rem',
md: '0.5rem',
lg: '0.625rem',
xl: '0.875rem',
},
backgroundImage: {
'gradient-brand': 'linear-gradient(135deg, #3a7bc8 0%, #2f6ab5 100%)',
'gradient-brand-soft': 'linear-gradient(135deg, #d8e5f4 0%, #ffffff 100%)',
'gradient-success': 'linear-gradient(135deg, #e8f5e9 0%, #ffffff 100%)',
'gradient-warning': 'linear-gradient(135deg, #fef3c7 0%, #ffffff 100%)',
},
transitionTimingFunction: {
spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
smooth: 'cubic-bezier(0.4, 0, 0.2, 1)',
},
width: {
sidebar: '256px',
'sidebar-collapsed': '64px',
},
transitionDuration: {
sidebar: '200ms',
fast: '150ms',
base: '200ms',
slow: '300ms',
},
spacing: {
safe: 'env(safe-area-inset-bottom)',
'safe-top': 'env(safe-area-inset-top)',
'safe-bottom': 'env(safe-area-inset-bottom)',
'safe-left': 'env(safe-area-inset-left)',
'safe-right': 'env(safe-area-inset-right)',
},
keyframes: {
'accordion-down': {
from: {
height: '0',
},
to: {
height: 'var(--radix-accordion-content-height)',
},
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)',
},
to: {
height: '0',
},
},
'badge-pop': {
'0%': { transform: 'scale(0.5)', opacity: '0' },
'60%': { transform: 'scale(1.18)', opacity: '1' },
'100%': { transform: 'scale(1)', opacity: '1' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'badge-pop': 'badge-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)',
},
},
},
plugins: [tailwindcssAnimate],
} satisfies Config;