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 (
<div className="flex h-dvh w-screen items-start pt-12 md:pt-0 md:items-center justify-center bg-background">
<div className="w-full max-w-md overflow-hidden rounded-2xl flex flex-col gap-12">
<div className="flex h-dvh w-screen items-start justify-center bg-background pt-12 md:items-center md:pt-0">
<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">
<h3 className="text-xl font-semibold dark:text-zinc-50">Sign In</h3>
<p className="text-sm text-gray-500 dark:text-zinc-400">
<h3 className="font-semibold text-xl dark:text-zinc-50">Sign In</h3>
<p className="text-gray-500 text-sm dark:text-zinc-400">
Use your email and password to sign in
</p>
</div>
<AuthForm action={handleSubmit} defaultEmail={email}>
<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? "}
<Link
href="/register"

View file

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

View file

@ -1,7 +1,180 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* set base path to root directory to include utility classes from app, components, pages, etc. */
@import "tailwindcss" source("..");
/* 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 {
--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;
--background-start-rgb: 214, 219, 220;
@ -15,95 +188,6 @@
--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%;
}
}
@layer base {
@ -147,22 +231,22 @@
.cm-editor,
.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-selectionBackground,
.ͼo.cm-content::selection {
@apply bg-zinc-200 dark:bg-zinc-900 !important;
@apply bg-zinc-200! dark:bg-zinc-900!;
}
.cm-activeLine,
.cm-activeLineGutter {
@apply bg-transparent !important;
@apply bg-transparent!;
}
.cm-activeLine {
@apply rounded-r-sm !important;
@apply rounded-r-sm!;
}
.cm-lineNumbers {
@ -174,7 +258,7 @@
}
.cm-lineNumbers .cm-activeLineGutter {
@apply rounded-l-sm !important;
@apply rounded-l-sm!;
}
.suggestion-highlight {
@ -192,13 +276,13 @@
}
::-webkit-scrollbar-thumb {
background: hsl(var(--border));
background: var(--border);
border-radius: 3px;
transition: background 0.2s ease;
}
::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.5);
background: --alpha(var(--muted-foreground) / 0.5);
}
::-webkit-scrollbar-corner {
@ -208,5 +292,5 @@
/* firefox scrollbar styling */
* {
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 (
<>
<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
content={content}
suggestions={metadata ? metadata.suggestions : []}
@ -88,7 +88,7 @@ export const textArtifact = new Artifact<'text', TextArtifactMetadata>({
/>
{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}
</div>
</>

View file

@ -62,7 +62,7 @@
"useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually
"noNewSymbol": "warn", // Not in recommended ruleset, turning on manually
"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
},
"security": {
@ -80,11 +80,12 @@
"noExplicitAny": "off" // We trust Vercelians to use any only when necessary
},
"nursery": {
"noStaticElementInteractions": "warn",
"noHeadImportInDocument": "warn",
"noDocumentImportInPage": "warn",
"noDuplicateElseIf": "warn",
"noHeadImportInDocument": "warn",
"noIrregularWhitespace": "warn",
"noStaticElementInteractions": "warn",
"useSortedClasses": "error",
"useValidAutocomplete": "warn"
}
}

View file

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

View file

@ -26,15 +26,15 @@ export function AppSidebar({ user }: { user: User | undefined }) {
<Sidebar className="group-data-[side=left]:border-r-0">
<SidebarHeader>
<SidebarMenu>
<div className="flex flex-row justify-between items-center">
<div className="flex flex-row items-center justify-between">
<Link
href="/"
onClick={() => {
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
</span>
</Link>
@ -43,7 +43,7 @@ export function AppSidebar({ user }: { user: User | undefined }) {
<Button
variant="ghost"
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={() => {
setOpenMobile(false);
router.push('/');
@ -53,7 +53,9 @@ export function AppSidebar({ user }: { user: User | undefined }) {
<PlusIcon />
</Button>
</TooltipTrigger>
<TooltipContent align="end" className="hidden md:block">New Chat</TooltipContent>
<TooltipContent align="end" className="hidden md:block">
New Chat
</TooltipContent>
</Tooltip>
</div>
</SidebarMenu>

View file

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

View file

@ -42,7 +42,7 @@ function PureArtifactMessages({
return (
<div
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) => (
<PreviewMessage
@ -71,7 +71,7 @@ function PureArtifactMessages({
<motion.div
ref={messagesEndRef}
className="shrink-0 min-w-[24px] min-h-[24px]"
className="min-h-[24px] min-w-[24px] shrink-0"
onViewportLeave={onViewportLeave}
onViewportEnter={onViewportEnter}
/>

View file

@ -259,14 +259,14 @@ function PureArtifact({
{artifact.isVisible && (
<motion.div
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 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0, transition: { delay: 0.4 } }}
>
{!isMobile && (
<motion.div
className="fixed bg-background h-dvh"
className="fixed h-dvh bg-background"
initial={{
width: isSidebarOpen ? windowWidth - 256 : windowWidth,
right: 0,
@ -281,7 +281,7 @@ function PureArtifact({
{!isMobile && (
<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 }}
animate={{
opacity: 1,
@ -304,7 +304,7 @@ function PureArtifact({
<AnimatePresence>
{!isCurrentVersion && (
<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 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
@ -312,7 +312,7 @@ function PureArtifact({
)}
</AnimatePresence>
<div className="flex flex-col h-full justify-between items-center">
<div className="flex h-full flex-col items-center justify-between">
<ArtifactMessages
chatId={chatId}
status={status}
@ -324,7 +324,7 @@ function PureArtifact({
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
chatId={chatId}
input={input}
@ -346,7 +346,7 @@ function PureArtifact({
)}
<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={
isMobile
? {
@ -412,19 +412,19 @@ function PureArtifact({
},
}}
>
<div className="p-2 flex flex-row justify-between items-start">
<div className="flex flex-row gap-4 items-start">
<div className="flex flex-row items-start justify-between p-2">
<div className="flex flex-row items-start gap-4">
<ArtifactCloseButton />
<div className="flex flex-col">
<div className="font-medium">{artifact.title}</div>
{isContentDirty ? (
<div className="text-sm text-muted-foreground">
<div className="text-muted-foreground text-sm">
Saving changes...
</div>
) : document ? (
<div className="text-sm text-muted-foreground">
<div className="text-muted-foreground text-sm">
{`Updated ${formatDistance(
new Date(document.createdAt),
new Date(),
@ -434,7 +434,7 @@ function PureArtifact({
)}`}
</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>
@ -450,7 +450,7 @@ function PureArtifact({
/>
</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
title={artifact.title}
content={

View file

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

View file

@ -29,13 +29,13 @@ function PureChatHeader({
const { width: windowWidth } = useWindowSize();
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 />
{(!open || windowWidth < 768) && (
<Button
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={() => {
router.push('/');
router.refresh();
@ -55,7 +55,7 @@ function PureChatHeader({
)}
<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
>
<Link

View file

@ -136,7 +136,7 @@ export function Chat({
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
chatId={id}
selectedVisibilityType={initialVisibilityType}
@ -156,7 +156,7 @@ export function Chat({
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 && (
<MultimodalInput
chatId={id}

View file

@ -92,7 +92,7 @@ function PureCodeEditor({ content, onSaveContent, status }: EditorProps) {
return (
<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}
/>
);

View file

@ -80,7 +80,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
return consoleOutputs.length > 0 ? (
<>
<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}
style={{ bottom: height - 4 }}
role="slider"
@ -89,15 +89,15 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
<div
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,
},
)}
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="flex flex-row gap-3 items-center pl-2 text-sm dark:text-zinc-50 text-zinc-800">
<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 items-center gap-3 pl-2 text-sm text-zinc-800 dark:text-zinc-50">
<div className="text-muted-foreground">
<TerminalWindowIcon />
</div>
@ -105,7 +105,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
</div>
<Button
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"
onClick={() => setConsoleOutputs([])}
>
@ -117,7 +117,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
{consoleOutputs.map((consoleOutput, index) => (
<div
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
className={cn('w-12 shrink-0', {
@ -135,7 +135,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
consoleOutput.status,
) ? (
<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} />
</div>
<div className="text-muted-foreground">
@ -149,14 +149,14 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
</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) =>
content.type === 'image' ? (
<picture key={`${consoleOutput.id}-${index}`}>
<img
src={content.value}
alt="output"
className="w-full rounded-md max-w-screen-toast-mobile"
className="w-full max-w-(--breakpoint-toast-mobile) rounded-md"
/>
</picture>
) : (

View file

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

View file

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

View file

@ -39,7 +39,7 @@ function PureDocumentToolResult({
return (
<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) => {
if (isReadonly) {
toast.error(
@ -68,7 +68,7 @@ function PureDocumentToolResult({
});
}}
>
<div className="text-muted-foreground mt-1">
<div className="mt-1 text-muted-foreground">
{type === 'create' ? (
<FileIcon />
) : type === 'update' ? (
@ -105,7 +105,7 @@ function PureDocumentToolCall({
return (
<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) => {
if (isReadonly) {
toast.error(
@ -130,8 +130,8 @@ function PureDocumentToolCall({
}));
}}
>
<div className="flex flex-row gap-3 items-start">
<div className="text-zinc-500 mt-1">
<div className="flex flex-row items-start gap-3">
<div className="mt-1 text-zinc-500">
{type === 'create' ? (
<FileIcon />
) : type === 'update' ? (
@ -154,7 +154,7 @@ function PureDocumentToolCall({
</div>
</div>
<div className="animate-spin mt-1">{<LoaderIcon />}</div>
<div className="mt-1 animate-spin">{<LoaderIcon />}</div>
</button>
);
}

View file

@ -13,7 +13,7 @@ import type { ComponentProps } from 'react';
export type ActionsProps = ComponentProps<'div'>;
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}
</div>
);
@ -35,7 +35,7 @@ export const Action = ({
const button = (
<Button
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,
)}
size={size}

View file

@ -12,7 +12,7 @@ export type ConversationProps = ComponentProps<typeof StickToBottom>;
export const Conversation = ({ className, ...props }: ConversationProps) => (
<StickToBottom
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,
)}
initial="smooth"
@ -49,7 +49,7 @@ export const ConversationScrollButton = ({
!isAtBottom && (
<Button
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,
)}
onClick={handleScrollToBottom}

View file

@ -24,7 +24,7 @@ export type PromptInputProps = HTMLAttributes<HTMLFormElement>;
export const PromptInput = ({ className, ...props }: PromptInputProps) => (
<form
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,
)}
{...props}
@ -72,8 +72,12 @@ export const PromptInputTextarea = ({
return (
<Textarea
className={cn(
'w-full resize-none rounded-none border-none p-3 shadow-none outline-none ring-0',
disableAutoResize ? 'field-sizing-fixed' : resizeOnNewLinesOnly ? 'field-sizing-fixed' : 'field-sizing-content max-h-[6lh]',
'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]',
'bg-transparent dark:bg-transparent',
'focus-visible:ring-0',
className,
@ -196,7 +200,7 @@ export const PromptInputModelSelectTrigger = ({
<SelectTrigger
className={cn(
'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,
)}
{...props}

View file

@ -119,7 +119,7 @@ export const ReasoningTrigger = memo(
return (
<CollapsibleTrigger
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,
)}
{...props}
@ -155,8 +155,8 @@ export const ReasoningContent = memo(
({ className, children, ...props }: ReasoningContentProps) => (
<CollapsibleContent
className={cn(
'mt-2 text-xs text-muted-foreground',
'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',
'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-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className,
)}
{...props}

View file

@ -10,7 +10,7 @@ export const Response = memo(
({ className, ...props }: ResponseProps) => (
<Streamdown
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,
)}
{...props}

View file

@ -47,7 +47,7 @@ export const SourcesContent = ({
<CollapsibleContent
className={cn(
'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,
)}
{...props}

View file

@ -82,7 +82,7 @@ export const TaskContent = ({
}: TaskContentProps) => (
<CollapsibleContent
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,
)}
{...props}

View file

@ -50,7 +50,10 @@ const getStatusBadge = (status: ToolUIPart['state']) => {
} as const;
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]}
<span>{labels[status]}</span>
</Badge>
@ -65,16 +68,16 @@ export const ToolHeader = ({
}: ToolHeaderProps) => (
<CollapsibleTrigger
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,
)}
{...props}
>
<div className="flex items-center gap-2 min-w-0 flex-1">
<WrenchIcon className="size-4 text-muted-foreground shrink-0" />
<span className="font-medium text-sm truncate">{type}</span>
<div className="flex min-w-0 flex-1 items-center gap-2">
<WrenchIcon className="size-4 shrink-0 text-muted-foreground" />
<span className="truncate font-medium text-sm">{type}</span>
</div>
<div className="flex items-center gap-2 shrink-0">
<div className="flex shrink-0 items-center gap-2">
{getStatusBadge(state)}
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</div>
@ -86,7 +89,7 @@ export type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
export const ToolContent = ({ className, ...props }: ToolContentProps) => (
<CollapsibleContent
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,
)}
{...props}

View file

@ -220,7 +220,7 @@ export const WebPreviewConsole = ({
<CollapsibleContent
className={cn(
'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">

View file

@ -4,14 +4,14 @@ export const Greeting = () => {
return (
<div
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
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ delay: 0.5 }}
className="text-xl md:text-2xl font-semibold"
className="font-semibold text-xl md:text-2xl"
>
Hello there!
</motion.div>
@ -20,7 +20,7 @@ export const Greeting = () => {
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
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?
</motion.div>

View file

@ -18,13 +18,13 @@ export function ImageEditor({
}: ImageEditorProps) {
return (
<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-[200px]': isInline,
})}
>
{status === 'streaming' ? (
<div className="flex flex-row gap-4 items-center">
<div className="flex flex-row items-center gap-4">
{!isInline && (
<div className="animate-spin">
<LoaderIcon />
@ -35,7 +35,7 @@ export function ImageEditor({
) : (
<picture>
<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,
})}
src={`data:image/png;base64,${content}`}

View file

@ -47,13 +47,13 @@ export function PureMessageActions({
// User messages get edit (on hover) and copy actions
if (message.role === 'user') {
return (
<Actions className="justify-end -mr-0.5">
<Actions className="-mr-0.5 justify-end">
<div className="relative">
{setMode && (
<Action
tooltip="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 />
</Action>

View file

@ -53,19 +53,19 @@ export function MessageEditor({
};
return (
<div className="flex flex-col gap-2 w-full">
<div className="flex w-full flex-col gap-2">
<Textarea
data-testid="message-editor"
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}
onChange={handleInput}
/>
<div className="flex flex-row gap-2 justify-end">
<div className="flex flex-row justify-end gap-2">
<Button
variant="outline"
className="h-fit py-2 px-3"
className="h-fit px-3 py-2"
onClick={() => {
setMode('view');
}}
@ -75,7 +75,7 @@ export function MessageEditor({
<Button
data-testid="message-editor-send-button"
variant="default"
className="h-fit py-2 px-3"
className="h-fit px-3 py-2"
disabled={isSubmitting}
onClick={async () => {
setIsSubmitting(true);

View file

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

View file

@ -57,19 +57,19 @@ const PurePreviewMessage = ({
return (
<motion.div
data-testid={`message-${message.role}`}
className="w-full group/message"
className="group/message w-full"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
data-role={message.role}
>
<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-start': 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} />
</div>
)}
@ -93,7 +93,7 @@ const PurePreviewMessage = ({
{attachmentsFromMessage.length > 0 && (
<div
data-testid={`message-attachments`}
className="flex flex-row gap-2 justify-end"
className="flex flex-row justify-end gap-2"
>
{attachmentsFromMessage.map((attachment) => (
<PreviewAttachment
@ -129,7 +129,7 @@ const PurePreviewMessage = ({
<MessageContent
data-testid="message-content"
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',
'bg-transparent px-0 py-0 text-left':
message.role === 'assistant',
@ -150,10 +150,10 @@ const PurePreviewMessage = ({
return (
<div
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="flex-1 min-w-0">
<div className="min-w-0 flex-1">
<MessageEditor
key={message.id}
message={message}
@ -195,7 +195,7 @@ const PurePreviewMessage = ({
return (
<div
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)}
</div>
@ -218,7 +218,7 @@ const PurePreviewMessage = ({
return (
<div
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)}
</div>
@ -250,7 +250,7 @@ const PurePreviewMessage = ({
<ToolOutput
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)}
</div>
) : (
@ -306,17 +306,17 @@ export const ThinkingMessage = () => {
return (
<motion.div
data-testid="message-assistant-loading"
className="w-full group/message"
className="group/message w-full"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
data-role={role}
>
<div className="flex gap-3 justify-start items-start">
<div className="flex justify-center items-center -mt-1 rounded-full ring-1 size-8 shrink-0 ring-border bg-background">
<div className="flex items-start justify-start gap-3">
<div className="-mt-1 flex size-8 shrink-0 items-center justify-center rounded-full bg-background ring-1 ring-border">
<SparklesIcon size={14} />
</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">
<LoadingText>Thinking...</LoadingText>
</div>

View file

@ -53,7 +53,7 @@ function PureMessages({
if (container) {
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
behavior: 'smooth',
});
}
});
@ -63,7 +63,7 @@ function PureMessages({
return (
<div
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' }}
>
<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' &&
messages.length > 0 &&
messages[messages.length - 1].role === 'user' &&
selectedModelId !== 'chat-model-reasoning' && (
<ThinkingMessage />
)}
selectedModelId !== 'chat-model-reasoning' && <ThinkingMessage />}
<div
ref={messagesEndRef}
className="shrink-0 min-w-[24px] min-h-[24px]"
className="min-h-[24px] min-w-[24px] shrink-0"
/>
</ConversationContent>
</Conversation>
{!isAtBottom && (
<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')}
type="button"
aria-label="Scroll to bottom"

View file

@ -56,7 +56,7 @@ export function ModelSelector({
<Button
data-testid="model-selector"
variant="outline"
className="md:px-2 md:h-[34px]"
className="md:h-[34px] md:px-2"
>
{selectedChatModel?.name}
<ChevronDownIcon />
@ -83,16 +83,16 @@ export function ModelSelector({
>
<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 className="text-xs text-muted-foreground">
<div className="text-muted-foreground text-xs">
{chatModel.description}
</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 />
</div>
</button>

View file

@ -29,7 +29,7 @@ import {
PromptInputModelSelectTrigger,
PromptInputModelSelectContent,
} from './elements/prompt-input';
import { SelectItem, } from '@/components/ui/select';
import { SelectItem } from '@/components/ui/select';
import equal from 'fast-deep-equal';
import type { UseChatHelpers } from '@ai-sdk/react';
import { AnimatePresence, motion } from 'framer-motion';
@ -253,7 +253,7 @@ function PureMultimodalInput({
}, [status, scrollToBottom]);
return (
<div className="flex relative flex-col gap-4 w-full">
<div className="relative flex w-full flex-col gap-4">
<AnimatePresence>
{!isAtBottom && (
<motion.div
@ -261,7 +261,7 @@ function PureMultimodalInput({
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
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
data-testid="scroll-to-bottom-button"
@ -291,7 +291,7 @@ function PureMultimodalInput({
<input
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}
multiple
onChange={handleFileChange}
@ -299,7 +299,7 @@ function PureMultimodalInput({
/>
<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) => {
event.preventDefault();
if (status !== 'ready') {
@ -312,7 +312,7 @@ function PureMultimodalInput({
{(attachments.length > 0 || uploadQueue.length > 0) && (
<div
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) => (
<PreviewAttachment
@ -352,13 +352,13 @@ function PureMultimodalInput({
minHeight={44}
maxHeight={200}
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}
autoFocus
/>{' '}
<Context {...contextProps} />
</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">
<AttachmentsButton
fileInputRef={fileInputRef}
@ -374,7 +374,7 @@ function PureMultimodalInput({
<PromptInputSubmit
status={status}
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} />
</PromptInputSubmit>
@ -413,7 +413,7 @@ function PureAttachmentsButton({
return (
<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) => {
event.preventDefault();
fileInputRef.current?.click();
@ -455,16 +455,16 @@ function PureModelSelectorCompact({
>
<PromptInputModelSelectTrigger
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'}
</PromptInputModelSelectTrigger>
<PromptInputModelSelectContent>
{chatModels.map((model) => (
<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="text-xs text-muted-foreground">
<div className="text-muted-foreground text-xs">
{model.description}
</div>
</div>
@ -487,7 +487,7 @@ function PureStopButton({
return (
<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) => {
event.preventDefault();
stop();

View file

@ -1,6 +1,6 @@
import type { Attachment } from '@/lib/types';
import { Loader } from './elements/loader';
import { CrossSmallIcon, } from './icons';
import { CrossSmallIcon } from './icons';
import { Button } from './ui/button';
import Image from 'next/image';
@ -44,13 +44,13 @@ export const PreviewAttachment = ({
onClick={onRemove}
size="sm"
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} />
</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}
</div>
</div>

View file

@ -53,7 +53,7 @@ const PureChatItem = ({
<DropdownMenu modal={true}>
<DropdownMenuTrigger asChild>
<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}
>
<MoreHorizontalIcon />
@ -75,7 +75,7 @@ const PureChatItem = ({
setVisibilityType('private');
}}
>
<div className="flex flex-row gap-2 items-center">
<div className="flex flex-row items-center gap-2">
<LockIcon size={12} />
<span>Private</span>
</div>
@ -89,7 +89,7 @@ const PureChatItem = ({
setVisibilityType('public');
}}
>
<div className="flex flex-row gap-2 items-center">
<div className="flex flex-row items-center gap-2">
<GlobeIcon />
<span>Public</span>
</div>

View file

@ -152,7 +152,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
return (
<SidebarGroup>
<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!
</div>
</SidebarGroupContent>
@ -163,7 +163,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
if (isLoading) {
return (
<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
</div>
<SidebarGroupContent>
@ -171,10 +171,10 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{[44, 32, 28, 64, 52].map((item) => (
<div
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
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={
{
'--skeleton-width': `${item}%`,
@ -193,7 +193,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
return (
<SidebarGroup>
<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!
</div>
</SidebarGroupContent>
@ -218,7 +218,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
<div className="flex flex-col gap-6">
{groupedChats.today.length > 0 && (
<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
</div>
{groupedChats.today.map((chat) => (
@ -238,7 +238,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.yesterday.length > 0 && (
<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
</div>
{groupedChats.yesterday.map((chat) => (
@ -258,7 +258,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.lastWeek.length > 0 && (
<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
</div>
{groupedChats.lastWeek.map((chat) => (
@ -278,7 +278,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.lastMonth.length > 0 && (
<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
</div>
{groupedChats.lastMonth.map((chat) => (
@ -298,7 +298,7 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
{groupedChats.older.length > 0 && (
<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
</div>
{groupedChats.older.map((chat) => (
@ -329,11 +329,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
/>
{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.
</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">
<LoaderIcon />
</div>

View file

@ -22,12 +22,14 @@ export function SidebarToggle({
data-testid="sidebar-toggle-button"
onClick={toggleSidebar}
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} />
</Button>
</TooltipTrigger>
<TooltipContent align="start" className="hidden md:block">Toggle Sidebar</TooltipContent>
<TooltipContent align="start" className="hidden md:block">
Toggle Sidebar
</TooltipContent>
</Tooltip>
);
}

View file

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

View file

@ -16,7 +16,7 @@ export const SignOutForm = () => {
>
<button
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
</button>

View file

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

View file

@ -20,13 +20,16 @@ function PureSuggestedActions({
}: SuggestedActionsProps) {
const suggestedActions = [
'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',
'What is the weather in San Francisco?',
];
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) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
@ -44,7 +47,7 @@ function PureSuggestedActions({
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}
</Suggestion>

View file

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

View file

@ -146,7 +146,7 @@ function PureEditor({
}, [suggestions, content]);
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]);
return (
<div className="flex w-full toast-mobile:w-[356px] justify-center">
<div className="flex toast-mobile:w-[356px] w-full justify-center">
<div
data-testid="toast"
key={id}
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',
)}
>
@ -58,7 +58,7 @@ function Toast(props: ToastProps) {
>
{iconsByType[type]}
</div>
<div ref={descriptionRef} className="text-zinc-950 text-sm">
<div ref={descriptionRef} className="text-sm text-zinc-950">
{description}
</div>
</div>

View file

@ -89,8 +89,8 @@ const Tool = ({
<Tooltip open={isHovered && !isAnimating}>
<TooltipTrigger asChild>
<motion.div
className={cx('p-3 rounded-full', {
'bg-primary !text-primary-foreground': selectedTool === description,
className={cx('rounded-full p-3', {
'bg-primary text-primary-foreground!': selectedTool === description,
})}
onHoverStart={() => {
setIsHovered(true);
@ -122,7 +122,7 @@ const Tool = ({
<TooltipContent
side="left"
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}
</TooltipContent>
@ -168,11 +168,11 @@ const ReadingLevelSelector = ({
}, [yToLevel]);
return (
<div className="relative flex flex-col justify-end items-center">
<div className="relative flex flex-col items-center justify-end">
{randomArr.map((id) => (
<motion.div
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 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
@ -187,7 +187,7 @@ const ReadingLevelSelector = ({
<TooltipTrigger asChild>
<motion.div
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-background text-foreground': currentLevel === 2,
@ -233,7 +233,7 @@ const ReadingLevelSelector = ({
<TooltipContent
side="left"
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]}
</TooltipContent>
@ -376,7 +376,7 @@ const PureToolbar = ({
return (
<TooltipProvider delayDuration={0}>
<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 }}
animate={
isToolbarVisible

View file

@ -18,7 +18,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
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,
)}
{...props}
@ -36,7 +36,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
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,
)}
{...props}
@ -79,7 +79,7 @@ const AlertDialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold', className)}
className={cn('font-semibold text-lg', className)}
{...props}
/>
));
@ -91,7 +91,7 @@ const AlertDialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
));

View file

@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
@ -12,13 +12,13 @@ const Avatar = React.forwardRef<
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
@ -26,11 +26,11 @@ const AvatarImage = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
@ -39,12 +39,12 @@ const AvatarFallback = React.forwardRef<
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className,
)}
{...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 { cva, type VariantProps } from "class-variance-authority"
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
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: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: "default",
variant: 'default',
},
}
)
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
@ -30,7 +30,7 @@ export interface BadgeProps
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<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';
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: {
variant: {

View file

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

View file

@ -1,45 +1,45 @@
"use client"
'use client';
import * as React from "react"
import * as React from 'react';
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
} from 'embla-carousel-react';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: 'horizontal' | 'vertical';
setApi?: (api: CarouselApi) => void;
};
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
} & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() {
const context = React.useContext(CarouselContext)
const context = React.useContext(CarouselContext);
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<
@ -48,7 +48,7 @@ const Carousel = React.forwardRef<
>(
(
{
orientation = "horizontal",
orientation = 'horizontal',
opts,
setApi,
plugins,
@ -56,69 +56,69 @@ const Carousel = React.forwardRef<
children,
...props
},
ref
ref,
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
axis: orientation === 'horizontal' ? 'x' : 'y',
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
plugins,
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return
return;
}
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
api?.scrollPrev();
}, [api]);
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
api?.scrollNext();
}, [api]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
if (event.key === 'ArrowLeft') {
event.preventDefault();
scrollPrev();
} else if (event.key === 'ArrowRight') {
event.preventDefault();
scrollNext();
}
},
[scrollPrev, scrollNext]
)
[scrollPrev, scrollNext],
);
React.useEffect(() => {
if (!api || !setApi) {
return
return;
}
setApi(api)
}, [api, setApi])
setApi(api);
}, [api, setApi]);
React.useEffect(() => {
if (!api) {
return
return;
}
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
onSelect(api);
api.on('reInit', onSelect);
api.on('select', onSelect);
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
api?.off('select', onSelect);
};
}, [api, onSelect]);
return (
<CarouselContext.Provider
@ -127,7 +127,7 @@ const Carousel = React.forwardRef<
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),
scrollPrev,
scrollNext,
canScrollPrev,
@ -137,7 +137,7 @@ const Carousel = React.forwardRef<
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
className={cn('relative', className)}
role="region"
aria-roledescription="carousel"
{...props}
@ -145,38 +145,38 @@ const Carousel = React.forwardRef<
{children}
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
);
},
);
Carousel.displayName = 'Carousel';
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel()
const { carouselRef, orientation } = useCarousel();
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
'flex',
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
className,
)}
{...props}
/>
</div>
)
})
CarouselContent.displayName = "CarouselContent"
);
});
CarouselContent.displayName = 'CarouselContent';
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel()
const { orientation } = useCarousel();
return (
<div
@ -184,21 +184,21 @@ const CarouselItem = React.forwardRef<
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
'min-w-0 shrink-0 grow-0 basis-full',
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
className,
)}
{...props}
/>
)
})
CarouselItem.displayName = "CarouselItem"
);
});
CarouselItem.displayName = 'CarouselItem';
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
<Button
@ -206,11 +206,11 @@ const CarouselPrevious = React.forwardRef<
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
'absolute h-8 w-8 rounded-full',
orientation === 'horizontal'
? '-left-12 -translate-y-1/2 top-1/2'
: '-top-12 -translate-x-1/2 left-1/2 rotate-90',
className,
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
@ -219,15 +219,15 @@ const CarouselPrevious = React.forwardRef<
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
)
})
CarouselPrevious.displayName = "CarouselPrevious"
);
});
CarouselPrevious.displayName = 'CarouselPrevious';
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel()
>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
<Button
@ -235,11 +235,11 @@ const CarouselNext = React.forwardRef<
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
'absolute h-8 w-8 rounded-full',
orientation === 'horizontal'
? '-right-12 -translate-y-1/2 top-1/2'
: '-bottom-12 -translate-x-1/2 left-1/2 rotate-90',
className,
)}
disabled={!canScrollNext}
onClick={scrollNext}
@ -248,9 +248,9 @@ const CarouselNext = React.forwardRef<
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
)
})
CarouselNext.displayName = "CarouselNext"
);
});
CarouselNext.displayName = 'CarouselNext';
export {
type CarouselApi,
@ -259,4 +259,4 @@ export {
CarouselItem,
CarouselPrevious,
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
ref={ref}
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',
className,
)}
@ -47,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'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,
)}
{...props}
@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
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,
)}
{...props}
@ -83,7 +83,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]: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',
className,
)}
@ -99,7 +99,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 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,
)}
checked={checked}
@ -123,7 +123,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 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,
)}
{...props}
@ -147,7 +147,7 @@ const DropdownMenuLabel = React.forwardRef<
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
'px-2 py-1.5 text-sm font-semibold',
'px-2 py-1.5 font-semibold text-sm',
inset && 'pl-8',
className,
)}

View file

@ -1,29 +1,29 @@
"use client"
'use client';
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import * as React from 'react';
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<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
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]",
className
'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,
)}
{...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
type={type}
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,
)}
ref={ref}

View file

@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@ -11,7 +11,7 @@ const ScrollArea = React.forwardRef<
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
@ -20,29 +20,29 @@ const ScrollArea = React.forwardRef<
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
'flex touch-none select-none transition-colors',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent p-px',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent p-px',
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</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
ref={ref}
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,
)}
{...props}
@ -75,9 +75,9 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
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' &&
'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,
)}
position={position}
@ -88,7 +88,7 @@ const SelectContent = React.forwardRef<
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)',
)}
>
{children}
@ -105,7 +105,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
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}
/>
));
@ -118,7 +118,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-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,
)}
{...props}

View file

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

View file

@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
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,
)}
{...props}
@ -65,7 +65,7 @@ const SheetContent = React.forwardRef<
{...props}
>
{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" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
@ -108,7 +108,7 @@ const SheetTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-foreground', className)}
className={cn('font-semibold text-foreground text-lg', className)}
{...props}
/>
));
@ -120,7 +120,7 @@ const SheetDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
));

View file

@ -149,7 +149,7 @@ const SidebarProvider = React.forwardRef<
} as React.CSSProperties
}
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,
)}
ref={ref}
@ -189,7 +189,7 @@ const Sidebar = React.forwardRef<
return (
<div
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,
)}
ref={ref}
@ -206,7 +206,7 @@ const Sidebar = React.forwardRef<
<SheetContent
data-sidebar="sidebar"
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={
{
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
@ -224,7 +224,7 @@ const Sidebar = React.forwardRef<
return (
<div
ref={ref}
className="group peer hidden md:block text-sidebar-foreground"
className="group peer hidden text-sidebar-foreground md:block"
data-state={state}
data-collapsible={state === 'collapsed' ? collapsible : ''}
data-variant={variant}
@ -233,31 +233,31 @@ const Sidebar = React.forwardRef<
{/* This is what handles the sidebar gap on desktop */}
<div
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-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]',
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
)}
/>
<div
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'
? 'left-0 group-data-[collapsible=offcanvas]:left-[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.
variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
? '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',
className,
)}
{...props}
>
<div
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}
</div>
@ -309,10 +309,10 @@ const SidebarRail = React.forwardRef<
onClick={toggleSidebar}
title="Toggle Sidebar"
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',
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
'-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',
'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',
'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=right][data-collapsible=offcanvas]_&]:-left-2',
className,
@ -332,7 +332,7 @@ const SidebarInset = React.forwardRef<
ref={ref}
className={cn(
'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,
)}
{...props}
@ -448,7 +448,7 @@ const SidebarGroupLabel = React.forwardRef<
ref={ref}
data-sidebar="group-label"
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',
className,
)}
@ -469,9 +469,9 @@ const SidebarGroupAction = React.forwardRef<
ref={ref}
data-sidebar="group-action"
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.
'after:absolute after:-inset-2 after:md:hidden',
'after:-inset-2 after:absolute md:after:hidden',
'group-data-[collapsible=icon]:hidden',
className,
)}
@ -521,7 +521,7 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = 'SidebarMenuItem';
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: {
variant: {
@ -532,7 +532,7 @@ const sidebarMenuButtonVariants = cva(
size: {
default: 'h-8 text-sm',
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: {
@ -615,9 +615,9 @@ const SidebarMenuAction = React.forwardRef<
ref={ref}
data-sidebar="menu-action"
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.
'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=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
@ -640,7 +640,7 @@ const SidebarMenuBadge = React.forwardRef<
ref={ref}
data-sidebar="menu-badge"
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-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
@ -668,7 +668,7 @@ const SidebarMenuSkeleton = React.forwardRef<
<div
ref={ref}
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}
>
{showIcon && (
@ -678,7 +678,7 @@ const SidebarMenuSkeleton = React.forwardRef<
/>
)}
<Skeleton
className="h-4 flex-1 max-w-[--skeleton-width]"
className="h-4 max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text"
style={
{
@ -699,7 +699,7 @@ const SidebarMenuSub = React.forwardRef<
ref={ref}
data-sidebar="menu-sub"
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',
className,
)}
@ -731,7 +731,7 @@ const SidebarMenuSubButton = React.forwardRef<
data-size={size}
data-active={isActive}
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',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',

View file

@ -9,7 +9,7 @@ const Textarea = React.forwardRef<
return (
<textarea
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,
)}
ref={ref}

View file

@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
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,
)}
{...props}

View file

@ -36,7 +36,7 @@ export const VersionFooter = ({
return (
<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 }}
animate={{ y: 0 }}
exit={{ y: isMobile ? 200 : 77 }}

View file

@ -71,7 +71,7 @@ export function VisibilitySelector({
<Button
data-testid="visibility-selector"
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?.label}
@ -88,18 +88,18 @@ export function VisibilitySelector({
setVisibilityType(visibility.id);
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}
>
<div className="flex flex-col gap-1 items-start">
<div className="flex flex-col items-start gap-1">
{visibility.label}
{visibility.description && (
<div className="text-xs text-muted-foreground">
<div className="text-muted-foreground text-xs">
{visibility.description}
</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 />
</div>
</DropdownMenuItem>

View file

@ -251,7 +251,7 @@ export function Weather({
return (
<div
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,
},
@ -260,11 +260,11 @@ export function Weather({
},
)}
>
<div className="flex flex-row justify-between items-center">
<div className="flex flex-row gap-2 items-center">
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center gap-2">
<div
className={cx(
'size-10 rounded-full skeleton-div',
'skeleton-div size-10 rounded-full',
{
'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)}
{weatherAtLocation.current_units.temperature_2m}
</div>
@ -290,7 +290,7 @@ export function Weather({
</div>
<div
className={cx(
'size-6 rounded-full skeleton-div',
'skeleton-div size-6 rounded-full',
{
'bg-yellow-300': isDay,
},

View file

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

View file

@ -15,6 +15,7 @@ export const chatModels: Array<ChatModel> = [
{
id: 'chat-model-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",
"build": "tsx lib/db/migrate && next build",
"start": "next start",
"lint": "next lint && biome lint --write --unsafe",
"lint:fix": "next lint --fix && biome lint --write --unsafe",
"lint": "biome lint --write --unsafe",
"lint:fix": "biome lint --write --unsafe && biome format --write",
"format": "biome format --write",
"db:generate": "drizzle-kit generate",
"db:migrate": "npx tsx lib/db/migrate.ts",
@ -104,6 +104,7 @@
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.15",
"@types/d3-scale": "^4.0.8",
"@types/node": "^22.8.6",
@ -119,7 +120,7 @@
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-tailwindcss": "^3.17.5",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"tailwindcss": "^4.1.13",
"tsx": "^4.19.1",
"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} */
const config = {
plugins: {
tailwindcss: {},
'tailwindcss/nesting': {},
'@tailwindcss/postcss': {},
},
};

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;