Skip to content

Commit 31de38a

Browse files
authored
ui: mobile chatbot improvements (#1155)
1 parent fd8ed4d commit 31de38a

16 files changed

+543
-445
lines changed

app/globals.css

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020
.text-balance {
2121
text-wrap: balance;
2222
}
23+
24+
.-webkit-overflow-scrolling-touch {
25+
-webkit-overflow-scrolling: touch;
26+
}
27+
28+
.touch-pan-y {
29+
touch-action: pan-y;
30+
}
31+
32+
.overscroll-behavior-contain {
33+
overscroll-behavior: contain;
34+
}
2335
}
2436

2537
@layer base {
@@ -101,6 +113,12 @@
101113

102114
body {
103115
@apply bg-background text-foreground;
116+
overflow-x: hidden;
117+
position: relative;
118+
}
119+
120+
html {
121+
overflow-x: hidden;
104122
}
105123
}
106124

@@ -162,3 +180,33 @@
162180
.suggestion-highlight {
163181
@apply bg-blue-200 hover:bg-blue-300 dark:hover:bg-blue-400/50 dark:text-blue-50 dark:bg-blue-500/40;
164182
}
183+
184+
/* minimal scrollbar styling */
185+
::-webkit-scrollbar {
186+
width: 6px;
187+
height: 6px;
188+
}
189+
190+
::-webkit-scrollbar-track {
191+
background: transparent;
192+
}
193+
194+
::-webkit-scrollbar-thumb {
195+
background: hsl(var(--border));
196+
border-radius: 3px;
197+
transition: background 0.2s ease;
198+
}
199+
200+
::-webkit-scrollbar-thumb:hover {
201+
background: hsl(var(--muted-foreground) / 0.5);
202+
}
203+
204+
::-webkit-scrollbar-corner {
205+
background: transparent;
206+
}
207+
208+
/* firefox scrollbar styling */
209+
* {
210+
scrollbar-width: thin;
211+
scrollbar-color: hsl(var(--border)) transparent;
212+
}

components/app-sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function AppSidebar({ user }: { user: User | undefined }) {
4343
<Button
4444
variant="ghost"
4545
type="button"
46-
className="p-2 h-fit"
46+
className="p-1 h-8 md:p-2 md:h-fit"
4747
onClick={() => {
4848
setOpenMobile(false);
4949
router.push('/');
@@ -53,7 +53,7 @@ export function AppSidebar({ user }: { user: User | undefined }) {
5353
<PlusIcon />
5454
</Button>
5555
</TooltipTrigger>
56-
<TooltipContent align="end">New Chat</TooltipContent>
56+
<TooltipContent align="end" className="hidden md:block">New Chat</TooltipContent>
5757
</Tooltip>
5858
</div>
5959
</SidebarMenu>

components/chat-header.tsx

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { Button } from '@/components/ui/button';
99
import { PlusIcon, VercelIcon } from './icons';
1010
import { useSidebar } from './ui/sidebar';
1111
import { memo } from 'react';
12-
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
1312
import { type VisibilityType, VisibilitySelector } from './visibility-selector';
1413
import type { Session } from 'next-auth';
1514

@@ -34,22 +33,17 @@ function PureChatHeader({
3433
<SidebarToggle />
3534

3635
{(!open || windowWidth < 768) && (
37-
<Tooltip>
38-
<TooltipTrigger asChild>
39-
<Button
40-
variant="outline"
41-
className="order-2 md:order-1 md:px-2 px-2 md:h-fit ml-auto md:ml-0"
42-
onClick={() => {
43-
router.push('/');
44-
router.refresh();
45-
}}
46-
>
47-
<PlusIcon />
48-
<span className="md:sr-only">New Chat</span>
49-
</Button>
50-
</TooltipTrigger>
51-
<TooltipContent>New Chat</TooltipContent>
52-
</Tooltip>
36+
<Button
37+
variant="outline"
38+
className="order-2 md:order-1 md:px-2 px-2 md:h-fit ml-auto md:ml-0"
39+
onClick={() => {
40+
router.push('/');
41+
router.refresh();
42+
}}
43+
>
44+
<PlusIcon />
45+
<span className="md:sr-only">New Chat</span>
46+
</Button>
5347
)}
5448

5549
{!isReadonly && (

components/chat.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useEffect, useState } from 'react';
66
import useSWR, { useSWRConfig } from 'swr';
77
import { ChatHeader } from '@/components/chat-header';
88
import type { Vote } from '@/lib/db/schema';
9-
import { fetcher, fetchWithErrorHandlers, generateUUID, cn } from '@/lib/utils';
9+
import { fetcher, fetchWithErrorHandlers, generateUUID } from '@/lib/utils';
1010
import { Artifact } from './artifact';
1111
import { MultimodalInput } from './multimodal-input';
1212
import { Messages } from './messages';
@@ -128,7 +128,7 @@ export function Chat({
128128

129129
return (
130130
<>
131-
<div className="flex flex-col min-w-0 h-dvh bg-background">
131+
<div className="flex flex-col min-w-0 h-dvh bg-background touch-pan-y overscroll-behavior-contain">
132132
<ChatHeader
133133
chatId={id}
134134
selectedVisibilityType={initialVisibilityType}
@@ -145,9 +145,10 @@ export function Chat({
145145
regenerate={regenerate}
146146
isReadonly={isReadonly}
147147
isArtifactVisible={isArtifactVisible}
148+
selectedModelId={initialChatModel}
148149
/>
149150

150-
<div className="sticky bottom-0 flex gap-2 px-4 pb-4 mx-auto w-full bg-background md:pb-6 md:max-w-3xl z-[1] border-t-0">
151+
<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">
151152
{!isReadonly && (
152153
<MultimodalInput
153154
chatId={id}

components/elements/actions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { ComponentProps } from 'react';
1313
export type ActionsProps = ComponentProps<'div'>;
1414

1515
export const Actions = ({ className, children, ...props }: ActionsProps) => (
16-
<div className={cn('flex items-center gap-1', className)} {...props}>
16+
<div className={cn('flex gap-1 items-center', className)} {...props}>
1717
{children}
1818
</div>
1919
);

components/elements/conversation.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export type ConversationProps = ComponentProps<typeof StickToBottom>;
1111

1212
export const Conversation = ({ className, ...props }: ConversationProps) => (
1313
<StickToBottom
14-
className={cn('relative flex-1 overflow-y-auto', className)}
14+
className={cn(
15+
'overflow-y-auto relative flex-1 touch-pan-y will-change-scroll',
16+
className,
17+
)}
1518
initial="smooth"
1619
resize="smooth"
1720
role="log"
@@ -46,7 +49,7 @@ export const ConversationScrollButton = ({
4649
!isAtBottom && (
4750
<Button
4851
className={cn(
49-
'absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full',
52+
'absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full z-10 shadow-lg',
5053
className,
5154
)}
5255
onClick={handleScrollToBottom}

components/elements/reasoning.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export type ReasoningProps = ComponentProps<typeof Collapsible> & {
3737
duration?: number;
3838
};
3939

40-
const AUTO_CLOSE_DELAY = 1000;
40+
const AUTO_CLOSE_DELAY = 500;
4141
const MS_IN_S = 1000;
4242

4343
export const Reasoning = memo(
@@ -98,7 +98,7 @@ export const Reasoning = memo(
9898
value={{ isStreaming, isOpen, setIsOpen, duration }}
9999
>
100100
<Collapsible
101-
className={cn('not-prose mb-4', className)}
101+
className={cn('not-prose', className)}
102102
onOpenChange={handleOpenChange}
103103
open={isOpen}
104104
{...props}
@@ -119,7 +119,7 @@ export const ReasoningTrigger = memo(
119119
return (
120120
<CollapsibleTrigger
121121
className={cn(
122-
'flex items-center gap-2 text-muted-foreground text-sm',
122+
'flex items-center gap-1.5 text-muted-foreground text-xs hover:text-foreground transition-colors',
123123
className,
124124
)}
125125
{...props}
@@ -130,11 +130,11 @@ export const ReasoningTrigger = memo(
130130
{isStreaming || duration === 0 ? (
131131
<p>Thinking...</p>
132132
) : (
133-
<p>Thought for {duration} seconds</p>
133+
<p>Thought for {duration}s</p>
134134
)}
135135
<ChevronDownIcon
136136
className={cn(
137-
'size-4 text-muted-foreground transition-transform',
137+
'size-3 text-muted-foreground transition-transform',
138138
isOpen ? 'rotate-180' : 'rotate-0',
139139
)}
140140
/>
@@ -155,8 +155,8 @@ export const ReasoningContent = memo(
155155
({ className, children, ...props }: ReasoningContentProps) => (
156156
<CollapsibleContent
157157
className={cn(
158-
'mt-4 text-sm',
159-
'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',
158+
'mt-2 text-xs text-muted-foreground',
159+
'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',
160160
className,
161161
)}
162162
{...props}

components/greeting.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ export const Greeting = () => {
44
return (
55
<div
66
key="overview"
7-
className="max-w-3xl mx-auto md:mt-20 px-8 size-full flex flex-col justify-center"
7+
className="max-w-3xl mx-auto mt-4 md:mt-16 px-4 md:px-8 size-full flex flex-col justify-center"
88
>
99
<motion.div
1010
initial={{ opacity: 0, y: 10 }}
1111
animate={{ opacity: 1, y: 0 }}
1212
exit={{ opacity: 0, y: 10 }}
1313
transition={{ delay: 0.5 }}
14-
className="text-2xl font-semibold"
14+
className="text-xl md:text-2xl font-semibold"
1515
>
1616
Hello there!
1717
</motion.div>
@@ -20,7 +20,7 @@ export const Greeting = () => {
2020
animate={{ opacity: 1, y: 0 }}
2121
exit={{ opacity: 0, y: 10 }}
2222
transition={{ delay: 0.6 }}
23-
className="text-2xl text-zinc-500"
23+
className="text-xl md:text-2xl text-zinc-500"
2424
>
2525
How can I help you today?
2626
</motion.div>

0 commit comments

Comments
 (0)