@@ -8,19 +8,23 @@ import { checkToolState, InvalidTokenError } from '@/lib/tinybird';
88import { TOOLS , type AppGridItem , type ToolState } from '@/lib/constants' ;
99import { SectionHeader } from '@/components/section-header' ;
1010import { ScrollArea } from '@/components/ui/scroll-area' ;
11- import { MessageSquare , HardDriveDownload , Settings } from 'lucide-react' ;
11+ import { MessageSquare , HardDriveDownload , Settings , ChevronRight , ChevronLeft , Menu , LayoutDashboard , Download , Plus } from 'lucide-react' ;
12+ import { Button } from '@/components/ui/button' ;
13+ import { Sheet , SheetContent , SheetTrigger } from '@/components/ui/sheet' ;
1214import Image from 'next/image' ;
1315
1416function AppCard ( {
1517 app,
1618 state,
1719 token,
1820 isActive,
21+ isCollapsed
1922} : {
2023 app : AppGridItem ;
2124 state : ToolState ;
2225 token ?: string | null ;
2326 isActive : boolean ;
27+ isCollapsed ?: boolean ;
2428} ) {
2529 const stateColors = {
2630 configured : '' ,
@@ -30,19 +34,21 @@ function AppCard({
3034
3135 return (
3236 < Link
33- key = { app . id }
37+ className = "block"
3438 href = { `/${ app . id } ${ token ? `?token=${ token } ` : '' } ` }
3539 >
36- < Card className = { `p-3 hover:bg-accent mb-2 ${ stateColors [ state ] } ${ isActive ? 'bg-accent' : '' } ` } >
37- < div className = "flex items-center justify-between w-full" >
38- < div className = "flex items-center gap-3" >
39- { app . icon_url && < Image src = { app . icon_url } width = { 16 } height = { 16 } alt = { app . name } /> }
40- < span className = "font-medium" > { app . name } </ span >
41- </ div >
42- < div >
43- { state === 'available' && < HardDriveDownload className = "w-4 h-4 text-muted-foreground" /> }
44- { state === 'installed' && < Settings className = "w-4 h-4 text-muted-foreground" /> }
40+ < Card className = { `h-[42px] hover:bg-accent mb-2 ${ stateColors [ state ] } ${ isActive ? 'bg-accent' : '' } ${ isCollapsed ? 'w-10' : '' } ` } >
41+ < div className = { `flex items-center justify-between h-full ${ isCollapsed ? 'px-2' : 'px-3' } w-full` } >
42+ < div className = { `flex items-center gap-3 min-w-0 ${ isCollapsed && 'mx-auto' } ` } >
43+ { app . icon_url && < Image src = { app . icon_url } width = { 16 } height = { 16 } alt = { app . name } className = "flex-shrink-0" /> }
44+ { ! isCollapsed && < span className = "font-medium truncate" > { app . name } </ span > }
4545 </ div >
46+ { ! isCollapsed && (
47+ < div >
48+ { state === 'available' && < HardDriveDownload className = "w-4 h-4 text-muted-foreground" /> }
49+ { state === 'installed' && < Settings className = "w-4 h-4 text-muted-foreground" /> }
50+ </ div >
51+ ) }
4652 </ div >
4753 </ Card >
4854 </ Link >
@@ -72,7 +78,7 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
7278 const [ token , setToken ] = useQueryState ( 'token' ) ;
7379 const [ toolStates , setToolStates ] = useState < Record < string , ToolState > > ( { } ) ;
7480 const [ isLoading , setIsLoading ] = useState ( false ) ;
75- // const [error,setError ] = useState<string>( );
81+ const [ isCollapsed , setIsCollapsed ] = useState ( false ) ;
7682
7783 useEffect ( ( ) => {
7884 async function fetchToolStates ( ) {
@@ -83,7 +89,6 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
8389 setToolStates ( Object . fromEntries ( states ) ) ;
8490 } else {
8591 setIsLoading ( true ) ;
86- // setError(undefined);
8792 try {
8893 const allStates = await checkToolState ( token ) ;
8994 const states = Object . values ( TOOLS ) . map ( ( app ) => {
@@ -92,11 +97,9 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
9297 setToolStates ( Object . fromEntries ( states ) ) ;
9398 } catch ( error ) {
9499 if ( error instanceof InvalidTokenError ) {
95- // setError('Invalid token');
96100 setToken ( null ) ;
97101 } else {
98102 console . error ( 'Failed to fetch tool states:' , error ) ;
99- // setError('Failed to fetch tool states');
100103 }
101104 } finally {
102105 setIsLoading ( false ) ;
@@ -107,17 +110,78 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
107110 } , [ token , setToken ] ) ;
108111
109112 return (
110- < div className = "w-64 border-r h-screen" >
113+ < >
114+ { /* Mobile Menu Button */ }
115+ < Sheet >
116+ < SheetTrigger asChild >
117+ < Button variant = "ghost" size = "icon" className = "fixed top-4 left-4 md:hidden" >
118+ < Menu className = "h-6 w-6" />
119+ </ Button >
120+ </ SheetTrigger >
121+ < SheetContent side = "left" className = "p-0 w-80" >
122+ < SidebarInner
123+ token = { token }
124+ activeAppId = { activeAppId }
125+ toolStates = { toolStates }
126+ isLoading = { isLoading }
127+ />
128+ </ SheetContent >
129+ </ Sheet >
130+
131+ { /* Desktop Sidebar */ }
132+ < div className = { `border-r h-screen hidden md:block relative ${ isCollapsed ? 'w-auto' : 'w-64' } transition-all duration-300` } >
133+ < SidebarInner
134+ token = { token }
135+ activeAppId = { activeAppId }
136+ toolStates = { toolStates }
137+ isLoading = { isLoading }
138+ isCollapsed = { isCollapsed }
139+ />
140+
141+ { /* Collapse Button */ }
142+ < Button
143+ variant = "ghost"
144+ size = "icon"
145+ className = { `absolute ${ isCollapsed ? '-right-4' : '-right-3' } bottom-4 rounded-full border shadow-md bg-background` }
146+ onClick = { ( ) => setIsCollapsed ( ! isCollapsed ) }
147+ >
148+ { isCollapsed ? (
149+ < ChevronRight className = "h-4 w-4" />
150+ ) : (
151+ < ChevronLeft className = "h-4 w-4" />
152+ ) }
153+ </ Button >
154+ </ div >
155+ </ >
156+ ) ;
157+ }
158+
159+ // New component for the inner content of the sidebar
160+ function SidebarInner ( {
161+ token,
162+ activeAppId,
163+ toolStates,
164+ isLoading,
165+ isCollapsed
166+ } : {
167+ token : string | null | undefined ;
168+ activeAppId ?: string ;
169+ toolStates : Record < string , ToolState > ;
170+ isLoading : boolean ;
171+ isCollapsed ?: boolean ;
172+ } ) {
173+ return (
174+ < >
111175 < div className = "p-4 border-b" >
112176 < Link
113177 href = { token ? `/?token=${ token } ` : '/' }
114- className = "text-xl font-bold hover:text-primary transition-colors"
178+ className = { ` font-bold hover:text-primary transition-colors ${ isCollapsed ? 'text-lg' : 'text-xl' } ` }
115179 >
116- tinynest
180+ { isCollapsed ? 'tn' : ' tinynest' }
117181 </ Link >
118182 </ div >
119183
120- < ScrollArea className = " h-[calc(100vh-65px)] px-4 py-6" >
184+ < ScrollArea className = { ` h-[calc(100vh-65px)] py-6 ${ isCollapsed ? ' px-1' : 'px-4' } ` } >
121185 { isLoading ? (
122186 < div className = "flex items-center justify-center" >
123187 < p className = "text-sm font-semibold" > Loading...</ p >
@@ -128,11 +192,15 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
128192 href = { token ? `/chat?token=${ token } ` : '/chat' }
129193 className = "block"
130194 >
131- < Card className = { `p-3 hover:bg-accent mb-2 ${ activeAppId === 'chat' ? 'bg-accent' : '' } ` } >
132- < div className = "flex items-center gap-3" >
133- < MessageSquare className = "w-5 h-5" />
134- < div >
135- < h3 className = "font-semibold text-sm" > Chat</ h3 >
195+ < Card className = { `h-[42px] hover:bg-accent mb-2 ${ activeAppId === 'chat' ? 'bg-accent' : '' } ${ isCollapsed ? 'w-10' : '' } ` } >
196+ < div className = { `flex items-center h-full ${ isCollapsed ? 'px-2' : 'px-3' } w-full` } >
197+ < div className = { `flex items-center gap-3 min-w-0 ${ isCollapsed && 'mx-auto' } ` } >
198+ < MessageSquare className = "w-4 h-4 flex-shrink-0" />
199+ { ! isCollapsed && (
200+ < div >
201+ < h3 className = "font-medium text-sm" > Chat</ h3 >
202+ </ div >
203+ ) }
136204 </ div >
137205 </ div >
138206 </ Card >
@@ -141,10 +209,16 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
141209 { /* Configured Apps */ }
142210 { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'configured' ) && (
143211 < div className = "space-y-2" >
144- < SectionHeader
145- title = "Dashboards"
146- tooltip = "These apps are fully set up and have data. They're ready to use!"
147- />
212+ { isCollapsed ? (
213+ < div className = "h-[24px] flex items-center justify-center mb-2 w-10" >
214+ < LayoutDashboard className = "w-4 h-4 text-muted-foreground" />
215+ </ div >
216+ ) : (
217+ < SectionHeader
218+ title = "Dashboards"
219+ tooltip = "These apps are fully set up and have data. They're ready to use!"
220+ />
221+ ) }
148222 < div className = "space-y-2" >
149223 { Object . values ( TOOLS )
150224 . filter ( app => toolStates [ app . id ] === 'configured' )
@@ -155,6 +229,7 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
155229 state = { toolStates [ app . id ] }
156230 token = { token }
157231 isActive = { app . id === activeAppId }
232+ isCollapsed = { isCollapsed }
158233 />
159234 ) ) }
160235 </ div >
@@ -164,10 +239,16 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
164239 { /* Installed Apps */ }
165240 { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'installed' ) && (
166241 < div className = "space-y-2" >
167- < SectionHeader
168- title = "Installed"
169- tooltip = "Your Tinybird Workspace has the Data Sources installed, but you're not receiving data. Click an app to learn how to add data."
170- />
242+ { isCollapsed ? (
243+ < div className = "h-[24px] flex items-center justify-center mb-2 w-10" >
244+ < Settings className = "w-4 h-4 text-muted-foreground" />
245+ </ div >
246+ ) : (
247+ < SectionHeader
248+ title = "Installed"
249+ tooltip = "Your Tinybird Workspace has the Data Sources installed, but you're not receiving data. Click an app to learn how to add data."
250+ />
251+ ) }
171252 < div className = "space-y-2" >
172253 { Object . values ( TOOLS )
173254 . filter ( app => toolStates [ app . id ] === 'installed' )
@@ -178,6 +259,7 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
178259 state = { toolStates [ app . id ] }
179260 token = { token }
180261 isActive = { app . id === activeAppId }
262+ isCollapsed = { isCollapsed }
181263 />
182264 ) ) }
183265 </ div >
@@ -187,10 +269,16 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
187269 { /* Available Apps */ }
188270 { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'available' ) && (
189271 < div className = "space-y-2" >
190- < SectionHeader
191- title = "Available"
192- tooltip = "Your Tinybird Workspace doesn't have the Data Sources installed yet. Click an app to learn how to install it."
193- />
272+ { isCollapsed ? (
273+ < div className = "h-[24px] flex items-center justify-center mb-2 w-10" >
274+ < Plus className = "w-4 h-4 text-muted-foreground" />
275+ </ div >
276+ ) : (
277+ < SectionHeader
278+ title = "Available"
279+ tooltip = "Your Tinybird Workspace doesn't have the Data Sources installed yet. Click an app to learn how to install it."
280+ />
281+ ) }
194282 < div className = "space-y-2" >
195283 { Object . values ( TOOLS )
196284 . filter ( app => ! toolStates [ app . id ] || toolStates [ app . id ] === 'available' )
@@ -201,16 +289,15 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
201289 state = { toolStates [ app . id ] || 'available' }
202290 token = { token }
203291 isActive = { app . id === activeAppId }
292+ isCollapsed = { isCollapsed }
204293 />
205294 ) ) }
206295 </ div >
207296 </ div >
208297 ) }
209-
210298 </ div >
211-
212299 ) }
213300 </ ScrollArea >
214- </ div >
301+ </ >
215302 ) ;
216303}
0 commit comments