Polish mobile layouts, extract ExpandableText, fix image sizing

- Stack title-hero poster/metadata vertically on mobile (flex-col md:flex-row),
  switch backdrop tall breakpoint from sm to md
- Extract bio expand/collapse logic into reusable ExpandableText component;
  use it in PersonHero and TitleHero
- Fix ContinueWatchingCard image to use fill + sizes instead of fixed
  width/height to avoid layout shift
- Add fade-out gradient on genre chip scrollbar edge on mobile
- Show scaled-down ambient glow on mobile instead of hiding it entirely
- Humanize knownForDepartment labels (Acting→Actor, Directing→Director, etc.)
- Change cast member name from truncate to line-clamp-2 for wrapping
- Hide tooltip arrow via [&>:last-child]:hidden instead of color-matching it
- Apply safe-area-inset padding to HeroBanner content on notched devices
This commit is contained in:
2026-03-07 12:09:51 -05:00
parent 5d63ad699f
commit 383f0b819b
97 changed files with 767 additions and 608 deletions
+3 -3
View File
@@ -25,17 +25,17 @@ export default async function RegisterPage() {
<IconLock aria-hidden={true} className="size-6 text-primary" />
</div>
<div className="space-y-2">
<h1 className="font-display text-xl tracking-tight text-balance">
<h1 className="text-balance font-display text-xl tracking-tight">
Registration Closed
</h1>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
New accounts are not being accepted right now. Contact the admin
if you need access.
</p>
</div>
<Link
href="/login"
className="inline-flex h-10 items-center rounded-lg bg-primary px-6 text-sm font-medium text-primary-foreground transition-all hover:shadow-md hover:shadow-primary/20"
className="inline-flex h-10 items-center rounded-lg bg-primary px-6 font-medium text-primary-foreground text-sm transition-all hover:shadow-md hover:shadow-primary/20"
>
Sign in instead
</Link>
+11 -11
View File
@@ -50,7 +50,7 @@ const steps = [
</a>{" "}
and request an API key. Choose &ldquo;Developer&rdquo; when asked. You
need the{" "}
<span className="font-mono text-xs text-primary">
<span className="font-mono text-primary text-xs">
API Read Access Token
</span>{" "}
(the long one).
@@ -123,11 +123,11 @@ export function SetupForm() {
>
{/* Header */}
<motion.div variants={sectionVariants} className="space-y-3">
<div className="inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/5 px-3 py-1 text-xs font-medium text-primary">
<div className="inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/5 px-3 py-1 font-medium text-primary text-xs">
<IconKey aria-hidden={true} className="size-3.5" />
Setup required
</div>
<h1 className="font-display text-3xl tracking-tight text-balance sm:text-4xl">
<h1 className="text-balance font-display text-3xl tracking-tight sm:text-4xl">
Connect to TMDB
</h1>
<p className="max-w-lg text-muted-foreground leading-relaxed">
@@ -150,13 +150,13 @@ export function SetupForm() {
{steps.map((step, i) => (
<div key={step.number} className="flex gap-4">
<div className="flex shrink-0 items-start pt-0.5">
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-primary/10 font-mono text-sm font-semibold text-primary">
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-primary/10 font-mono font-semibold text-primary text-sm">
{step.number}
</span>
</div>
<div className="space-y-1">
<h2 className="font-medium">{step.title}</h2>
<p className="text-sm leading-relaxed text-muted-foreground">
<p className="text-muted-foreground text-sm leading-relaxed">
{step.description}
</p>
@@ -168,8 +168,8 @@ export function SetupForm() {
key={snippet.label}
className="group relative overflow-hidden rounded-lg border border-border/50 bg-card/60"
>
<div className="flex items-center justify-between border-b border-border/30 px-3 py-1.5">
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
<div className="flex items-center justify-between border-border/30 border-b px-3 py-1.5">
<span className="font-medium text-[11px] text-muted-foreground uppercase tracking-wider">
{snippet.label}
</span>
<Button
@@ -194,7 +194,7 @@ export function SetupForm() {
)}
</Button>
</div>
<pre className="overflow-x-auto p-3 font-mono text-sm text-foreground/80">
<pre className="overflow-x-auto p-3 font-mono text-foreground/80 text-sm">
{snippet.code}
</pre>
</div>
@@ -221,7 +221,7 @@ export function SetupForm() {
<p className="font-medium text-green-400">
TMDB API key detected
</p>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Redirecting you to Sofa
</p>
</div>
@@ -229,10 +229,10 @@ export function SetupForm() {
) : (
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">
<p className="font-medium text-sm">
After setting the key and restarting:
</p>
<p className="text-xs text-muted-foreground">
<p className="text-muted-foreground text-xs">
Click the button to verify your configuration
</p>
</div>
@@ -35,16 +35,16 @@ export function ContinueWatchingCard({
return (
<Link
href={`/titles/${item.title.id}`}
className="group relative w-[calc(100vw-3rem)] shrink-0 overflow-hidden rounded-xl bg-card/50 ring-1 ring-white/[0.06] transition-shadow hover:shadow-lg hover:shadow-black/25 sm:w-72"
className="group relative inline-block w-64 shrink-0 overflow-hidden rounded-xl bg-card/50 ring-1 ring-white/[0.06] transition-shadow hover:shadow-black/25 hover:shadow-lg sm:w-72"
>
<div className="relative aspect-video overflow-hidden rounded-t-xl bg-muted">
{stillUrl ? (
<Image
src={stillUrl}
alt={item.nextEpisode?.name ?? item.title.title}
width={500}
height={281}
className="h-full w-full object-cover motion-safe:transition-transform motion-safe:duration-300 motion-safe:group-hover:scale-105"
fill
sizes="(min-width: 640px) 288px, 256px"
className="object-cover motion-safe:transition-transform motion-safe:duration-300 motion-safe:group-hover:scale-105"
/>
) : (
<div className="flex h-full items-center justify-center bg-gradient-to-br from-card via-secondary to-muted">
@@ -56,13 +56,13 @@ export function ContinueWatchingCard({
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" />
{item.nextEpisode && (
<div className="absolute bottom-2.5 left-3 right-3">
<p className="flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wider text-primary">
<span className="inline-block h-1.5 w-1.5 motion-safe:animate-pulse rounded-full bg-primary" />
<div className="absolute right-3 bottom-2.5 left-3">
<p className="flex items-center gap-1.5 font-medium text-[10px] text-primary uppercase tracking-wider">
<span className="inline-block h-1.5 w-1.5 rounded-full bg-primary motion-safe:animate-pulse" />
Up next
</p>
<p className="mt-0.5 truncate text-sm font-medium text-white">
<span className="font-mono text-xs text-white/60 [word-spacing:-0.25em] mr-0.5">
<p className="mt-0.5 truncate font-medium text-sm text-white">
<span className="mr-0.5 font-mono text-white/60 text-xs [word-spacing:-0.25em]">
S{item.nextEpisode.seasonNumber} E
{item.nextEpisode.episodeNumber}
</span>{" "}
@@ -73,8 +73,8 @@ export function ContinueWatchingCard({
</div>
<div className="flex items-center gap-3 p-3">
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{item.title.title}</p>
<p className="text-xs text-muted-foreground">
<p className="truncate font-medium text-sm">{item.title.title}</p>
<p className="text-muted-foreground text-xs">
{item.watchedEpisodes}/{item.totalEpisodes} episodes
</p>
</div>
@@ -83,7 +83,7 @@ export function ContinueWatchingCard({
</div>
</div>
{progress > 0 && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-muted">
<div className="absolute right-0 bottom-0 left-0 h-0.5 bg-muted">
<div
className="h-full bg-primary transition-[width]"
style={{ width: `${progress}%` }}
@@ -18,7 +18,7 @@ export function ContinueWatchingList({
return (
<Carousel
opts={{ align: "start", dragFree: true, containScroll: "trimSnaps" }}
className="-mx-4 sm:-mx-0"
className="-mx-4 sm:-mx-0 [&>[data-slot=carousel-content]]:px-px"
>
<CarouselContent className="px-4 sm:px-0">
{items.map((item, i) => (
@@ -15,7 +15,7 @@ export function FeedSection({
<section className="space-y-4">
<div className="flex items-center gap-2">
<span aria-hidden={true}>{icon}</span>
<h2 className="font-display text-xl tracking-tight text-balance">
<h2 className="text-balance font-display text-xl tracking-tight">
{title}
</h2>
</div>
@@ -53,7 +53,7 @@ function StatCard({
}: StatCardProps) {
return (
<div
className="animate-stagger-item relative overflow-hidden rounded-xl border border-border/30 bg-card/50 p-4"
className="relative animate-stagger-item overflow-hidden rounded-xl border border-border/30 bg-card/50 p-4"
style={{ "--stagger-index": index } as React.CSSProperties}
>
{sparklineData && <Sparkline data={sparklineData} color={color} />}
@@ -63,7 +63,7 @@ function StatCard({
>
<Icon aria-hidden={true} className={`size-[13px] ${color}`} />
</div>
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
{label}
</span>
</div>
@@ -97,7 +97,7 @@ function PeriodSelector({
onValueChange={(v) => v && onPeriodChange(v as TimePeriod)}
>
<SelectTrigger
className={`${inlineTriggerClass} uppercase text-foreground/80`}
className={`${inlineTriggerClass} text-foreground/80 uppercase`}
>
<SelectValue>
{(value: TimePeriod | null) => (value ? periodLabels[value] : null)}
@@ -132,11 +132,11 @@ export function StatsDisplay({ stats }: { stats: DashboardStats }) {
} | null>(null);
useEffect(() => {
getStatsAction("movies", moviePeriod).then(setMovieStats);
void getStatsAction("movies", moviePeriod).then(setMovieStats);
}, [moviePeriod]);
useEffect(() => {
getStatsAction("episodes", episodePeriod).then(setEpisodeStats);
void getStatsAction("episodes", episodePeriod).then(setEpisodeStats);
}, [episodePeriod]);
const movieCount = movieStats?.count ?? stats.moviesThisMonth;
@@ -16,19 +16,19 @@ export async function StatsSection({ userId }: { userId: string }) {
<>
<StatsDisplay stats={stats} />
{isEmpty && (
<div className="flex flex-col items-center gap-4 rounded-xl border border-dashed border-border/50 py-16 text-center">
<div className="flex flex-col items-center gap-4 rounded-xl border border-border/50 border-dashed py-16 text-center">
<div className="rounded-full bg-primary/10 p-4">
<IconDeviceTv aria-hidden={true} className="size-8 text-primary" />
</div>
<div className="space-y-1">
<p className="font-medium">Your library is empty</p>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Search for movies and TV shows to start tracking
</p>
</div>
<Link
href="/explore"
className="inline-flex h-9 items-center rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground transition-shadow hover:shadow-md hover:shadow-primary/20"
className="inline-flex h-9 items-center rounded-lg bg-primary px-4 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-md hover:shadow-primary/20"
>
Start exploring
</Link>
@@ -1,10 +1,10 @@
export function WelcomeHeader({ name }: { name?: string | null }) {
return (
<div>
<h1 className="font-display text-3xl tracking-tight text-balance">
<h1 className="text-balance font-display text-3xl tracking-tight">
Welcome back{name ? `, ${name}` : ""}
</h1>
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-muted-foreground text-sm">
Here&apos;s what&apos;s happening with your library
</p>
</div>
+3 -3
View File
@@ -26,7 +26,7 @@ export default function PagesError({
<h1 className="font-display text-2xl tracking-tight sm:text-3xl">
Something went wrong
</h1>
<p className="mx-auto max-w-sm text-sm leading-relaxed text-muted-foreground">
<p className="mx-auto max-w-sm text-muted-foreground text-sm leading-relaxed">
An unexpected error occurred while loading this page. You can try
again or head back to the dashboard.
</p>
@@ -42,7 +42,7 @@ export default function PagesError({
<button
type="button"
onClick={reset}
className="group relative inline-flex h-10 items-center justify-center gap-2 overflow-hidden rounded-lg bg-primary px-5 text-sm font-medium text-primary-foreground transition-shadow hover:shadow-lg hover:shadow-primary/20"
className="group relative inline-flex h-10 items-center justify-center gap-2 overflow-hidden rounded-lg bg-primary px-5 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-lg hover:shadow-primary/20"
>
<IconRefresh className="size-4" />
<span className="relative z-10">Try again</span>
@@ -50,7 +50,7 @@ export default function PagesError({
</button>
<Link
href="/dashboard"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 text-sm font-medium transition-colors hover:border-primary/40 hover:bg-primary/5"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 font-medium text-sm transition-colors hover:border-primary/40 hover:bg-primary/5"
>
Dashboard
</Link>
@@ -106,28 +106,31 @@ function FilterableTitleRowInner({
<section className="space-y-4">
<div className="flex items-center gap-2">
{icon}
<h2 className="font-display text-xl tracking-tight text-balance">
<h2 className="text-balance font-display text-xl tracking-tight">
{heading}
</h2>
</div>
{/* Genre chips */}
<div className="no-scrollbar -mx-4 flex gap-2 overflow-x-auto px-4 pb-1 sm:-mx-0 sm:flex-wrap sm:px-0">
{genres.map((genre) => (
<Button
key={genre.id}
variant={selectedGenre === genre.id ? "default" : "outline"}
size="xs"
onClick={() => toggleGenre(genre.id)}
className={`shrink-0 rounded-full ${
selectedGenre === genre.id
? "border-primary bg-primary/10 text-primary hover:bg-primary/20"
: "border-border/50 bg-card/50 text-muted-foreground hover:border-primary/20 hover:text-foreground"
}`}
>
{genre.name}
</Button>
))}
<div className="relative">
<div className="no-scrollbar -mx-4 flex gap-2 overflow-x-auto px-4 pb-1 sm:mx-0 sm:px-0">
{genres.map((genre) => (
<Button
key={genre.id}
variant={selectedGenre === genre.id ? "default" : "outline"}
size="sm"
onClick={() => toggleGenre(genre.id)}
className={`shrink-0 rounded-full ${
selectedGenre === genre.id
? "border-primary bg-primary/10 text-primary hover:bg-primary/20"
: "border-border/50 bg-card/50 text-muted-foreground hover:border-primary/20 hover:text-foreground"
}`}
>
{genre.name}
</Button>
))}
</div>
<div className="pointer-events-none absolute top-0 right-0 h-full w-8 bg-gradient-to-l from-background to-transparent sm:hidden" />
</div>
{/* Loading skeleton */}
@@ -147,7 +150,7 @@ function FilterableTitleRowInner({
{/* Empty state */}
{!loading && selectedGenre !== null && items.length === 0 && (
<p className="py-8 text-center text-sm text-muted-foreground">
<p className="py-8 text-center text-muted-foreground text-sm">
No titles found for this genre.
</p>
)}
@@ -161,13 +164,13 @@ function FilterableTitleRowInner({
dragFree: true,
containScroll: "trimSnaps",
}}
className="-mx-6 sm:-mx-2 carousel-tilt"
className="carousel-tilt -mx-6 sm:-mx-2"
>
<CarouselContent className="px-6 sm:px-2">
{items.slice(0, 20).map((item, i) => (
<CarouselItem
key={`${item.type}-${item.tmdbId}`}
className="basis-auto pl-4 w-[140px] shrink-0 sm:w-[160px]"
className="w-[140px] shrink-0 basis-auto pl-4 sm:w-[160px]"
>
<div
className="animate-stagger-item"
+10 -10
View File
@@ -24,8 +24,8 @@ export function HeroBanner({
const href = `/titles/tmdb-${tmdbId}-${type}`;
return (
<div className="animate-stagger-item relative -mt-6 mb-4 ml-[calc(-50vw+50%)] mr-[calc(-50vw+50%)] overflow-hidden">
<div className="relative w-full min-h-[280px] max-h-[420px] aspect-[21/9]">
<div className="relative -mt-6 mr-[calc(-50vw+50%)] mb-4 ml-[calc(-50vw+50%)] animate-stagger-item overflow-hidden">
<div className="relative aspect-[21/9] max-h-[420px] min-h-[280px] w-full">
{backdropPath ? (
<Image
src={backdropPath}
@@ -44,18 +44,18 @@ export function HeroBanner({
{/* Content */}
<div className="absolute inset-0 flex items-end">
<div className="w-full px-4 pb-8 sm:px-6">
<div className="mx-auto max-w-6xl">
<div className="w-full pb-8">
<div className="mx-auto max-w-6xl pr-[max(1rem,env(safe-area-inset-right))] pl-[max(1rem,env(safe-area-inset-left))] sm:pr-[max(1.5rem,env(safe-area-inset-right))] sm:pl-[max(1.5rem,env(safe-area-inset-left))]">
<div
className="animate-stagger-item"
style={{ "--stagger-index": 3 } as React.CSSProperties}
>
<div className="mb-3 flex items-center gap-2">
<span className="rounded bg-primary/20 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-primary">
<span className="rounded bg-primary/20 px-2 py-0.5 font-semibold text-[10px] text-primary uppercase tracking-wider">
{type}
</span>
{voteAverage > 0 && (
<span className="flex items-center gap-1 text-sm text-primary">
<span className="flex items-center gap-1 text-primary text-sm">
<IconStar
aria-hidden={true}
className="size-3.5 fill-primary"
@@ -63,21 +63,21 @@ export function HeroBanner({
{voteAverage.toFixed(1)}
</span>
)}
<span className="text-xs text-muted-foreground">
<span className="text-muted-foreground text-xs">
Trending today
</span>
</div>
<Link href={href} className="group/title">
<h2 className="font-display text-3xl tracking-tight text-balance sm:text-4xl transition-colors group-hover/title:text-primary">
<h2 className="text-balance font-display text-3xl tracking-tight transition-colors group-hover/title:text-primary sm:text-4xl">
{title}
</h2>
</Link>
<p className="mt-2 line-clamp-2 max-w-2xl text-sm text-muted-foreground">
<p className="mt-2 line-clamp-2 max-w-2xl text-muted-foreground text-sm">
{overview}
</p>
<Link
href={href}
className="mt-4 inline-flex h-9 items-center gap-2 rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground transition-shadow hover:shadow-md hover:shadow-primary/20"
className="mt-4 inline-flex h-9 items-center gap-2 rounded-lg bg-primary px-4 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-md hover:shadow-primary/20"
>
<IconPlus aria-hidden={true} className="size-4" />
Add to Library
@@ -37,19 +37,19 @@ export function TitleRow({
<section className="space-y-4">
<div className="flex items-center gap-2">
{icon}
<h2 className="font-display text-xl tracking-tight text-balance">
<h2 className="text-balance font-display text-xl tracking-tight">
{heading}
</h2>
</div>
<Carousel
opts={{ align: "start", dragFree: true, containScroll: "trimSnaps" }}
className="-mx-6 sm:-mx-2 carousel-tilt"
className="carousel-tilt -mx-6 sm:-mx-2"
>
<CarouselContent className="px-6 sm:px-2">
{items.map((item, i) => (
<CarouselItem
key={`${item.type}-${item.tmdbId}`}
className="basis-auto pl-4 w-[140px] shrink-0 sm:w-[160px]"
className="w-[140px] shrink-0 basis-auto pl-4 sm:w-[160px]"
>
<div
className="animate-stagger-item"
+2 -2
View File
@@ -35,8 +35,8 @@ export default function ExploreLoading() {
return (
<div className="space-y-10">
{/* Hero skeleton — full viewport width like the actual HeroBanner */}
<div className="-mt-6 mb-4 ml-[calc(-50vw+50%)] mr-[calc(-50vw+50%)]">
<Skeleton className="aspect-[21/9] min-h-[280px] max-h-[420px] w-full rounded-none" />
<div className="-mt-6 mr-[calc(-50vw+50%)] mb-4 ml-[calc(-50vw+50%)]">
<Skeleton className="aspect-[21/9] max-h-[420px] min-h-[280px] w-full rounded-none" />
</div>
<TitleRowSkeleton />
+3 -3
View File
@@ -20,11 +20,11 @@ export default async function PagesLayout({
<ProgressProvider>
<div className="relative z-0 min-h-screen pb-[calc(3.5rem+env(safe-area-inset-bottom))] sm:pb-0">
<NavBar userName={session.user.name} />
{/* Ambient glow — hidden on mobile where it overwhelms the viewport */}
<div className="pointer-events-none fixed left-1/2 top-1/4 -translate-x-1/2 -translate-y-1/2 hidden h-[600px] w-[800px] rounded-full bg-primary/3 blur-[200px] sm:block" />
{/* Ambient glow — smaller on mobile to add warmth without overwhelming */}
<div className="pointer-events-none fixed top-1/4 left-1/2 h-[300px] w-[300px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/3 blur-[120px] sm:h-[600px] sm:w-[800px] sm:blur-[200px]" />
<main
id="main-content"
className="relative mx-auto max-w-6xl pl-[max(1rem,env(safe-area-inset-left))] pr-[max(1rem,env(safe-area-inset-right))] py-6 sm:pl-[max(1.5rem,env(safe-area-inset-left))] sm:pr-[max(1.5rem,env(safe-area-inset-right))]"
className="relative mx-auto max-w-6xl py-6 pr-[max(1rem,env(safe-area-inset-right))] pl-[max(1rem,env(safe-area-inset-left))] sm:pr-[max(1.5rem,env(safe-area-inset-right))] sm:pl-[max(1.5rem,env(safe-area-inset-left))]"
>
{children}
</main>
+6 -6
View File
@@ -4,37 +4,37 @@ export default function PagesNotFound() {
return (
<div className="flex flex-col items-center gap-6 py-24 text-center">
{/* Ghosted 404 */}
<h1 className="animate-stagger-item font-display text-[6rem] leading-[0.85] tracking-tight text-foreground/[0.06] sm:text-[8rem]">
<h1 className="animate-stagger-item font-display text-[6rem] text-foreground/[0.06] leading-[0.85] tracking-tight sm:text-[8rem]">
404
</h1>
<div
className="animate-stagger-item -mt-4 space-y-2"
className="-mt-4 animate-stagger-item space-y-2"
style={{ "--stagger-index": 1 } as React.CSSProperties}
>
<h2 className="font-display text-2xl tracking-tight sm:text-3xl">
Page not found
</h2>
<p className="mx-auto max-w-sm text-sm leading-relaxed text-muted-foreground">
<p className="mx-auto max-w-sm text-muted-foreground text-sm leading-relaxed">
This page doesn&apos;t exist or may have been moved. Try searching for
what you&apos;re looking for instead.
</p>
</div>
<div
className="animate-stagger-item flex items-center gap-3"
className="flex animate-stagger-item items-center gap-3"
style={{ "--stagger-index": 2 } as React.CSSProperties}
>
<Link
href="/dashboard"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-5 text-sm font-medium text-primary-foreground transition-shadow hover:shadow-lg hover:shadow-primary/20"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-5 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-lg hover:shadow-primary/20"
>
<span className="relative z-10">Dashboard</span>
<div className="absolute inset-0 bg-gradient-to-t from-black/10 to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
</Link>
<Link
href="/explore"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 text-sm font-medium transition-colors hover:border-primary/40 hover:bg-primary/5"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 font-medium text-sm transition-colors hover:border-primary/40 hover:bg-primary/5"
>
Explore
</Link>
@@ -65,10 +65,10 @@ export function FilmographyGrid({
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<IconMovie aria-hidden={true} className="size-5 text-primary" />
<h2 className="font-display text-xl tracking-tight text-balance">
<h2 className="text-balance font-display text-xl tracking-tight">
Filmography
</h2>
<span className="text-sm text-muted-foreground">
<span className="text-muted-foreground text-sm">
({filtered.length})
</span>
</div>
@@ -105,7 +105,7 @@ export function FilmographyGrid({
<Button
key={f.value}
variant={filter === f.value ? "default" : "secondary"}
size="xs"
size="sm"
onClick={() => setFilter(f.value)}
className="rounded-full"
>
@@ -3,7 +3,7 @@
import { IconCalendar, IconMapPin } from "@tabler/icons-react";
import { format, parseISO } from "date-fns";
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import { ExpandableText } from "@/components/expandable-text";
import { Badge } from "@/components/ui/badge";
import type { ResolvedPerson } from "@/lib/types/title";
@@ -23,27 +23,13 @@ function calculateAge(birthday: string, deathday?: string | null): number {
}
export function PersonHero({ person }: PersonHeroProps) {
const [bioExpanded, setBioExpanded] = useState(false);
const [isClamped, setIsClamped] = useState(false);
const bioRef = useRef<HTMLParagraphElement>(null);
useEffect(() => {
const el = bioRef.current;
if (!el) return;
const check = () => setIsClamped(el.scrollHeight > el.clientHeight);
check();
const observer = new ResizeObserver(check);
observer.observe(el);
return () => observer.disconnect();
}, []);
const age = person.birthday
? calculateAge(person.birthday, person.deathday)
: null;
return (
<div className="animate-stagger-item flex flex-col gap-6 sm:flex-row sm:gap-8">
<div className="size-40 shrink-0 self-center overflow-hidden rounded-2xl ring-1 ring-white/10 shadow-2xl sm:size-56 sm:self-start">
<div className="flex animate-stagger-item flex-col gap-6 sm:flex-row sm:gap-8">
<div className="size-40 shrink-0 self-center overflow-hidden rounded-2xl shadow-2xl ring-1 ring-white/10 sm:size-56 sm:self-start">
{person.profilePath ? (
<Image
src={person.profilePath}
@@ -63,17 +49,27 @@ export function PersonHero({ person }: PersonHeroProps) {
</div>
<div className="min-w-0 flex-1 space-y-3">
<h1 className="font-display text-3xl tracking-tight text-balance sm:text-5xl">
<h1 className="text-balance font-display text-3xl tracking-tight sm:text-5xl">
{person.name}
</h1>
{person.knownForDepartment && (
<Badge className="border-0 bg-primary/10 px-2.5 font-semibold uppercase tracking-wider text-primary">
{person.knownForDepartment}
<Badge className="border-0 bg-primary/10 px-2.5 font-semibold text-primary uppercase tracking-wider">
{person.knownForDepartment === "Acting"
? "Actor"
: person.knownForDepartment === "Directing"
? "Director"
: person.knownForDepartment === "Writing"
? "Writer"
: person.knownForDepartment === "Production"
? "Producer"
: person.knownForDepartment === "Editing"
? "Editor"
: person.knownForDepartment}
</Badge>
)}
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground">
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-muted-foreground text-sm">
{person.birthday && (
<span className="flex items-center gap-1.5">
<IconCalendar aria-hidden={true} className="size-3.5" />
@@ -93,27 +89,7 @@ export function PersonHero({ person }: PersonHeroProps) {
)}
</div>
{person.biography && (
<div className="max-w-3xl">
<p
ref={bioRef}
className={`text-sm leading-relaxed text-muted-foreground ${
!bioExpanded ? "line-clamp-3" : ""
}`}
>
{person.biography}
</p>
{(isClamped || bioExpanded) && (
<button
type="button"
onClick={() => setBioExpanded(!bioExpanded)}
className="mt-1 text-xs font-medium text-primary transition-colors hover:text-primary/80"
>
{bioExpanded ? "Show less" : "Read more"}
</button>
)}
</div>
)}
{person.biography && <ExpandableText text={person.biography} />}
</div>
</div>
);
+6 -6
View File
@@ -4,39 +4,39 @@ export default function PersonNotFound() {
return (
<div className="flex flex-col items-center gap-6 py-24 text-center">
<h1
className="animate-stagger-item font-display text-[6rem] leading-[0.85] tracking-tight text-foreground/[0.06] sm:text-[8rem]"
className="animate-stagger-item font-display text-[6rem] text-foreground/[0.06] leading-[0.85] tracking-tight sm:text-[8rem]"
style={{ "--stagger-index": 0 } as React.CSSProperties}
>
404
</h1>
<div
className="animate-stagger-item -mt-4 space-y-2"
className="-mt-4 animate-stagger-item space-y-2"
style={{ "--stagger-index": 1 } as React.CSSProperties}
>
<h2 className="font-display text-2xl tracking-tight sm:text-3xl">
Person not found
</h2>
<p className="mx-auto max-w-sm text-sm leading-relaxed text-muted-foreground">
<p className="mx-auto max-w-sm text-muted-foreground text-sm leading-relaxed">
The person you&apos;re looking for doesn&apos;t exist or may have been
removed from the database.
</p>
</div>
<div
className="animate-stagger-item flex items-center gap-3"
className="flex animate-stagger-item items-center gap-3"
style={{ "--stagger-index": 2 } as React.CSSProperties}
>
<Link
href="/explore"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-5 text-sm font-medium text-primary-foreground transition-shadow hover:shadow-lg hover:shadow-primary/20"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-5 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-lg hover:shadow-primary/20"
>
<span className="relative z-10">Explore titles</span>
<div className="absolute inset-0 bg-gradient-to-t from-black/10 to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
</Link>
<Link
href="/dashboard"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 text-sm font-medium transition-colors hover:border-primary/40 hover:bg-primary/5"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 font-medium text-sm transition-colors hover:border-primary/40 hover:bg-primary/5"
>
Dashboard
</Link>
@@ -29,7 +29,7 @@ export function AccountSection({
<div>
<div className="mb-3 flex items-center gap-2">
<IconUser aria-hidden={true} className="size-4 text-muted-foreground" />
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
<h2 className="font-medium text-muted-foreground text-xs uppercase tracking-wider">
Account
</h2>
</div>
@@ -48,7 +48,7 @@ export function AccountSection({
)}
</CardTitle>
<CardDescription>{user.email}</CardDescription>
<p className="mt-0.5 text-xs text-muted-foreground/60">
<p className="mt-0.5 text-muted-foreground/60 text-xs">
Member since {memberSince}
</p>
</div>
@@ -184,7 +184,7 @@ function BackupScheduleInner() {
value={String(maxRetention)}
onValueChange={(v) => v && changeMaxRetention(Number(v))}
>
<SelectTrigger className="h-auto w-auto gap-0.5 rounded-none border-0 bg-transparent p-0 shadow-none underline decoration-dotted decoration-muted-foreground/50 underline-offset-4 hover:bg-transparent hover:text-foreground hover:decoration-foreground/50 focus-visible:ring-0 focus-visible:decoration-solid focus-visible:decoration-foreground dark:bg-transparent dark:hover:bg-transparent">
<SelectTrigger className="h-auto w-auto gap-0.5 rounded-none border-0 bg-transparent p-0 underline decoration-muted-foreground/50 decoration-dotted underline-offset-4 shadow-none hover:bg-transparent hover:text-foreground hover:decoration-foreground/50 focus-visible:decoration-foreground focus-visible:decoration-solid focus-visible:ring-0 dark:bg-transparent dark:hover:bg-transparent">
<SelectValue>
{(value: string | null) =>
value === "0"
@@ -226,7 +226,7 @@ function BackupScheduleInner() {
<AnimatePresence initial={false}>
{enabled && (
<CardContent className="border-t border-border/30 pt-4">
<CardContent className="border-border/30 border-t pt-4">
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
@@ -237,7 +237,7 @@ function BackupScheduleInner() {
<div className="space-y-3">
{/* Frequency selector */}
<div className="space-y-1.5">
<span className="inline-block text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="inline-block font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
Frequency
</span>
<ButtonGroup>
@@ -270,7 +270,7 @@ function BackupScheduleInner() {
transition={{ duration: 0.15 }}
className="overflow-hidden"
>
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
Day:{" "}
</span>
<Select
@@ -279,7 +279,7 @@ function BackupScheduleInner() {
v && changeSchedule(frequency, time, Number(v))
}
>
<SelectTrigger className="h-auto gap-1 border-border/50 bg-muted/30 px-2.5 py-1 text-xs text-foreground hover:bg-muted/50 dark:bg-muted/30 dark:hover:bg-muted/50">
<SelectTrigger className="h-auto gap-1 border-border/50 bg-muted/30 px-2.5 py-1 text-foreground text-xs hover:bg-muted/50 dark:bg-muted/30 dark:hover:bg-muted/50">
<SelectValue>
{(value: string | null) =>
value !== null
@@ -314,14 +314,14 @@ function BackupScheduleInner() {
transition={{ duration: 0.15 }}
className="overflow-hidden"
>
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
{frequency === "12h" ? "Starting at" : "Time:"}{" "}
</span>
<Select
value={time}
onValueChange={(v) => v && changeSchedule(frequency, v)}
>
<SelectTrigger className="h-auto gap-1 border-border/50 bg-muted/30 px-2.5 py-1 text-xs text-foreground hover:bg-muted/50 dark:bg-muted/30 dark:hover:bg-muted/50">
<SelectTrigger className="h-auto gap-1 border-border/50 bg-muted/30 px-2.5 py-1 text-foreground text-xs hover:bg-muted/50 dark:bg-muted/30 dark:hover:bg-muted/50">
<SelectValue>
{(value: string | null) =>
value
@@ -127,7 +127,7 @@ export function BackupSection({
{/* Backup list */}
<AnimatePresence initial={false}>
{backups.length > 0 && (
<CardContent className="border-t border-border/30 pt-4">
<CardContent className="border-border/30 border-t pt-4">
<div className="space-y-1.5">
{backups.map((backup) => (
<motion.div
@@ -169,7 +169,7 @@ export function BackupSection({
</Tooltip>
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-2">
<span className="text-xs font-medium text-foreground">
<span className="font-medium text-foreground text-xs">
{formatBackupDate(backup.createdAt)}
</span>
<span className="text-[11px] text-muted-foreground">
@@ -185,7 +185,7 @@ export function BackupSection({
</span>
</div>
</div>
<div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
<div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100">
<Tooltip>
<TooltipTrigger
render={
@@ -135,7 +135,7 @@ export function IntegrationCard({ config }: { config: IntegrationConfig }) {
</CardContent>
<CollapsibleContent className="h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-200 ease-out data-[ending-style]:h-0 data-[starting-style]:h-0">
<CardContent className="space-y-3 border-t border-border/30 pt-4">
<CardContent className="space-y-3 border-border/30 border-t pt-4">
{config.alert}
{!connection ? (
@@ -216,7 +216,7 @@ export function IntegrationCard({ config }: { config: IntegrationConfig }) {
)}
<Collapsible open={setupOpen} onOpenChange={setSetupOpen}>
<CollapsibleTrigger className="flex w-full items-center gap-1.5 rounded-md py-1 text-xs text-muted-foreground transition-colors hover:text-foreground">
<CollapsibleTrigger className="flex w-full items-center gap-1.5 rounded-md py-1 text-muted-foreground text-xs transition-colors hover:text-foreground">
<IconChevronDown
aria-hidden={true}
className={`size-3 transition-transform ${setupOpen ? "rotate-0" : "-rotate-90"}`}
@@ -224,7 +224,7 @@ export function IntegrationCard({ config }: { config: IntegrationConfig }) {
Setup instructions
</CollapsibleTrigger>
<CollapsibleContent className="h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-200 ease-out data-[ending-style]:h-0 data-[starting-style]:h-0">
<div className="mt-2 rounded-lg bg-muted/30 border border-border/50 p-3 text-xs leading-relaxed text-muted-foreground">
<div className="mt-2 rounded-lg border border-border/50 bg-muted/30 p-3 text-muted-foreground text-xs leading-relaxed">
<ol className="list-inside list-decimal space-y-1.5">
{config.setupSteps}
</ol>
@@ -232,7 +232,7 @@ export function IntegrationCard({ config }: { config: IntegrationConfig }) {
<p className="mt-2 -ml-0.5">
<IconBook2
aria-hidden={true}
className="inline-block size-3 translate-y-[-1px] mr-1"
className="mr-1 inline-block size-3 translate-y-[-1px]"
/>
Need more help?{" "}
<a
@@ -17,7 +17,7 @@ function origin() {
/** Reusable alert banner for integrations that require a subscription. */
function RequirementAlert({ children }: { children: React.ReactNode }) {
return (
<Alert className="border-primary/20 bg-primary/5 [&>svg]:text-primary gap-0">
<Alert className="gap-0 border-primary/20 bg-primary/5 [&>svg]:text-primary">
<IconInfoCircle aria-hidden={true} className="inline-block size-3.5" />
<AlertDescription className="text-foreground/80">
{children}
@@ -29,7 +29,7 @@ export function IntegrationsSection({
aria-hidden={true}
className="size-4 text-muted-foreground"
/>
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
<h2 className="font-medium text-muted-foreground text-xs uppercase tracking-wider">
Integrations
</h2>
</div>
@@ -33,11 +33,11 @@ export function SettingsShell({
<motion.div variants={sectionVariants}>
<div className="flex items-center gap-2">
<IconSettings aria-hidden={true} className="size-5 text-primary" />
<h1 className="font-display text-3xl tracking-tight text-balance">
<h1 className="text-balance font-display text-3xl tracking-tight">
Settings
</h1>
</div>
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-muted-foreground text-sm">
Manage your account and preferences
</p>
</motion.div>
@@ -225,9 +225,9 @@ function SystemStatusCard() {
</CardContent>
{/* Database */}
<CardContent className="border-t border-border/30 pt-4">
<CardContent className="border-border/30 border-t pt-4">
<div className="flex items-center gap-2">
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
Database
</span>
<span className="font-mono text-[11px] text-muted-foreground">
@@ -239,22 +239,22 @@ function SystemStatusCard() {
</CardContent>
{/* TMDB */}
<CardContent className="border-t border-border/30 pt-4">
<CardContent className="border-border/30 border-t pt-4">
<div className="flex items-center gap-2">
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
TMDB API
</span>
{!data.tmdb.tokenConfigured ? (
<>
<StatusDot status="error" />
<span className="text-xs text-muted-foreground/50">
<span className="text-muted-foreground/50 text-xs">
Not configured
</span>
</>
) : data.tmdb.connected && data.tmdb.tokenValid ? (
<>
<StatusDot status="ok" />
<span className="text-xs text-muted-foreground">Connected</span>
<span className="text-muted-foreground text-xs">Connected</span>
<span className="font-mono text-[11px] text-muted-foreground/80">
{data.tmdb.responseTimeMs}ms
</span>
@@ -262,12 +262,12 @@ function SystemStatusCard() {
) : data.tmdb.connected && !data.tmdb.tokenValid ? (
<>
<StatusDot status="error" />
<span className="text-xs text-destructive">Invalid token</span>
<span className="text-destructive text-xs">Invalid token</span>
</>
) : (
<>
<StatusDot status="error" />
<span className="text-xs text-destructive">Unreachable</span>
<span className="text-destructive text-xs">Unreachable</span>
{data.tmdb.error && (
<span className="text-[11px] text-muted-foreground/50">
{data.tmdb.error}
@@ -279,9 +279,9 @@ function SystemStatusCard() {
</CardContent>
{/* Environment */}
<CardContent className="border-t border-border/30 pt-4">
<CardContent className="border-border/30 border-t pt-4">
<div className="space-y-2">
<span className="inline-flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="inline-flex items-center gap-1.5 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
Environment
{data.environment.dataDirWritable ? (
<IconCheck aria-hidden={true} className="size-3 text-green-500" />
@@ -301,7 +301,7 @@ function SystemStatusCard() {
className="flex items-baseline gap-[1px] font-mono text-[11px] leading-relaxed"
>
<span className="text-muted-foreground/60">{env.name}=</span>
<span className="text-muted-foreground break-all">
<span className="break-all text-muted-foreground">
{env.value}
</span>
</div>
@@ -365,23 +365,23 @@ function BackgroundJobsCard() {
<RefreshButton />
</div>
</CardContent>
<CardContent className="border-t border-border/30 px-0 pt-0 pb-0">
<CardContent className="border-border/30 border-t px-0 pt-0 pb-0">
<Table>
<TableHeader>
<TableRow className="border-b-border/30 hover:bg-transparent">
<TableHead className="h-8 pl-5 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
<TableHead className="h-8 pl-5 font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Job
</TableHead>
<TableHead className="h-8 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
<TableHead className="h-8 font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Schedule
</TableHead>
<TableHead className="h-8 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
<TableHead className="h-8 font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Last run
</TableHead>
<TableHead className="h-8 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
<TableHead className="h-8 font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Next run
</TableHead>
<TableHead className="h-8 pr-5 text-right text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
<TableHead className="h-8 pr-5 text-right font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
<span className="sr-only">Actions</span>
</TableHead>
</TableRow>
@@ -413,7 +413,7 @@ function BackgroundJobsCard() {
label={job.lastError ?? "Last run failed"}
/>
)}
<span className="text-xs text-muted-foreground">
<span className="text-muted-foreground text-xs">
{JOB_LABELS[job.jobName] ?? job.jobName}
</span>
</div>
@@ -424,7 +424,7 @@ function BackgroundJobsCard() {
{job.cronPattern ? (
<Tooltip>
<TooltipTrigger className="cursor-default">
<span className="text-xs text-muted-foreground/80">
<span className="text-muted-foreground/80 text-xs">
{cronToHuman(job.cronPattern)}
</span>
</TooltipTrigger>
@@ -433,7 +433,7 @@ function BackgroundJobsCard() {
</TooltipContent>
</Tooltip>
) : (
<span className="text-xs text-muted-foreground/50">
<span className="text-muted-foreground/50 text-xs">
</span>
)}
@@ -446,7 +446,7 @@ function BackgroundJobsCard() {
<TooltipTrigger className="cursor-default">
<div className="flex items-baseline gap-1.5">
<span
className="text-xs text-muted-foreground/80"
className="text-muted-foreground/80 text-xs"
suppressHydrationWarning
>
<LiveTimeAgo date={job.lastRunAt} />
@@ -469,7 +469,7 @@ function BackgroundJobsCard() {
</TooltipContent>
</Tooltip>
) : (
<span className="text-xs text-muted-foreground/50">
<span className="text-muted-foreground/50 text-xs">
Never
</span>
)}
@@ -481,7 +481,7 @@ function BackgroundJobsCard() {
<Tooltip>
<TooltipTrigger className="cursor-default">
<span
className="text-xs text-muted-foreground/80"
className="text-muted-foreground/80 text-xs"
suppressHydrationWarning
>
<LiveTimeAgo date={job.nextRunAt} />
@@ -492,7 +492,7 @@ function BackgroundJobsCard() {
</TooltipContent>
</Tooltip>
) : (
<span className="text-xs text-muted-foreground/50">
<span className="text-muted-foreground/50 text-xs">
</span>
)}
@@ -561,9 +561,9 @@ function StorageCard() {
</CardContent>
{/* Image cache */}
<CardContent className="border-t border-border/30 pt-4">
<CardContent className="border-border/30 border-t pt-4">
<div className="flex items-center justify-between">
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
Image cache
</span>
{data.imageCache.enabled ? (
@@ -574,17 +574,17 @@ function StorageCard() {
</div>
{data.imageCache.enabled ? (
<>
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-muted-foreground text-xs">
{data.imageCache.imageCount.toLocaleString()} cached images
</p>
<p className="mt-0.5 text-[10px] leading-relaxed text-muted-foreground/50">
<p className="mt-0.5 text-[10px] text-muted-foreground/50 leading-relaxed">
{Object.entries(data.imageCache.categories)
.map(([name, cat]) => `${name} ${cat.count}`)
.join(" · ")}
</p>
</>
) : (
<p className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground/50">
<p className="mt-1 flex items-center gap-1.5 text-muted-foreground/50 text-xs">
<StatusDot status="inactive" />
Disabled
</p>
@@ -592,9 +592,9 @@ function StorageCard() {
</CardContent>
{/* Backup summary */}
<CardContent className="border-t border-border/30 pt-4">
<CardContent className="border-border/30 border-t pt-4">
<div className="flex items-center justify-between">
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
<span className="font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wider">
Backups
</span>
{data.backups.backupCount > 0 && (
@@ -605,14 +605,14 @@ function StorageCard() {
</div>
{data.backups.backupCount > 0 ? (
<p
className="mt-1 text-xs text-muted-foreground"
className="mt-1 text-muted-foreground text-xs"
suppressHydrationWarning
>
{data.backups.backupCount} backups · last{" "}
<LiveTimeAgo date={data.backups.lastBackupAt} fallback="unknown" />
</p>
) : (
<p className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground/50">
<p className="mt-1 flex items-center gap-1.5 text-muted-foreground/50 text-xs">
<StatusDot status="inactive" />
No backups yet
</p>
+12 -12
View File
@@ -110,13 +110,13 @@ export default async function SettingsPage() {
return (
<SettingsShell
footer={
<footer className="border-t border-border/50 pt-6 pb-2 text-center text-xs text-muted-foreground">
<footer className="border-border/50 border-t pt-6 pb-2 text-center text-muted-foreground text-xs">
<p>
<a
href={`https://github.com/${GITHUB_REPO}`}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-primary/80 hover:text-primary transition-colors"
className="font-medium text-primary/80 transition-colors hover:text-primary"
>
Sofa
</a>{" "}
@@ -129,7 +129,7 @@ export default async function SettingsPage() {
href={`https://github.com/${GITHUB_REPO}/commit/${GIT_COMMIT_SHA}`}
target="_blank"
rel="noopener noreferrer"
className="font-mono hover:text-primary transition-colors"
className="font-mono transition-colors hover:text-primary"
>
{GIT_COMMIT_SHA}
</a>
@@ -137,7 +137,7 @@ export default async function SettingsPage() {
</>
)}
{updateCheck?.updateAvailable && (
<span className="ml-1.5 inline-flex items-center gap-1 rounded-full bg-primary/15 px-2 py-0.5 text-[10px] font-medium text-primary">
<span className="ml-1.5 inline-flex items-center gap-1 rounded-full bg-primary/15 px-2 py-0.5 font-medium text-[10px] text-primary">
<span className="relative flex h-1.5 w-1.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" />
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-primary" />
@@ -161,11 +161,11 @@ export default async function SettingsPage() {
href="https://www.themoviedb.org/"
target="_blank"
rel="noopener noreferrer"
className="hover:opacity-70 transition-opacity"
className="transition-opacity hover:opacity-70"
>
<TmdbLogo className="h-3" />
</a>
<p className="text-[10px] leading-relaxed text-muted-foreground">
<p className="text-[10px] text-muted-foreground leading-relaxed">
This product uses the TMDB API but is not endorsed or certified by
TMDB.
</p>
@@ -191,10 +191,10 @@ export default async function SettingsPage() {
aria-hidden={true}
className="size-4 text-muted-foreground"
/>
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
<h2 className="font-medium text-muted-foreground text-xs uppercase tracking-wider">
Server
</h2>
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary">
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 font-medium text-[10px] text-primary">
Admin only
</span>
</div>
@@ -210,10 +210,10 @@ export default async function SettingsPage() {
aria-hidden={true}
className="size-4 text-muted-foreground"
/>
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
<h2 className="font-medium text-muted-foreground text-xs uppercase tracking-wider">
Security
</h2>
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary">
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 font-medium text-[10px] text-primary">
Admin only
</span>
</div>
@@ -236,10 +236,10 @@ export default async function SettingsPage() {
aria-hidden={true}
className="size-4 text-muted-foreground"
/>
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
<h2 className="font-medium text-muted-foreground text-xs uppercase tracking-wider">
Backups
</h2>
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary">
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 font-medium text-[10px] text-primary">
Admin only
</span>
</div>
@@ -65,7 +65,7 @@ export function CastCarousel({ actors, titleType }: CastCarouselProps) {
)}
</div>
<div className="w-full text-center">
<p className="truncate text-xs font-medium">
<p className="line-clamp-2 font-medium text-xs leading-tight">
{member.name}
</p>
{member.character && (
@@ -29,7 +29,7 @@ export function GenreCollapse({ genres }: { genres: string[] }) {
{remaining.map((genre) => (
<span
key={genre}
className="px-2 py-1 text-xs text-popover-foreground"
className="px-2 py-1 text-popover-foreground text-xs"
>
{genre}
</span>
@@ -48,7 +48,7 @@ export function StatusButton({ currentStatus, onChange }: StatusButtonProps) {
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.15 }}
className="inline-flex h-9 items-center gap-2 rounded-lg bg-primary/10 px-4 text-sm font-medium text-primary ring-1 ring-primary/20 transition-all hover:bg-primary/15 hover:ring-primary/30 active:scale-[0.97]"
className="inline-flex h-9 items-center gap-2 rounded-lg bg-primary/10 px-4 font-medium text-primary text-sm ring-1 ring-primary/20 transition-all hover:bg-primary/15 hover:ring-primary/30 active:scale-[0.97]"
>
<IconPlus aria-hidden={true} className="size-3.5" strokeWidth={2.5} />
Watchlist
@@ -63,7 +63,7 @@ export function StatusButton({ currentStatus, onChange }: StatusButtonProps) {
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.15 }}
title="Remove from library"
className={`group inline-flex h-9 items-center gap-2 rounded-lg px-4 text-sm font-medium ring-1 transition-all active:scale-[0.97] ${config.class} ${config.bgClass} ${config.borderClass} hover:ring-destructive/30 hover:bg-destructive/10 hover:text-destructive`}
className={`group inline-flex h-9 items-center gap-2 rounded-lg px-4 font-medium text-sm ring-1 transition-all active:scale-[0.97] ${config.class} ${config.bgClass} ${config.borderClass} hover:bg-destructive/10 hover:text-destructive hover:ring-destructive/30`}
>
<span className="grid [&>svg]:col-start-1 [&>svg]:row-start-1">
<config.icon
@@ -72,7 +72,7 @@ export function StatusButton({ currentStatus, onChange }: StatusButtonProps) {
/>
<IconX
aria-hidden={true}
className="size-3.5 opacity-0 text-destructive transition-opacity group-hover:opacity-100"
className="size-3.5 text-destructive opacity-0 transition-opacity group-hover:opacity-100"
/>
</span>
<span className="grid [&>span]:col-start-1 [&>span]:row-start-1">
@@ -30,7 +30,7 @@ export function TitleActions() {
<Button
onClick={handleWatchMovie}
size="lg"
className="h-9 rounded-lg px-4 text-sm active:scale-[0.97] hover:shadow-md hover:shadow-primary/20"
className="h-9 rounded-lg px-4 text-sm hover:shadow-md hover:shadow-primary/20 active:scale-[0.97]"
>
<IconCheck aria-hidden={true} className="size-3.5" />
Mark Watched
@@ -43,7 +43,7 @@ function ProviderBadge({
),
}
: {})}
className={`flex h-10 w-10 items-center justify-center overflow-hidden rounded-lg border border-border/30 bg-card motion-safe:transition-transform motion-safe:hover:scale-105${watchUrl ? "" : " cursor-default"}`}
className={`flex h-10 w-10 items-center justify-center overflow-hidden rounded-lg border border-border/30 bg-card motion-safe:transition-transform motion-safe:hover:scale-105${watchUrl ? "" : "cursor-default"}`}
>
{logoPath ? (
<Image
@@ -54,12 +54,12 @@ function ProviderBadge({
className="h-full w-full object-cover"
/>
) : (
<span className="text-[8px] font-medium text-muted-foreground">
<span className="font-medium text-[8px] text-muted-foreground">
{name.slice(0, 2)}
</span>
)}
</TooltipTrigger>
<TooltipContent className="bg-popover px-2 py-1 text-[10px] font-medium text-popover-foreground shadow-md [&>:last-child]:bg-popover [&>:last-child]:fill-popover">
<TooltipContent className="bg-popover px-2 py-1 font-medium text-[10px] text-popover-foreground shadow-md [&>:last-child]:hidden">
{watchUrl ? `Watch on ${name}` : name}
</TooltipContent>
</Tooltip>
@@ -78,7 +78,7 @@ function OverflowProviderIcon({ offer }: { offer: AvailabilityOffer }) {
className="h-7 w-7 object-cover"
/>
) : (
<span className="text-[7px] font-medium text-muted-foreground">
<span className="font-medium text-[7px] text-muted-foreground">
{offer.providerName.slice(0, 2)}
</span>
)}
@@ -93,7 +93,7 @@ function OverflowBadge({ offers }: { offers: AvailabilityOffer[] }) {
openOnHover
delay={0}
closeDelay={300}
className="flex h-10 w-10 cursor-default items-center justify-center rounded-lg border border-border/30 bg-card text-xs font-semibold text-muted-foreground motion-safe:transition-transform motion-safe:hover:scale-105"
className="flex h-10 w-10 cursor-default items-center justify-center rounded-lg border border-border/30 bg-card font-semibold text-muted-foreground text-xs motion-safe:transition-transform motion-safe:hover:scale-105"
>
+{offers.length}
</PopoverTrigger>
@@ -108,7 +108,7 @@ function OverflowBadge({ offers }: { offers: AvailabilityOffer[] }) {
className="flex items-center gap-2.5 px-2 py-1.5 hover:bg-muted/50"
>
<OverflowProviderIcon offer={offer} />
<span className="truncate text-xs text-popover-foreground">
<span className="truncate text-popover-foreground text-xs">
{offer.providerName}
</span>
</a>
@@ -118,7 +118,7 @@ function OverflowBadge({ offers }: { offers: AvailabilityOffer[] }) {
className="flex items-center gap-2.5 px-2 py-1.5"
>
<OverflowProviderIcon offer={offer} />
<span className="truncate text-xs text-popover-foreground">
<span className="truncate text-popover-foreground text-xs">
{offer.providerName}
</span>
</div>
@@ -143,8 +143,8 @@ export function TitleAvailability({
if (Object.keys(availByType).length === 0) return null;
return (
<div className="pt-1 space-y-2">
<h2 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
<div className="space-y-2 pt-1">
<h2 className="font-semibold text-muted-foreground text-xs uppercase tracking-wider">
Where to Watch
</h2>
<div className="flex flex-wrap gap-4">
@@ -154,7 +154,7 @@ export function TitleAvailability({
return (
<div key={type} className="space-y-1.5">
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60">
<span className="font-medium text-[10px] text-muted-foreground/60 uppercase tracking-wider">
{offerLabels[type] ?? type}
</span>
<div className="flex gap-1.5">
@@ -8,6 +8,7 @@ import {
} from "@tabler/icons-react";
import Image from "next/image";
import type { ReactNode } from "react";
import { ExpandableText } from "@/components/expandable-text";
import { TmdbLogo } from "@/components/tmdb-logo";
import type { ColorPalette, ResolvedTitle } from "@/lib/types/title";
import { GenreCollapse } from "./genre-collapse";
@@ -33,7 +34,7 @@ export function TitleHero({
<>
{/* Backdrop hero */}
{title.backdropPath && (
<div className="relative -mt-6 ml-[calc(-50vw+50%)] mr-[calc(-50vw+50%)] h-80 overflow-hidden sm:h-[28rem]">
<div className="relative -mt-6 mr-[calc(-50vw+50%)] ml-[calc(-50vw+50%)] h-80 overflow-hidden md:h-[28rem]">
<Image
src={title.backdropPath}
alt=""
@@ -80,12 +81,12 @@ export function TitleHero({
{/* Title header */}
<div
className={`flex flex-row gap-4 sm:gap-8 ${title.backdropPath ? "-mt-32 relative z-10" : ""}`}
className={`flex flex-col gap-4 md:flex-row md:gap-8 ${title.backdropPath ? "relative z-10 -mt-32" : ""}`}
>
{title.posterPath && (
<div className="shrink-0">
<div className="shrink-0 self-center md:self-start">
<div
className="overflow-hidden rounded-xl sm:rounded-2xl ring-1 ring-foreground/5 shadow-2xl transition-shadow duration-500"
className="overflow-hidden rounded-xl shadow-2xl ring-1 ring-foreground/5 transition-shadow duration-500 md:rounded-2xl"
style={{
boxShadow: palette?.darkVibrant
? `0 25px 60px -12px ${palette.darkVibrant}50, 0 12px 28px -8px rgba(0,0,0,0.5)`
@@ -97,7 +98,7 @@ export function TitleHero({
alt={title.title}
width={220}
height={330}
className="h-auto w-[120px] sm:w-[220px]"
className="aspect-[2/3] w-[140px] object-cover md:w-[220px]"
priority
/>
</div>
@@ -106,30 +107,35 @@ export function TitleHero({
<div className="flex-1 space-y-5">
<div>
<h1 className="font-display text-2xl tracking-tight text-balance sm:text-5xl">
<h1 className="text-balance font-display text-2xl tracking-tight md:text-5xl">
{title.title}
</h1>
<div className="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground">
<TypeBadge type={title.type} />
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 [&>*+*]:before:mr-2 [&>*+*]:before:text-border [&>*+*]:before:content-['·']">
{title.contentRating && <span>{title.contentRating}</span>}
{year && <span>{year}</span>}
{title.genres.length > 0 && (
<GenreCollapse genres={title.genres} />
)}
{title.voteAverage != null && title.voteAverage > 0 && (
<span className="inline-flex items-center gap-1 text-primary">
<IconStarFilled className="size-3.5" />
{title.voteAverage.toFixed(1)}
</span>
)}
{title.status &&
!(title.type === "movie" && title.status === "Released") && (
<span className="inline-flex items-center gap-1">
<StatusIcon status={title.status} />
{title.status}
<div className="mt-2 space-y-1.5 text-muted-foreground text-sm md:space-y-0">
{/* Desktop: single row with dot separators */}
<div className="hidden md:flex md:items-center md:gap-x-2 md:gap-y-1">
<TypeBadge type={title.type} />
<div className="flex items-center gap-x-2 [&>*+*]:before:mr-2 [&>*+*]:before:text-border [&>*+*]:before:content-['·']">
{title.contentRating && <span>{title.contentRating}</span>}
{year && <span>{year}</span>}
{title.genres.length > 0 && (
<GenreCollapse genres={title.genres} />
)}
{title.voteAverage != null && title.voteAverage > 0 && (
<span className="inline-flex items-center gap-1 text-primary">
<IconStarFilled className="size-3.5" />
{title.voteAverage.toFixed(1)}
</span>
)}
{title.status &&
!(
title.type === "movie" && title.status === "Released"
) && (
<span className="inline-flex items-center gap-1">
<StatusIcon status={title.status} />
{title.status}
</span>
)}
</div>
<a
href={`https://www.themoviedb.org/${title.type === "movie" ? "movie" : "tv"}/${title.tmdbId}`}
target="_blank"
@@ -140,14 +146,70 @@ export function TitleHero({
<TmdbLogo className="h-2.5 w-auto" />
</a>
</div>
{/* Mobile: two clean rows */}
<div className="flex items-center gap-2 md:hidden">
<TypeBadge type={title.type} />
{title.contentRating && <span>{title.contentRating}</span>}
{year && (
<>
{title.contentRating && (
<span className="text-border">·</span>
)}
<span>{year}</span>
</>
)}
{title.genres.length > 0 && (
<>
<span className="text-border">·</span>
<GenreCollapse genres={title.genres} />
</>
)}
{title.voteAverage != null && title.voteAverage > 0 && (
<>
<span className="text-border">·</span>
<span className="inline-flex items-center gap-1 text-primary">
<IconStarFilled className="size-3.5" />
{title.voteAverage.toFixed(1)}
</span>
</>
)}
</div>
{title.status &&
!(title.type === "movie" && title.status === "Released") && (
<div className="flex items-center gap-2 md:hidden">
<span className="inline-flex items-center gap-1 text-muted-foreground/70">
<StatusIcon status={title.status} />
{title.status}
</span>
<a
href={`https://www.themoviedb.org/${title.type === "movie" ? "movie" : "tv"}/${title.tmdbId}`}
target="_blank"
rel="noopener noreferrer"
aria-label="View on TMDB"
className="inline-flex items-center opacity-70 transition-opacity hover:opacity-40"
>
<TmdbLogo className="h-2.5 w-auto" />
</a>
</div>
)}
{/* TMDB link for mobile when no status is shown */}
{(!title.status ||
(title.type === "movie" && title.status === "Released")) && (
<a
href={`https://www.themoviedb.org/${title.type === "movie" ? "movie" : "tv"}/${title.tmdbId}`}
target="_blank"
rel="noopener noreferrer"
aria-label="View on TMDB"
className="inline-flex items-center opacity-70 transition-opacity hover:opacity-40 md:hidden"
>
<TmdbLogo className="h-2.5 w-auto" />
</a>
)}
</div>
</div>
{title.overview && (
<p className="max-w-2xl break-words leading-relaxed text-muted-foreground">
{title.overview}
</p>
)}
{title.overview && <ExpandableText text={title.overview} />}
{actions}
@@ -176,26 +238,41 @@ function StatusIcon({ status }: { status: string }) {
function AmbientGlow({ palette }: { palette: ColorPalette | null }) {
if (!palette) return null;
const glowColor = palette.vibrant ?? palette.darkMuted ?? palette.muted;
return (
<div className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[800px] overflow-hidden">
{palette.vibrant && (
<>
{/* Mobile: single full-bleed color wash that flows from the backdrop edge-to-edge */}
{glowColor && (
<div
className="absolute -left-32 top-16 h-[500px] w-[500px] rounded-full opacity-[0.07] blur-[120px]"
style={{ background: palette.vibrant }}
className="pointer-events-none absolute top-0 right-[calc(-50vw+50%)] left-[calc(-50vw+50%)] -z-10 h-[600px] md:hidden"
style={{
background: `radial-gradient(ellipse 100% 60% at 50% 0%, ${glowColor}18 0%, transparent 70%)`,
}}
/>
)}
{palette.darkMuted && (
<div
className="absolute -right-24 top-48 h-[400px] w-[600px] rounded-full opacity-[0.05] blur-[140px]"
style={{ background: palette.darkMuted }}
/>
)}
{palette.muted && (
<div
className="absolute left-1/3 top-[500px] h-[300px] w-[400px] rounded-full opacity-[0.04] blur-[100px]"
style={{ background: palette.muted }}
/>
)}
</div>
{/* Desktop: multi-orb ambient glow with room to breathe */}
<div className="pointer-events-none absolute inset-x-0 top-0 -z-10 hidden h-[800px] overflow-hidden md:block">
{palette.vibrant && (
<div
className="absolute top-16 -left-32 h-[500px] w-[500px] rounded-full opacity-[0.07] blur-[120px]"
style={{ background: palette.vibrant }}
/>
)}
{palette.darkMuted && (
<div
className="absolute top-48 -right-24 h-[400px] w-[600px] rounded-full opacity-[0.05] blur-[140px]"
style={{ background: palette.darkMuted }}
/>
)}
{palette.muted && (
<div
className="absolute top-[500px] left-1/3 h-[300px] w-[400px] rounded-full opacity-[0.04] blur-[100px]"
style={{ background: palette.muted }}
/>
)}
</div>
</>
);
}
@@ -88,7 +88,7 @@ export function TitleSeasons({
<Button
variant="ghost"
size="xs"
className="uppercase tracking-wider text-muted-foreground"
className="text-muted-foreground uppercase tracking-wider"
>
<IconChecks aria-hidden={true} className="size-3.5" />
Mark All Watched
@@ -167,9 +167,13 @@ export function TitleSeasons({
e.stopPropagation();
handleMarkSeason(season);
}}
className="w-24 uppercase tracking-wider text-primary hover:bg-primary/10 hover:text-primary sm:hidden sm:group-hover/season:block"
className="text-primary uppercase tracking-wider hover:bg-primary/10 hover:text-primary sm:hidden sm:w-24 sm:group-hover/season:block"
>
Watch all
<IconChecks
aria-hidden={true}
className="size-3.5 sm:hidden"
/>
<span className="hidden sm:inline">Watch all</span>
</Button>
)}
{totalCount > 0 && watchedCount === totalCount && (
@@ -180,13 +184,17 @@ export function TitleSeasons({
e.stopPropagation();
handleUnmarkSeason(season);
}}
className="w-24 uppercase tracking-wider text-muted-foreground hover:bg-destructive/10 hover:text-destructive sm:hidden sm:group-hover/season:block"
className="text-muted-foreground uppercase tracking-wider hover:bg-destructive/10 hover:text-destructive sm:hidden sm:w-24 sm:group-hover/season:block"
>
Unwatch all
<IconChecks
aria-hidden={true}
className="size-3.5 sm:hidden"
/>
<span className="hidden sm:inline">Unwatch all</span>
</Button>
)}
{totalCount > 0 && (
<span className="font-mono text-xs tabular-nums text-muted-foreground">
<span className="font-mono text-muted-foreground text-xs tabular-nums">
{watchedCount}/{totalCount}
</span>
)}
@@ -215,7 +223,7 @@ export function TitleSeasons({
stiffness: 300,
damping: 30,
}}
className="overflow-hidden border-t border-border/50"
className="overflow-hidden border-border/50 border-t"
>
{season.episodes.map((ep) => {
const isWatched = watchedSet.has(ep.id);
@@ -223,72 +231,99 @@ export function TitleSeasons({
return (
<div
key={ep.id}
className={`flex gap-3 border-b border-border/30 px-4 py-3 last:border-b-0 transition-colors ${isWatched ? "opacity-60" : ""}`}
className={`border-border/30 border-b transition-colors last:border-b-0 ${isWatched ? "opacity-60" : ""}`}
>
<button
type="button"
aria-label={`Mark episode ${ep.episodeNumber} as ${isWatched ? "unwatched" : "watched"}`}
onClick={() =>
handleWatchEpisode(
ep.id,
season.seasonNumber,
ep.episodeNumber,
isWatched,
)
}
disabled={watchingEp === ep.id}
className={`mt-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 transition-all ${
isWatched
? "border-primary bg-primary text-primary-foreground"
: "border-muted-foreground/40 bg-muted-foreground/5 hover:border-primary/70 hover:bg-primary/10"
}`}
>
{isWatched && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
type: "spring",
stiffness: 500,
damping: 15,
}}
>
<IconCheck className="size-3.5" />
</motion.div>
)}
</button>
{/* Mobile: still banner above episode info */}
{stillPath && (
<div className="hidden h-14 w-24 shrink-0 overflow-hidden rounded-md bg-muted sm:block">
<div className="relative aspect-video w-full overflow-hidden bg-muted sm:hidden">
<Image
src={stillPath}
alt={ep.name ?? ""}
width={300}
height={169}
width={600}
height={338}
className="h-full w-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent" />
<span className="absolute bottom-2 left-3 font-mono text-[10px] text-white/70">
E{String(ep.episodeNumber).padStart(2, "0")}
</span>
</div>
)}
<div className="min-w-0 flex-1">
<p className="text-sm">
<span className="font-mono text-xs text-muted-foreground">
E{String(ep.episodeNumber).padStart(2, "0")}
</span>{" "}
<span className="font-medium">
{ep.name ?? "Untitled"}
</span>
</p>
<p className="text-xs text-muted-foreground">
{ep.airDate
? format(parseISO(ep.airDate), "MMM d, yyyy")
: ""}
{ep.airDate && ep.runtimeMinutes ? " · " : ""}
{ep.runtimeMinutes ? `${ep.runtimeMinutes}m` : ""}
</p>
{ep.overview && (
<p className="mt-1 hidden line-clamp-2 text-xs leading-relaxed text-muted-foreground/70 sm:block">
{ep.overview}
</p>
<div className="flex gap-3 px-4 py-3">
<button
type="button"
aria-label={`Mark episode ${ep.episodeNumber} as ${isWatched ? "unwatched" : "watched"}`}
onClick={() =>
handleWatchEpisode(
ep.id,
season.seasonNumber,
ep.episodeNumber,
isWatched,
)
}
disabled={watchingEp === ep.id}
className={`mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 transition-all ${
isWatched
? "border-primary bg-primary text-primary-foreground"
: "border-muted-foreground/40 bg-muted-foreground/5 hover:border-primary/70 hover:bg-primary/10"
}`}
>
{isWatched && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
type: "spring",
stiffness: 500,
damping: 15,
}}
>
<IconCheck className="size-3.5" />
</motion.div>
)}
</button>
{/* Desktop: inline thumbnail */}
{stillPath && (
<div className="hidden h-14 w-24 shrink-0 overflow-hidden rounded-md bg-muted sm:block">
<Image
src={stillPath}
alt={ep.name ?? ""}
width={300}
height={169}
className="h-full w-full object-cover"
/>
</div>
)}
<div className="min-w-0 flex-1">
<p className="text-sm">
{/* Episode number shown inline on desktop, or mobile without still */}
<span
className={`font-mono text-muted-foreground text-xs ${stillPath ? "hidden sm:inline" : ""}`}
>
E{String(ep.episodeNumber).padStart(2, "0")}
</span>
{stillPath && (
<span className="hidden sm:inline"> </span>
)}
<span className="font-medium">
{ep.name ?? "Untitled"}
</span>
</p>
<p className="text-muted-foreground text-xs">
{ep.airDate
? format(parseISO(ep.airDate), "MMM d, yyyy")
: ""}
{ep.airDate && ep.runtimeMinutes ? " · " : ""}
{ep.runtimeMinutes
? `${ep.runtimeMinutes}m`
: ""}
</p>
{ep.overview && (
<p className="mt-1 line-clamp-2 text-muted-foreground/70 text-xs leading-relaxed">
{ep.overview}
</p>
)}
</div>
</div>
</div>
);
@@ -36,13 +36,13 @@ export function TrailerDialog({
<button
type="button"
onClick={() => setOpen(true)}
className="inline-flex h-5 items-center gap-1 rounded border border-border/50 px-2 text-xs text-muted-foreground transition-colors hover:border-border hover:text-foreground"
className="inline-flex h-5 items-center gap-1 rounded border border-border/50 px-2 text-muted-foreground text-xs transition-colors hover:border-border hover:text-foreground"
>
<IconPlayerPlayFilled aria-hidden={true} className="h-2.5 w-2.5" />
Trailer
</button>
)}
<DialogContent className="sm:max-w-4xl p-0 overflow-hidden bg-black border-white/10">
<DialogContent className="overflow-hidden border-white/10 bg-black p-0 sm:max-w-4xl">
<DialogTitle className="sr-only">Trailer</DialogTitle>
<div className="aspect-video w-full">
<MediaThemeSutro
+6 -6
View File
@@ -4,39 +4,39 @@ export default function TitleNotFound() {
return (
<div className="flex flex-col items-center gap-6 py-24 text-center">
<h1
className="animate-stagger-item font-display text-[6rem] leading-[0.85] tracking-tight text-foreground/[0.06] sm:text-[8rem]"
className="animate-stagger-item font-display text-[6rem] text-foreground/[0.06] leading-[0.85] tracking-tight sm:text-[8rem]"
style={{ "--stagger-index": 0 } as React.CSSProperties}
>
404
</h1>
<div
className="animate-stagger-item -mt-4 space-y-2"
className="-mt-4 animate-stagger-item space-y-2"
style={{ "--stagger-index": 1 } as React.CSSProperties}
>
<h2 className="font-display text-2xl tracking-tight sm:text-3xl">
Title not found
</h2>
<p className="mx-auto max-w-sm text-sm leading-relaxed text-muted-foreground">
<p className="mx-auto max-w-sm text-muted-foreground text-sm leading-relaxed">
The title you&apos;re looking for doesn&apos;t exist or may have been
removed from the database.
</p>
</div>
<div
className="animate-stagger-item flex items-center gap-3"
className="flex animate-stagger-item items-center gap-3"
style={{ "--stagger-index": 2 } as React.CSSProperties}
>
<Link
href="/explore"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-5 text-sm font-medium text-primary-foreground transition-shadow hover:shadow-lg hover:shadow-primary/20"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-5 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-lg hover:shadow-primary/20"
>
<span className="relative z-10">Explore titles</span>
<div className="absolute inset-0 bg-gradient-to-t from-black/10 to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
</Link>
<Link
href="/dashboard"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 text-sm font-medium transition-colors hover:border-primary/40 hover:bg-primary/5"
className="inline-flex h-10 items-center justify-center rounded-lg border border-border px-5 font-medium text-sm transition-colors hover:border-primary/40 hover:bg-primary/5"
>
Dashboard
</Link>
+1 -1
View File
@@ -49,7 +49,7 @@ export default function RootLayout({
>
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-md"
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:rounded-md focus:bg-primary focus:px-4 focus:py-2 focus:text-primary-foreground"
>
Skip to main content
</a>
+9 -9
View File
@@ -5,11 +5,11 @@ export default function NotFound() {
return (
<div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-6">
{/* Warm projector glow */}
<div className="pointer-events-none absolute left-1/2 top-[40%] h-[500px] w-[500px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/4 blur-[160px]" />
<div className="pointer-events-none absolute top-[40%] left-1/2 h-[500px] w-[500px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/4 blur-[160px]" />
{/* Subtle vertical light beam */}
<div
className="pointer-events-none absolute left-1/2 top-0 h-[60vh] w-px -translate-x-1/2"
className="pointer-events-none absolute top-0 left-1/2 h-[60vh] w-px -translate-x-1/2"
style={{
background:
"linear-gradient(to bottom, oklch(0.8 0.14 65 / 0.12), transparent)",
@@ -19,7 +19,7 @@ export default function NotFound() {
<div className="relative z-10 flex flex-col items-center text-center">
{/* Frame counter label */}
<p
className="animate-stagger-item font-mono text-[10px] tracking-[0.5em] uppercase text-muted-foreground/40"
className="animate-stagger-item font-mono text-[10px] text-muted-foreground/40 uppercase tracking-[0.5em]"
style={{ "--stagger-index": 0 } as React.CSSProperties}
>
Scene not found
@@ -27,7 +27,7 @@ export default function NotFound() {
{/* Large ghosted 404 */}
<h1
className="animate-stagger-item font-display text-[8rem] leading-[0.85] tracking-tight text-foreground/[0.06] sm:text-[11rem] md:text-[14rem]"
className="animate-stagger-item font-display text-[8rem] text-foreground/[0.06] leading-[0.85] tracking-tight sm:text-[11rem] md:text-[14rem]"
style={{ "--stagger-index": 1 } as React.CSSProperties}
>
404
@@ -35,13 +35,13 @@ export default function NotFound() {
{/* Message */}
<div
className="animate-stagger-item -mt-2 space-y-3 sm:-mt-4"
className="-mt-2 animate-stagger-item space-y-3 sm:-mt-4"
style={{ "--stagger-index": 2 } as React.CSSProperties}
>
<h2 className="font-display text-2xl tracking-tight sm:text-3xl">
Lost in the credits
</h2>
<p className="mx-auto max-w-sm text-sm leading-relaxed text-muted-foreground">
<p className="mx-auto max-w-sm text-muted-foreground text-sm leading-relaxed">
This page was left on the cutting room floor. It may have been
moved, removed, or never made it past the screenplay.
</p>
@@ -49,12 +49,12 @@ export default function NotFound() {
{/* Actions */}
<div
className="animate-stagger-item mt-8 flex items-center gap-3"
className="mt-8 flex animate-stagger-item items-center gap-3"
style={{ "--stagger-index": 3 } as React.CSSProperties}
>
<Link
href="/"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-6 text-sm font-medium text-primary-foreground transition-shadow hover:shadow-lg hover:shadow-primary/20"
className="group relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg bg-primary px-6 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-lg hover:shadow-primary/20"
>
<span className="relative z-10">Return home</span>
<div className="absolute inset-0 bg-gradient-to-t from-black/10 to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
@@ -63,7 +63,7 @@ export default function NotFound() {
{/* Logo watermark */}
<div
className="animate-stagger-item mt-16 text-muted-foreground/20"
className="mt-16 animate-stagger-item text-muted-foreground/20"
style={{ "--stagger-index": 4 } as React.CSSProperties}
>
<SofaLogo className="size-8" />
+16 -4
View File
@@ -22,15 +22,27 @@
},
"linter": {
"enabled": true,
"domains": {
"next": "recommended",
"react": "recommended"
},
"rules": {
"recommended": true,
"suspicious": {
"noUnknownAtRules": "off"
},
"nursery": {
"useSortedClasses": {
"level": "error",
"options": {
"attributes": ["classList"],
"functions": ["cn", "clsx", "cva"]
}
},
"noFloatingPromises": "error",
"noMisusedPromises": "error",
"useDestructuring": "error"
}
},
"domains": {
"next": "recommended",
"react": "recommended"
}
},
"assist": {
+7 -7
View File
@@ -107,10 +107,10 @@ export function AuthForm({
<Link href="/" className="inline-flex justify-center text-primary">
<SofaLogo className="size-9" />
</Link>
<h1 className="text-lg font-medium text-balance">
<h1 className="text-balance font-medium text-lg">
{isRegister ? "Create your account" : "Welcome back"}
</h1>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
{isRegister ? "Start tracking your watches" : "Sign in to continue"}
</p>
</div>
@@ -144,7 +144,7 @@ export function AuthForm({
{showOidc && showPasswordForm && (
<div className="flex items-center gap-3">
<div className="h-px flex-1 bg-border/50" />
<span className="text-xs text-muted-foreground">or</span>
<span className="text-muted-foreground text-xs">or</span>
<div className="h-px flex-1 bg-border/50" />
</div>
)}
@@ -164,7 +164,7 @@ export function AuthForm({
<motion.div variants={fieldVariants} className="space-y-1.5">
<Label
htmlFor="name"
className="uppercase tracking-wider text-muted-foreground"
className="text-muted-foreground uppercase tracking-wider"
>
Name
</Label>
@@ -184,7 +184,7 @@ export function AuthForm({
<motion.div variants={fieldVariants} className="space-y-1.5">
<Label
htmlFor="email"
className="uppercase tracking-wider text-muted-foreground"
className="text-muted-foreground uppercase tracking-wider"
>
Email
</Label>
@@ -204,7 +204,7 @@ export function AuthForm({
<motion.div variants={fieldVariants} className="space-y-1.5">
<Label
htmlFor="password"
className="uppercase tracking-wider text-muted-foreground"
className="text-muted-foreground uppercase tracking-wider"
>
Password
</Label>
@@ -258,7 +258,7 @@ export function AuthForm({
{showPasswordForm &&
(isRegister || authConfig?.registrationOpen !== false) && (
<p className="text-center text-sm text-muted-foreground">
<p className="text-center text-muted-foreground text-sm">
{isRegister ? (
<>
Already have an account?{" "}
+4 -4
View File
@@ -227,7 +227,7 @@ export function CommandPalette() {
</div>
)}
<div className="min-w-0 flex-1">
<p className="truncate text-xs font-medium">
<p className="truncate font-medium text-xs">
{r.title}
</p>
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground">
@@ -275,7 +275,7 @@ export function CommandPalette() {
<button
type="button"
onClick={handleClearRecent}
className="text-[10px] font-normal text-muted-foreground transition-colors hover:text-foreground"
className="font-normal text-[10px] text-muted-foreground transition-colors hover:text-foreground"
>
Clear all
</button>
@@ -363,7 +363,7 @@ export function CommandPalette() {
<div className="space-y-5 py-2">
{Object.entries(groupedShortcuts).map(([scope, items]) => (
<div key={scope} className="space-y-2">
<h3 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
<h3 className="font-semibold text-[10px] text-muted-foreground uppercase tracking-wider">
{scope}
</h3>
<div className="space-y-1">
@@ -372,7 +372,7 @@ export function CommandPalette() {
key={item.description}
className="flex items-center justify-between rounded-md px-2 py-1.5"
>
<span className="text-xs text-foreground">
<span className="text-foreground text-xs">
{item.description}
</span>
<div className="flex items-center gap-1">
+57
View File
@@ -0,0 +1,57 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
interface ExpandableTextProps {
text: string;
/** Tailwind line-clamp class applied when collapsed (default: "line-clamp-3") */
clampClass?: string;
className?: string;
textClassName?: string;
}
export function ExpandableText({
text,
clampClass = "line-clamp-3",
className,
textClassName,
}: ExpandableTextProps) {
const [expanded, setExpanded] = useState(false);
const [clamped, setClamped] = useState(false);
const ref = useRef<HTMLParagraphElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const check = () => setClamped(el.scrollHeight > el.clientHeight + 1);
check();
const observer = new ResizeObserver(check);
observer.observe(el);
return () => observer.disconnect();
}, []);
return (
<div className={className}>
<p
ref={ref}
className={cn(
"break-words text-muted-foreground leading-relaxed",
!expanded && clampClass,
textClassName,
)}
>
{text}
</p>
{(clamped || expanded) && (
<button
type="button"
onClick={() => setExpanded(!expanded)}
className="mt-1 font-medium text-primary text-xs transition-colors hover:text-primary/80"
>
{expanded ? "Show less" : "Read more"}
</button>
)}
</div>
);
}
+4 -4
View File
@@ -81,7 +81,7 @@ export function LandingPage({
{/* Warm primary glow */}
<motion.div
className="pointer-events-none absolute left-1/2 top-1/3 h-[600px] w-[600px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/5 blur-[120px]"
className="pointer-events-none absolute top-1/3 left-1/2 h-[600px] w-[600px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/5 blur-[120px]"
animate={{ opacity: [0.4, 0.7, 0.4] }}
transition={{
duration: 6,
@@ -93,7 +93,7 @@ export function LandingPage({
<main className="relative z-10 flex flex-col items-center gap-10 px-6 text-center">
<div className="space-y-4">
<motion.p
className="text-sm font-medium uppercase tracking-[0.3em] text-primary"
className="font-medium text-primary text-sm uppercase tracking-[0.3em]"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
@@ -118,7 +118,7 @@ export function LandingPage({
<SofaLogo className="size-24 sm:size-28 md:size-32" />
</motion.div>
<motion.p
className="mx-auto max-w-md text-lg leading-relaxed text-muted-foreground"
className="mx-auto max-w-md text-lg text-muted-foreground leading-relaxed"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
@@ -176,7 +176,7 @@ export function LandingPage({
</main>
{/* Bottom fade */}
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-background to-transparent" />
<div className="pointer-events-none absolute right-0 bottom-0 left-0 h-32 bg-gradient-to-t from-background to-transparent" />
</div>
);
}
+2 -2
View File
@@ -15,7 +15,7 @@ export function MobileTabBar() {
const pathname = usePathname();
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 border-t border-border/50 bg-background/90 pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)] backdrop-blur-xl sm:hidden">
<nav className="fixed right-0 bottom-0 left-0 z-50 border-border/50 border-t bg-background/90 pr-[env(safe-area-inset-right)] pl-[env(safe-area-inset-left)] backdrop-blur-xl sm:hidden">
<div className="flex h-14 items-stretch">
{tabs.map((tab) => {
const Icon = tab.icon;
@@ -33,7 +33,7 @@ export function MobileTabBar() {
className={`size-5 ${isActive ? "text-primary" : "text-muted-foreground"}`}
/>
<span
className={`text-[10px] font-medium ${isActive ? "text-primary" : "text-muted-foreground"}`}
className={`font-medium text-[10px] ${isActive ? "text-primary" : "text-muted-foreground"}`}
>
{tab.label}
</span>
+6 -6
View File
@@ -27,8 +27,8 @@ export function NavBar({ userName }: { userName: string }) {
const setCommandPaletteOpen = useSetAtom(commandPaletteOpenAtom);
return (
<header className="sticky top-0 z-50 border-b border-border/50 bg-background/80 pt-[env(safe-area-inset-top)] backdrop-blur-xl">
<nav className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-5 pl-[max(1rem,env(safe-area-inset-left))] pr-[max(1rem,env(safe-area-inset-right))] sm:gap-0 sm:pl-[max(1.5rem,env(safe-area-inset-left))] sm:pr-[max(1.5rem,env(safe-area-inset-right))]">
<header className="sticky top-0 z-50 border-border/50 border-b bg-background/80 pt-[env(safe-area-inset-top)] backdrop-blur-xl">
<nav className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-5 pr-[max(1rem,env(safe-area-inset-right))] pl-[max(1rem,env(safe-area-inset-left))] sm:gap-0 sm:pr-[max(1.5rem,env(safe-area-inset-right))] sm:pl-[max(1.5rem,env(safe-area-inset-left))]">
<div className="flex items-center gap-3 sm:gap-6">
<Link
href="/dashboard"
@@ -46,7 +46,7 @@ export function NavBar({ userName }: { userName: string }) {
<Link
key={link.href}
href={link.href}
className="relative inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
className="relative inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-muted-foreground text-sm transition-colors hover:text-foreground"
>
{link.label}
{isActive && (
@@ -71,7 +71,7 @@ export function NavBar({ userName }: { userName: string }) {
<button
type="button"
onClick={() => setCommandPaletteOpen(true)}
className="flex flex-1 items-center gap-2 rounded-lg border border-border/50 bg-card/50 px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:border-primary/20 hover:bg-card sm:hidden"
className="flex flex-1 items-center gap-2 rounded-lg border border-border/50 bg-card/50 px-3 py-1.5 text-muted-foreground text-sm transition-colors hover:border-primary/20 hover:bg-card sm:hidden"
>
<IconSearch aria-hidden={true} className="size-3.5" />
<span>Search</span>
@@ -80,7 +80,7 @@ export function NavBar({ userName }: { userName: string }) {
<button
type="button"
onClick={() => setCommandPaletteOpen(true)}
className="hidden items-center gap-2 rounded-lg border border-border/50 bg-card/50 px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:border-primary/20 hover:bg-card sm:inline-flex"
className="hidden items-center gap-2 rounded-lg border border-border/50 bg-card/50 px-3 py-1.5 text-muted-foreground text-sm transition-colors hover:border-primary/20 hover:bg-card sm:inline-flex"
>
<IconSearch aria-hidden={true} className="size-3.5" />
<span>Search</span>
@@ -93,7 +93,7 @@ export function NavBar({ userName }: { userName: string }) {
<Tooltip>
<TooltipTrigger
render={<Link href="/settings" />}
className="hidden items-center gap-1.5 rounded-md px-2 py-1.5 text-sm leading-none text-muted-foreground transition-colors hover:text-foreground sm:inline-flex"
className="hidden items-center gap-1.5 rounded-md px-2 py-1.5 text-muted-foreground text-sm leading-none transition-colors hover:text-foreground sm:inline-flex"
>
{userName}
<IconSettings aria-hidden={true} className="size-3.5" />
+2 -2
View File
@@ -13,7 +13,7 @@ export function TitleCardSkeleton() {
return (
<div className="overflow-hidden rounded-xl bg-card ring-1 ring-white/[0.06]">
<Skeleton className="aspect-[2/3] w-full rounded-none" />
<div className="px-3 pb-3 pt-2.5">
<div className="px-3 pt-2.5 pb-3">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="mt-1.5 h-3 w-1/2" />
</div>
@@ -174,7 +174,7 @@ export function RecommendationsSkeleton() {
export function TitleDetailSkeleton() {
return (
<div className="space-y-10">
<Skeleton className="-mt-6 ml-[calc(-50vw+50%)] mr-[calc(-50vw+50%)] h-80 rounded-none sm:h-[28rem]" />
<Skeleton className="-mt-6 mr-[calc(-50vw+50%)] ml-[calc(-50vw+50%)] h-80 rounded-none sm:h-[28rem]" />
<div className="flex flex-row gap-4 sm:gap-8">
<Skeleton className="h-[180px] w-[120px] shrink-0 rounded-xl sm:h-[330px] sm:w-[220px]" />
<div className="flex-1 space-y-5">
+10 -10
View File
@@ -112,7 +112,7 @@ function QuickAddButton({
return (
<Tooltip>
<TooltipTrigger
className="absolute top-2 right-2 z-10 flex size-8 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm text-white cursor-default"
className="absolute top-2 right-2 z-10 flex size-8 cursor-default items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-sm"
render={<div />}
>
<StatusIcon className="size-4" />
@@ -126,7 +126,7 @@ function QuickAddButton({
<Tooltip>
<TooltipTrigger
onClick={handleClick}
className="absolute top-2 right-2 z-10 flex size-8 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm text-white opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 hover:bg-black/70"
className="absolute top-2 right-2 z-10 flex size-8 items-center justify-center rounded-full bg-black/50 text-white opacity-60 backdrop-blur-sm transition-opacity hover:bg-black/70 focus-visible:opacity-100 sm:opacity-0 sm:group-hover:opacity-100"
render={<button type="button" />}
>
{state === "idle" && <IconPlus className="size-4" />}
@@ -145,7 +145,7 @@ function ProgressBar({ watched, total }: { watched: number; total: number }) {
return (
<Tooltip>
<TooltipTrigger
className="absolute bottom-0 left-0 right-0 z-10 h-1 bg-white/10 cursor-default"
className="absolute right-0 bottom-0 left-0 z-10 h-1 cursor-default bg-white/10"
render={<div />}
>
<div
@@ -179,7 +179,7 @@ function CardInner({
return (
<div
className={`relative overflow-hidden rounded-xl bg-card ring-1 transition-[box-shadow,ring-color] duration-200 ease-out hover:ring-primary/25 hover:shadow-lg hover:shadow-primary/5 ${ringClass}`}
className={`relative overflow-hidden rounded-xl bg-card ring-1 transition-[box-shadow,ring-color] duration-200 ease-out hover:shadow-lg hover:shadow-primary/5 hover:ring-primary/25 ${ringClass}`}
>
<div className="aspect-[2/3] overflow-hidden bg-card">
{posterPath ? (
@@ -202,7 +202,7 @@ function CardInner({
/>
<div className="absolute inset-0 bg-gradient-to-t from-primary/10 via-transparent to-transparent" />
<div className="relative px-3 text-center">
<p className="font-display text-sm leading-snug tracking-tight text-foreground/70">
<p className="font-display text-foreground/70 text-sm leading-snug tracking-tight">
{title}
</p>
</div>
@@ -220,12 +220,12 @@ function CardInner({
)}
</div>
<div className="px-3 pb-3 pt-2.5">
<div className="px-3 pt-2.5 pb-3">
<div className="flex items-center gap-1.5">
{userStatus && (
<Tooltip>
<TooltipTrigger
className="cursor-default shrink-0"
className="shrink-0 cursor-default"
render={<div className="flex items-center" />}
>
<span className="relative flex size-2">
@@ -240,11 +240,11 @@ function CardInner({
<TooltipContent>{statusConfig[userStatus].label}</TooltipContent>
</Tooltip>
)}
<p className="line-clamp-1 text-sm font-medium leading-snug">
<p className="line-clamp-1 font-medium text-sm leading-snug">
{title}
</p>
</div>
<div className="mt-1.5 flex items-center gap-2 text-xs text-muted-foreground">
<div className="mt-1.5 flex items-center gap-2 text-muted-foreground text-xs">
<TypeIcon
aria-hidden={true}
className="size-3.5 shrink-0 text-primary/60"
@@ -283,7 +283,7 @@ export function TitleCard({
const href = id ? `/titles/${id}` : `/titles/tmdb-${tmdbId}-${type}`;
const tilt = useTiltEffect();
return (
<div className="relative group">
<div className="group relative">
<QuickAddButton
tmdbId={tmdbId}
type={type as "movie" | "tv"}
+4 -4
View File
@@ -21,7 +21,7 @@ function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("data-open:bg-muted/50 not-last:border-b", className)}
className={cn("not-last:border-b data-open:bg-muted/50", className)}
{...props}
/>
);
@@ -37,7 +37,7 @@ function AccordionTrigger({
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"**:data-[slot=accordion-trigger-icon]:text-muted-foreground group/accordion-trigger relative flex flex-1 items-start justify-between gap-6 border border-transparent p-2 text-left text-xs/relaxed font-medium transition-all outline-none hover:underline aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4",
"group/accordion-trigger relative flex flex-1 items-start justify-between gap-6 border border-transparent p-2 text-left font-medium text-xs/relaxed outline-none transition-all hover:underline aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
className,
)}
{...props}
@@ -64,12 +64,12 @@ function AccordionContent({
return (
<AccordionPrimitive.Panel
data-slot="accordion-content"
className="data-open:animate-accordion-down data-closed:animate-accordion-up overflow-hidden px-2 text-xs/relaxed"
className="overflow-hidden px-2 text-xs/relaxed data-closed:animate-accordion-up data-open:animate-accordion-down"
{...props}
>
<div
className={cn(
"[&_a]:hover:text-foreground h-(--accordion-panel-height) pt-0 pb-4 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4",
"h-(--accordion-panel-height) pt-0 pb-4 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
className,
)}
>
+5 -5
View File
@@ -29,7 +29,7 @@ function AlertDialogOverlay({
<AlertDialogPrimitive.Backdrop
data-slot="alert-dialog-overlay"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs",
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-black/80 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs",
className,
)}
{...props}
@@ -51,7 +51,7 @@ function AlertDialogContent({
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl p-4 ring-1 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl bg-background p-4 outline-none ring-1 ring-foreground/10 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-closed:animate-out data-open:animate-in data-[size=default]:sm:max-w-sm",
className,
)}
{...props}
@@ -100,7 +100,7 @@ function AlertDialogMedia({
<div
data-slot="alert-dialog-media"
className={cn(
"bg-muted mb-2 inline-flex size-8 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4",
"mb-2 inline-flex size-8 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
@@ -116,7 +116,7 @@ function AlertDialogTitle({
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn(
"text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
"font-medium text-sm sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className,
)}
{...props}
@@ -132,7 +132,7 @@ function AlertDialogDescription({
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn(
"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3",
"text-balance text-muted-foreground text-xs/relaxed md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className,
)}
{...props}
+4 -4
View File
@@ -4,13 +4,13 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"grid gap-0.5 rounded-lg border px-2 py-1.5 text-left text-xs/relaxed has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-3.5 w-full relative group/alert",
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2 py-1.5 text-left text-xs/relaxed has-data-[slot=alert-action]:relative has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 has-data-[slot=alert-action]:pr-18 *:[svg:not([class*='size-'])]:size-3.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current",
{
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
},
},
defaultVariants: {
@@ -39,7 +39,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="alert-title"
className={cn(
"[&_a]:hover:text-foreground font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3",
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className,
)}
{...props}
@@ -55,7 +55,7 @@ function AlertDescription({
<div
data-slot="alert-description"
className={cn(
"text-muted-foreground [&_a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4",
"text-balance text-muted-foreground text-xs/relaxed md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
className,
)}
{...props}
+5 -5
View File
@@ -17,7 +17,7 @@ function Avatar({
data-slot="avatar"
data-size={size}
className={cn(
"after:border-border group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
"group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
className,
)}
{...props}
@@ -46,7 +46,7 @@ function AvatarFallback({
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
"flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs",
className,
)}
{...props}
@@ -59,7 +59,7 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
<span
data-slot="avatar-badge"
className={cn(
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
"absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
@@ -75,7 +75,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="avatar-group"
className={cn(
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className,
)}
{...props}
@@ -91,7 +91,7 @@ function AvatarGroupCount({
<div
data-slot="avatar-group-count"
className={cn(
"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-xs/relaxed ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-xs/relaxed ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className,
)}
{...props}
+3 -3
View File
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-full border border-transparent px-2 py-0.5 font-medium text-[0.625rem] transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-2.5!",
{
variants: {
variant: {
@@ -13,9 +13,9 @@ const badgeVariants = cva(
secondary:
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
outline:
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
"border-border bg-input/20 text-foreground dark:bg-input/30 [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
+3 -3
View File
@@ -20,7 +20,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
<ol
data-slot="breadcrumb-list"
className={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-xs/relaxed wrap-break-word",
"wrap-break-word flex flex-wrap items-center gap-1.5 text-muted-foreground text-xs/relaxed",
className,
)}
{...props}
@@ -47,7 +47,7 @@ function BreadcrumbLink({
defaultTagName: "a",
props: mergeProps<"a">(
{
className: cn("hover:text-foreground transition-colors", className),
className: cn("transition-colors hover:text-foreground", className),
},
props,
),
@@ -67,7 +67,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-foreground font-normal", className)}
className={cn("font-normal text-foreground", className)}
{...props}
/>
);
+5 -5
View File
@@ -5,14 +5,14 @@ import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
const buttonGroupVariants = cva(
"has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md flex w-fit items-stretch *:focus-visible:z-10 *:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
{
variants: {
orientation: {
horizontal:
"[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0 *:data-slot:rounded-r-none",
"*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
vertical:
"[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! flex-col [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0 *:data-slot:rounded-b-none",
"flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
},
},
defaultVariants: {
@@ -48,7 +48,7 @@ function ButtonGroupText({
props: mergeProps<"div">(
{
className: cn(
"bg-muted gap-2 rounded-md border px-2.5 text-xs/relaxed font-medium [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none",
"flex items-center gap-2 rounded-md border bg-muted px-2.5 font-medium text-xs/relaxed [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
className,
),
},
@@ -71,7 +71,7 @@ function ButtonGroupSeparator({
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"bg-input relative self-stretch data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
"relative self-stretch bg-input data-horizontal:mx-px data-vertical:my-px data-vertical:h-auto data-horizontal:w-auto",
className,
)}
{...props}
+4 -4
View File
@@ -6,19 +6,19 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium focus-visible:ring-2 aria-invalid:ring-2 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-md border border-transparent bg-clip-padding font-medium text-xs/relaxed outline-none transition-colors focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/80",
outline:
"border-border dark:bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
"border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost:
"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
+19 -19
View File
@@ -34,7 +34,7 @@ function Calendar({
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-background group/calendar p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(6)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
"group/calendar bg-background in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(6)]",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
@@ -49,30 +49,30 @@ function Calendar({
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"flex gap-4 flex-col md:flex-row relative",
"relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months,
),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn(
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav,
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
"size-(--cell-size) select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
"size-(--cell-size) select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_next,
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
defaultClassNames.month_caption,
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 font-medium text-sm",
defaultClassNames.dropdowns,
),
dropdown_root: cn(
@@ -80,49 +80,49 @@ function Calendar({
defaultClassNames.dropdown_root,
),
dropdown: cn(
"absolute bg-popover inset-0 opacity-0",
"absolute inset-0 bg-popover opacity-0",
defaultClassNames.dropdown,
),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "rounded-(--cell-radius) flex items-center gap-1 text-sm [&>svg]:text-muted-foreground [&>svg]:size-3.5",
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
defaultClassNames.caption_label,
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground rounded-(--cell-radius) flex-1 font-normal text-[0.8rem] select-none",
"flex-1 select-none rounded-(--cell-radius) font-normal text-[0.8rem] text-muted-foreground",
defaultClassNames.weekday,
),
week: cn("flex w-full mt-2", defaultClassNames.week),
week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn(
"select-none w-(--cell-size)",
"w-(--cell-size) select-none",
defaultClassNames.week_number_header,
),
week_number: cn(
"text-[0.8rem] select-none text-muted-foreground",
"select-none text-[0.8rem] text-muted-foreground",
defaultClassNames.week_number,
),
day: cn(
"relative w-full rounded-(--cell-radius) h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius) group/day aspect-square select-none",
"group/day relative aspect-square h-full w-full select-none rounded-(--cell-radius) p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
defaultClassNames.day,
),
range_start: cn(
"rounded-l-(--cell-radius) bg-muted relative after:bg-muted after:absolute after:inset-y-0 after:w-4 after:right-0 z-0 isolate",
"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
defaultClassNames.range_start,
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn(
"rounded-r-(--cell-radius) bg-muted relative after:bg-muted after:absolute after:inset-y-0 after:w-4 after:left-0 z-0 isolate",
"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
defaultClassNames.range_end,
),
today: cn(
"bg-muted text-foreground rounded-(--cell-radius) data-[selected=true]:rounded-none",
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
defaultClassNames.today,
),
outside: cn(
@@ -215,7 +215,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-muted data-[range-middle=true]:text-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 dark:hover:text-foreground relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) [&>span]:text-xs [&>span]:opacity-70",
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 font-normal leading-none data-[range-end=true]:rounded-(--cell-radius) data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-end=true]:bg-primary data-[range-middle=true]:bg-muted data-[range-start=true]:bg-primary data-[selected-single=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:text-foreground data-[range-start=true]:text-primary-foreground data-[selected-single=true]:text-primary-foreground group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className,
)}
+3 -3
View File
@@ -12,7 +12,7 @@ function Card({
data-slot="card"
data-size={size}
className={cn(
"ring-foreground/10 bg-card text-card-foreground group/card flex flex-col gap-4 overflow-hidden rounded-lg py-4 text-xs/relaxed ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
"group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-card-foreground text-xs/relaxed ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
className,
)}
{...props}
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
className,
)}
{...props}
@@ -37,7 +37,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("text-sm font-medium", className)}
className={cn("font-medium text-sm", className)}
{...props}
/>
);
+5 -5
View File
@@ -55,7 +55,7 @@ function ChartContainer({
data-slot="chart"
data-chart={chartId}
className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden",
className,
)}
{...props}
@@ -177,7 +177,7 @@ function ChartTooltipContent({
return (
<div
className={cn(
"border-border/50 bg-background grid min-w-32 items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs/relaxed shadow-xl",
"grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs/relaxed shadow-xl",
className,
)}
>
@@ -194,7 +194,7 @@ function ChartTooltipContent({
<div
key={item.dataKey}
className={cn(
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center",
)}
>
@@ -239,7 +239,7 @@ function ChartTooltipContent({
</span>
</div>
{item.value && (
<span className="text-foreground font-mono font-medium tabular-nums">
<span className="font-medium font-mono text-foreground tabular-nums">
{item.value.toLocaleString()}
</span>
)}
@@ -292,7 +292,7 @@ function ChartLegendContent({
<div
key={item.value}
className={cn(
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3",
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
)}
>
{itemConfig?.icon && !hideIcon ? (
+1 -1
View File
@@ -9,7 +9,7 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-2",
"peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-shadow after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 group-has-disabled/field:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
+8 -8
View File
@@ -30,7 +30,7 @@ function ComboboxTrigger({
{...props}
>
{children}
<IconChevronDown className="text-muted-foreground pointer-events-none size-3.5" />
<IconChevronDown className="pointer-events-none size-3.5 text-muted-foreground" />
</ComboboxPrimitive.Trigger>
);
}
@@ -110,7 +110,7 @@ function ComboboxContent({
data-slot="combobox-content"
data-chips={!!anchor}
className={cn(
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 *:data-[slot=input-group]:bg-input/20 dark:bg-popover data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg shadow-md ring-1 duration-100 data-[chips=true]:min-w-(--anchor-width) *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-7 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:shadow-none",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 group/combobox-content relative max-h-(--available-height) w-(--anchor-width) min-w-[calc(var(--anchor-width)+--spacing(7))] max-w-(--available-width) origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-closed:animate-out data-open:animate-in *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-7 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:bg-input/20 *:data-[slot=input-group]:shadow-none dark:bg-popover",
className,
)}
{...props}
@@ -142,7 +142,7 @@ function ComboboxItem({
<ComboboxPrimitive.Item
data-slot="combobox-item"
className={cn(
"data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"relative flex min-h-7 w-full cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-50 not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
@@ -176,7 +176,7 @@ function ComboboxLabel({
return (
<ComboboxPrimitive.GroupLabel
data-slot="combobox-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
className={cn("px-2 py-1.5 text-muted-foreground text-xs", className)}
{...props}
/>
);
@@ -193,7 +193,7 @@ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
<ComboboxPrimitive.Empty
data-slot="combobox-empty"
className={cn(
"text-muted-foreground hidden w-full justify-center py-2 text-center text-xs/relaxed group-data-empty/combobox-content:flex",
"hidden w-full justify-center py-2 text-center text-muted-foreground text-xs/relaxed group-data-empty/combobox-content:flex",
className,
)}
{...props}
@@ -208,7 +208,7 @@ function ComboboxSeparator({
return (
<ComboboxPrimitive.Separator
data-slot="combobox-separator"
className={cn("bg-border/50 -mx-1 my-1 h-px", className)}
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
{...props}
/>
);
@@ -223,7 +223,7 @@ function ComboboxChips({
<ComboboxPrimitive.Chips
data-slot="combobox-chips"
className={cn(
"bg-input/20 dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/30 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-7 flex-wrap items-center gap-1 rounded-md border bg-clip-padding px-2 py-0.5 text-xs/relaxed transition-colors focus-within:ring-2 has-aria-invalid:ring-2 has-data-[slot=combobox-chip]:px-1",
"flex min-h-7 flex-wrap items-center gap-1 rounded-md border border-input bg-input/20 bg-clip-padding px-2 py-0.5 text-xs/relaxed transition-colors focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30 has-aria-invalid:border-destructive has-data-[slot=combobox-chip]:px-1 has-aria-invalid:ring-2 has-aria-invalid:ring-destructive/20 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
className,
)}
{...props}
@@ -243,7 +243,7 @@ function ComboboxChip({
<ComboboxPrimitive.Chip
data-slot="combobox-chip"
className={cn(
"bg-muted-foreground/10 text-foreground flex h-[calc(--spacing(4.75))] w-fit items-center justify-center gap-1 rounded-[calc(var(--radius-sm)-2px)] px-1.5 text-xs/relaxed font-medium whitespace-nowrap has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
"flex h-[calc(--spacing(4.75))] w-fit items-center justify-center gap-1 whitespace-nowrap rounded-[calc(var(--radius-sm)-2px)] bg-muted-foreground/10 px-1.5 font-medium text-foreground text-xs/relaxed has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-data-[slot=combobox-chip-remove]:pr-0 has-disabled:opacity-50",
className,
)}
{...props}
+7 -7
View File
@@ -21,7 +21,7 @@ function Command({
<CommandPrimitive
data-slot="command"
className={cn(
"bg-popover text-popover-foreground flex size-full flex-col overflow-hidden rounded-xl p-1",
"flex size-full flex-col overflow-hidden rounded-xl bg-popover p-1 text-popover-foreground",
className,
)}
{...props}
@@ -68,7 +68,7 @@ function CommandInput({
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div data-slot="command-input-wrapper" className="p-1 pb-0">
<InputGroup className="bg-input/20 dark:bg-input/30 h-8!">
<InputGroup className="h-8! bg-input/20 dark:bg-input/30">
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
@@ -93,7 +93,7 @@ function CommandList({
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
"no-scrollbar max-h-72 scroll-py-1 overflow-y-auto overflow-x-hidden outline-none",
className,
)}
{...props}
@@ -122,7 +122,7 @@ function CommandGroup({
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2.5 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium",
"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2.5 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group-heading]]:text-xs",
className,
)}
{...props}
@@ -137,7 +137,7 @@ function CommandSeparator({
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("bg-border/50 -mx-1 my-1 h-px", className)}
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
{...props}
/>
);
@@ -152,7 +152,7 @@ function CommandItem({
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-selected:bg-muted data-selected:text-foreground data-selected:*:[svg]:text-foreground group/command-item relative flex min-h-7 cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-xs/relaxed outline-hidden select-none in-data-[slot=dialog-content]:rounded-md data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"group/command-item relative flex min-h-7 cursor-default select-none items-center gap-2 in-data-[slot=dialog-content]:rounded-md rounded-md px-2.5 py-1.5 text-xs/relaxed outline-hidden data-[disabled=true]:pointer-events-none data-selected:bg-muted data-selected:text-foreground data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-selected:*:[svg]:text-foreground",
className,
)}
{...props}
@@ -171,7 +171,7 @@ function CommandShortcut({
<span
data-slot="command-shortcut"
className={cn(
"text-muted-foreground group-data-selected/command-item:text-foreground ml-auto text-[0.625rem] tracking-widest",
"ml-auto text-[0.625rem] text-muted-foreground tracking-widest group-data-selected/command-item:text-foreground",
className,
)}
{...props}
+8 -8
View File
@@ -52,7 +52,7 @@ function ContextMenuContent({
<ContextMenuPrimitive.Popup
data-slot="context-menu-content"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
className,
)}
{...props}
@@ -80,7 +80,7 @@ function ContextMenuLabel({
data-slot="context-menu-label"
data-inset={inset}
className={cn(
"text-muted-foreground px-2 py-1.5 text-xs data-inset:pl-7.5",
"px-2 py-1.5 text-muted-foreground text-xs data-inset:pl-7.5",
className,
)}
{...props}
@@ -103,7 +103,7 @@ function ContextMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/context-menu-item relative flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"group/context-menu-item relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive",
className,
)}
{...props}
@@ -130,7 +130,7 @@ function ContextMenuSubTrigger({
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"flex min-h-7 cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-inset:pl-7.5 data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
@@ -168,7 +168,7 @@ function ContextMenuCheckboxItem({
data-slot="context-menu-checkbox-item"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
checked={checked}
@@ -208,7 +208,7 @@ function ContextMenuRadioItem({
data-slot="context-menu-radio-item"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
@@ -230,7 +230,7 @@ function ContextMenuSeparator({
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("bg-border/50 -mx-1 my-1 h-px", className)}
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
{...props}
/>
);
@@ -244,7 +244,7 @@ function ContextMenuShortcut({
<span
data-slot="context-menu-shortcut"
className={cn(
"text-muted-foreground group-focus/context-menu-item:text-accent-foreground ml-auto text-[0.625rem] tracking-widest",
"ml-auto text-[0.625rem] text-muted-foreground tracking-widest group-focus/context-menu-item:text-accent-foreground",
className,
)}
{...props}
+4 -4
View File
@@ -30,7 +30,7 @@ function DialogOverlay({
<DialogPrimitive.Backdrop
data-slot="dialog-overlay"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs",
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-black/80 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs",
className,
)}
{...props}
@@ -52,7 +52,7 @@ function DialogContent({
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl p-4 text-xs/relaxed ring-1 duration-100 outline-none sm:max-w-sm",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-background p-4 text-xs/relaxed outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm",
className,
)}
{...props}
@@ -119,7 +119,7 @@ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-sm font-medium", className)}
className={cn("font-medium text-sm", className)}
{...props}
/>
);
@@ -133,7 +133,7 @@ function DialogDescription({
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3",
"text-muted-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className,
)}
{...props}
+4 -4
View File
@@ -37,7 +37,7 @@ function DrawerOverlay({
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 supports-backdrop-filter:backdrop-blur-xs",
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs",
className,
)}
{...props}
@@ -56,12 +56,12 @@ function DrawerContent({
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"before:bg-background before:border-border group/drawer-content fixed z-50 flex h-auto flex-col bg-transparent p-2 text-xs/relaxed overscroll-contain before:absolute before:inset-2 before:-z-10 before:rounded-xl before:border data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
"group/drawer-content fixed z-50 flex h-auto flex-col overscroll-contain bg-transparent p-2 text-xs/relaxed before:absolute before:inset-2 before:-z-10 before:rounded-xl before:border before:border-border before:bg-background data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
className,
)}
{...props}
>
<div className="bg-muted mx-auto mt-4 hidden h-1.5 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
<div className="mx-auto mt-4 hidden h-1.5 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
@@ -98,7 +98,7 @@ function DrawerTitle({
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn("text-foreground text-sm font-medium", className)}
className={cn("font-medium text-foreground text-sm", className)}
{...props}
/>
);
+9 -9
View File
@@ -41,7 +41,7 @@ function DropdownMenuContent({
<MenuPrimitive.Popup
data-slot="dropdown-menu-content"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none data-closed:overflow-hidden",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden",
className,
)}
{...props}
@@ -67,7 +67,7 @@ function DropdownMenuLabel({
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"text-muted-foreground px-2 py-1.5 text-xs data-inset:pl-7.5",
"px-2 py-1.5 text-muted-foreground text-xs data-inset:pl-7.5",
className,
)}
{...props}
@@ -90,7 +90,7 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/dropdown-menu-item relative flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"group/dropdown-menu-item relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive",
className,
)}
{...props}
@@ -115,7 +115,7 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-popup-open:bg-accent data-popup-open:text-accent-foreground flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"flex min-h-7 cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7.5 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
@@ -138,7 +138,7 @@ function DropdownMenuSubContent({
<DropdownMenuContent
data-slot="dropdown-menu-sub-content"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground w-auto min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 w-auto min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
className,
)}
align={align}
@@ -164,7 +164,7 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
checked={checked}
@@ -205,7 +205,7 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
@@ -230,7 +230,7 @@ function DropdownMenuSeparator({
return (
<MenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border/50 -mx-1 my-1 h-px", className)}
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
{...props}
/>
);
@@ -244,7 +244,7 @@ function DropdownMenuShortcut({
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-[0.625rem] tracking-widest",
"ml-auto text-[0.625rem] text-muted-foreground tracking-widest group-focus/dropdown-menu-item:text-accent-foreground",
className,
)}
{...props}
+5 -5
View File
@@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="empty"
className={cn(
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance",
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 text-balance rounded-xl border-dashed p-6 text-center",
className,
)}
{...props}
@@ -31,7 +31,7 @@ const emptyMediaVariants = cva(
variants: {
variant: {
default: "bg-transparent",
icon: "bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-md [&_svg:not([class*='size-'])]:size-4",
icon: "flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4",
},
},
defaultVariants: {
@@ -59,7 +59,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-title"
className={cn("text-sm font-medium tracking-tight", className)}
className={cn("font-medium text-sm tracking-tight", className)}
{...props}
/>
);
@@ -70,7 +70,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
<div
data-slot="empty-description"
className={cn(
"text-muted-foreground [&>a:hover]:text-primary text-xs/relaxed [&>a]:underline [&>a]:underline-offset-4",
"text-muted-foreground text-xs/relaxed [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
className,
)}
{...props}
@@ -83,7 +83,7 @@ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="empty-content"
className={cn(
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2 text-xs/relaxed text-balance",
"flex w-full min-w-0 max-w-sm flex-col items-center gap-2 text-balance text-xs/relaxed",
className,
)}
{...props}
+9 -9
View File
@@ -51,15 +51,15 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
}
const fieldVariants = cva(
"data-[invalid=true]:text-destructive gap-2 group/field flex w-full",
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
{
variants: {
orientation: {
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
horizontal:
"flex-row items-center *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
responsive:
"flex-col *:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:*:data-[slot=field-label]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
},
},
defaultVariants: {
@@ -106,7 +106,7 @@ function FieldLabel({
<Label
data-slot="field-label"
className={cn(
"has-data-checked:bg-primary/5 dark:has-data-checked:bg-primary/10 group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-2",
"group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border has-data-checked:bg-primary/5 *:data-[slot=field]:p-2 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:bg-primary/10",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
className,
)}
@@ -120,7 +120,7 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="field-label"
className={cn(
"flex w-fit items-center gap-2 text-xs/relaxed leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
"flex w-fit items-center gap-2 font-medium text-xs/relaxed leading-snug group-data-[disabled=true]/field:opacity-50",
className,
)}
{...props}
@@ -133,8 +133,8 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
<p
data-slot="field-description"
className={cn(
"text-muted-foreground text-left text-xs/relaxed leading-normal font-normal group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
"last:mt-0 nth-last-2:-mt-1",
"text-left font-normal text-muted-foreground text-xs/relaxed leading-normal group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
"nth-last-2:-mt-1 last:mt-0",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
className,
)}
@@ -163,7 +163,7 @@ function FieldSeparator({
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="text-muted-foreground bg-background relative mx-auto block w-fit px-2"
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
data-slot="field-separator-content"
>
{children}
@@ -224,7 +224,7 @@ function FieldError({
<div
role="alert"
data-slot="field-error"
className={cn("text-destructive text-xs/relaxed font-normal", className)}
className={cn("font-normal text-destructive text-xs/relaxed", className)}
{...props}
>
{content}
+1 -1
View File
@@ -38,7 +38,7 @@ function HoverCardContent({
<PreviewCardPrimitive.Popup
data-slot="hover-card-content"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 w-72 origin-(--transform-origin) rounded-lg p-2.5 text-xs/relaxed shadow-md ring-1 outline-hidden duration-100",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 w-72 origin-(--transform-origin) rounded-lg bg-popover p-2.5 text-popover-foreground text-xs/relaxed shadow-md outline-hidden ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
className,
)}
{...props}
+8 -8
View File
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
data-slot="input-group"
role="group"
className={cn(
"border-input bg-input/20 dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 group/input-group relative flex h-7 w-full min-w-0 items-center rounded-md border transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-2 has-[textarea]:rounded-md has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
"group/input-group relative flex h-7 w-full min-w-0 items-center rounded-md border border-input bg-input/20 outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[textarea]:rounded-md has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
className,
)}
{...props}
@@ -23,18 +23,18 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
}
const inputGroupAddonVariants = cva(
"text-muted-foreground **:data-[slot=kbd]:bg-muted-foreground/10 h-auto gap-1 py-2 text-xs/relaxed font-medium group-data-[disabled=true]/input-group:opacity-50 **:data-[slot=kbd]:rounded-[calc(var(--radius-sm)-2px)] **:data-[slot=kbd]:px-1 **:data-[slot=kbd]:text-[0.625rem] [&>svg:not([class*='size-'])]:size-3.5 flex cursor-text items-center justify-center select-none",
"flex h-auto cursor-text select-none items-center justify-center gap-1 py-2 font-medium text-muted-foreground text-xs/relaxed **:data-[slot=kbd]:rounded-[calc(var(--radius-sm)-2px)] **:data-[slot=kbd]:bg-muted-foreground/10 **:data-[slot=kbd]:px-1 **:data-[slot=kbd]:text-[0.625rem] group-data-[disabled=true]/input-group:opacity-50 [&>svg:not([class*='size-'])]:size-3.5",
{
variants: {
align: {
"inline-start":
"pl-2 has-[>button]:ml-[-0.275rem] has-[>kbd]:ml-[-0.275rem] order-first",
"order-first pl-2 has-[>button]:ml-[-0.275rem] has-[>kbd]:ml-[-0.275rem]",
"inline-end":
"pr-2 has-[>button]:mr-[-0.275rem] has-[>kbd]:mr-[-0.275rem] order-last",
"order-last pr-2 has-[>button]:mr-[-0.275rem] has-[>kbd]:mr-[-0.275rem]",
"block-start":
"px-2 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start",
"order-first w-full justify-start px-2 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
"block-end":
"px-2 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start",
"order-last w-full justify-start px-2 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
},
},
defaultVariants: {
@@ -68,7 +68,7 @@ function InputGroupAddon({
}
const inputGroupButtonVariants = cva(
"gap-2 rounded-md text-xs/relaxed shadow-none flex items-center",
"flex items-center gap-2 rounded-md text-xs/relaxed shadow-none",
{
variants: {
size: {
@@ -109,7 +109,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"text-muted-foreground flex items-center gap-2 text-xs/relaxed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
"flex items-center gap-2 text-muted-foreground text-xs/relaxed [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
className,
)}
{...props}
+1 -1
View File
@@ -9,7 +9,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"bg-input/20 dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 file:text-foreground placeholder:text-muted-foreground h-7 w-full min-w-0 rounded-md border px-2 py-0.5 text-sm transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs/relaxed file:font-medium focus-visible:ring-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-2 md:text-xs/relaxed",
"h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-xs/relaxed placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
+6 -6
View File
@@ -35,18 +35,18 @@ function ItemSeparator({
}
const itemVariants = cva(
"[a]:hover:bg-muted rounded-md border text-xs/relaxed w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors",
"group/item flex w-full flex-wrap items-center rounded-md border text-xs/relaxed outline-none transition-colors duration-100 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted",
{
variants: {
variant: {
default: "border-transparent",
outline: "border-border",
muted: "bg-muted/50 border-transparent",
muted: "border-transparent bg-muted/50",
},
size: {
default: "gap-2.5 px-3 py-2.5",
sm: "gap-2.5 px-3 py-2.5",
xs: "gap-2.5 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",
xs: "gap-2.5 in-data-[slot=dropdown-menu-content]:p-0 px-2.5 py-2",
},
},
defaultVariants: {
@@ -81,7 +81,7 @@ function Item({
}
const itemMediaVariants = cva(
"gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none",
"flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
{
variants: {
variant: {
@@ -130,7 +130,7 @@ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="item-title"
className={cn(
"line-clamp-1 flex w-fit items-center gap-2 text-xs/relaxed leading-snug font-medium underline-offset-4",
"line-clamp-1 flex w-fit items-center gap-2 font-medium text-xs/relaxed leading-snug underline-offset-4",
className,
)}
{...props}
@@ -143,7 +143,7 @@ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
<p
data-slot="item-description"
className={cn(
"text-muted-foreground [&>a:hover]:text-primary line-clamp-2 text-left text-xs/relaxed font-normal [&>a]:underline [&>a]:underline-offset-4",
"line-clamp-2 text-left font-normal text-muted-foreground text-xs/relaxed [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
className,
)}
{...props}
+1 -1
View File
@@ -5,7 +5,7 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
<kbd
data-slot="kbd"
className={cn(
"bg-muted text-muted-foreground in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-xs px-1 font-sans text-[0.625rem] font-medium select-none [&_svg:not([class*='size-'])]:size-3",
"pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-xs bg-muted in-data-[slot=tooltip-content]:bg-background/20 px-1 font-medium font-sans in-data-[slot=tooltip-content]:text-background text-[0.625rem] text-muted-foreground dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
className,
)}
{...props}
+1 -1
View File
@@ -10,7 +10,7 @@ function Label({ className, ...props }: React.ComponentProps<"label">) {
<label
data-slot="label"
className={cn(
"flex items-center gap-2 text-xs/relaxed leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
"flex select-none items-center gap-2 font-medium text-xs/relaxed leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
className,
)}
{...props}
+11 -11
View File
@@ -26,7 +26,7 @@ function Menubar({ className, ...props }: MenubarPrimitive.Props) {
<MenubarPrimitive
data-slot="menubar"
className={cn(
"bg-background flex h-9 items-center rounded-lg border p-1",
"flex h-9 items-center rounded-lg border bg-background p-1",
className,
)}
{...props}
@@ -58,7 +58,7 @@ function MenubarTrigger({
<DropdownMenuTrigger
data-slot="menubar-trigger"
className={cn(
"hover:bg-muted aria-expanded:bg-muted flex items-center rounded-[calc(var(--radius-md)-2px)] px-2 py-[calc(--spacing(0.85))] text-xs/relaxed font-medium outline-hidden select-none",
"flex select-none items-center rounded-[calc(var(--radius-md)-2px)] px-2 py-[calc(--spacing(0.85))] font-medium text-xs/relaxed outline-hidden hover:bg-muted aria-expanded:bg-muted",
className,
)}
{...props}
@@ -80,7 +80,7 @@ function MenubarContent({
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-open:animate-in data-open:fade-in-0 data-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 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100",
"data-open:fade-in-0 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-open:animate-in",
className,
)}
{...props}
@@ -100,7 +100,7 @@ function MenubarItem({
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive! not-data-[variant=destructive]:focus:**:text-accent-foreground group/menubar-item min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed data-disabled:opacity-50 data-inset:pl-7.5 [&_svg:not([class*='size-'])]:size-3.5",
"group/menubar-item min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive!",
className,
)}
{...props}
@@ -122,7 +122,7 @@ function MenubarCheckboxItem({
data-slot="menubar-checkbox-item"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
checked={checked}
@@ -157,7 +157,7 @@ function MenubarRadioItem({
data-slot="menubar-radio-item"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7.5 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
@@ -184,7 +184,7 @@ function MenubarLabel({
data-slot="menubar-label"
data-inset={inset}
className={cn(
"text-muted-foreground px-2 py-1.5 text-xs data-inset:pl-7.5",
"px-2 py-1.5 text-muted-foreground text-xs data-inset:pl-7.5",
className,
)}
{...props}
@@ -199,7 +199,7 @@ function MenubarSeparator({
return (
<DropdownMenuSeparator
data-slot="menubar-separator"
className={cn("bg-border/50 -mx-1 my-1 h-px", className)}
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
{...props}
/>
);
@@ -213,7 +213,7 @@ function MenubarShortcut({
<DropdownMenuShortcut
data-slot="menubar-shortcut"
className={cn(
"text-muted-foreground group-focus/menubar-item:text-accent-foreground ml-auto text-[0.625rem] tracking-widest",
"ml-auto text-[0.625rem] text-muted-foreground tracking-widest group-focus/menubar-item:text-accent-foreground",
className,
)}
{...props}
@@ -239,7 +239,7 @@ function MenubarSubTrigger({
data-slot="menubar-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs data-inset:pl-7.5 [&_svg:not([class*='size-'])]:size-3.5",
"min-h-7 gap-2 rounded-md px-2 py-1 text-xs focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-inset:pl-7.5 data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
{...props}
@@ -255,7 +255,7 @@ function MenubarSubContent({
<DropdownMenuSubContent
data-slot="menubar-sub-content"
className={cn(
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
className,
)}
{...props}
+6 -6
View File
@@ -55,7 +55,7 @@ function NavigationMenuItem({
}
const navigationMenuTriggerStyle = cva(
"bg-background hover:bg-muted focus:bg-muted data-open:hover:bg-muted data-open:focus:bg-muted data-open:bg-muted/50 focus-visible:ring-ring/30 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted rounded-md px-2.5 py-1.5 text-xs/relaxed font-medium transition-all focus-visible:ring-2 focus-visible:outline-1 disabled:opacity-50 group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center disabled:pointer-events-none outline-none",
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-2.5 py-1.5 font-medium text-xs/relaxed outline-none transition-all hover:bg-muted focus:bg-muted focus-visible:outline-1 focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50 data-open:bg-muted/50 data-popup-open:bg-muted/50 data-open:focus:bg-muted data-open:hover:bg-muted data-popup-open:hover:bg-muted",
);
function NavigationMenuTrigger({
@@ -86,7 +86,7 @@ function NavigationMenuContent({
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:ring-foreground/10 data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] h-full w-auto p-1.5 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-xl group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
"data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 h-full w-auto p-1.5 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=navigation-menu-link]:focus:outline-none **:data-[slot=navigation-menu-link]:focus:ring-0 group-data-[viewport=false]/navigation-menu:rounded-xl group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-open:animate-in",
className,
)}
{...props}
@@ -115,7 +115,7 @@ function NavigationMenuPositioner({
)}
{...props}
>
<NavigationMenuPrimitive.Popup className="bg-popover text-popover-foreground ring-foreground/10 data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-xl shadow ring-1 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0">
<NavigationMenuPrimitive.Popup className="data-[ending-style]:easing-[ease] relative h-(--popup-height) w-(--popup-width) xs:w-(--popup-width) origin-(--transform-origin) rounded-xl bg-popover text-popover-foreground shadow outline-none ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-ending-style:scale-90 data-starting-style:scale-90 data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:duration-150">
<NavigationMenuPrimitive.Viewport className="relative size-full overflow-hidden" />
</NavigationMenuPrimitive.Popup>
</NavigationMenuPrimitive.Positioner>
@@ -131,7 +131,7 @@ function NavigationMenuLink({
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-muted data-[active=true]:hover:bg-muted data-[active=true]:bg-muted/50 focus-visible:ring-ring/30 hover:bg-muted focus:bg-muted flex items-center gap-1.5 rounded-lg p-2 text-xs/relaxed transition-all outline-none focus-visible:ring-2 focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
"flex items-center gap-1.5 rounded-lg p-2 text-xs/relaxed outline-none transition-all hover:bg-muted focus:bg-muted focus-visible:outline-1 focus-visible:ring-2 focus-visible:ring-ring/30 data-[active=true]:bg-muted/50 data-[active=true]:focus:bg-muted data-[active=true]:hover:bg-muted [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
@@ -147,12 +147,12 @@ function NavigationMenuIndicator({
<NavigationMenuPrimitive.Icon
data-slot="navigation-menu-indicator"
className={cn(
"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=hidden]:fade-out data-[state=visible]:fade-in top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=visible]:animate-in",
className,
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Icon>
);
}
+2 -2
View File
@@ -37,7 +37,7 @@ function PopoverContent({
<PopoverPrimitive.Popup
data-slot="popover-content"
className={cn(
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-lg p-2.5 text-xs shadow-md ring-1 outline-hidden duration-100",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-lg bg-popover p-2.5 text-popover-foreground text-xs shadow-md outline-hidden ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
className,
)}
{...props}
@@ -61,7 +61,7 @@ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
return (
<PopoverPrimitive.Title
data-slot="popover-title"
className={cn("text-sm font-medium", className)}
className={cn("font-medium text-sm", className)}
{...props}
/>
);
+4 -4
View File
@@ -29,7 +29,7 @@ function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
return (
<ProgressPrimitive.Track
className={cn(
"bg-muted relative flex h-1 w-full items-center overflow-x-hidden rounded-md",
"relative flex h-1 w-full items-center overflow-x-hidden rounded-md bg-muted",
className,
)}
data-slot="progress-track"
@@ -45,7 +45,7 @@ function ProgressIndicator({
return (
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className={cn("bg-primary h-full transition-all", className)}
className={cn("h-full bg-primary transition-all", className)}
{...props}
/>
);
@@ -54,7 +54,7 @@ function ProgressIndicator({
function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
return (
<ProgressPrimitive.Label
className={cn("text-xs/relaxed font-medium", className)}
className={cn("font-medium text-xs/relaxed", className)}
data-slot="progress-label"
{...props}
/>
@@ -65,7 +65,7 @@ function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
return (
<ProgressPrimitive.Value
className={cn(
"text-muted-foreground ml-auto text-xs/relaxed tabular-nums",
"ml-auto text-muted-foreground text-xs/relaxed tabular-nums",
className,
)}
data-slot="progress-value"
+2 -2
View File
@@ -20,7 +20,7 @@ function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
<RadioPrimitive.Root
data-slot="radio-group-item"
className={cn(
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive/50 group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3",
"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
@@ -29,7 +29,7 @@ function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
data-slot="radio-group-indicator"
className="flex size-4 items-center justify-center"
>
<span className="bg-primary-foreground absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full" />
<span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
</RadioPrimitive.Indicator>
</RadioPrimitive.Root>
);
+2 -2
View File
@@ -35,13 +35,13 @@ function ResizableHandle({
<ResizablePrimitive.Separator
data-slot="resizable-handle"
className={cn(
"bg-border focus-visible:ring-ring ring-offset-background relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
"relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
className,
)}
{...props}
>
{withHandle && (
<div className="bg-border z-10 flex h-6 w-1 shrink-0 rounded-lg" />
<div className="z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" />
)}
</ResizablePrimitive.Separator>
);
+3 -3
View File
@@ -19,7 +19,7 @@ function ScrollArea({
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
className="size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] focus-visible:ring-ring/50"
>
{children}
</ScrollAreaPrimitive.Viewport>
@@ -40,14 +40,14 @@ function ScrollBar({
data-orientation={orientation}
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
"flex touch-none select-none p-px transition-colors data-horizontal:h-2.5 data-vertical:h-full data-vertical:w-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:border-l data-vertical:border-l-transparent",
className,
)}
{...props}
>
<ScrollAreaPrimitive.Thumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
className="relative flex-1 rounded-full bg-border"
/>
</ScrollAreaPrimitive.Scrollbar>
);
+8 -8
View File
@@ -45,7 +45,7 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-placeholder:text-muted-foreground bg-input/20 dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 flex w-fit items-center justify-between gap-1.5 rounded-md border px-2 py-1.5 text-xs/relaxed whitespace-nowrap transition-colors outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-2 data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"flex w-fit items-center justify-between gap-1.5 whitespace-nowrap rounded-md border border-input bg-input/20 px-2 py-1.5 text-xs/relaxed outline-none transition-colors focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-[size=default]:h-7 data-[size=sm]:h-6 data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
@@ -53,7 +53,7 @@ function SelectTrigger({
{children}
<SelectPrimitive.Icon
render={
<IconSelector className="text-muted-foreground pointer-events-none size-3.5" />
<IconSelector className="pointer-events-none size-3.5 text-muted-foreground" />
}
/>
</SelectPrimitive.Trigger>
@@ -88,7 +88,7 @@ function SelectContent({
data-slot="select-content"
data-align-trigger={alignItemWithTrigger}
className={cn(
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in",
className,
)}
{...props}
@@ -109,7 +109,7 @@ function SelectLabel({
return (
<SelectPrimitive.GroupLabel
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
className={cn("px-2 py-1.5 text-muted-foreground text-xs", className)}
{...props}
/>
);
@@ -124,7 +124,7 @@ function SelectItem({
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
"relative flex min-h-7 w-full cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
@@ -151,7 +151,7 @@ function SelectSeparator({
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn(
"bg-border/50 pointer-events-none -mx-1 my-1 h-px",
"pointer-events-none -mx-1 my-1 h-px bg-border/50",
className,
)}
{...props}
@@ -167,7 +167,7 @@ function SelectScrollUpButton({
<SelectPrimitive.ScrollUpArrow
data-slot="select-scroll-up-button"
className={cn(
"bg-popover top-0 z-10 flex w-full cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5",
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
{...props}
@@ -185,7 +185,7 @@ function SelectScrollDownButton({
<SelectPrimitive.ScrollDownArrow
data-slot="select-scroll-down-button"
className={cn(
"bg-popover bottom-0 z-10 flex w-full cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5",
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
{...props}
+1 -1
View File
@@ -14,7 +14,7 @@ function Separator({
data-slot="separator"
orientation={orientation}
className={cn(
"bg-border shrink-0 data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
className,
)}
{...props}
+3 -3
View File
@@ -27,7 +27,7 @@ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
<SheetPrimitive.Backdrop
data-slot="sheet-overlay"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 duration-100 data-closed:animate-out data-open:animate-in data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
className,
)}
{...props}
@@ -52,7 +52,7 @@ function SheetContent({
data-slot="sheet-content"
data-side={side}
className={cn(
"bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
"data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col bg-background bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-closed:animate-out data-open:animate-in data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
className,
)}
{...props}
@@ -102,7 +102,7 @@ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("text-foreground text-sm font-medium", className)}
className={cn("font-medium text-foreground text-sm", className)}
{...props}
/>
);
+20 -20
View File
@@ -140,7 +140,7 @@ function SidebarProvider({
} as React.CSSProperties
}
className={cn(
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
className,
)}
{...props}
@@ -171,7 +171,7 @@ function Sidebar({
<div
data-slot="sidebar"
className={cn(
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
className,
)}
{...props}
@@ -189,7 +189,7 @@ function Sidebar({
data-sidebar="sidebar"
data-slot="sidebar"
data-mobile="true"
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@@ -209,7 +209,7 @@ function Sidebar({
return (
<div
className="group peer text-sidebar-foreground hidden md:block"
className="group peer hidden text-sidebar-foreground md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
@@ -232,7 +232,7 @@ function Sidebar({
data-slot="sidebar-container"
data-side={side}
className={cn(
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
@@ -244,7 +244,7 @@ function Sidebar({
<div
data-sidebar="sidebar"
data-slot="sidebar-inner"
className="bg-sidebar group-data-[variant=floating]:ring-sidebar-border flex size-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1"
className="flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border"
>
{children}
</div>
@@ -291,10 +291,10 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
"group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className,
@@ -309,7 +309,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main
data-slot="sidebar-inset"
className={cn(
"bg-background relative flex w-full flex-1 flex-col md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm",
className,
)}
{...props}
@@ -326,7 +326,7 @@ function SidebarInput({
data-slot="sidebar-input"
data-sidebar="input"
className={cn(
"bg-muted/20 dark:bg-muted/30 border-input h-8 w-full",
"h-8 w-full border-input bg-muted/20 dark:bg-muted/30",
className,
)}
{...props}
@@ -364,7 +364,7 @@ function SidebarSeparator({
<Separator
data-slot="sidebar-separator"
data-sidebar="separator"
className={cn("bg-sidebar-border mx-2 w-auto", className)}
className={cn("mx-2 w-auto bg-sidebar-border", className)}
{...props}
/>
);
@@ -408,7 +408,7 @@ function SidebarGroupLabel({
props: mergeProps<"div">(
{
className: cn(
"text-sidebar-foreground/70 ring-sidebar-ring h-8 rounded-md px-2 text-xs transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 flex shrink-0 items-center outline-hidden [&>svg]:shrink-0",
"flex h-8 shrink-0 items-center rounded-md px-2 text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0",
className,
),
},
@@ -432,7 +432,7 @@ function SidebarGroupAction({
props: mergeProps<"button">(
{
className: cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 w-5 rounded-md p-0 focus-visible:ring-2 [&>svg]:size-4 flex aspect-square items-center justify-center outline-hidden transition-transform [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden group-data-[collapsible=icon]:hidden",
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
className,
),
},
@@ -483,13 +483,13 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
}
const sidebarMenuButtonVariants = cva(
"ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-[calc(var(--radius-sm)+2px)] p-2 text-left text-xs transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0",
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-[calc(var(--radius-sm)+2px)] p-2 text-left text-xs outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-xs",
@@ -572,9 +572,9 @@ function SidebarMenuAction({
props: mergeProps<"button">(
{
className: cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 aspect-square w-5 rounded-[calc(var(--radius-sm)-2px)] p-0 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 focus-visible:ring-2 [&>svg]:size-4 flex items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden [&>svg]:shrink-0",
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-[calc(var(--radius-sm)-2px)] p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
showOnHover &&
"peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 md:opacity-0",
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0",
className,
),
},
@@ -597,7 +597,7 @@ function SidebarMenuBadge({
data-slot="sidebar-menu-badge"
data-sidebar="menu-badge"
className={cn(
"text-sidebar-foreground peer-hover/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-[calc(var(--radius-sm)-2px)] px-1 text-xs font-medium tabular-nums select-none group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1",
"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-[calc(var(--radius-sm)-2px)] px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
className,
)}
{...props}
@@ -649,7 +649,7 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
data-slot="sidebar-menu-sub"
data-sidebar="menu-sub"
className={cn(
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden",
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
@@ -687,7 +687,7 @@ function SidebarMenuSubButton({
props: mergeProps<"a">(
{
className: cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 focus-visible:ring-2 data-[size=md]:text-xs data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-xs data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
className,
),
},
+1 -1
View File
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("bg-muted animate-pulse rounded-md", className)}
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
+5 -5
View File
@@ -25,7 +25,7 @@ function Slider({
return (
<SliderPrimitive.Root
className={cn("data-horizontal:w-full data-vertical:h-full", className)}
className={cn("data-vertical:h-full data-horizontal:w-full", className)}
data-slot="slider"
defaultValue={defaultValue}
value={value}
@@ -34,14 +34,14 @@ function Slider({
thumbAlignment="edge"
{...props}
>
<SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">
<SliderPrimitive.Control className="relative flex w-full touch-none select-none items-center data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col data-disabled:opacity-50">
<SliderPrimitive.Track
data-slot="slider-track"
className="bg-muted relative grow overflow-hidden rounded-md select-none data-horizontal:h-3 data-horizontal:w-full data-vertical:h-full data-vertical:w-3"
className="relative grow select-none overflow-hidden rounded-md bg-muted data-horizontal:h-3 data-vertical:h-full data-horizontal:w-full data-vertical:w-3"
>
<SliderPrimitive.Indicator
data-slot="slider-range"
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
className="select-none bg-primary data-horizontal:h-full data-vertical:w-full"
/>
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
@@ -49,7 +49,7 @@ function Slider({
data-slot="slider-thumb"
// biome-ignore lint/suspicious/noArrayIndexKey: shadcn generated
key={index}
className="border-primary ring-ring/30 block size-4 shrink-0 rounded-md border bg-white shadow-sm transition-colors select-none hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
className="block size-4 shrink-0 select-none rounded-md border border-primary bg-white shadow-sm ring-ring/30 transition-colors hover:ring-4 focus-visible:outline-hidden focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50"
/>
))}
</SliderPrimitive.Control>
+2 -2
View File
@@ -16,14 +16,14 @@ function Switch({
data-slot="switch"
data-size={size}
className={cn(
"data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-2 aria-invalid:ring-2 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[size=default]:h-[18px] data-[size=default]:w-[30px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px]",
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent outline-none transition-all after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-[size=default]:h-[18px] data-[size=sm]:h-[14px] data-[size=default]:w-[30px] data-[size=sm]:w-[24px] data-disabled:cursor-not-allowed data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-50 dark:data-unchecked:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-3.5 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-1px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-1px)] group-data-[size=default]/switch:data-unchecked:translate-x-px group-data-[size=sm]/switch:data-unchecked:translate-x-px"
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-3.5 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-1px)] group-data-[size=default]/switch:data-unchecked:translate-x-px group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-1px)] group-data-[size=sm]/switch:data-unchecked:translate-x-px dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground"
/>
</SwitchPrimitive.Root>
);
+5 -5
View File
@@ -44,7 +44,7 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
<tfoot
data-slot="table-footer"
className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className,
)}
{...props}
@@ -57,7 +57,7 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
<tr
data-slot="table-row"
className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className,
)}
{...props}
@@ -70,7 +70,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0",
"h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
@@ -83,7 +83,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
"whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
@@ -98,7 +98,7 @@ function TableCaption({
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-xs", className)}
className={cn("mt-4 text-muted-foreground text-xs", className)}
{...props}
/>
);
+4 -4
View File
@@ -24,7 +24,7 @@ function Tabs({
}
const tabsListVariants = cva(
"rounded-lg p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground data-[variant=line]:rounded-none group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
{
variants: {
variant: {
@@ -58,10 +58,10 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
<TabsPrimitive.Tab
data-slot="tabs-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-xs font-medium whitespace-nowrap transition-colors group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:py-[calc(--spacing(1.25))] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-1.5 py-0.5 font-medium text-foreground/60 text-xs transition-colors hover:text-foreground focus-visible:border-ring focus-visible:outline-1 focus-visible:outline-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:py-[calc(--spacing(1.25))] dark:text-muted-foreground dark:hover:text-foreground [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className,
)}
{...props}
+1 -1
View File
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea
data-slot="textarea"
className={cn(
"border-input bg-input/20 dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full resize-none rounded-md border px-2 py-2 text-sm transition-colors outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-2 md:text-xs/relaxed",
"field-sizing-content flex min-h-16 w-full resize-none rounded-md border border-input bg-input/20 px-2 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
+1 -1
View File
@@ -71,7 +71,7 @@ function ToggleGroupItem({
data-size={context.size || size}
data-spacing={context.spacing}
className={cn(
"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
"shrink-0 focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:rounded-none group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-[spacing=0]/toggle-group:px-2 group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md",
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
+2 -2
View File
@@ -6,12 +6,12 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const toggleVariants = cva(
"hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-md text-xs font-medium transition-all [&_svg:not([class*='size-'])]:size-3.5 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"group/toggle inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md font-medium text-xs outline-none transition-all hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-pressed:bg-muted aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-transparent",
outline: "border-input hover:bg-muted border bg-transparent",
outline: "border border-input bg-transparent hover:bg-muted",
},
size: {
default: "h-7 min-w-7 px-2",
+2 -2
View File
@@ -50,13 +50,13 @@ function TooltipContent({
<TooltipPrimitive.Popup
data-slot="tooltip-content"
className={cn(
"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md px-3 py-1.5 text-xs **:data-[slot=kbd]:rounded-md",
"data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:fade-out-0 data-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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md bg-foreground px-3 py-1.5 text-background text-xs data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:rounded-md",
className,
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2! data-[side=inline-start]:-right-1 data-[side=left]:-right-1 data-[side=top]:-bottom-2.5 data-[side=inline-end]:-left-1 data-[side=right]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-translate-y-1/2 data-[side=right]:-translate-y-1/2" />
</TooltipPrimitive.Popup>
</TooltipPrimitive.Positioner>
</TooltipPrimitive.Portal>
+1 -1
View File
@@ -12,7 +12,7 @@ export function UpdateToast() {
useEffect(() => {
if (shown) return;
getUpdateCheckAction().then((data) => {
void getUpdateCheckAction().then((data) => {
if (!data?.updateAvailable) return;
setShown(true);
+1 -1
View File
@@ -91,7 +91,7 @@ export async function getSonarrList(
const result: { TvdbId: number; Title: string }[] = [];
for (const row of rows) {
let tvdbId = row.tvdbId;
let { tvdbId } = row;
if (tvdbId == null) {
try {
+2 -2
View File
@@ -86,7 +86,7 @@ export function logEpisodeWatch(
.where(eq(seasons.id, ep.seasonId))
.get();
if (!season) return;
const titleId = season.titleId;
const { titleId } = season;
// Auto-set status to in_progress if not set
const existing = db
@@ -141,7 +141,7 @@ export function logEpisodeWatchBatch(
.all();
if (seasonRows.length === 0) return;
const titleId = seasonRows[0].titleId;
const { titleId } = seasonRows[0];
// Set status to in_progress if not already set
const existing = tx
+1 -2
View File
@@ -178,8 +178,7 @@ async function resolveEpisode(event: WebhookEvent): Promise<{
seasonNumber: number;
episodeNumber: number;
} | null> {
const seasonNumber = event.seasonNumber;
const episodeNumber = event.episodeNumber;
const { seasonNumber, episodeNumber } = event;
if (seasonNumber == null || episodeNumber == null) return null;
// Strategy 1: Use IMDB ID to find the episode and get show_id