@@ -8,40 +8,47 @@ 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 } from 'lucide-react' ;
11+ import { MessageSquare , HardDriveDownload , Settings , ChevronRight , ChevronLeft , Menu , LayoutDashboard , Plus } from 'lucide-react' ;
12+ import { Button } from '@/components/ui/button' ;
13+ import { Sheet , SheetContent , SheetTrigger , SheetTitle } 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 = {
26- configured : 'border-green-500 ' ,
27- installed : 'border-blue-500 ' ,
30+ configured : '' ,
31+ installed : '' ,
2832 available : ''
2933 } ;
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 gap-3" >
38- { app . icon_url && < Image src = { app . icon_url } width = { 16 } height = { 16 } alt = { app . name } /> }
39- < div >
40- < div className = "flex items-center gap-2" >
41- < h3 className = "font-semibold text-sm" > { app . name } </ h3 >
42- < span className = "text-xs text-muted-foreground" > ({ state } )</ span >
43- </ div >
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 > }
4445 </ 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+ ) }
4552 </ div >
4653 </ Card >
4754 </ Link >
@@ -71,46 +78,111 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
7178 const [ token , setToken ] = useQueryState ( 'token' ) ;
7279 const [ toolStates , setToolStates ] = useState < Record < string , ToolState > > ( { } ) ;
7380 const [ isLoading , setIsLoading ] = useState ( false ) ;
74- // const [error,setError ] = useState<string>( );
81+ const [ isCollapsed , setIsCollapsed ] = useState ( false ) ;
7582
7683 useEffect ( ( ) => {
7784 async function fetchToolStates ( ) {
78- if ( ! token ) return ;
79- setIsLoading ( true ) ;
80- // setError(undefined);
81- try {
82- const allStates = await checkToolState ( token ) ;
85+ if ( ! token ) {
8386 const states = Object . values ( TOOLS ) . map ( ( app ) => {
84- return [ app . id , allStates [ app . ds ] ] as const ;
87+ return [ app . id , 'available' ] as const ;
8588 } ) ;
8689 setToolStates ( Object . fromEntries ( states ) ) ;
87- } catch ( error ) {
88- if ( error instanceof InvalidTokenError ) {
89- // setError('Invalid token');
90- setToken ( null ) ;
91- } else {
92- console . error ( 'Failed to fetch tool states:' , error ) ;
93- // setError('Failed to fetch tool states');
90+ } else {
91+ setIsLoading ( true ) ;
92+ try {
93+ const allStates = await checkToolState ( token ) ;
94+ const states = Object . values ( TOOLS ) . map ( ( app ) => {
95+ return [ app . id , allStates [ app . ds ] ?? 'available' ] as const ;
96+ } ) ;
97+ setToolStates ( Object . fromEntries ( states ) ) ;
98+ } catch ( error ) {
99+ if ( error instanceof InvalidTokenError ) {
100+ setToken ( null ) ;
101+ } else {
102+ console . error ( 'Failed to fetch tool states:' , error ) ;
103+ }
104+ } finally {
105+ setIsLoading ( false ) ;
94106 }
95- } finally {
96- setIsLoading ( false ) ;
97107 }
98108 }
99- if ( token ) fetchToolStates ( ) ;
109+ fetchToolStates ( ) ;
100110 } , [ token , setToken ] ) ;
101111
102112 return (
103- < 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+ < SheetTitle className = "sr-only" > Navigation Menu</ SheetTitle >
123+ < SidebarInner
124+ token = { token }
125+ activeAppId = { activeAppId }
126+ toolStates = { toolStates }
127+ isLoading = { isLoading }
128+ />
129+ </ SheetContent >
130+ </ Sheet >
131+
132+ { /* Desktop Sidebar */ }
133+ < div className = { `border-r h-screen hidden md:block relative ${ isCollapsed ? 'w-auto' : 'w-64' } transition-all duration-300` } >
134+ < SidebarInner
135+ token = { token }
136+ activeAppId = { activeAppId }
137+ toolStates = { toolStates }
138+ isLoading = { isLoading }
139+ isCollapsed = { isCollapsed }
140+ />
141+
142+ { /* Collapse Button */ }
143+ < Button
144+ variant = "ghost"
145+ size = "icon"
146+ className = { `absolute ${ isCollapsed ? '-right-4' : '-right-3' } bottom-4 rounded-full border shadow-md bg-background` }
147+ onClick = { ( ) => setIsCollapsed ( ! isCollapsed ) }
148+ >
149+ { isCollapsed ? (
150+ < ChevronRight className = "h-4 w-4" />
151+ ) : (
152+ < ChevronLeft className = "h-4 w-4" />
153+ ) }
154+ </ Button >
155+ </ div >
156+ </ >
157+ ) ;
158+ }
159+
160+ // New component for the inner content of the sidebar
161+ function SidebarInner ( {
162+ token,
163+ activeAppId,
164+ toolStates,
165+ isLoading,
166+ isCollapsed
167+ } : {
168+ token : string | null | undefined ;
169+ activeAppId ?: string ;
170+ toolStates : Record < string , ToolState > ;
171+ isLoading : boolean ;
172+ isCollapsed ?: boolean ;
173+ } ) {
174+ return (
175+ < >
104176 < div className = "p-4 border-b" >
105177 < Link
106178 href = { token ? `/?token=${ token } ` : '/' }
107- className = "text-xl font-bold hover:text-primary transition-colors"
179+ className = { ` font-bold hover:text-primary transition-colors ${ isCollapsed ? 'text-lg' : 'text-xl' } ` }
108180 >
109- tinynest
181+ { isCollapsed ? 'tn' : ' tinynest' }
110182 </ Link >
111183 </ div >
112184
113- < ScrollArea className = " h-[calc(100vh-65px)] px-4 py-6" >
185+ < ScrollArea className = { ` h-[calc(100vh-65px)] py-6 ${ isCollapsed ? ' px-1' : 'px-4' } ` } >
114186 { isLoading ? (
115187 < div className = "flex items-center justify-center" >
116188 < p className = "text-sm font-semibold" > Loading...</ p >
@@ -121,11 +193,15 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
121193 href = { token ? `/chat?token=${ token } ` : '/chat' }
122194 className = "block"
123195 >
124- < Card className = { `p-3 hover:bg-accent mb-2 ${ activeAppId === 'chat' ? 'bg-accent' : '' } ` } >
125- < div className = "flex items-center gap-3" >
126- < MessageSquare className = "w-5 h-5" />
127- < div >
128- < h3 className = "font-semibold text-sm" > Chat</ h3 >
196+ < Card className = { `h-[42px] hover:bg-accent mb-2 ${ activeAppId === 'chat' ? 'bg-accent' : '' } ${ isCollapsed ? 'w-10' : '' } ` } >
197+ < div className = { `flex items-center h-full ${ isCollapsed ? 'px-2' : 'px-3' } w-full` } >
198+ < div className = { `flex items-center gap-3 min-w-0 ${ isCollapsed && 'mx-auto' } ` } >
199+ < MessageSquare className = "w-4 h-4 flex-shrink-0" />
200+ { ! isCollapsed && (
201+ < div >
202+ < h3 className = "font-medium text-sm" > Chat</ h3 >
203+ </ div >
204+ ) }
129205 </ div >
130206 </ div >
131207 </ Card >
@@ -134,10 +210,16 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
134210 { /* Configured Apps */ }
135211 { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'configured' ) && (
136212 < div className = "space-y-2" >
137- < SectionHeader
138- title = "Configured Apps"
139- tooltip = "These apps are fully set up and have data. They're ready to use!"
140- />
213+ { isCollapsed ? (
214+ < div className = "h-[24px] flex items-center justify-center mb-2 w-10" >
215+ < LayoutDashboard className = "w-4 h-4 text-muted-foreground" />
216+ </ div >
217+ ) : (
218+ < SectionHeader
219+ title = "Dashboards"
220+ tooltip = "These apps are fully set up and have data. They're ready to use!"
221+ />
222+ ) }
141223 < div className = "space-y-2" >
142224 { Object . values ( TOOLS )
143225 . filter ( app => toolStates [ app . id ] === 'configured' )
@@ -148,6 +230,7 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
148230 state = { toolStates [ app . id ] }
149231 token = { token }
150232 isActive = { app . id === activeAppId }
233+ isCollapsed = { isCollapsed }
151234 />
152235 ) ) }
153236 </ div >
@@ -157,10 +240,16 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
157240 { /* Installed Apps */ }
158241 { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'installed' ) && (
159242 < div className = "space-y-2" >
160- < SectionHeader
161- title = "Installed Apps"
162- tooltip = "Your Tinybird Workspace has the Data Sources installed, but you're not receiving data. Click an app to learn how to add data."
163- />
243+ { isCollapsed ? (
244+ < div className = "h-[24px] flex items-center justify-center mb-2 w-10" >
245+ < Settings className = "w-4 h-4 text-muted-foreground" />
246+ </ div >
247+ ) : (
248+ < SectionHeader
249+ title = "Installed"
250+ tooltip = "Your Tinybird Workspace has the Data Sources installed, but you're not receiving data. Click an app to learn how to add data."
251+ />
252+ ) }
164253 < div className = "space-y-2" >
165254 { Object . values ( TOOLS )
166255 . filter ( app => toolStates [ app . id ] === 'installed' )
@@ -171,6 +260,7 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
171260 state = { toolStates [ app . id ] }
172261 token = { token }
173262 isActive = { app . id === activeAppId }
263+ isCollapsed = { isCollapsed }
174264 />
175265 ) ) }
176266 </ div >
@@ -180,10 +270,16 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
180270 { /* Available Apps */ }
181271 { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'available' ) && (
182272 < div className = "space-y-2" >
183- < SectionHeader
184- title = "Available Apps"
185- tooltip = "Your Tinybird Workspace doesn't have the Data Sources installed yet. Click an app to learn how to install it."
186- />
273+ { isCollapsed ? (
274+ < div className = "h-[24px] flex items-center justify-center mb-2 w-10" >
275+ < Plus className = "w-4 h-4 text-muted-foreground" />
276+ </ div >
277+ ) : (
278+ < SectionHeader
279+ title = "Available"
280+ tooltip = "Your Tinybird Workspace doesn't have the Data Sources installed yet. Click an app to learn how to install it."
281+ />
282+ ) }
187283 < div className = "space-y-2" >
188284 { Object . values ( TOOLS )
189285 . filter ( app => ! toolStates [ app . id ] || toolStates [ app . id ] === 'available' )
@@ -194,16 +290,15 @@ function SidebarContent({ activeAppId }: { activeAppId?: string }) {
194290 state = { toolStates [ app . id ] || 'available' }
195291 token = { token }
196292 isActive = { app . id === activeAppId }
293+ isCollapsed = { isCollapsed }
197294 />
198295 ) ) }
199296 </ div >
200297 </ div >
201298 ) }
202-
203299 </ div >
204-
205300 ) }
206301 </ ScrollArea >
207- </ div >
302+ </ >
208303 ) ;
209304}
0 commit comments