Upgrade to Tailwind CSS v4 (#1173)

This commit is contained in:
Brandon McConnell 2025-09-09 15:44:07 -04:00 committed by GitHub
parent 4ce76987a1
commit 848205f5cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 1098 additions and 1050 deletions

View file

@ -1,23 +0,0 @@
{
"extends": [
"next/core-web-vitals",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier",
"plugin:tailwindcss/recommended"
],
"plugins": ["tailwindcss"],
"rules": {
"tailwindcss/no-custom-classname": "off",
"tailwindcss/classnames-order": "off"
},
"settings": {
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"project": "./tsconfig.json"
}
}
},
"ignorePatterns": ["**/components/ui/**"]
}

View file

@ -50,17 +50,17 @@ export default function Page() {
}; };
return ( return (
<div className="flex h-dvh w-screen items-start pt-12 md:pt-0 md:items-center justify-center bg-background"> <div className="flex h-dvh w-screen items-start justify-center bg-background pt-12 md:items-center md:pt-0">
<div className="w-full max-w-md overflow-hidden rounded-2xl flex flex-col gap-12"> <div className="flex w-full max-w-md flex-col gap-12 overflow-hidden rounded-2xl">
<div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16"> <div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16">
<h3 className="text-xl font-semibold dark:text-zinc-50">Sign In</h3> <h3 className="font-semibold text-xl dark:text-zinc-50">Sign In</h3>
<p className="text-sm text-gray-500 dark:text-zinc-400"> <p className="text-gray-500 text-sm dark:text-zinc-400">
Use your email and password to sign in Use your email and password to sign in
</p> </p>
</div> </div>
<AuthForm action={handleSubmit} defaultEmail={email}> <AuthForm action={handleSubmit} defaultEmail={email}>
<SubmitButton isSuccessful={isSuccessful}>Sign in</SubmitButton> <SubmitButton isSuccessful={isSuccessful}>Sign in</SubmitButton>
<p className="text-center text-sm text-gray-600 mt-4 dark:text-zinc-400"> <p className="mt-4 text-center text-gray-600 text-sm dark:text-zinc-400">
{"Don't have an account? "} {"Don't have an account? "}
<Link <Link
href="/register" href="/register"

View file

@ -51,17 +51,17 @@ export default function Page() {
}; };
return ( return (
<div className="flex h-dvh w-screen items-start pt-12 md:pt-0 md:items-center justify-center bg-background"> <div className="flex h-dvh w-screen items-start justify-center bg-background pt-12 md:items-center md:pt-0">
<div className="w-full max-w-md overflow-hidden rounded-2xl gap-12 flex flex-col"> <div className="flex w-full max-w-md flex-col gap-12 overflow-hidden rounded-2xl">
<div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16"> <div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16">
<h3 className="text-xl font-semibold dark:text-zinc-50">Sign Up</h3> <h3 className="font-semibold text-xl dark:text-zinc-50">Sign Up</h3>
<p className="text-sm text-gray-500 dark:text-zinc-400"> <p className="text-gray-500 text-sm dark:text-zinc-400">
Create an account with your email and password Create an account with your email and password
</p> </p>
</div> </div>
<AuthForm action={handleSubmit} defaultEmail={email}> <AuthForm action={handleSubmit} defaultEmail={email}>
<SubmitButton isSuccessful={isSuccessful}>Sign Up</SubmitButton> <SubmitButton isSuccessful={isSuccessful}>Sign Up</SubmitButton>
<p className="text-center text-sm text-gray-600 mt-4 dark:text-zinc-400"> <p className="mt-4 text-center text-gray-600 text-sm dark:text-zinc-400">
{'Already have an account? '} {'Already have an account? '}
<Link <Link
href="/login" href="/login"

View file

@ -1,108 +1,192 @@
@tailwind base; /* set base path to root directory to include utility classes from app, components, pages, etc. */
@tailwind components; @import "tailwindcss" source("..");
@tailwind utilities;
/* include utility classes in streamdown */
@source '../node_modules/streamdown/dist/index.js';
/* custom variant for setting dark mode programmatically */
@custom-variant dark (&:is(.dark, .dark *));
/* include plugins */
@plugin "tailwindcss-animate";
@plugin "@tailwindcss/typography";
/* define design tokens (light mode) */
:root { :root {
--background: hsl(0 0% 100%);
--foreground: hsl(240 10% 3.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(240 10% 3.9%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(240 10% 3.9%);
--primary: hsl(240 5.9% 10%);
--primary-foreground: hsl(0 0% 98%);
--secondary: hsl(240 4.8% 95.9%);
--secondary-foreground: hsl(240 5.9% 10%);
--muted: hsl(240 4.8% 95.9%);
--muted-foreground: hsl(240 3.8% 46.1%);
--accent: hsl(240 4.8% 95.9%);
--accent-foreground: hsl(240 5.9% 10%);
--destructive: hsl(0 84.2% 60.2%);
--destructive-foreground: hsl(0 0% 98%);
--border: hsl(240 5.9% 90%);
--input: hsl(240 5.9% 90%);
--ring: hsl(240 10% 3.9%);
--chart-1: hsl(12 76% 61%);
--chart-2: hsl(173 58% 39%);
--chart-3: hsl(197 37% 24%);
--chart-4: hsl(43 74% 66%);
--chart-5: hsl(27 87% 67%);
--sidebar-background: hsl(0 0% 98%);
--sidebar-foreground: hsl(240 5.3% 26.1%);
--sidebar-primary: hsl(240 5.9% 10%);
--sidebar-primary-foreground: hsl(0 0% 98%);
--sidebar-accent: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(220 13% 91%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
/* border radius unit */
--radius: 0.5rem;
}
/* define design tokens (dark mode) */
.dark {
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
--card: hsl(240 10% 3.9%);
--card-foreground: hsl(0 0% 98%);
--popover: hsl(240 10% 3.9%);
--popover-foreground: hsl(0 0% 98%);
--primary: hsl(0 0% 98%);
--primary-foreground: hsl(240 5.9% 10%);
--secondary: hsl(240 3.7% 15.9%);
--secondary-foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%);
--accent: hsl(240 3.7% 15.9%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(0 0% 98%);
--border: hsl(240 3.7% 15.9%);
--input: hsl(240 3.7% 15.9%);
--ring: hsl(240 4.9% 83.9%);
--chart-1: hsl(220 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
--sidebar-background: hsl(240 5.9% 10%);
--sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
}
/* define theme */
@theme {
--font-sans: var(--font-geist);
--font-mono: var(--font-geist-mono);
--breakpoint-toast-mobile: 600px;
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar-background);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@utility text-balance {
text-wrap: balance;
}
@utility -webkit-overflow-scrolling-touch {
-webkit-overflow-scrolling: touch;
}
@utility touch-pan-y {
touch-action: pan-y;
}
@utility overscroll-behavior-contain {
overscroll-behavior: contain;
}
@layer utilities {
:root {
--foreground-rgb: 0, 0, 0; --foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220; --background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255; --background-end-rgb: 255, 255, 255;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--foreground-rgb: 255, 255, 255; --foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0; --background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0; --background-end-rgb: 0, 0, 0;
} }
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.-webkit-overflow-scrolling-touch {
-webkit-overflow-scrolling: touch;
}
.touch-pan-y {
touch-action: pan-y;
}
.overscroll-behavior-contain {
overscroll-behavior: contain;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
} }
} }
@ -147,22 +231,22 @@
.cm-editor, .cm-editor,
.cm-gutters { .cm-gutters {
@apply bg-background dark:bg-zinc-800 outline-none selection:bg-zinc-900 !important; @apply bg-background! dark:bg-zinc-800! outline-hidden! selection:bg-zinc-900!;
} }
.ͼo.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .ͼo.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground,
.ͼo.cm-selectionBackground, .ͼo.cm-selectionBackground,
.ͼo.cm-content::selection { .ͼo.cm-content::selection {
@apply bg-zinc-200 dark:bg-zinc-900 !important; @apply bg-zinc-200! dark:bg-zinc-900!;
} }
.cm-activeLine, .cm-activeLine,
.cm-activeLineGutter { .cm-activeLineGutter {
@apply bg-transparent !important; @apply bg-transparent!;
} }
.cm-activeLine { .cm-activeLine {
@apply rounded-r-sm !important; @apply rounded-r-sm!;
} }
.cm-lineNumbers { .cm-lineNumbers {
@ -174,7 +258,7 @@
} }
.cm-lineNumbers .cm-activeLineGutter { .cm-lineNumbers .cm-activeLineGutter {
@apply rounded-l-sm !important; @apply rounded-l-sm!;
} }
.suggestion-highlight { .suggestion-highlight {
@ -192,13 +276,13 @@
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: hsl(var(--border)); background: var(--border);
border-radius: 3px; border-radius: 3px;
transition: background 0.2s ease; transition: background 0.2s ease;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.5); background: --alpha(var(--muted-foreground) / 0.5);
} }
::-webkit-scrollbar-corner { ::-webkit-scrollbar-corner {
@ -208,5 +292,5 @@
/* firefox scrollbar styling */ /* firefox scrollbar styling */
* { * {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: hsl(var(--border)) transparent; scrollbar-color: var(--border) transparent;
} }

View file

@ -77,7 +77,7 @@ export const textArtifact = new Artifact<'text', TextArtifactMetadata>({
return ( return (
<> <>
<div className="flex flex-row py-8 md:p-20 px-4"> <div className="flex flex-row px-4 py-8 md:p-20">
<Editor <Editor
content={content} content={content}
suggestions={metadata ? metadata.suggestions : []} suggestions={metadata ? metadata.suggestions : []}
@ -88,7 +88,7 @@ export const textArtifact = new Artifact<'text', TextArtifactMetadata>({
/> />
{metadata?.suggestions && metadata.suggestions.length > 0 ? ( {metadata?.suggestions && metadata.suggestions.length > 0 ? (
<div className="md:hidden h-dvh w-12 shrink-0" /> <div className="h-dvh w-12 shrink-0 md:hidden" />
) : null} ) : null}
</div> </div>
</> </>

View file

@ -62,7 +62,7 @@
"useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually "useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually
"noNewSymbol": "warn", // Not in recommended ruleset, turning on manually "noNewSymbol": "warn", // Not in recommended ruleset, turning on manually
"useJsxKeyInIterable": "off", // Rule is buggy, revisit later "useJsxKeyInIterable": "off", // Rule is buggy, revisit later
"useExhaustiveDependencies": "off", // Community feedback on this rule has been poor, we will continue with ESLint "useExhaustiveDependencies": "warn", // Errors by default, switching to warn instead
"noUnnecessaryContinue": "off" // Turned off due to developer preferences "noUnnecessaryContinue": "off" // Turned off due to developer preferences
}, },
"security": { "security": {
@ -80,11 +80,12 @@
"noExplicitAny": "off" // We trust Vercelians to use any only when necessary "noExplicitAny": "off" // We trust Vercelians to use any only when necessary
}, },
"nursery": { "nursery": {
"noStaticElementInteractions": "warn",
"noHeadImportInDocument": "warn",
"noDocumentImportInPage": "warn", "noDocumentImportInPage": "warn",
"noDuplicateElseIf": "warn", "noDuplicateElseIf": "warn",
"noHeadImportInDocument": "warn",
"noIrregularWhitespace": "warn", "noIrregularWhitespace": "warn",
"noStaticElementInteractions": "warn",
"useSortedClasses": "error",
"useValidAutocomplete": "warn" "useValidAutocomplete": "warn"
} }
} }

View file

@ -4,7 +4,7 @@
"rsc": true, "rsc": true,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "",
"css": "app/globals.css", "css": "app/globals.css",
"baseColor": "zinc", "baseColor": "zinc",
"cssVariables": true, "cssVariables": true,

View file

@ -26,15 +26,15 @@ export function AppSidebar({ user }: { user: User | undefined }) {
<Sidebar className="group-data-[side=left]:border-r-0"> <Sidebar className="group-data-[side=left]:border-r-0">
<SidebarHeader> <SidebarHeader>
<SidebarMenu> <SidebarMenu>
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row items-center justify-between">
<Link <Link
href="/" href="/"
onClick={() => { onClick={() => {
setOpenMobile(false); setOpenMobile(false);
}} }}
className="flex flex-row gap-3 items-center" className="flex flex-row items-center gap-3"
> >
<span className="text-lg font-semibold px-2 hover:bg-muted rounded-md cursor-pointer"> <span className="cursor-pointer rounded-md px-2 font-semibold text-lg hover:bg-muted">
Chatbot Chatbot
</span> </span>
</Link> </Link>
@ -43,7 +43,7 @@ export function AppSidebar({ user }: { user: User | undefined }) {
<Button <Button
variant="ghost" variant="ghost"
type="button" type="button"
className="p-1 h-8 md:p-2 md:h-fit" className="h-8 p-1 md:h-fit md:p-2"
onClick={() => { onClick={() => {
setOpenMobile(false); setOpenMobile(false);
router.push('/'); router.push('/');
@ -53,7 +53,9 @@ export function AppSidebar({ user }: { user: User | undefined }) {
<PlusIcon /> <PlusIcon />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent align="end" className="hidden md:block">New Chat</TooltipContent> <TooltipContent align="end" className="hidden md:block">
New Chat
</TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
</SidebarMenu> </SidebarMenu>

View file

@ -54,7 +54,7 @@ function PureArtifactActions({
variant="outline" variant="outline"
className={cn('h-fit dark:hover:bg-zinc-700', { className={cn('h-fit dark:hover:bg-zinc-700', {
'p-2': !action.label, 'p-2': !action.label,
'py-1.5 px-2': action.label, 'px-2 py-1.5': action.label,
})} })}
onClick={async () => { onClick={async () => {
setIsLoading(true); setIsLoading(true);

View file

@ -42,7 +42,7 @@ function PureArtifactMessages({
return ( return (
<div <div
ref={messagesContainerRef} ref={messagesContainerRef}
className="flex overflow-y-scroll flex-col gap-4 items-center px-4 pt-20 h-full" className="flex h-full flex-col items-center gap-4 overflow-y-scroll px-4 pt-20"
> >
{messages.map((message, index) => ( {messages.map((message, index) => (
<PreviewMessage <PreviewMessage
@ -71,7 +71,7 @@ function PureArtifactMessages({
<motion.div <motion.div
ref={messagesEndRef} ref={messagesEndRef}
className="shrink-0 min-w-[24px] min-h-[24px]" className="min-h-[24px] min-w-[24px] shrink-0"
onViewportLeave={onViewportLeave} onViewportLeave={onViewportLeave}
onViewportEnter={onViewportEnter} onViewportEnter={onViewportEnter}
/> />

View file

@ -259,14 +259,14 @@ function PureArtifact({
{artifact.isVisible && ( {artifact.isVisible && (
<motion.div <motion.div
data-testid="artifact" data-testid="artifact"
className="flex flex-row h-dvh w-dvw fixed top-0 left-0 z-50 bg-transparent" className="fixed top-0 left-0 z-50 flex h-dvh w-dvw flex-row bg-transparent"
initial={{ opacity: 1 }} initial={{ opacity: 1 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0, transition: { delay: 0.4 } }} exit={{ opacity: 0, transition: { delay: 0.4 } }}
> >
{!isMobile && ( {!isMobile && (
<motion.div <motion.div
className="fixed bg-background h-dvh" className="fixed h-dvh bg-background"
initial={{ initial={{
width: isSidebarOpen ? windowWidth - 256 : windowWidth, width: isSidebarOpen ? windowWidth - 256 : windowWidth,
right: 0, right: 0,
@ -281,7 +281,7 @@ function PureArtifact({
{!isMobile && ( {!isMobile && (
<motion.div <motion.div
className="relative w-[400px] bg-muted dark:bg-background h-dvh shrink-0" className="relative h-dvh w-[400px] shrink-0 bg-muted dark:bg-background"
initial={{ opacity: 0, x: 10, scale: 1 }} initial={{ opacity: 0, x: 10, scale: 1 }}
animate={{ animate={{
opacity: 1, opacity: 1,
@ -304,7 +304,7 @@ function PureArtifact({
<AnimatePresence> <AnimatePresence>
{!isCurrentVersion && ( {!isCurrentVersion && (
<motion.div <motion.div
className="left-0 absolute h-dvh w-[400px] top-0 bg-zinc-900/50 z-50" className="absolute top-0 left-0 z-50 h-dvh w-[400px] bg-zinc-900/50"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
@ -312,7 +312,7 @@ function PureArtifact({
)} )}
</AnimatePresence> </AnimatePresence>
<div className="flex flex-col h-full justify-between items-center"> <div className="flex h-full flex-col items-center justify-between">
<ArtifactMessages <ArtifactMessages
chatId={chatId} chatId={chatId}
status={status} status={status}
@ -324,7 +324,7 @@ function PureArtifact({
artifactStatus={artifact.status} artifactStatus={artifact.status}
/> />
<div className="flex flex-row gap-2 relative items-end w-full px-4 pb-4"> <div className="relative flex w-full flex-row items-end gap-2 px-4 pb-4">
<MultimodalInput <MultimodalInput
chatId={chatId} chatId={chatId}
input={input} input={input}
@ -346,7 +346,7 @@ function PureArtifact({
)} )}
<motion.div <motion.div
className="fixed dark:bg-muted bg-background h-dvh flex flex-col overflow-y-scroll md:border-l dark:border-zinc-700 border-zinc-200" className="fixed flex h-dvh flex-col overflow-y-scroll border-zinc-200 bg-background md:border-l dark:border-zinc-700 dark:bg-muted"
initial={ initial={
isMobile isMobile
? { ? {
@ -412,19 +412,19 @@ function PureArtifact({
}, },
}} }}
> >
<div className="p-2 flex flex-row justify-between items-start"> <div className="flex flex-row items-start justify-between p-2">
<div className="flex flex-row gap-4 items-start"> <div className="flex flex-row items-start gap-4">
<ArtifactCloseButton /> <ArtifactCloseButton />
<div className="flex flex-col"> <div className="flex flex-col">
<div className="font-medium">{artifact.title}</div> <div className="font-medium">{artifact.title}</div>
{isContentDirty ? ( {isContentDirty ? (
<div className="text-sm text-muted-foreground"> <div className="text-muted-foreground text-sm">
Saving changes... Saving changes...
</div> </div>
) : document ? ( ) : document ? (
<div className="text-sm text-muted-foreground"> <div className="text-muted-foreground text-sm">
{`Updated ${formatDistance( {`Updated ${formatDistance(
new Date(document.createdAt), new Date(document.createdAt),
new Date(), new Date(),
@ -434,7 +434,7 @@ function PureArtifact({
)}`} )}`}
</div> </div>
) : ( ) : (
<div className="w-32 h-3 mt-2 bg-muted-foreground/20 rounded-md animate-pulse" /> <div className="mt-2 h-3 w-32 animate-pulse rounded-md bg-muted-foreground/20" />
)} )}
</div> </div>
</div> </div>
@ -450,7 +450,7 @@ function PureArtifact({
/> />
</div> </div>
<div className="dark:bg-muted bg-background h-full overflow-y-scroll !max-w-full items-center"> <div className="h-full max-w-full! items-center overflow-y-scroll bg-background dark:bg-muted">
<artifactDefinition.content <artifactDefinition.content
title={artifact.title} title={artifact.title}
content={ content={

View file

@ -19,7 +19,7 @@ export function AuthForm({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label <Label
htmlFor="email" htmlFor="email"
className="text-zinc-600 font-normal dark:text-zinc-400" className="font-normal text-zinc-600 dark:text-zinc-400"
> >
Email Address Email Address
</Label> </Label>
@ -40,7 +40,7 @@ export function AuthForm({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label <Label
htmlFor="password" htmlFor="password"
className="text-zinc-600 font-normal dark:text-zinc-400" className="font-normal text-zinc-600 dark:text-zinc-400"
> >
Password Password
</Label> </Label>

View file

@ -29,13 +29,13 @@ function PureChatHeader({
const { width: windowWidth } = useWindowSize(); const { width: windowWidth } = useWindowSize();
return ( return (
<header className="flex sticky top-0 bg-background py-1.5 items-center px-2 md:px-2 gap-2"> <header className="sticky top-0 flex items-center gap-2 bg-background px-2 py-1.5 md:px-2">
<SidebarToggle /> <SidebarToggle />
{(!open || windowWidth < 768) && ( {(!open || windowWidth < 768) && (
<Button <Button
variant="outline" variant="outline"
className="order-2 md:order-1 md:px-2 px-2 md:h-fit ml-auto md:ml-0" className="order-2 ml-auto px-2 md:order-1 md:ml-0 md:h-fit md:px-2"
onClick={() => { onClick={() => {
router.push('/'); router.push('/');
router.refresh(); router.refresh();
@ -55,7 +55,7 @@ function PureChatHeader({
)} )}
<Button <Button
className="bg-zinc-900 dark:bg-zinc-100 hover:bg-zinc-800 dark:hover:bg-zinc-200 text-zinc-50 dark:text-zinc-900 hidden md:flex py-1.5 px-2 h-fit md:h-[34px] order-3 md:ml-auto" className="order-3 hidden h-fit bg-zinc-900 px-2 py-1.5 text-zinc-50 hover:bg-zinc-800 md:ml-auto md:flex md:h-[34px] dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
asChild asChild
> >
<Link <Link

View file

@ -136,7 +136,7 @@ export function Chat({
return ( return (
<> <>
<div className="flex flex-col min-w-0 h-dvh bg-background touch-pan-y overscroll-behavior-contain"> <div className="overscroll-behavior-contain flex h-dvh min-w-0 touch-pan-y flex-col bg-background">
<ChatHeader <ChatHeader
chatId={id} chatId={id}
selectedVisibilityType={initialVisibilityType} selectedVisibilityType={initialVisibilityType}
@ -156,7 +156,7 @@ export function Chat({
selectedModelId={initialChatModel} selectedModelId={initialChatModel}
/> />
<div className="sticky bottom-0 flex gap-2 px-2 md:px-4 pb-3 md:pb-4 mx-auto w-full bg-background max-w-4xl z-[1] border-t-0"> <div className="sticky bottom-0 z-1 mx-auto flex w-full max-w-4xl gap-2 border-t-0 bg-background px-2 pb-3 md:px-4 md:pb-4">
{!isReadonly && ( {!isReadonly && (
<MultimodalInput <MultimodalInput
chatId={id} chatId={id}

View file

@ -92,7 +92,7 @@ function PureCodeEditor({ content, onSaveContent, status }: EditorProps) {
return ( return (
<div <div
className="relative not-prose w-full pb-[calc(80dvh)] text-sm" className="not-prose relative w-full pb-[calc(80dvh)] text-sm"
ref={containerRef} ref={containerRef}
/> />
); );

View file

@ -80,7 +80,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
return consoleOutputs.length > 0 ? ( return consoleOutputs.length > 0 ? (
<> <>
<div <div
className="fixed z-50 w-full h-2 cursor-ns-resize" className="fixed z-50 h-2 w-full cursor-ns-resize"
onMouseDown={startResizing} onMouseDown={startResizing}
style={{ bottom: height - 4 }} style={{ bottom: height - 4 }}
role="slider" role="slider"
@ -89,15 +89,15 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
<div <div
className={cn( className={cn(
'flex overflow-x-hidden overflow-y-scroll fixed bottom-0 z-40 flex-col w-full border-t dark:bg-zinc-900 bg-zinc-50 dark:border-zinc-700 border-zinc-200', 'fixed bottom-0 z-40 flex w-full flex-col overflow-x-hidden overflow-y-scroll border-zinc-200 border-t bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900',
{ {
'select-none': isResizing, 'select-none': isResizing,
}, },
)} )}
style={{ height }} style={{ height }}
> >
<div className="flex sticky top-0 z-50 flex-row justify-between items-center px-2 py-1 w-full border-b h-fit dark:border-zinc-700 border-zinc-200 bg-muted"> <div className="sticky top-0 z-50 flex h-fit w-full flex-row items-center justify-between border-zinc-200 border-b bg-muted px-2 py-1 dark:border-zinc-700">
<div className="flex flex-row gap-3 items-center pl-2 text-sm dark:text-zinc-50 text-zinc-800"> <div className="flex flex-row items-center gap-3 pl-2 text-sm text-zinc-800 dark:text-zinc-50">
<div className="text-muted-foreground"> <div className="text-muted-foreground">
<TerminalWindowIcon /> <TerminalWindowIcon />
</div> </div>
@ -105,7 +105,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
</div> </div>
<Button <Button
variant="ghost" variant="ghost"
className="p-1 size-fit hover:dark:bg-zinc-700 hover:bg-zinc-200" className="size-fit p-1 hover:bg-zinc-200 dark:hover:bg-zinc-700"
size="icon" size="icon"
onClick={() => setConsoleOutputs([])} onClick={() => setConsoleOutputs([])}
> >
@ -117,7 +117,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
{consoleOutputs.map((consoleOutput, index) => ( {consoleOutputs.map((consoleOutput, index) => (
<div <div
key={consoleOutput.id} key={consoleOutput.id}
className="flex flex-row px-4 py-2 font-mono text-sm border-b dark:border-zinc-700 border-zinc-200 dark:bg-zinc-900 bg-zinc-50" className="flex flex-row border-zinc-200 border-b bg-zinc-50 px-4 py-2 font-mono text-sm dark:border-zinc-700 dark:bg-zinc-900"
> >
<div <div
className={cn('w-12 shrink-0', { className={cn('w-12 shrink-0', {
@ -135,7 +135,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
consoleOutput.status, consoleOutput.status,
) ? ( ) ? (
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<div className="size-fit self-center mb-auto mt-0.5"> <div className="mt-0.5 mb-auto size-fit self-center">
<Loader size={16} /> <Loader size={16} />
</div> </div>
<div className="text-muted-foreground"> <div className="text-muted-foreground">
@ -149,14 +149,14 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
</div> </div>
</div> </div>
) : ( ) : (
<div className="flex overflow-x-scroll flex-col gap-2 w-full dark:text-zinc-50 text-zinc-900"> <div className="flex w-full flex-col gap-2 overflow-x-scroll text-zinc-900 dark:text-zinc-50">
{consoleOutput.contents.map((content, index) => {consoleOutput.contents.map((content, index) =>
content.type === 'image' ? ( content.type === 'image' ? (
<picture key={`${consoleOutput.id}-${index}`}> <picture key={`${consoleOutput.id}-${index}`}>
<img <img
src={content.value} src={content.value}
alt="output" alt="output"
className="w-full rounded-md max-w-screen-toast-mobile" className="w-full max-w-(--breakpoint-toast-mobile) rounded-md"
/> />
</picture> </picture>
) : ( ) : (

View file

@ -118,23 +118,23 @@ export function DocumentPreview({
const LoadingSkeleton = ({ artifactKind }: { artifactKind: ArtifactKind }) => ( const LoadingSkeleton = ({ artifactKind }: { artifactKind: ArtifactKind }) => (
<div className="w-full"> <div className="w-full">
<div className="p-4 border rounded-t-2xl flex flex-row gap-2 items-center justify-between dark:bg-muted h-[57px] dark:border-zinc-700 border-b-0"> <div className="flex h-[57px] flex-row items-center justify-between gap-2 rounded-t-2xl border border-b-0 p-4 dark:border-zinc-700 dark:bg-muted">
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className="text-muted-foreground"> <div className="text-muted-foreground">
<div className="animate-pulse rounded-md size-4 bg-muted-foreground/20" /> <div className="size-4 animate-pulse rounded-md bg-muted-foreground/20" />
</div> </div>
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-24" /> <div className="h-4 w-24 animate-pulse rounded-lg bg-muted-foreground/20" />
</div> </div>
<div> <div>
<FullscreenIcon /> <FullscreenIcon />
</div> </div>
</div> </div>
{artifactKind === 'image' ? ( {artifactKind === 'image' ? (
<div className="overflow-y-scroll border rounded-b-2xl bg-muted border-t-0 dark:border-zinc-700"> <div className="overflow-y-scroll rounded-b-2xl border border-t-0 bg-muted dark:border-zinc-700">
<div className="animate-pulse h-[257px] bg-muted-foreground/20 w-full" /> <div className="h-[257px] w-full animate-pulse bg-muted-foreground/20" />
</div> </div>
) : ( ) : (
<div className="overflow-y-scroll border rounded-b-2xl p-8 pt-4 bg-muted border-t-0 dark:border-zinc-700"> <div className="overflow-y-scroll rounded-b-2xl border border-t-0 bg-muted p-8 pt-4 dark:border-zinc-700">
<InlineDocumentSkeleton /> <InlineDocumentSkeleton />
</div> </div>
)} )}
@ -179,14 +179,14 @@ const PureHitboxLayer = ({
return ( return (
<div <div
className="size-full absolute top-0 left-0 rounded-xl z-10" className="absolute top-0 left-0 z-10 size-full rounded-xl"
ref={hitboxRef} ref={hitboxRef}
onClick={handleClick} onClick={handleClick}
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
> >
<div className="w-full p-4 flex justify-end items-center"> <div className="flex w-full items-center justify-end p-4">
<div className="absolute right-[9px] top-[13px] p-2 hover:dark:bg-zinc-700 rounded-md hover:bg-zinc-100"> <div className="absolute top-[13px] right-[9px] rounded-md p-2 hover:bg-zinc-100 dark:hover:bg-zinc-700">
<FullscreenIcon /> <FullscreenIcon />
</div> </div>
</div> </div>
@ -208,8 +208,8 @@ const PureDocumentHeader = ({
kind: ArtifactKind; kind: ArtifactKind;
isStreaming: boolean; isStreaming: boolean;
}) => ( }) => (
<div className="p-4 border rounded-t-2xl flex flex-row gap-2 items-start sm:items-center justify-between dark:bg-muted border-b-0 dark:border-zinc-700"> <div className="flex flex-row items-start justify-between gap-2 rounded-t-2xl border border-b-0 p-4 sm:items-center dark:border-zinc-700 dark:bg-muted">
<div className="flex flex-row items-start sm:items-center gap-3"> <div className="flex flex-row items-start gap-3 sm:items-center">
<div className="text-muted-foreground"> <div className="text-muted-foreground">
{isStreaming ? ( {isStreaming ? (
<div className="animate-spin"> <div className="animate-spin">
@ -221,7 +221,7 @@ const PureDocumentHeader = ({
<FileIcon /> <FileIcon />
)} )}
</div> </div>
<div className="-translate-y-1 sm:translate-y-0 font-medium">{title}</div> <div className="-translate-y-1 font-medium sm:translate-y-0">{title}</div>
</div> </div>
<div className="w-8" /> <div className="w-8" />
</div> </div>
@ -259,13 +259,13 @@ const DocumentContent = ({ document }: { document: Document }) => {
{document.kind === 'text' ? ( {document.kind === 'text' ? (
<Editor {...commonProps} onSaveContent={() => {}} /> <Editor {...commonProps} onSaveContent={() => {}} />
) : document.kind === 'code' ? ( ) : document.kind === 'code' ? (
<div className="flex flex-1 relative w-full"> <div className="relative flex w-full flex-1">
<div className="absolute inset-0"> <div className="absolute inset-0">
<CodeEditor {...commonProps} onSaveContent={() => {}} /> <CodeEditor {...commonProps} onSaveContent={() => {}} />
</div> </div>
</div> </div>
) : document.kind === 'sheet' ? ( ) : document.kind === 'sheet' ? (
<div className="flex flex-1 relative size-full p-4"> <div className="relative flex size-full flex-1 p-4">
<div className="absolute inset-0"> <div className="absolute inset-0">
<SpreadsheetEditor {...commonProps} /> <SpreadsheetEditor {...commonProps} />
</div> </div>

View file

@ -8,32 +8,32 @@ export const DocumentSkeleton = ({
artifactKind: ArtifactKind; artifactKind: ArtifactKind;
}) => { }) => {
return artifactKind === 'image' ? ( return artifactKind === 'image' ? (
<div className="flex flex-col gap-4 w-full justify-center items-center h-[calc(100dvh-60px)]"> <div className="flex h-[calc(100dvh-60px)] w-full flex-col items-center justify-center gap-4">
<div className="animate-pulse rounded-lg bg-muted-foreground/20 size-96" /> <div className="size-96 animate-pulse rounded-lg bg-muted-foreground/20" />
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-4 w-full"> <div className="flex w-full flex-col gap-4">
<div className="animate-pulse rounded-lg h-12 bg-muted-foreground/20 w-1/2" /> <div className="h-12 w-1/2 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-5 bg-muted-foreground/20 w-full" /> <div className="h-5 w-full animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-5 bg-muted-foreground/20 w-full" /> <div className="h-5 w-full animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-5 bg-muted-foreground/20 w-1/3" /> <div className="h-5 w-1/3 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-5 bg-transparent w-52" /> <div className="h-5 w-52 animate-pulse rounded-lg bg-transparent" />
<div className="animate-pulse rounded-lg h-8 bg-muted-foreground/20 w-52" /> <div className="h-8 w-52 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-5 bg-muted-foreground/20 w-2/3" /> <div className="h-5 w-2/3 animate-pulse rounded-lg bg-muted-foreground/20" />
</div> </div>
); );
}; };
export const InlineDocumentSkeleton = () => { export const InlineDocumentSkeleton = () => {
return ( return (
<div className="flex flex-col gap-4 w-full"> <div className="flex w-full flex-col gap-4">
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-48" /> <div className="h-4 w-48 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-3/4" /> <div className="h-4 w-3/4 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-1/2" /> <div className="h-4 w-1/2 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-64" /> <div className="h-4 w-64 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-40" /> <div className="h-4 w-40 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-36" /> <div className="h-4 w-36 animate-pulse rounded-lg bg-muted-foreground/20" />
<div className="animate-pulse rounded-lg h-4 bg-muted-foreground/20 w-64" /> <div className="h-4 w-64 animate-pulse rounded-lg bg-muted-foreground/20" />
</div> </div>
); );
}; };

View file

@ -39,7 +39,7 @@ function PureDocumentToolResult({
return ( return (
<button <button
type="button" type="button"
className="bg-background cursor-pointer border py-2 px-3 rounded-xl w-fit flex flex-row gap-3 items-start" className="flex w-fit cursor-pointer flex-row items-start gap-3 rounded-xl border bg-background px-3 py-2"
onClick={(event) => { onClick={(event) => {
if (isReadonly) { if (isReadonly) {
toast.error( toast.error(
@ -68,7 +68,7 @@ function PureDocumentToolResult({
}); });
}} }}
> >
<div className="text-muted-foreground mt-1"> <div className="mt-1 text-muted-foreground">
{type === 'create' ? ( {type === 'create' ? (
<FileIcon /> <FileIcon />
) : type === 'update' ? ( ) : type === 'update' ? (
@ -105,7 +105,7 @@ function PureDocumentToolCall({
return ( return (
<button <button
type="button" type="button"
className="cursor pointer w-fit border py-2 px-3 rounded-xl flex flex-row items-start justify-between gap-3" className="cursor pointer flex w-fit flex-row items-start justify-between gap-3 rounded-xl border px-3 py-2"
onClick={(event) => { onClick={(event) => {
if (isReadonly) { if (isReadonly) {
toast.error( toast.error(
@ -130,8 +130,8 @@ function PureDocumentToolCall({
})); }));
}} }}
> >
<div className="flex flex-row gap-3 items-start"> <div className="flex flex-row items-start gap-3">
<div className="text-zinc-500 mt-1"> <div className="mt-1 text-zinc-500">
{type === 'create' ? ( {type === 'create' ? (
<FileIcon /> <FileIcon />
) : type === 'update' ? ( ) : type === 'update' ? (
@ -154,7 +154,7 @@ function PureDocumentToolCall({
</div> </div>
</div> </div>
<div className="animate-spin mt-1">{<LoaderIcon />}</div> <div className="mt-1 animate-spin">{<LoaderIcon />}</div>
</button> </button>
); );
} }

View file

@ -13,7 +13,7 @@ import type { ComponentProps } from 'react';
export type ActionsProps = ComponentProps<'div'>; export type ActionsProps = ComponentProps<'div'>;
export const Actions = ({ className, children, ...props }: ActionsProps) => ( export const Actions = ({ className, children, ...props }: ActionsProps) => (
<div className={cn('flex gap-1 items-center', className)} {...props}> <div className={cn('flex items-center gap-1', className)} {...props}>
{children} {children}
</div> </div>
); );
@ -35,7 +35,7 @@ export const Action = ({
const button = ( const button = (
<Button <Button
className={cn( className={cn(
'size-9 p-1.5 text-muted-foreground hover:text-foreground relative', 'relative size-9 p-1.5 text-muted-foreground hover:text-foreground',
className, className,
)} )}
size={size} size={size}

View file

@ -12,7 +12,7 @@ export type ConversationProps = ComponentProps<typeof StickToBottom>;
export const Conversation = ({ className, ...props }: ConversationProps) => ( export const Conversation = ({ className, ...props }: ConversationProps) => (
<StickToBottom <StickToBottom
className={cn( className={cn(
'overflow-y-auto relative flex-1 touch-pan-y will-change-scroll', 'relative flex-1 touch-pan-y overflow-y-auto will-change-scroll',
className, className,
)} )}
initial="smooth" initial="smooth"
@ -49,7 +49,7 @@ export const ConversationScrollButton = ({
!isAtBottom && ( !isAtBottom && (
<Button <Button
className={cn( className={cn(
'absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full z-10 shadow-lg', '-translate-x-1/2 absolute bottom-4 left-1/2 z-10 rounded-full shadow-lg',
className, className,
)} )}
onClick={handleScrollToBottom} onClick={handleScrollToBottom}

View file

@ -24,7 +24,7 @@ export type PromptInputProps = HTMLAttributes<HTMLFormElement>;
export const PromptInput = ({ className, ...props }: PromptInputProps) => ( export const PromptInput = ({ className, ...props }: PromptInputProps) => (
<form <form
className={cn( className={cn(
'w-full overflow-hidden rounded-xl border bg-background shadow-sm', 'w-full overflow-hidden rounded-xl border bg-background shadow-xs',
className, className,
)} )}
{...props} {...props}
@ -72,8 +72,12 @@ export const PromptInputTextarea = ({
return ( return (
<Textarea <Textarea
className={cn( className={cn(
'w-full resize-none rounded-none border-none p-3 shadow-none outline-none ring-0', 'w-full resize-none rounded-none border-none p-3 shadow-none outline-hidden ring-0',
disableAutoResize ? 'field-sizing-fixed' : resizeOnNewLinesOnly ? 'field-sizing-fixed' : 'field-sizing-content max-h-[6lh]', disableAutoResize
? 'field-sizing-fixed'
: resizeOnNewLinesOnly
? 'field-sizing-fixed'
: 'field-sizing-content max-h-[6lh]',
'bg-transparent dark:bg-transparent', 'bg-transparent dark:bg-transparent',
'focus-visible:ring-0', 'focus-visible:ring-0',
className, className,
@ -196,7 +200,7 @@ export const PromptInputModelSelectTrigger = ({
<SelectTrigger <SelectTrigger
className={cn( className={cn(
'border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors', 'border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors',
'hover:bg-accent hover:text-foreground [&[aria-expanded="true"]]:bg-accent [&[aria-expanded="true"]]:text-foreground', 'hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground',
className, className,
)} )}
{...props} {...props}

View file

@ -119,7 +119,7 @@ export const ReasoningTrigger = memo(
return ( return (
<CollapsibleTrigger <CollapsibleTrigger
className={cn( className={cn(
'flex items-center gap-1.5 text-muted-foreground text-xs hover:text-foreground transition-colors', 'flex items-center gap-1.5 text-muted-foreground text-xs transition-colors hover:text-foreground',
className, className,
)} )}
{...props} {...props}
@ -155,8 +155,8 @@ export const ReasoningContent = memo(
({ className, children, ...props }: ReasoningContentProps) => ( ({ className, children, ...props }: ReasoningContentProps) => (
<CollapsibleContent <CollapsibleContent
className={cn( className={cn(
'mt-2 text-xs text-muted-foreground', 'mt-2 text-muted-foreground text-xs',
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in', 'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}

View file

@ -10,7 +10,7 @@ export const Response = memo(
({ className, ...props }: ResponseProps) => ( ({ className, ...props }: ResponseProps) => (
<Streamdown <Streamdown
className={cn( className={cn(
'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_pre]:overflow-x-auto [&_pre]:max-w-full [&_code]:break-words [&_code]:whitespace-pre-wrap', 'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_code]:whitespace-pre-wrap [&_code]:break-words [&_pre]:max-w-full [&_pre]:overflow-x-auto',
className, className,
)} )}
{...props} {...props}

View file

@ -47,7 +47,7 @@ export const SourcesContent = ({
<CollapsibleContent <CollapsibleContent
className={cn( className={cn(
'mt-3 flex w-fit flex-col gap-2', 'mt-3 flex w-fit flex-col gap-2',
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in', 'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}

View file

@ -82,7 +82,7 @@ export const TaskContent = ({
}: TaskContentProps) => ( }: TaskContentProps) => (
<CollapsibleContent <CollapsibleContent
className={cn( className={cn(
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in', 'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}

View file

@ -50,7 +50,10 @@ const getStatusBadge = (status: ToolUIPart['state']) => {
} as const; } as const;
return ( return (
<Badge className="rounded-full text-xs flex items-center gap-1" variant="secondary"> <Badge
className="flex items-center gap-1 rounded-full text-xs"
variant="secondary"
>
{icons[status]} {icons[status]}
<span>{labels[status]}</span> <span>{labels[status]}</span>
</Badge> </Badge>
@ -65,16 +68,16 @@ export const ToolHeader = ({
}: ToolHeaderProps) => ( }: ToolHeaderProps) => (
<CollapsibleTrigger <CollapsibleTrigger
className={cn( className={cn(
'flex w-full items-center justify-between gap-2 p-3 min-w-0', 'flex w-full min-w-0 items-center justify-between gap-2 p-3',
className, className,
)} )}
{...props} {...props}
> >
<div className="flex items-center gap-2 min-w-0 flex-1"> <div className="flex min-w-0 flex-1 items-center gap-2">
<WrenchIcon className="size-4 text-muted-foreground shrink-0" /> <WrenchIcon className="size-4 shrink-0 text-muted-foreground" />
<span className="font-medium text-sm truncate">{type}</span> <span className="truncate font-medium text-sm">{type}</span>
</div> </div>
<div className="flex items-center gap-2 shrink-0"> <div className="flex shrink-0 items-center gap-2">
{getStatusBadge(state)} {getStatusBadge(state)}
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" /> <ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</div> </div>
@ -86,7 +89,7 @@ export type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
export const ToolContent = ({ className, ...props }: ToolContentProps) => ( export const ToolContent = ({ className, ...props }: ToolContentProps) => (
<CollapsibleContent <CollapsibleContent
className={cn( className={cn(
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in', 'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}

View file

@ -220,7 +220,7 @@ export const WebPreviewConsole = ({
<CollapsibleContent <CollapsibleContent
className={cn( className={cn(
'px-4 pb-4', 'px-4 pb-4',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
)} )}
> >
<div className="max-h-48 space-y-1 overflow-y-auto"> <div className="max-h-48 space-y-1 overflow-y-auto">

View file

@ -4,14 +4,14 @@ export const Greeting = () => {
return ( return (
<div <div
key="overview" key="overview"
className="max-w-3xl mx-auto mt-4 md:mt-16 px-4 md:px-8 size-full flex flex-col justify-center" className="mx-auto mt-4 flex size-full max-w-3xl flex-col justify-center px-4 md:mt-16 md:px-8"
> >
<motion.div <motion.div
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }} exit={{ opacity: 0, y: 10 }}
transition={{ delay: 0.5 }} transition={{ delay: 0.5 }}
className="text-xl md:text-2xl font-semibold" className="font-semibold text-xl md:text-2xl"
> >
Hello there! Hello there!
</motion.div> </motion.div>
@ -20,7 +20,7 @@ export const Greeting = () => {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }} exit={{ opacity: 0, y: 10 }}
transition={{ delay: 0.6 }} transition={{ delay: 0.6 }}
className="text-xl md:text-2xl text-zinc-500" className="text-xl text-zinc-500 md:text-2xl"
> >
How can I help you today? How can I help you today?
</motion.div> </motion.div>

View file

@ -18,13 +18,13 @@ export function ImageEditor({
}: ImageEditorProps) { }: ImageEditorProps) {
return ( return (
<div <div
className={cn('flex flex-row items-center justify-center w-full', { className={cn('flex w-full flex-row items-center justify-center', {
'h-[calc(100dvh-60px)]': !isInline, 'h-[calc(100dvh-60px)]': !isInline,
'h-[200px]': isInline, 'h-[200px]': isInline,
})} })}
> >
{status === 'streaming' ? ( {status === 'streaming' ? (
<div className="flex flex-row gap-4 items-center"> <div className="flex flex-row items-center gap-4">
{!isInline && ( {!isInline && (
<div className="animate-spin"> <div className="animate-spin">
<LoaderIcon /> <LoaderIcon />
@ -35,7 +35,7 @@ export function ImageEditor({
) : ( ) : (
<picture> <picture>
<img <img
className={cn('w-full h-fit max-w-[800px]', { className={cn('h-fit w-full max-w-[800px]', {
'p-0 md:p-20': !isInline, 'p-0 md:p-20': !isInline,
})} })}
src={`data:image/png;base64,${content}`} src={`data:image/png;base64,${content}`}

View file

@ -47,13 +47,13 @@ export function PureMessageActions({
// User messages get edit (on hover) and copy actions // User messages get edit (on hover) and copy actions
if (message.role === 'user') { if (message.role === 'user') {
return ( return (
<Actions className="justify-end -mr-0.5"> <Actions className="-mr-0.5 justify-end">
<div className="relative"> <div className="relative">
{setMode && ( {setMode && (
<Action <Action
tooltip="Edit" tooltip="Edit"
onClick={() => setMode('edit')} onClick={() => setMode('edit')}
className="absolute top-0 -left-10 opacity-0 transition-opacity group-hover/message:opacity-100" className="-left-10 absolute top-0 opacity-0 transition-opacity group-hover/message:opacity-100"
> >
<PencilEditIcon /> <PencilEditIcon />
</Action> </Action>

View file

@ -53,19 +53,19 @@ export function MessageEditor({
}; };
return ( return (
<div className="flex flex-col gap-2 w-full"> <div className="flex w-full flex-col gap-2">
<Textarea <Textarea
data-testid="message-editor" data-testid="message-editor"
ref={textareaRef} ref={textareaRef}
className="bg-transparent outline-none overflow-hidden resize-none !text-base rounded-xl w-full" className="w-full resize-none overflow-hidden rounded-xl bg-transparent text-base! outline-hidden"
value={draftContent} value={draftContent}
onChange={handleInput} onChange={handleInput}
/> />
<div className="flex flex-row gap-2 justify-end"> <div className="flex flex-row justify-end gap-2">
<Button <Button
variant="outline" variant="outline"
className="h-fit py-2 px-3" className="h-fit px-3 py-2"
onClick={() => { onClick={() => {
setMode('view'); setMode('view');
}} }}
@ -75,7 +75,7 @@ export function MessageEditor({
<Button <Button
data-testid="message-editor-send-button" data-testid="message-editor-send-button"
variant="default" variant="default"
className="h-fit py-2 px-3" className="h-fit px-3 py-2"
disabled={isSubmitting} disabled={isSubmitting}
onClick={async () => { onClick={async () => {
setIsSubmitting(true); setIsSubmitting(true);

View file

@ -1,6 +1,10 @@
'use client'; 'use client';
import { Reasoning, ReasoningTrigger, ReasoningContent } from './elements/reasoning'; import {
Reasoning,
ReasoningTrigger,
ReasoningContent,
} from './elements/reasoning';
interface MessageReasoningProps { interface MessageReasoningProps {
isLoading: boolean; isLoading: boolean;

View file

@ -57,19 +57,19 @@ const PurePreviewMessage = ({
return ( return (
<motion.div <motion.div
data-testid={`message-${message.role}`} data-testid={`message-${message.role}`}
className="w-full group/message" className="group/message w-full"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
data-role={message.role} data-role={message.role}
> >
<div <div
className={cn('flex items-start gap-3 w-full', { className={cn('flex w-full items-start gap-3', {
'justify-end': message.role === 'user' && mode !== 'edit', 'justify-end': message.role === 'user' && mode !== 'edit',
'justify-start': message.role === 'assistant', 'justify-start': message.role === 'assistant',
})} })}
> >
{message.role === 'assistant' && ( {message.role === 'assistant' && (
<div className="flex justify-center items-center -mt-1 rounded-full ring-1 size-8 shrink-0 ring-border bg-background"> <div className="-mt-1 flex size-8 shrink-0 items-center justify-center rounded-full bg-background ring-1 ring-border">
<SparklesIcon size={14} /> <SparklesIcon size={14} />
</div> </div>
)} )}
@ -93,7 +93,7 @@ const PurePreviewMessage = ({
{attachmentsFromMessage.length > 0 && ( {attachmentsFromMessage.length > 0 && (
<div <div
data-testid={`message-attachments`} data-testid={`message-attachments`}
className="flex flex-row gap-2 justify-end" className="flex flex-row justify-end gap-2"
> >
{attachmentsFromMessage.map((attachment) => ( {attachmentsFromMessage.map((attachment) => (
<PreviewAttachment <PreviewAttachment
@ -129,7 +129,7 @@ const PurePreviewMessage = ({
<MessageContent <MessageContent
data-testid="message-content" data-testid="message-content"
className={cn({ className={cn({
'rounded-2xl px-3 py-2 break-words text-white text-right w-fit': 'w-fit break-words rounded-2xl px-3 py-2 text-right text-white':
message.role === 'user', message.role === 'user',
'bg-transparent px-0 py-0 text-left': 'bg-transparent px-0 py-0 text-left':
message.role === 'assistant', message.role === 'assistant',
@ -150,10 +150,10 @@ const PurePreviewMessage = ({
return ( return (
<div <div
key={key} key={key}
className="flex flex-row gap-3 items-start w-full" className="flex w-full flex-row items-start gap-3"
> >
<div className="size-8" /> <div className="size-8" />
<div className="flex-1 min-w-0"> <div className="min-w-0 flex-1">
<MessageEditor <MessageEditor
key={message.id} key={message.id}
message={message} message={message}
@ -195,7 +195,7 @@ const PurePreviewMessage = ({
return ( return (
<div <div
key={toolCallId} key={toolCallId}
className="p-4 text-red-500 bg-red-50 rounded-lg border border-red-200 dark:bg-red-950/50" className="rounded-lg border border-red-200 bg-red-50 p-4 text-red-500 dark:bg-red-950/50"
> >
Error creating document: {String(part.output.error)} Error creating document: {String(part.output.error)}
</div> </div>
@ -218,7 +218,7 @@ const PurePreviewMessage = ({
return ( return (
<div <div
key={toolCallId} key={toolCallId}
className="p-4 text-red-500 bg-red-50 rounded-lg border border-red-200 dark:bg-red-950/50" className="rounded-lg border border-red-200 bg-red-50 p-4 text-red-500 dark:bg-red-950/50"
> >
Error updating document: {String(part.output.error)} Error updating document: {String(part.output.error)}
</div> </div>
@ -250,7 +250,7 @@ const PurePreviewMessage = ({
<ToolOutput <ToolOutput
output={ output={
'error' in part.output ? ( 'error' in part.output ? (
<div className="p-2 text-red-500 rounded border"> <div className="rounded border p-2 text-red-500">
Error: {String(part.output.error)} Error: {String(part.output.error)}
</div> </div>
) : ( ) : (
@ -306,17 +306,17 @@ export const ThinkingMessage = () => {
return ( return (
<motion.div <motion.div
data-testid="message-assistant-loading" data-testid="message-assistant-loading"
className="w-full group/message" className="group/message w-full"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
data-role={role} data-role={role}
> >
<div className="flex gap-3 justify-start items-start"> <div className="flex items-start justify-start gap-3">
<div className="flex justify-center items-center -mt-1 rounded-full ring-1 size-8 shrink-0 ring-border bg-background"> <div className="-mt-1 flex size-8 shrink-0 items-center justify-center rounded-full bg-background ring-1 ring-border">
<SparklesIcon size={14} /> <SparklesIcon size={14} />
</div> </div>
<div className="flex flex-col gap-2 w-full md:gap-4"> <div className="flex w-full flex-col gap-2 md:gap-4">
<div className="p-0 text-sm text-muted-foreground"> <div className="p-0 text-sm text-muted-foreground">
<LoadingText>Thinking...</LoadingText> <LoadingText>Thinking...</LoadingText>
</div> </div>

View file

@ -53,7 +53,7 @@ function PureMessages({
if (container) { if (container) {
container.scrollTo({ container.scrollTo({
top: container.scrollHeight, top: container.scrollHeight,
behavior: 'smooth' behavior: 'smooth',
}); });
} }
}); });
@ -63,7 +63,7 @@ function PureMessages({
return ( return (
<div <div
ref={messagesContainerRef} ref={messagesContainerRef}
className="overflow-y-scroll flex-1 touch-pan-y overscroll-behavior-contain -webkit-overflow-scrolling-touch" className="overscroll-behavior-contain -webkit-overflow-scrolling-touch flex-1 touch-pan-y overflow-y-scroll"
style={{ overflowAnchor: 'none' }} style={{ overflowAnchor: 'none' }}
> >
<Conversation className="flex flex-col gap-4 px-2 py-4 mx-auto min-w-0 max-w-4xl md:gap-6 md:px-4"> <Conversation className="flex flex-col gap-4 px-2 py-4 mx-auto min-w-0 max-w-4xl md:gap-6 md:px-4">
@ -96,20 +96,18 @@ function PureMessages({
{status === 'submitted' && {status === 'submitted' &&
messages.length > 0 && messages.length > 0 &&
messages[messages.length - 1].role === 'user' && messages[messages.length - 1].role === 'user' &&
selectedModelId !== 'chat-model-reasoning' && ( selectedModelId !== 'chat-model-reasoning' && <ThinkingMessage />}
<ThinkingMessage />
)}
<div <div
ref={messagesEndRef} ref={messagesEndRef}
className="shrink-0 min-w-[24px] min-h-[24px]" className="min-h-[24px] min-w-[24px] shrink-0"
/> />
</ConversationContent> </ConversationContent>
</Conversation> </Conversation>
{!isAtBottom && ( {!isAtBottom && (
<button <button
className="absolute bottom-40 left-1/2 z-10 p-2 rounded-full border shadow-lg transition-colors -translate-x-1/2 bg-background hover:bg-muted" className="-translate-x-1/2 absolute bottom-40 left-1/2 z-10 rounded-full border bg-background p-2 shadow-lg transition-colors hover:bg-muted"
onClick={() => scrollToBottom('smooth')} onClick={() => scrollToBottom('smooth')}
type="button" type="button"
aria-label="Scroll to bottom" aria-label="Scroll to bottom"

View file

@ -56,7 +56,7 @@ export function ModelSelector({
<Button <Button
data-testid="model-selector" data-testid="model-selector"
variant="outline" variant="outline"
className="md:px-2 md:h-[34px]" className="md:h-[34px] md:px-2"
> >
{selectedChatModel?.name} {selectedChatModel?.name}
<ChevronDownIcon /> <ChevronDownIcon />
@ -83,16 +83,16 @@ export function ModelSelector({
> >
<button <button
type="button" type="button"
className="gap-4 group/item flex flex-row justify-between items-center w-full" className="group/item flex w-full flex-row items-center justify-between gap-4"
> >
<div className="flex flex-col gap-1 items-start"> <div className="flex flex-col items-start gap-1">
<div>{chatModel.name}</div> <div>{chatModel.name}</div>
<div className="text-xs text-muted-foreground"> <div className="text-muted-foreground text-xs">
{chatModel.description} {chatModel.description}
</div> </div>
</div> </div>
<div className="text-foreground dark:text-foreground opacity-0 group-data-[active=true]/item:opacity-100"> <div className="text-foreground opacity-0 group-data-[active=true]/item:opacity-100 dark:text-foreground">
<CheckCircleFillIcon /> <CheckCircleFillIcon />
</div> </div>
</button> </button>

View file

@ -29,7 +29,7 @@ import {
PromptInputModelSelectTrigger, PromptInputModelSelectTrigger,
PromptInputModelSelectContent, PromptInputModelSelectContent,
} from './elements/prompt-input'; } from './elements/prompt-input';
import { SelectItem, } from '@/components/ui/select'; import { SelectItem } from '@/components/ui/select';
import equal from 'fast-deep-equal'; import equal from 'fast-deep-equal';
import type { UseChatHelpers } from '@ai-sdk/react'; import type { UseChatHelpers } from '@ai-sdk/react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
@ -253,7 +253,7 @@ function PureMultimodalInput({
}, [status, scrollToBottom]); }, [status, scrollToBottom]);
return ( return (
<div className="flex relative flex-col gap-4 w-full"> <div className="relative flex w-full flex-col gap-4">
<AnimatePresence> <AnimatePresence>
{!isAtBottom && ( {!isAtBottom && (
<motion.div <motion.div
@ -261,7 +261,7 @@ function PureMultimodalInput({
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }} exit={{ opacity: 0, y: 10 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="absolute -top-12 left-1/2 z-50 -translate-x-1/2" className="-top-12 -translate-x-1/2 absolute left-1/2 z-50"
> >
<Button <Button
data-testid="scroll-to-bottom-button" data-testid="scroll-to-bottom-button"
@ -291,7 +291,7 @@ function PureMultimodalInput({
<input <input
type="file" type="file"
className="fixed -top-4 -left-4 size-0.5 opacity-0 pointer-events-none" className="-top-4 -left-4 pointer-events-none fixed size-0.5 opacity-0"
ref={fileInputRef} ref={fileInputRef}
multiple multiple
onChange={handleFileChange} onChange={handleFileChange}
@ -299,7 +299,7 @@ function PureMultimodalInput({
/> />
<PromptInput <PromptInput
className="rounded-xl border shadow-sm transition-all duration-200 bg-background border-border focus-within:border-border hover:border-muted-foreground/50" className="rounded-xl border border-border bg-background shadow-xs transition-all duration-200 focus-within:border-border hover:border-muted-foreground/50"
onSubmit={(event) => { onSubmit={(event) => {
event.preventDefault(); event.preventDefault();
if (status !== 'ready') { if (status !== 'ready') {
@ -312,7 +312,7 @@ function PureMultimodalInput({
{(attachments.length > 0 || uploadQueue.length > 0) && ( {(attachments.length > 0 || uploadQueue.length > 0) && (
<div <div
data-testid="attachments-preview" data-testid="attachments-preview"
className="flex overflow-x-scroll flex-row gap-2 items-end px-3 py-2" className="flex flex-row items-end gap-2 overflow-x-scroll px-3 py-2"
> >
{attachments.map((attachment) => ( {attachments.map((attachment) => (
<PreviewAttachment <PreviewAttachment
@ -352,13 +352,13 @@ function PureMultimodalInput({
minHeight={44} minHeight={44}
maxHeight={200} maxHeight={200}
disableAutoResize={true} disableAutoResize={true}
className="text-sm flex-grow resize-none py-3 px-3 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none] bg-transparent !border-0 !border-none outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none placeholder:text-muted-foreground" className="text-sm grow resize-none py-3 px-3 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none] bg-transparent border-0! border-none! outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none placeholder:text-muted-foreground"
rows={1} rows={1}
autoFocus autoFocus
/>{' '} />{' '}
<Context {...contextProps} /> <Context {...contextProps} />
</div> </div>
<PromptInputToolbar className="px-3 py-2 !border-t-0 !border-top-0 shadow-none dark:!border-transparent dark:border-0"> <PromptInputToolbar className="px-3 py-2 border-t-0! !border-top-0 shadow-none dark:border-transparent! dark:border-0">
<PromptInputTools className="gap-2"> <PromptInputTools className="gap-2">
<AttachmentsButton <AttachmentsButton
fileInputRef={fileInputRef} fileInputRef={fileInputRef}
@ -374,7 +374,7 @@ function PureMultimodalInput({
<PromptInputSubmit <PromptInputSubmit
status={status} status={status}
disabled={!input.trim() || uploadQueue.length > 0} disabled={!input.trim() || uploadQueue.length > 0}
className="p-2 rounded-full transition-colors duration-200 text-primary-foreground bg-primary hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground" className="rounded-full bg-primary p-2 text-primary-foreground transition-colors duration-200 hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground"
> >
<ArrowUpIcon size={16} /> <ArrowUpIcon size={16} />
</PromptInputSubmit> </PromptInputSubmit>
@ -413,7 +413,7 @@ function PureAttachmentsButton({
return ( return (
<Button <Button
data-testid="attachments-button" data-testid="attachments-button"
className="rounded-md p-1.5 h-fit hover:bg-muted transition-colors duration-200" className="h-fit rounded-md p-1.5 transition-colors duration-200 hover:bg-muted"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
fileInputRef.current?.click(); fileInputRef.current?.click();
@ -455,16 +455,16 @@ function PureModelSelectorCompact({
> >
<PromptInputModelSelectTrigger <PromptInputModelSelectTrigger
type="button" type="button"
className="text-xs focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:ring-0 focus-visible:ring-offset-0 data-[state=open]:ring-0 data-[state=closed]:ring-0" className="text-xs focus:outline-hidden focus:ring-0 focus:ring-offset-0 focus-visible:ring-0 focus-visible:ring-offset-0 data-[state=closed]:ring-0 data-[state=open]:ring-0"
> >
{selectedModel?.name || 'Select model'} {selectedModel?.name || 'Select model'}
</PromptInputModelSelectTrigger> </PromptInputModelSelectTrigger>
<PromptInputModelSelectContent> <PromptInputModelSelectContent>
{chatModels.map((model) => ( {chatModels.map((model) => (
<SelectItem key={model.id} value={model.name}> <SelectItem key={model.id} value={model.name}>
<div className="flex flex-col gap-1 items-start py-1"> <div className="flex flex-col items-start gap-1 py-1">
<div className="font-medium">{model.name}</div> <div className="font-medium">{model.name}</div>
<div className="text-xs text-muted-foreground"> <div className="text-muted-foreground text-xs">
{model.description} {model.description}
</div> </div>
</div> </div>
@ -487,7 +487,7 @@ function PureStopButton({
return ( return (
<Button <Button
data-testid="stop-button" data-testid="stop-button"
className="p-2 rounded-full border transition-colors duration-200 h-fit border-border hover:bg-muted" className="h-fit rounded-full border border-border p-2 transition-colors duration-200 hover:bg-muted"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
stop(); stop();

View file

@ -1,6 +1,6 @@
import type { Attachment } from '@/lib/types'; import type { Attachment } from '@/lib/types';
import { Loader } from './elements/loader'; import { Loader } from './elements/loader';
import { CrossSmallIcon, } from './icons'; import { CrossSmallIcon } from './icons';
import { Button } from './ui/button'; import { Button } from './ui/button';
import Image from 'next/image'; import Image from 'next/image';
@ -44,13 +44,13 @@ export const PreviewAttachment = ({
onClick={onRemove} onClick={onRemove}
size="sm" size="sm"
variant="destructive" variant="destructive"
className="absolute top-0.5 right-0.5 opacity-0 group-hover:opacity-100 transition-opacity size-4 p-0 rounded-full" className="absolute top-0.5 right-0.5 size-4 rounded-full p-0 opacity-0 transition-opacity group-hover:opacity-100"
> >
<CrossSmallIcon size={8} /> <CrossSmallIcon size={8} />
</Button> </Button>
)} )}
<div className="absolute bottom-0 inset-x-0 bg-gradient-to-t from-black/80 to-transparent text-white text-[10px] px-1 py-0.5 truncate"> <div className="absolute bottom-0 inset-x-0 bg-linear-to-t from-black/80 to-transparent text-white text-[10px] px-1 py-0.5 truncate">
{name} {name}
</div> </div>
</div> </div>

View file

@ -53,7 +53,7 @@ const PureChatItem = ({
<DropdownMenu modal={true}> <DropdownMenu modal={true}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<SidebarMenuAction <SidebarMenuAction
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground mr-0.5" className="mr-0.5 data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
showOnHover={!isActive} showOnHover={!isActive}
> >
<MoreHorizontalIcon /> <MoreHorizontalIcon />
@ -75,7 +75,7 @@ const PureChatItem = ({
setVisibilityType('private'); setVisibilityType('private');
}} }}
> >
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row items-center gap-2">
<LockIcon size={12} /> <LockIcon size={12} />
<span>Private</span> <span>Private</span>
</div> </div>
@ -89,7 +89,7 @@ const PureChatItem = ({
setVisibilityType('public'); setVisibilityType('public');
}} }}
> >
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row items-center gap-2">
<GlobeIcon /> <GlobeIcon />
<span>Public</span> <span>Public</span>
</div> </div>

View file

@ -152,7 +152,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupContent>
<div className="flex flex-row gap-2 justify-center items-center px-2 w-full text-sm text-zinc-500"> <div className="flex w-full flex-row items-center justify-center gap-2 px-2 text-sm text-zinc-500">
Login to save and revisit previous chats! Login to save and revisit previous chats!
</div> </div>
</SidebarGroupContent> </SidebarGroupContent>
@ -163,7 +163,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
if (isLoading) { if (isLoading) {
return ( return (
<SidebarGroup> <SidebarGroup>
<div className="px-2 py-1 text-xs text-sidebar-foreground/50"> <div className="px-2 py-1 text-sidebar-foreground/50 text-xs">
Today Today
</div> </div>
<SidebarGroupContent> <SidebarGroupContent>
@ -171,10 +171,10 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{[44, 32, 28, 64, 52].map((item) => ( {[44, 32, 28, 64, 52].map((item) => (
<div <div
key={item} key={item}
className="flex gap-2 items-center px-2 h-8 rounded-md" className="flex h-8 items-center gap-2 rounded-md px-2"
> >
<div <div
className="h-4 rounded-md flex-1 max-w-[--skeleton-width] bg-sidebar-accent-foreground/10" className="h-4 max-w-(--skeleton-width) flex-1 rounded-md bg-sidebar-accent-foreground/10"
style={ style={
{ {
'--skeleton-width': `${item}%`, '--skeleton-width': `${item}%`,
@ -193,7 +193,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupContent>
<div className="flex flex-row gap-2 justify-center items-center px-2 w-full text-sm text-zinc-500"> <div className="flex w-full flex-row items-center justify-center gap-2 px-2 text-sm text-zinc-500">
Your conversations will appear here once you start chatting! Your conversations will appear here once you start chatting!
</div> </div>
</SidebarGroupContent> </SidebarGroupContent>
@ -218,7 +218,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{groupedChats.today.length > 0 && ( {groupedChats.today.length > 0 && (
<div> <div>
<div className="px-2 py-1 text-xs text-sidebar-foreground/50"> <div className="px-2 py-1 text-sidebar-foreground/50 text-xs">
Today Today
</div> </div>
{groupedChats.today.map((chat) => ( {groupedChats.today.map((chat) => (
@ -238,7 +238,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.yesterday.length > 0 && ( {groupedChats.yesterday.length > 0 && (
<div> <div>
<div className="px-2 py-1 text-xs text-sidebar-foreground/50"> <div className="px-2 py-1 text-sidebar-foreground/50 text-xs">
Yesterday Yesterday
</div> </div>
{groupedChats.yesterday.map((chat) => ( {groupedChats.yesterday.map((chat) => (
@ -258,7 +258,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.lastWeek.length > 0 && ( {groupedChats.lastWeek.length > 0 && (
<div> <div>
<div className="px-2 py-1 text-xs text-sidebar-foreground/50"> <div className="px-2 py-1 text-sidebar-foreground/50 text-xs">
Last 7 days Last 7 days
</div> </div>
{groupedChats.lastWeek.map((chat) => ( {groupedChats.lastWeek.map((chat) => (
@ -278,7 +278,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.lastMonth.length > 0 && ( {groupedChats.lastMonth.length > 0 && (
<div> <div>
<div className="px-2 py-1 text-xs text-sidebar-foreground/50"> <div className="px-2 py-1 text-sidebar-foreground/50 text-xs">
Last 30 days Last 30 days
</div> </div>
{groupedChats.lastMonth.map((chat) => ( {groupedChats.lastMonth.map((chat) => (
@ -298,7 +298,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.older.length > 0 && ( {groupedChats.older.length > 0 && (
<div> <div>
<div className="px-2 py-1 text-xs text-sidebar-foreground/50"> <div className="px-2 py-1 text-sidebar-foreground/50 text-xs">
Older than last month Older than last month
</div> </div>
{groupedChats.older.map((chat) => ( {groupedChats.older.map((chat) => (
@ -329,11 +329,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
/> />
{hasReachedEnd ? ( {hasReachedEnd ? (
<div className="flex flex-row gap-2 justify-center items-center px-2 mt-8 w-full text-sm text-zinc-500"> <div className="mt-8 flex w-full flex-row items-center justify-center gap-2 px-2 text-sm text-zinc-500">
You have reached the end of your chat history. You have reached the end of your chat history.
</div> </div>
) : ( ) : (
<div className="flex flex-row gap-2 items-center p-2 mt-8 text-zinc-500 dark:text-zinc-400"> <div className="mt-8 flex flex-row items-center gap-2 p-2 text-zinc-500 dark:text-zinc-400">
<div className="animate-spin"> <div className="animate-spin">
<LoaderIcon /> <LoaderIcon />
</div> </div>

View file

@ -22,12 +22,14 @@ export function SidebarToggle({
data-testid="sidebar-toggle-button" data-testid="sidebar-toggle-button"
onClick={toggleSidebar} onClick={toggleSidebar}
variant="outline" variant="outline"
className="px-1 h-8 md:px-2 md:h-fit" className="h-8 px-1 md:h-fit md:px-2"
> >
<SidebarLeftIcon size={16} /> <SidebarLeftIcon size={16} />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent align="start" className="hidden md:block">Toggle Sidebar</TooltipContent> <TooltipContent align="start" className="hidden md:block">
Toggle Sidebar
</TooltipContent>
</Tooltip> </Tooltip>
); );
} }

View file

@ -36,10 +36,10 @@ export function SidebarUserNav({ user }: { user: User }) {
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
{status === 'loading' ? ( {status === 'loading' ? (
<SidebarMenuButton className="data-[state=open]:bg-sidebar-accent bg-background data-[state=open]:text-sidebar-accent-foreground h-10 justify-between"> <SidebarMenuButton className="h-10 justify-between bg-background data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<div className="size-6 bg-zinc-500/30 rounded-full animate-pulse" /> <div className="size-6 animate-pulse rounded-full bg-zinc-500/30" />
<span className="bg-zinc-500/30 text-transparent rounded-md animate-pulse"> <span className="animate-pulse rounded-md bg-zinc-500/30 text-transparent">
Loading auth status Loading auth status
</span> </span>
</div> </div>
@ -50,7 +50,7 @@ export function SidebarUserNav({ user }: { user: User }) {
) : ( ) : (
<SidebarMenuButton <SidebarMenuButton
data-testid="user-nav-button" data-testid="user-nav-button"
className="data-[state=open]:bg-sidebar-accent bg-background data-[state=open]:text-sidebar-accent-foreground h-10" className="h-10 bg-background data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
> >
<Image <Image
src={`https://avatar.vercel.sh/${user.email}`} src={`https://avatar.vercel.sh/${user.email}`}
@ -69,12 +69,14 @@ export function SidebarUserNav({ user }: { user: User }) {
<DropdownMenuContent <DropdownMenuContent
data-testid="user-nav-menu" data-testid="user-nav-menu"
side="top" side="top"
className="w-[--radix-popper-anchor-width]" className="w-(--radix-popper-anchor-width)"
> >
<DropdownMenuItem <DropdownMenuItem
data-testid="user-nav-item-theme" data-testid="user-nav-item-theme"
className="cursor-pointer" className="cursor-pointer"
onSelect={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')} onSelect={() =>
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
}
> >
{`Toggle ${resolvedTheme === 'light' ? 'dark' : 'light'} mode`} {`Toggle ${resolvedTheme === 'light' ? 'dark' : 'light'} mode`}
</DropdownMenuItem> </DropdownMenuItem>

View file

@ -16,7 +16,7 @@ export const SignOutForm = () => {
> >
<button <button
type="submit" type="submit"
className="w-full text-left px-1 py-0.5 text-red-500" className="w-full px-1 py-0.5 text-left text-red-500"
> >
Sign out Sign out
</button> </button>

View file

@ -25,7 +25,7 @@ export function SubmitButton({
{children} {children}
{(pending || isSuccessful) && ( {(pending || isSuccessful) && (
<span className="animate-spin absolute right-4"> <span className="absolute right-4 animate-spin">
<LoaderIcon /> <LoaderIcon />
</span> </span>
)} )}

View file

@ -20,13 +20,16 @@ function PureSuggestedActions({
}: SuggestedActionsProps) { }: SuggestedActionsProps) {
const suggestedActions = [ const suggestedActions = [
'What are the advantages of using Next.js?', 'What are the advantages of using Next.js?',
'Write code to demonstrate Dijkstra\'s algorithm', "Write code to demonstrate Dijkstra's algorithm",
'Help me write an essay about Silicon Valley', 'Help me write an essay about Silicon Valley',
'What is the weather in San Francisco?', 'What is the weather in San Francisco?',
]; ];
return ( return (
<div data-testid="suggested-actions" className="grid sm:grid-cols-2 gap-2 w-full"> <div
data-testid="suggested-actions"
className="grid w-full gap-2 sm:grid-cols-2"
>
{suggestedActions.map((suggestedAction, index) => ( {suggestedActions.map((suggestedAction, index) => (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
@ -44,7 +47,7 @@ function PureSuggestedActions({
parts: [{ type: 'text', text: suggestion }], parts: [{ type: 'text', text: suggestion }],
}); });
}} }}
className="text-left w-full h-auto whitespace-normal p-3" className="h-auto w-full whitespace-normal p-3 text-left"
> >
{suggestedAction} {suggestedAction}
</Suggestion> </Suggestion>

View file

@ -27,8 +27,8 @@ export const Suggestion = ({
<AnimatePresence> <AnimatePresence>
{!isExpanded ? ( {!isExpanded ? (
<motion.div <motion.div
className={cn('cursor-pointer text-muted-foreground p-1', { className={cn('cursor-pointer p-1 text-muted-foreground', {
'absolute -right-8': artifactKind === 'text', '-right-8 absolute': artifactKind === 'text',
'sticky top-0 right-4': artifactKind === 'code', 'sticky top-0 right-4': artifactKind === 'code',
})} })}
onClick={() => { onClick={() => {
@ -41,21 +41,21 @@ export const Suggestion = ({
) : ( ) : (
<motion.div <motion.div
key={suggestion.id} key={suggestion.id}
className="flex absolute -right-12 z-50 flex-col gap-3 p-3 w-56 font-sans text-sm rounded-2xl border shadow-xl bg-background md:-right-16" className="-right-12 md:-right-16 absolute z-50 flex w-56 flex-col gap-3 rounded-2xl border bg-background p-3 font-sans text-sm shadow-xl"
transition={{ type: 'spring', stiffness: 500, damping: 30 }} transition={{ type: 'spring', stiffness: 500, damping: 30 }}
initial={{ opacity: 0, y: -10 }} initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: -20 }} animate={{ opacity: 1, y: -20 }}
exit={{ opacity: 0, y: -10 }} exit={{ opacity: 0, y: -10 }}
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
> >
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row items-center justify-between">
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row items-center gap-2">
<div className="rounded-full size-4 bg-muted-foreground/25" /> <div className="size-4 rounded-full bg-muted-foreground/25" />
<div className="font-medium">Assistant</div> <div className="font-medium">Assistant</div>
</div> </div>
<button <button
type="button" type="button"
className="text-xs text-gray-500 cursor-pointer" className="cursor-pointer text-gray-500 text-xs"
onClick={() => { onClick={() => {
setIsExpanded(false); setIsExpanded(false);
}} }}
@ -66,7 +66,7 @@ export const Suggestion = ({
<div>{suggestion.description}</div> <div>{suggestion.description}</div>
<Button <Button
variant="outline" variant="outline"
className="w-fit py-1.5 px-3 rounded-full" className="w-fit rounded-full px-3 py-1.5"
onClick={onApply} onClick={onApply}
> >
Apply Apply

View file

@ -146,7 +146,7 @@ function PureEditor({
}, [suggestions, content]); }, [suggestions, content]);
return ( return (
<div className="relative prose dark:prose-invert" ref={containerRef} /> <div className="prose dark:prose-invert relative" ref={containerRef} />
); );
} }

View file

@ -40,12 +40,12 @@ function Toast(props: ToastProps) {
}, [description]); }, [description]);
return ( return (
<div className="flex w-full toast-mobile:w-[356px] justify-center"> <div className="flex toast-mobile:w-[356px] w-full justify-center">
<div <div
data-testid="toast" data-testid="toast"
key={id} key={id}
className={cn( className={cn(
'bg-zinc-100 p-3 rounded-lg w-full toast-mobile:w-fit flex flex-row gap-3', 'flex toast-mobile:w-fit w-full flex-row gap-3 rounded-lg bg-zinc-100 p-3',
multiLine ? 'items-start' : 'items-center', multiLine ? 'items-start' : 'items-center',
)} )}
> >
@ -58,7 +58,7 @@ function Toast(props: ToastProps) {
> >
{iconsByType[type]} {iconsByType[type]}
</div> </div>
<div ref={descriptionRef} className="text-zinc-950 text-sm"> <div ref={descriptionRef} className="text-sm text-zinc-950">
{description} {description}
</div> </div>
</div> </div>

View file

@ -89,8 +89,8 @@ const Tool = ({
<Tooltip open={isHovered && !isAnimating}> <Tooltip open={isHovered && !isAnimating}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<motion.div <motion.div
className={cx('p-3 rounded-full', { className={cx('rounded-full p-3', {
'bg-primary !text-primary-foreground': selectedTool === description, 'bg-primary text-primary-foreground!': selectedTool === description,
})} })}
onHoverStart={() => { onHoverStart={() => {
setIsHovered(true); setIsHovered(true);
@ -122,7 +122,7 @@ const Tool = ({
<TooltipContent <TooltipContent
side="left" side="left"
sideOffset={16} sideOffset={16}
className="bg-foreground text-background rounded-2xl p-3 px-4" className="rounded-2xl bg-foreground p-3 px-4 text-background"
> >
{description} {description}
</TooltipContent> </TooltipContent>
@ -168,11 +168,11 @@ const ReadingLevelSelector = ({
}, [yToLevel]); }, [yToLevel]);
return ( return (
<div className="relative flex flex-col justify-end items-center"> <div className="relative flex flex-col items-center justify-end">
{randomArr.map((id) => ( {randomArr.map((id) => (
<motion.div <motion.div
key={id} key={id}
className="size-[40px] flex flex-row items-center justify-center" className="flex size-[40px] flex-row items-center justify-center"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
@ -187,7 +187,7 @@ const ReadingLevelSelector = ({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<motion.div <motion.div
className={cx( className={cx(
'absolute bg-background p-3 border rounded-full flex flex-row items-center', 'absolute flex flex-row items-center rounded-full border bg-background p-3',
{ {
'bg-primary text-primary-foreground': currentLevel !== 2, 'bg-primary text-primary-foreground': currentLevel !== 2,
'bg-background text-foreground': currentLevel === 2, 'bg-background text-foreground': currentLevel === 2,
@ -233,7 +233,7 @@ const ReadingLevelSelector = ({
<TooltipContent <TooltipContent
side="left" side="left"
sideOffset={16} sideOffset={16}
className="bg-foreground text-background text-sm rounded-2xl p-3 px-4" className="rounded-2xl bg-foreground p-3 px-4 text-background text-sm"
> >
{LEVELS[currentLevel]} {LEVELS[currentLevel]}
</TooltipContent> </TooltipContent>
@ -376,7 +376,7 @@ const PureToolbar = ({
return ( return (
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<motion.div <motion.div
className="cursor-pointer absolute right-6 bottom-6 p-1.5 border rounded-full shadow-lg bg-background flex flex-col justify-end" className="absolute right-6 bottom-6 flex cursor-pointer flex-col justify-end rounded-full border bg-background p-1.5 shadow-lg"
initial={{ opacity: 0, y: -20, scale: 1 }} initial={{ opacity: 0, y: -20, scale: 1 }}
animate={ animate={
isToolbarVisible isToolbarVisible

View file

@ -18,7 +18,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
className={cn( className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}
@ -36,7 +36,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:rounded-lg',
className, className,
)} )}
{...props} {...props}
@ -79,7 +79,7 @@ const AlertDialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
ref={ref} ref={ref}
className={cn('text-lg font-semibold', className)} className={cn('font-semibold text-lg', className)}
{...props} {...props}
/> />
)); ));
@ -91,7 +91,7 @@ const AlertDialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
ref={ref} ref={ref}
className={cn('text-sm text-muted-foreground', className)} className={cn('text-muted-foreground text-sm', className)}
{...props} {...props}
/> />
)); ));

View file

@ -1,9 +1,9 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as AvatarPrimitive from "@radix-ui/react-avatar" import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const Avatar = React.forwardRef< const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>, React.ElementRef<typeof AvatarPrimitive.Root>,
@ -12,13 +12,13 @@ const Avatar = React.forwardRef<
<AvatarPrimitive.Root <AvatarPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className className,
)} )}
{...props} {...props}
/> />
)) ));
Avatar.displayName = AvatarPrimitive.Root.displayName Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef< const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>, React.ElementRef<typeof AvatarPrimitive.Image>,
@ -26,11 +26,11 @@ const AvatarImage = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Image <AvatarPrimitive.Image
ref={ref} ref={ref}
className={cn("aspect-square h-full w-full", className)} className={cn('aspect-square h-full w-full', className)}
{...props} {...props}
/> />
)) ));
AvatarImage.displayName = AvatarPrimitive.Image.displayName AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef< const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>, React.ElementRef<typeof AvatarPrimitive.Fallback>,
@ -39,12 +39,12 @@ const AvatarFallback = React.forwardRef<
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted", 'flex h-full w-full items-center justify-center rounded-full bg-muted',
className className,
)} )}
{...props} {...props}
/> />
)) ));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback };

View file

@ -1,27 +1,27 @@
import * as React from "react" import * as React from 'react';
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',
{ {
variants: { variants: {
variant: { variant: {
default: default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary: secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: "text-foreground", outline: 'text-foreground',
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
}, },
} },
) );
export interface BadgeProps export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, extends React.HTMLAttributes<HTMLDivElement>,
@ -30,7 +30,7 @@ export interface BadgeProps
function Badge({ className, variant, ...props }: BadgeProps) { function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <div className={cn(badgeVariants({ variant }), className)} {...props} />
) );
} }
export { Badge, badgeVariants } export { Badge, badgeVariants };

View file

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

View file

@ -9,7 +9,7 @@ const Card = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
'rounded-lg border bg-card text-card-foreground shadow-sm', 'rounded-lg border bg-card text-card-foreground shadow-xs',
className, className,
)} )}
{...props} {...props}
@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
'text-2xl font-semibold leading-none tracking-tight', 'font-semibold text-2xl leading-none tracking-tight',
className, className,
)} )}
{...props} {...props}
@ -50,7 +50,7 @@ const CardDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn('text-sm text-muted-foreground', className)} className={cn('text-muted-foreground text-sm', className)}
{...props} {...props}
/> />
)); ));

View file

@ -1,45 +1,45 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import useEmblaCarousel, { import useEmblaCarousel, {
type UseEmblaCarouselType, type UseEmblaCarouselType,
} from "embla-carousel-react" } from 'embla-carousel-react';
import { ArrowLeft, ArrowRight } from "lucide-react" import { ArrowLeft, ArrowRight } from 'lucide-react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
import { Button } from "@/components/ui/button" import { Button } from '@/components/ui/button';
type CarouselApi = UseEmblaCarouselType[1] type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel> type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0] type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1] type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = { type CarouselProps = {
opts?: CarouselOptions opts?: CarouselOptions;
plugins?: CarouselPlugin plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical" orientation?: 'horizontal' | 'vertical';
setApi?: (api: CarouselApi) => void setApi?: (api: CarouselApi) => void;
} };
type CarouselContextProps = { type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0] carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1] api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void scrollPrev: () => void;
scrollNext: () => void scrollNext: () => void;
canScrollPrev: boolean canScrollPrev: boolean;
canScrollNext: boolean canScrollNext: boolean;
} & CarouselProps } & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null) const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() { function useCarousel() {
const context = React.useContext(CarouselContext) const context = React.useContext(CarouselContext);
if (!context) { if (!context) {
throw new Error("useCarousel must be used within a <Carousel />") throw new Error('useCarousel must be used within a <Carousel />');
} }
return context return context;
} }
const Carousel = React.forwardRef< const Carousel = React.forwardRef<
@ -48,7 +48,7 @@ const Carousel = React.forwardRef<
>( >(
( (
{ {
orientation = "horizontal", orientation = 'horizontal',
opts, opts,
setApi, setApi,
plugins, plugins,
@ -56,69 +56,69 @@ const Carousel = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const [carouselRef, api] = useEmblaCarousel( const [carouselRef, api] = useEmblaCarousel(
{ {
...opts, ...opts,
axis: orientation === "horizontal" ? "x" : "y", axis: orientation === 'horizontal' ? 'x' : 'y',
}, },
plugins plugins,
) );
const [canScrollPrev, setCanScrollPrev] = React.useState(false) const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false) const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => { const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) { if (!api) {
return return;
} }
setCanScrollPrev(api.canScrollPrev()) setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext()) setCanScrollNext(api.canScrollNext());
}, []) }, []);
const scrollPrev = React.useCallback(() => { const scrollPrev = React.useCallback(() => {
api?.scrollPrev() api?.scrollPrev();
}, [api]) }, [api]);
const scrollNext = React.useCallback(() => { const scrollNext = React.useCallback(() => {
api?.scrollNext() api?.scrollNext();
}, [api]) }, [api]);
const handleKeyDown = React.useCallback( const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => { (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") { if (event.key === 'ArrowLeft') {
event.preventDefault() event.preventDefault();
scrollPrev() scrollPrev();
} else if (event.key === "ArrowRight") { } else if (event.key === 'ArrowRight') {
event.preventDefault() event.preventDefault();
scrollNext() scrollNext();
} }
}, },
[scrollPrev, scrollNext] [scrollPrev, scrollNext],
) );
React.useEffect(() => { React.useEffect(() => {
if (!api || !setApi) { if (!api || !setApi) {
return return;
} }
setApi(api) setApi(api);
}, [api, setApi]) }, [api, setApi]);
React.useEffect(() => { React.useEffect(() => {
if (!api) { if (!api) {
return return;
} }
onSelect(api) onSelect(api);
api.on("reInit", onSelect) api.on('reInit', onSelect);
api.on("select", onSelect) api.on('select', onSelect);
return () => { return () => {
api?.off("select", onSelect) api?.off('select', onSelect);
} };
}, [api, onSelect]) }, [api, onSelect]);
return ( return (
<CarouselContext.Provider <CarouselContext.Provider
@ -127,7 +127,7 @@ const Carousel = React.forwardRef<
api: api, api: api,
opts, opts,
orientation: orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),
scrollPrev, scrollPrev,
scrollNext, scrollNext,
canScrollPrev, canScrollPrev,
@ -137,7 +137,7 @@ const Carousel = React.forwardRef<
<div <div
ref={ref} ref={ref}
onKeyDownCapture={handleKeyDown} onKeyDownCapture={handleKeyDown}
className={cn("relative", className)} className={cn('relative', className)}
role="region" role="region"
aria-roledescription="carousel" aria-roledescription="carousel"
{...props} {...props}
@ -145,38 +145,38 @@ const Carousel = React.forwardRef<
{children} {children}
</div> </div>
</CarouselContext.Provider> </CarouselContext.Provider>
) );
} },
) );
Carousel.displayName = "Carousel" Carousel.displayName = 'Carousel';
const CarouselContent = React.forwardRef< const CarouselContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel() const { carouselRef, orientation } = useCarousel();
return ( return (
<div ref={carouselRef} className="overflow-hidden"> <div ref={carouselRef} className="overflow-hidden">
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"flex", 'flex',
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
className className,
)} )}
{...props} {...props}
/> />
</div> </div>
) );
}) });
CarouselContent.displayName = "CarouselContent" CarouselContent.displayName = 'CarouselContent';
const CarouselItem = React.forwardRef< const CarouselItem = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { orientation } = useCarousel() const { orientation } = useCarousel();
return ( return (
<div <div
@ -184,21 +184,21 @@ const CarouselItem = React.forwardRef<
role="group" role="group"
aria-roledescription="slide" aria-roledescription="slide"
className={cn( className={cn(
"min-w-0 shrink-0 grow-0 basis-full", 'min-w-0 shrink-0 grow-0 basis-full',
orientation === "horizontal" ? "pl-4" : "pt-4", orientation === 'horizontal' ? 'pl-4' : 'pt-4',
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
CarouselItem.displayName = "CarouselItem" CarouselItem.displayName = 'CarouselItem';
const CarouselPrevious = React.forwardRef< const CarouselPrevious = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { >(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel() const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return ( return (
<Button <Button
@ -206,11 +206,11 @@ const CarouselPrevious = React.forwardRef<
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute h-8 w-8 rounded-full", 'absolute h-8 w-8 rounded-full',
orientation === "horizontal" orientation === 'horizontal'
? "-left-12 top-1/2 -translate-y-1/2" ? '-left-12 -translate-y-1/2 top-1/2'
: "-top-12 left-1/2 -translate-x-1/2 rotate-90", : '-top-12 -translate-x-1/2 left-1/2 rotate-90',
className className,
)} )}
disabled={!canScrollPrev} disabled={!canScrollPrev}
onClick={scrollPrev} onClick={scrollPrev}
@ -219,15 +219,15 @@ const CarouselPrevious = React.forwardRef<
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span> <span className="sr-only">Previous slide</span>
</Button> </Button>
) );
}) });
CarouselPrevious.displayName = "CarouselPrevious" CarouselPrevious.displayName = 'CarouselPrevious';
const CarouselNext = React.forwardRef< const CarouselNext = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { >(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel() const { orientation, scrollNext, canScrollNext } = useCarousel();
return ( return (
<Button <Button
@ -235,11 +235,11 @@ const CarouselNext = React.forwardRef<
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute h-8 w-8 rounded-full", 'absolute h-8 w-8 rounded-full',
orientation === "horizontal" orientation === 'horizontal'
? "-right-12 top-1/2 -translate-y-1/2" ? '-right-12 -translate-y-1/2 top-1/2'
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", : '-bottom-12 -translate-x-1/2 left-1/2 rotate-90',
className className,
)} )}
disabled={!canScrollNext} disabled={!canScrollNext}
onClick={scrollNext} onClick={scrollNext}
@ -248,9 +248,9 @@ const CarouselNext = React.forwardRef<
<ArrowRight className="h-4 w-4" /> <ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span> <span className="sr-only">Next slide</span>
</Button> </Button>
) );
}) });
CarouselNext.displayName = "CarouselNext" CarouselNext.displayName = 'CarouselNext';
export { export {
type CarouselApi, type CarouselApi,
@ -259,4 +259,4 @@ export {
CarouselItem, CarouselItem,
CarouselPrevious, CarouselPrevious,
CarouselNext, CarouselNext,
} };

View file

@ -1,11 +1,11 @@
"use client" 'use client';
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
const Collapsible = CollapsiblePrimitive.Root const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent } export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View file

@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
'flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', 'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8', inset && 'pl-8',
className, className,
)} )}
@ -47,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}
@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}
@ -83,7 +83,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8', inset && 'pl-8',
className, className,
)} )}
@ -99,7 +99,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
className, className,
)} )}
checked={checked} checked={checked}
@ -123,7 +123,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
className, className,
)} )}
{...props} {...props}
@ -147,7 +147,7 @@ const DropdownMenuLabel = React.forwardRef<
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
'px-2 py-1.5 text-sm font-semibold', 'px-2 py-1.5 font-semibold text-sm',
inset && 'pl-8', inset && 'pl-8',
className, className,
)} )}

View file

@ -1,29 +1,29 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as HoverCardPrimitive from "@radix-ui/react-hover-card" import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const HoverCard = HoverCardPrimitive.Root const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef< const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>, React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content> React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content <HoverCardPrimitive.Content
ref={ref} ref={ref}
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]", 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className className,
)} )}
{...props} {...props}
/> />
)) ));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent } export { HoverCard, HoverCardTrigger, HoverCardContent };

View file

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

View file

@ -1,9 +1,9 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef< const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>, React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@ -11,7 +11,7 @@ const ScrollArea = React.forwardRef<
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
ref={ref} ref={ref}
className={cn("relative overflow-hidden", className)} className={cn('relative overflow-hidden', className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
@ -20,29 +20,29 @@ const ScrollArea = React.forwardRef<
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
)) ));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef< const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => ( >(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar <ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref} ref={ref}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none select-none transition-colors", 'flex touch-none select-none transition-colors',
orientation === "vertical" && orientation === 'vertical' &&
"h-full w-2.5 border-l border-l-transparent p-[1px]", 'h-full w-2.5 border-l border-l-transparent p-px',
orientation === "horizontal" && orientation === 'horizontal' &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]", 'h-2.5 flex-col border-t border-t-transparent p-px',
className className,
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
)) ));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar };

View file

@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className, className,
)} )}
{...props} {...props}
@ -75,9 +75,9 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in',
position === 'popper' && position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', 'data-[side=left]:-translate-x-1 data-[side=top]:-translate-y-1 data-[side=right]:translate-x-1 data-[side=bottom]:translate-y-1',
className, className,
)} )}
position={position} position={position}
@ -88,7 +88,7 @@ const SelectContent = React.forwardRef<
className={cn( className={cn(
'p-1', 'p-1',
position === 'popper' && position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]', 'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)',
)} )}
> >
{children} {children}
@ -105,7 +105,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Label <SelectPrimitive.Label
ref={ref} ref={ref}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)} className={cn('py-1.5 pr-2 pl-8 font-semibold text-sm', className)}
{...props} {...props}
/> />
)); ));
@ -118,7 +118,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
className, className,
)} )}
{...props} {...props}

View file

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

View file

@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay <SheetPrimitive.Overlay
className={cn( className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in',
className, className,
)} )}
{...props} {...props}
@ -65,7 +65,7 @@ const SheetContent = React.forwardRef<
{...props} {...props}
> >
{children} {children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</SheetPrimitive.Close> </SheetPrimitive.Close>
@ -108,7 +108,7 @@ const SheetTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SheetPrimitive.Title <SheetPrimitive.Title
ref={ref} ref={ref}
className={cn('text-lg font-semibold text-foreground', className)} className={cn('font-semibold text-foreground text-lg', className)}
{...props} {...props}
/> />
)); ));
@ -120,7 +120,7 @@ const SheetDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SheetPrimitive.Description <SheetPrimitive.Description
ref={ref} ref={ref}
className={cn('text-sm text-muted-foreground', className)} className={cn('text-muted-foreground text-sm', className)}
{...props} {...props}
/> />
)); ));

View file

@ -149,7 +149,7 @@ const SidebarProvider = React.forwardRef<
} as React.CSSProperties } as React.CSSProperties
} }
className={cn( className={cn(
'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar', 'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',
className, className,
)} )}
ref={ref} ref={ref}
@ -189,7 +189,7 @@ const Sidebar = React.forwardRef<
return ( return (
<div <div
className={cn( className={cn(
'flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground', 'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',
className, className,
)} )}
ref={ref} ref={ref}
@ -206,7 +206,7 @@ const Sidebar = React.forwardRef<
<SheetContent <SheetContent
data-sidebar="sidebar" data-sidebar="sidebar"
data-mobile="true" data-mobile="true"
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden" className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={ style={
{ {
'--sidebar-width': SIDEBAR_WIDTH_MOBILE, '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
@ -224,7 +224,7 @@ const Sidebar = React.forwardRef<
return ( return (
<div <div
ref={ref} ref={ref}
className="group peer hidden md:block text-sidebar-foreground" className="group peer hidden text-sidebar-foreground md:block"
data-state={state} data-state={state}
data-collapsible={state === 'collapsed' ? collapsible : ''} data-collapsible={state === 'collapsed' ? collapsible : ''}
data-variant={variant} data-variant={variant}
@ -233,31 +233,31 @@ const Sidebar = React.forwardRef<
{/* This is what handles the sidebar gap on desktop */} {/* This is what handles the sidebar gap on desktop */}
<div <div
className={cn( className={cn(
'duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear', 'relative h-svh w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
'group-data-[collapsible=offcanvas]:w-0', 'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180', 'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset' variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]' ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]', : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
)} )}
/> />
<div <div
className={cn( className={cn(
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex', 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
side === 'left' side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]' ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]', : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants. // Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset' variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]' ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l', : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
className, className,
)} )}
{...props} {...props}
> >
<div <div
data-sidebar="sidebar" data-sidebar="sidebar"
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow" className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
> >
{children} {children}
</div> </div>
@ -309,10 +309,10 @@ const SidebarRail = React.forwardRef<
onClick={toggleSidebar} onClick={toggleSidebar}
title="Toggle Sidebar" title="Toggle Sidebar"
className={cn( className={cn(
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex', '-translate-x-1/2 group-data-[side=left]:-right-4 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize', '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', '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar', '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=left][data-collapsible=offcanvas]_&]:-right-2',
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2', '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
className, className,
@ -332,7 +332,7 @@ const SidebarInset = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
'relative flex min-h-svh flex-1 flex-col bg-background', 'relative flex min-h-svh flex-1 flex-col bg-background',
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow', 'peer-data-[variant=inset]:min-h-[calc(100svh-(--spacing(4)))] 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, className,
)} )}
{...props} {...props}
@ -448,7 +448,7 @@ const SidebarGroupLabel = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="group-label" data-sidebar="group-label"
className={cn( className={cn(
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', 'flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0', 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
className, className,
)} )}
@ -469,9 +469,9 @@ const SidebarGroupAction = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="group-action" data-sidebar="group-action"
className={cn( className={cn(
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', '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 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden', 'after:-inset-2 after:absolute md:after:hidden',
'group-data-[collapsible=icon]:hidden', 'group-data-[collapsible=icon]:hidden',
className, className,
)} )}
@ -521,7 +521,7 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = 'SidebarMenuItem'; SidebarMenuItem.displayName = 'SidebarMenuItem';
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none 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=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=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', 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm 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=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=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: { variants: {
variant: { variant: {
@ -532,7 +532,7 @@ const sidebarMenuButtonVariants = cva(
size: { size: {
default: 'h-8 text-sm', default: 'h-8 text-sm',
sm: 'h-7 text-xs', sm: 'h-7 text-xs',
lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0', lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
}, },
}, },
defaultVariants: { defaultVariants: {
@ -615,9 +615,9 @@ const SidebarMenuAction = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="menu-action" data-sidebar="menu-action"
className={cn( className={cn(
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0', 'absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden', 'after:-inset-2 after:absolute md:after:hidden',
'peer-data-[size=sm]/menu-button:top-1', 'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5', 'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5', 'peer-data-[size=lg]/menu-button:top-2.5',
@ -640,7 +640,7 @@ const SidebarMenuBadge = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="menu-badge" data-sidebar="menu-badge"
className={cn( className={cn(
'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none', 'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums',
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground', 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
'peer-data-[size=sm]/menu-button:top-1', 'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5', 'peer-data-[size=default]/menu-button:top-1.5',
@ -668,7 +668,7 @@ const SidebarMenuSkeleton = React.forwardRef<
<div <div
ref={ref} ref={ref}
data-sidebar="menu-skeleton" data-sidebar="menu-skeleton"
className={cn('rounded-md h-8 flex gap-2 px-2 items-center', className)} className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
{...props} {...props}
> >
{showIcon && ( {showIcon && (
@ -678,7 +678,7 @@ const SidebarMenuSkeleton = React.forwardRef<
/> />
)} )}
<Skeleton <Skeleton
className="h-4 flex-1 max-w-[--skeleton-width]" className="h-4 max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text" data-sidebar="menu-skeleton-text"
style={ style={
{ {
@ -699,7 +699,7 @@ const SidebarMenuSub = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="menu-sub" data-sidebar="menu-sub"
className={cn( className={cn(
'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5', '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', 'group-data-[collapsible=icon]:hidden',
className, className,
)} )}
@ -731,7 +731,7 @@ const SidebarMenuSubButton = React.forwardRef<
data-size={size} data-size={size}
data-active={isActive} data-active={isActive}
className={cn( className={cn(
'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none 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 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground', '-translate-x-px flex h-7 min-w-0 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 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground', 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs', size === 'sm' && 'text-xs',
size === 'md' && 'text-sm', size === 'md' && 'text-sm',

View file

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

View file

@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 animate-in overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md data-[state=closed]:animate-out',
className, className,
)} )}
{...props} {...props}

View file

@ -36,7 +36,7 @@ export const VersionFooter = ({
return ( return (
<motion.div <motion.div
className="absolute flex flex-col gap-4 lg:flex-row bottom-0 bg-background p-4 w-full border-t z-50 justify-between" className="absolute bottom-0 z-50 flex w-full flex-col justify-between gap-4 border-t bg-background p-4 lg:flex-row"
initial={{ y: isMobile ? 200 : 77 }} initial={{ y: isMobile ? 200 : 77 }}
animate={{ y: 0 }} animate={{ y: 0 }}
exit={{ y: isMobile ? 200 : 77 }} exit={{ y: isMobile ? 200 : 77 }}

View file

@ -71,7 +71,7 @@ export function VisibilitySelector({
<Button <Button
data-testid="visibility-selector" data-testid="visibility-selector"
variant="outline" variant="outline"
className="hidden md:flex md:px-2 md:h-[34px] focus:outline-none focus:ring-0" className="hidden focus:outline-hidden focus:ring-0 md:flex md:h-[34px] md:px-2"
> >
{selectedVisibility?.icon} {selectedVisibility?.icon}
{selectedVisibility?.label} {selectedVisibility?.label}
@ -88,18 +88,18 @@ export function VisibilitySelector({
setVisibilityType(visibility.id); setVisibilityType(visibility.id);
setOpen(false); setOpen(false);
}} }}
className="gap-4 group/item flex flex-row justify-between items-center" className="group/item flex flex-row items-center justify-between gap-4"
data-active={visibility.id === visibilityType} data-active={visibility.id === visibilityType}
> >
<div className="flex flex-col gap-1 items-start"> <div className="flex flex-col items-start gap-1">
{visibility.label} {visibility.label}
{visibility.description && ( {visibility.description && (
<div className="text-xs text-muted-foreground"> <div className="text-muted-foreground text-xs">
{visibility.description} {visibility.description}
</div> </div>
)} )}
</div> </div>
<div className="text-foreground dark:text-foreground opacity-0 group-data-[active=true]/item:opacity-100"> <div className="text-foreground opacity-0 group-data-[active=true]/item:opacity-100 dark:text-foreground">
<CheckCircleFillIcon /> <CheckCircleFillIcon />
</div> </div>
</DropdownMenuItem> </DropdownMenuItem>

View file

@ -251,7 +251,7 @@ export function Weather({
return ( return (
<div <div
className={cx( className={cx(
'flex flex-col gap-4 rounded-2xl p-4 skeleton-bg max-w-[500px]', 'skeleton-bg flex max-w-[500px] flex-col gap-4 rounded-2xl p-4',
{ {
'bg-blue-400': isDay, 'bg-blue-400': isDay,
}, },
@ -260,11 +260,11 @@ export function Weather({
}, },
)} )}
> >
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row items-center justify-between">
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row items-center gap-2">
<div <div
className={cx( className={cx(
'size-10 rounded-full skeleton-div', 'skeleton-div size-10 rounded-full',
{ {
'bg-yellow-300': isDay, 'bg-yellow-300': isDay,
}, },
@ -273,7 +273,7 @@ export function Weather({
}, },
)} )}
/> />
<div className="text-4xl font-medium text-blue-50"> <div className="font-medium text-4xl text-blue-50">
{n(weatherAtLocation.current.temperature_2m)} {n(weatherAtLocation.current.temperature_2m)}
{weatherAtLocation.current_units.temperature_2m} {weatherAtLocation.current_units.temperature_2m}
</div> </div>
@ -290,7 +290,7 @@ export function Weather({
</div> </div>
<div <div
className={cx( className={cx(
'size-6 rounded-full skeleton-div', 'skeleton-div size-6 rounded-full',
{ {
'bg-yellow-300': isDay, 'bg-yellow-300': isDay,
}, },

View file

@ -36,7 +36,7 @@ export function useScrollToBottom() {
const container = containerRef.current; const container = containerRef.current;
const scrollOptions: ScrollToOptions = { const scrollOptions: ScrollToOptions = {
top: container.scrollHeight, top: container.scrollHeight,
behavior: scrollBehavior behavior: scrollBehavior,
}; };
container.scrollTo(scrollOptions); container.scrollTo(scrollOptions);
setScrollBehavior(false); setScrollBehavior(false);

View file

@ -15,6 +15,7 @@ export const chatModels: Array<ChatModel> = [
{ {
id: 'chat-model-reasoning', id: 'chat-model-reasoning',
name: 'Grok Reasoning', name: 'Grok Reasoning',
description: 'Uses advanced chain-of-thought reasoning for complex problems', description:
'Uses advanced chain-of-thought reasoning for complex problems',
}, },
]; ];

View file

@ -6,8 +6,8 @@
"dev": "next dev --turbo", "dev": "next dev --turbo",
"build": "tsx lib/db/migrate && next build", "build": "tsx lib/db/migrate && next build",
"start": "next start", "start": "next start",
"lint": "next lint && biome lint --write --unsafe", "lint": "biome lint --write --unsafe",
"lint:fix": "next lint --fix && biome lint --write --unsafe", "lint:fix": "biome lint --write --unsafe && biome format --write",
"format": "biome format --write", "format": "biome format --write",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:migrate": "npx tsx lib/db/migrate.ts", "db:migrate": "npx tsx lib/db/migrate.ts",
@ -104,6 +104,7 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
"@playwright/test": "^1.50.1", "@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/d3-scale": "^4.0.8", "@types/d3-scale": "^4.0.8",
"@types/node": "^22.8.6", "@types/node": "^22.8.6",
@ -119,7 +120,7 @@
"eslint-import-resolver-typescript": "^3.6.3", "eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-tailwindcss": "^3.17.5", "eslint-plugin-tailwindcss": "^3.17.5",
"postcss": "^8", "postcss": "^8",
"tailwindcss": "^3.4.1", "tailwindcss": "^4.1.13",
"tsx": "^4.19.1", "tsx": "^4.19.1",
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, },

694
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,81 +0,0 @@
import type { Config } from 'tailwindcss';
const config: Config = {
darkMode: ['class'],
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./node_modules/streamdown/dist/index.js',
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-geist)'],
mono: ['var(--font-geist-mono)'],
},
screens: {
'toast-mobile': '600px',
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))',
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))',
},
},
},
},
plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
};
export default config;