mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
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:
@@ -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>
|
||||
|
||||
@@ -50,7 +50,7 @@ const steps = [
|
||||
</a>{" "}
|
||||
and request an API key. Choose “Developer” 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's what's happening with your library
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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't exist or may have been moved. Try searching for
|
||||
what you'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>
|
||||
);
|
||||
|
||||
@@ -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're looking for doesn'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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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're looking for doesn'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
@@ -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
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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?{" "}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
@@ -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"}
|
||||
|
||||
@@ -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,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
)}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
),
|
||||
},
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function UpdateToast() {
|
||||
useEffect(() => {
|
||||
if (shown) return;
|
||||
|
||||
getUpdateCheckAction().then((data) => {
|
||||
void getUpdateCheckAction().then((data) => {
|
||||
if (!data?.updateAvailable) return;
|
||||
|
||||
setShown(true);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user