Skip to content

Commit a9e8faa

Browse files
authored
Feature/UI improvements (#16)
- Update homepage UI - Improve ChatPanel performance
1 parent 65293f1 commit a9e8faa

File tree

8 files changed

+177
-61
lines changed

8 files changed

+177
-61
lines changed

app/page.tsx

+25-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { siteConfig } from "@/config/site";
66
import { cn } from "@/lib/utils";
77
import { buttonVariants } from "@/components/ui/Button";
88
import { MainLayout } from "@/components/ui/common/MainLayout";
9+
import { Heading1 } from "@/components/ui/typography";
910
import { DescriptionHeadingText } from "@/components/modules/home/DescriptionHeadingText";
1011
import { FeatureItems } from "@/components/modules/home/FeatureItems";
1112
import { HeroBannerImage } from "@/components/modules/home/HeroBannerImage";
@@ -21,27 +22,30 @@ export default async function Home() {
2122
return (
2223
<MainLayout>
2324
<div className="px-4 pt-16">
24-
<section className="space-y-6 pb-8 pt-6 md:pb-12 md:pt-10 lg:py-20">
25-
<div className="container flex max-w-5xl flex-col items-center gap-6 text-center">
26-
<h1 className="font-heading bg-gradient-to-r from-gray-900 to-gray-500 bg-clip-text text-3xl font-bold leading-[1.1] tracking-tighter text-transparent dark:from-white dark:to-gray-500 sm:text-5xl md:text-6xl">
27-
{siteConfig.name}
28-
</h1>
29-
<DescriptionHeadingText />
30-
<div className="flex items-center space-x-4">
31-
<Link
32-
href="https://www.youtube.com/watch?v=GWalKzuC0Rg"
33-
target="_blank"
34-
className={cn(
35-
buttonVariants({
36-
variant: "outline",
37-
})
38-
)}
39-
>
40-
<Play className="mr-2" size={16} /> Demo
41-
</Link>
42-
<Link href="/apps/chat" className={cn(buttonVariants())}>
43-
Get Started
44-
</Link>
25+
<section className="space-y-6 pb-8 md:pb-12">
26+
<div className="relative flex h-80 w-full items-center justify-center bg-white bg-dot-black/[0.2] dark:bg-black dark:bg-dot-white/[0.2]">
27+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] dark:bg-black"></div>
28+
<div className="container flex max-w-5xl flex-col items-center gap-6 text-center">
29+
<Heading1 className="font-heading bg-gradient-to-r from-gray-900 to-gray-500 bg-clip-text text-3xl font-bold leading-[1.1] tracking-tighter text-transparent dark:from-white dark:to-gray-500 sm:!text-5xl md:!text-7xl">
30+
{siteConfig.name}
31+
</Heading1>
32+
<DescriptionHeadingText />
33+
<div className="flex items-center space-x-4">
34+
<Link
35+
href="https://www.youtube.com/watch?v=GWalKzuC0Rg"
36+
target="_blank"
37+
className={cn(
38+
buttonVariants({
39+
variant: "outline",
40+
})
41+
)}
42+
>
43+
<Play className="mr-2" size={16} /> Demo
44+
</Link>
45+
<Link href="/apps/chat" className={cn(buttonVariants())}>
46+
Get Started
47+
</Link>
48+
</div>
4549
</div>
4650
</div>
4751
</section>

components/modules/apps/chat/ChatPanel.tsx

+16-37
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import React from "react";
44
import { useRouter } from "next/navigation";
55
import { zodResolver } from "@hookform/resolvers/zod";
66
import { Message, useChat } from "ai/react";
7-
import { SendHorizonal, Settings } from "lucide-react";
8-
import { FormProvider, useForm } from "react-hook-form";
7+
import { SendHorizonal } from "lucide-react";
8+
import { useForm } from "react-hook-form";
99
import { v4 as uuidv4 } from "uuid";
1010

1111
import { Chat, Message as SupabaseMessage } from "@/lib/db";
@@ -14,14 +14,14 @@ import { Button } from "@/components/ui/Button";
1414
import { ChatInput, ChatList } from "@/components/ui/chat";
1515
import { ChatScrollAnchor } from "@/components/ui/common/ChatScrollAnchor";
1616
import { Separator } from "@/components/ui/Separator";
17-
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/Sheet";
17+
import { Sheet } from "@/components/ui/Sheet";
1818
import { toast } from "@/components/ui/use-toast";
1919

2020
import { revalidateChatLayout } from "./action";
21-
import { ChatHistoryDrawer } from "./ChatHistoryDrawer";
22-
import { ControlSidebar } from "./control-side-bar";
21+
import { ControlSidebarSheet } from "./control-side-bar/ControlSidebarSheet";
2322
import { defaultSystemPrompt } from "./control-side-bar/data/models";
2423
import { Header } from "./Header";
24+
import { MobileDrawerControl } from "./MobileDrawerControls";
2525
import { ChatParamSchema } from "./schema";
2626
import { ChatParams } from "./types";
2727
import { buildChatRequestParams } from "./utils";
@@ -36,7 +36,7 @@ const defaultValues: ChatParams = {
3636
presencePenalty: [0],
3737
};
3838

39-
type ChatPanelProps = {
39+
export type ChatPanelProps = {
4040
chatId: Chat["id"];
4141
initialMessages: Message[];
4242
chats: Chat[] | null;
@@ -165,20 +165,9 @@ export const ChatPanel = ({
165165
setInput("");
166166
};
167167

168-
const closeSidebarSheet = () => {
168+
const closeSidebarSheet = React.useCallback(() => {
169169
setSidebarSheetOpen(false);
170-
};
171-
172-
const renderControlSidebar = () => {
173-
return (
174-
<ControlSidebar
175-
setMessages={setMessages}
176-
messages={messages}
177-
closeSidebarSheet={closeSidebarSheet}
178-
isNewChat={isNewChat}
179-
/>
180-
);
181-
};
170+
}, []);
182171

183172
return (
184173
<Sheet open={sidebarSheetOpen} onOpenChange={setSidebarSheetOpen}>
@@ -210,16 +199,7 @@ export const ChatPanel = ({
210199
onKeyDown={onKeyDown}
211200
onChange={handleOnChange}
212201
/>
213-
<div className="absolute bottom-0 left-0 flex w-1/2 px-2 pb-2 lg:hidden">
214-
<ChatHistoryDrawer data={chats} />
215-
</div>
216-
<div className="absolute bottom-0 left-12 flex w-1/2 justify-start px-2 pb-2 lg:hidden">
217-
<SheetTrigger asChild>
218-
<Button size="sm" variant="ghost">
219-
<Settings />
220-
</Button>
221-
</SheetTrigger>
222-
</div>
202+
<MobileDrawerControl chats={chats} />
223203
<div className="absolute bottom-0 right-0 flex w-1/2 justify-end px-2 pb-2">
224204
<Button size="sm" type="submit" disabled={isLoading}>
225205
Send
@@ -230,14 +210,13 @@ export const ChatPanel = ({
230210
</div>
231211
</div>
232212
</div>
233-
<FormProvider {...formReturn}>
234-
<SheetContent className="w-[400px] overflow-y-auto sm:w-[540px]">
235-
<div className="pt-4">{renderControlSidebar()}</div>
236-
</SheetContent>
237-
<div className="size-0 overflow-x-hidden transition-[width] lg:h-auto lg:max-h-[calc(100vh_-_65px)] lg:w-[450px] lg:border-x">
238-
{renderControlSidebar()}
239-
</div>
240-
</FormProvider>
213+
<ControlSidebarSheet
214+
closeSidebarSheet={closeSidebarSheet}
215+
formReturn={formReturn}
216+
isNewChat={isNewChat}
217+
messages={messages}
218+
setMessages={setMessages}
219+
/>
241220
</div>
242221
</div>
243222
</Sheet>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from "react";
2+
import { Settings } from "lucide-react";
3+
4+
import { Chat } from "@/lib/db";
5+
import { Button } from "@/components/ui/Button";
6+
import { SheetTrigger } from "@/components/ui/Sheet";
7+
8+
import { ChatHistoryDrawer } from "./ChatHistoryDrawer";
9+
10+
type MobileDrawerControlProps = {
11+
chats: Chat[] | null;
12+
};
13+
14+
export const MobileDrawerControl = React.memo(function MobileDrawerControl({
15+
chats,
16+
}: MobileDrawerControlProps) {
17+
return (
18+
<>
19+
<div className="absolute bottom-0 left-0 flex w-1/2 px-2 pb-2 lg:hidden">
20+
<ChatHistoryDrawer data={chats} />
21+
</div>
22+
<div className="absolute bottom-0 left-12 flex w-1/2 justify-start px-2 pb-2 lg:hidden">
23+
<SheetTrigger asChild>
24+
<Button size="sm" variant="ghost">
25+
<Settings />
26+
</Button>
27+
</SheetTrigger>
28+
</div>
29+
</>
30+
);
31+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from "react";
2+
import { Message, UseChatHelpers } from "ai/react";
3+
import { FormProvider, FormProviderProps } from "react-hook-form";
4+
5+
import { SheetContent } from "@/components/ui/Sheet";
6+
7+
import { ChatPanelProps } from "../ChatPanel";
8+
import { ChatParams } from "../types";
9+
import { ControlSidebar } from "./ControlSidebar";
10+
11+
type ControlSidebarSheetProps = {
12+
setMessages: UseChatHelpers["setMessages"];
13+
messages: Message[];
14+
closeSidebarSheet: () => void;
15+
isNewChat: ChatPanelProps["isNewChat"];
16+
formReturn: Omit<FormProviderProps<ChatParams>, "children">;
17+
};
18+
19+
export const ControlSidebarSheet = React.memo(function ControlSidebarSheet({
20+
setMessages,
21+
messages,
22+
closeSidebarSheet,
23+
isNewChat,
24+
formReturn,
25+
}: ControlSidebarSheetProps) {
26+
const renderControlSidebar = () => {
27+
return (
28+
<ControlSidebar
29+
setMessages={setMessages}
30+
messages={messages}
31+
closeSidebarSheet={closeSidebarSheet}
32+
isNewChat={isNewChat}
33+
/>
34+
);
35+
};
36+
37+
return (
38+
<FormProvider {...formReturn}>
39+
<SheetContent className="w-[400px] overflow-y-auto sm:w-[540px]">
40+
<div className="pt-4">{renderControlSidebar()}</div>
41+
</SheetContent>
42+
<div className="size-0 overflow-x-hidden transition-[width] lg:h-auto lg:max-h-[calc(100vh_-_65px)] lg:w-[450px] lg:border-x">
43+
{renderControlSidebar()}
44+
</div>
45+
</FormProvider>
46+
);
47+
});

components/modules/home/DescriptionHeadingText.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { motion } from "framer-motion";
55

66
export function DescriptionHeadingText() {
77
const text =
8-
"A feature-rich, highly customizable AI Chatbot Template, empowered by Next.js.";
8+
"A feature-rich, highly customizable AI Chatbot Template, powered by Next.js and Supabase.";
99
const [displayedText, setDisplayedText] = React.useState("");
1010
const [i, setI] = React.useState(0);
1111

@@ -34,7 +34,7 @@ export function DescriptionHeadingText() {
3434
>
3535
{displayedText
3636
? displayedText
37-
: "A feature-rich, highly customizable AI Chatbot Template, empowered by Next.js."}
37+
: "A feature-rich, highly customizable AI Chatbot Template, empowered by Next.js x Supabase."}
3838
</motion.span>
3939
<motion.span
4040
className="ml-1 inline-flex h-[22px] w-[2px] animate-blink rounded-full bg-current align-sub opacity-75 delay-1000"

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"geist": "^1.3.0",
4848
"lodash": "^4.17.21",
4949
"lucide-react": "0.331.0",
50+
"mini-svg-data-uri": "^1.4.4",
5051
"next": "14.1.4",
5152
"next-axiom": "^1.1.1",
5253
"next-themes": "^0.3.0",

tailwind.config.js

+50-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1+
/* eslint-disable @typescript-eslint/no-var-requires */
12
/** @type {import('tailwindcss').Config} */
3+
4+
const svgToDataUri = require("mini-svg-data-uri");
5+
6+
const {
7+
default: flattenColorPalette,
8+
} = require("tailwindcss/lib/util/flattenColorPalette");
9+
210
module.exports = {
311
darkMode: ["class"],
412
content: [
@@ -85,5 +93,46 @@ module.exports = {
8593
},
8694
},
8795
},
88-
plugins: [require("tailwindcss-animate")],
96+
plugins: [
97+
require("tailwindcss-animate"),
98+
addVariablesForColors,
99+
function ({ matchUtilities, theme }) {
100+
matchUtilities(
101+
{
102+
"bg-grid": (value) => ({
103+
backgroundImage: `url("${svgToDataUri(
104+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`
105+
)}")`,
106+
}),
107+
"bg-grid-small": (value) => ({
108+
backgroundImage: `url("${svgToDataUri(
109+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="8" height="8" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`
110+
)}")`,
111+
}),
112+
"bg-dot": (value) => ({
113+
backgroundImage: `url("${svgToDataUri(
114+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" fill="none"><circle fill="${value}" id="pattern-circle" cx="10" cy="10" r="1.6257413380501518"></circle></svg>`
115+
)}")`,
116+
}),
117+
"bg-dot-thick": (value) => ({
118+
backgroundImage: `url("${svgToDataUri(
119+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" fill="none"><circle fill="${value}" id="pattern-circle" cx="10" cy="10" r="2.5"></circle></svg>`
120+
)}")`,
121+
}),
122+
},
123+
{ values: flattenColorPalette(theme("backgroundColor")), type: "color" }
124+
);
125+
},
126+
],
89127
};
128+
129+
function addVariablesForColors({ addBase, theme }) {
130+
let allColors = flattenColorPalette(theme("colors"));
131+
let newVars = Object.fromEntries(
132+
Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
133+
);
134+
135+
addBase({
136+
":root": newVars,
137+
});
138+
}

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -4424,6 +4424,11 @@ mime-types@^2.1.12:
44244424
dependencies:
44254425
mime-db "1.52.0"
44264426

4427+
mini-svg-data-uri@^1.4.4:
4428+
version "1.4.4"
4429+
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939"
4430+
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
4431+
44274432
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
44284433
version "3.1.2"
44294434
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"

0 commit comments

Comments
 (0)