Girish Jeswani commited on
Commit
fbdb190
·
1 Parent(s): 0b5b8ef

add collapse button

Browse files
phd-advisor-frontend/src/components/Sidebar.js CHANGED
@@ -8,7 +8,9 @@ import {
8
  Edit3,
9
  LogOut,
10
  User,
11
- Settings
 
 
12
  } from 'lucide-react';
13
  import '../styles/Sidebar.css';
14
 
@@ -18,12 +20,14 @@ const Sidebar = ({
18
  onSelectSession,
19
  onNewChat,
20
  onSignOut,
21
- authToken
 
22
  }) => {
23
  const [chatSessions, setChatSessions] = useState([]);
24
  const [searchTerm, setSearchTerm] = useState('');
25
  const [isLoading, setIsLoading] = useState(true);
26
  const [showUserMenu, setShowUserMenu] = useState(false);
 
27
 
28
  useEffect(() => {
29
  if (authToken) {
@@ -31,6 +35,13 @@ const Sidebar = ({
31
  }
32
  }, [authToken]);
33
 
 
 
 
 
 
 
 
34
  const fetchChatSessions = async () => {
35
  try {
36
  const response = await fetch('http://localhost:8000/api/chat-sessions', {
@@ -101,6 +112,14 @@ const Sidebar = ({
101
  }
102
  };
103
 
 
 
 
 
 
 
 
 
104
  const filteredSessions = chatSessions.filter(session =>
105
  session.title.toLowerCase().includes(searchTerm.toLowerCase())
106
  );
@@ -118,101 +137,139 @@ const Sidebar = ({
118
  };
119
 
120
  return (
121
- <div className="sidebar">
122
  {/* Header */}
123
  <div className="sidebar-header">
124
- <div className="user-section">
125
- <div className="user-info">
126
- <div className="user-avatar">
127
- <User size={20} />
128
- </div>
129
- <div className="user-details">
130
- <span className="user-name">{user.firstName} {user.lastName}</span>
131
- <span className="user-email">{user.email}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  </div>
133
- </div>
134
-
135
- <div className="user-menu-container">
 
 
 
 
 
 
 
 
136
  <button
137
- className="user-menu-button"
138
- onClick={() => setShowUserMenu(!showUserMenu)}
 
139
  >
140
- <MoreVertical size={16} />
 
 
 
141
  </button>
142
-
143
- {showUserMenu && (
144
- <div className="user-menu">
145
- <button className="user-menu-item">
146
- <Settings size={16} />
147
- <span>Settings</span>
148
- </button>
149
- <button className="user-menu-item sign-out" onClick={onSignOut}>
150
- <LogOut size={16} />
151
- <span>Sign Out</span>
152
- </button>
153
- </div>
154
- )}
155
  </div>
156
- </div>
157
-
158
- <button className="new-chat-button" onClick={handleNewChat}>
159
- <Plus size={16} />
160
- <span>New Chat</span>
161
- </button>
162
  </div>
163
 
164
- {/* Search */}
165
- <div className="sidebar-search">
166
- <div className="search-container">
167
- <Search size={16} className="search-icon" />
168
- <input
169
- type="text"
170
- placeholder="Search chats..."
171
- value={searchTerm}
172
- onChange={(e) => setSearchTerm(e.target.value)}
173
- className="search-input"
174
- />
 
 
175
  </div>
176
- </div>
177
 
178
  {/* Chat Sessions */}
179
  <div className="chat-sessions">
180
  {isLoading ? (
181
  <div className="loading-sessions">
182
  <div className="loading-spinner"></div>
183
- <span>Loading chats...</span>
184
  </div>
185
  ) : filteredSessions.length === 0 ? (
186
  <div className="no-sessions">
187
- {searchTerm ? 'No chats found' : 'No chats yet'}
188
  </div>
189
  ) : (
190
  <div className="sessions-list">
191
  {filteredSessions.map((session) => (
192
  <div
193
  key={session.id}
194
- className={`session-item ${currentSessionId === session.id ? 'active' : ''}`}
195
  onClick={() => onSelectSession(session.id)}
 
196
  >
197
  <div className="session-content">
198
  <div className="session-icon">
199
  <MessageSquare size={16} />
200
  </div>
201
- <div className="session-details">
202
- <div className="session-title">{session.title}</div>
203
- <div className="session-meta">
204
- <span className="session-date">{formatDate(session.updated_at)}</span>
205
- <span className="session-messages">{session.message_count} messages</span>
 
 
206
  </div>
207
- </div>
208
  </div>
209
 
210
- <button
211
- className="session-menu-button"
212
- onClick={(e) => handleDeleteSession(session.id, e)}
213
- >
214
- <Trash2 size={14} />
215
- </button>
 
 
216
  </div>
217
  ))}
218
  </div>
 
8
  Edit3,
9
  LogOut,
10
  User,
11
+ Settings,
12
+ ChevronLeft,
13
+ ChevronRight
14
  } from 'lucide-react';
15
  import '../styles/Sidebar.css';
16
 
 
20
  onSelectSession,
21
  onNewChat,
22
  onSignOut,
23
+ authToken,
24
+ onSidebarToggle // New prop to notify parent of sidebar state
25
  }) => {
26
  const [chatSessions, setChatSessions] = useState([]);
27
  const [searchTerm, setSearchTerm] = useState('');
28
  const [isLoading, setIsLoading] = useState(true);
29
  const [showUserMenu, setShowUserMenu] = useState(false);
30
+ const [isCollapsed, setIsCollapsed] = useState(false);
31
 
32
  useEffect(() => {
33
  if (authToken) {
 
35
  }
36
  }, [authToken]);
37
 
38
+ // Notify parent when sidebar state changes
39
+ useEffect(() => {
40
+ if (onSidebarToggle) {
41
+ onSidebarToggle(isCollapsed);
42
+ }
43
+ }, [isCollapsed, onSidebarToggle]);
44
+
45
  const fetchChatSessions = async () => {
46
  try {
47
  const response = await fetch('http://localhost:8000/api/chat-sessions', {
 
112
  }
113
  };
114
 
115
+ const toggleSidebar = () => {
116
+ setIsCollapsed(!isCollapsed);
117
+ // Close user menu when collapsing
118
+ if (!isCollapsed) {
119
+ setShowUserMenu(false);
120
+ }
121
+ };
122
+
123
  const filteredSessions = chatSessions.filter(session =>
124
  session.title.toLowerCase().includes(searchTerm.toLowerCase())
125
  );
 
137
  };
138
 
139
  return (
140
+ <div className={`sidebar ${isCollapsed ? 'collapsed' : ''}`}>
141
  {/* Header */}
142
  <div className="sidebar-header">
143
+ {!isCollapsed && (
144
+ <>
145
+ <div className="user-section">
146
+ <div className="user-info">
147
+ <div className="user-avatar">
148
+ <User size={20} />
149
+ </div>
150
+ <div className="user-details">
151
+ <span className="user-name">{user.firstName} {user.lastName}</span>
152
+ <span className="user-email">{user.email}</span>
153
+ </div>
154
+ </div>
155
+
156
+ <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
157
+ {/* Toggle button next to user menu when expanded */}
158
+ <button
159
+ className="sidebar-toggle"
160
+ onClick={toggleSidebar}
161
+ title="Collapse sidebar"
162
+ >
163
+ <ChevronLeft size={16} />
164
+ </button>
165
+
166
+ <div className="user-menu-container">
167
+ <button
168
+ className="user-menu-button"
169
+ onClick={() => setShowUserMenu(!showUserMenu)}
170
+ >
171
+ <MoreVertical size={16} />
172
+ </button>
173
+
174
+ {showUserMenu && (
175
+ <div className="user-menu">
176
+ <button className="user-menu-item">
177
+ <Settings size={16} />
178
+ <span>Settings</span>
179
+ </button>
180
+ <button className="user-menu-item sign-out" onClick={onSignOut}>
181
+ <LogOut size={16} />
182
+ <span>Sign Out</span>
183
+ </button>
184
+ </div>
185
+ )}
186
+ </div>
187
+ </div>
188
  </div>
189
+
190
+ <button className="new-chat-button" onClick={handleNewChat}>
191
+ <Plus size={16} />
192
+ <span>New Chat</span>
193
+ </button>
194
+ </>
195
+ )}
196
+
197
+ {isCollapsed && (
198
+ <div className="collapsed-header">
199
+ {/* Toggle button replaces user avatar when collapsed */}
200
  <button
201
+ className="collapsed-toggle-avatar"
202
+ onClick={toggleSidebar}
203
+ title="Expand sidebar"
204
  >
205
+ <ChevronRight size={20} />
206
+ </button>
207
+ <button className="collapsed-new-chat" onClick={handleNewChat} title="New Chat">
208
+ <Plus size={20} />
209
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  </div>
211
+ )}
 
 
 
 
 
212
  </div>
213
 
214
+ {/* Search - only show when expanded */}
215
+ {!isCollapsed && (
216
+ <div className="sidebar-search">
217
+ <div className="search-container">
218
+ <Search size={16} className="search-icon" />
219
+ <input
220
+ type="text"
221
+ placeholder="Search chats..."
222
+ value={searchTerm}
223
+ onChange={(e) => setSearchTerm(e.target.value)}
224
+ className="search-input"
225
+ />
226
+ </div>
227
  </div>
228
+ )}
229
 
230
  {/* Chat Sessions */}
231
  <div className="chat-sessions">
232
  {isLoading ? (
233
  <div className="loading-sessions">
234
  <div className="loading-spinner"></div>
235
+ {!isCollapsed && <span>Loading chats...</span>}
236
  </div>
237
  ) : filteredSessions.length === 0 ? (
238
  <div className="no-sessions">
239
+ {!isCollapsed && (searchTerm ? 'No chats found' : 'No chats yet')}
240
  </div>
241
  ) : (
242
  <div className="sessions-list">
243
  {filteredSessions.map((session) => (
244
  <div
245
  key={session.id}
246
+ className={`session-item ${currentSessionId === session.id ? 'active' : ''} ${isCollapsed ? 'collapsed' : ''}`}
247
  onClick={() => onSelectSession(session.id)}
248
+ title={isCollapsed ? session.title : ''}
249
  >
250
  <div className="session-content">
251
  <div className="session-icon">
252
  <MessageSquare size={16} />
253
  </div>
254
+ {!isCollapsed && (
255
+ <div className="session-details">
256
+ <div className="session-title">{session.title}</div>
257
+ <div className="session-meta">
258
+ <span className="session-date">{formatDate(session.updated_at)}</span>
259
+ <span className="session-messages">{session.message_count} messages</span>
260
+ </div>
261
  </div>
262
+ )}
263
  </div>
264
 
265
+ {!isCollapsed && (
266
+ <button
267
+ className="session-menu-button"
268
+ onClick={(e) => handleDeleteSession(session.id, e)}
269
+ >
270
+ <Trash2 size={14} />
271
+ </button>
272
+ )}
273
  </div>
274
  ))}
275
  </div>
phd-advisor-frontend/src/styles/Sidebar.css CHANGED
@@ -11,6 +11,12 @@
11
  top: 0;
12
  z-index: 100;
13
  overflow: hidden;
 
 
 
 
 
 
14
  }
15
 
16
  /* Dark theme support */
@@ -19,17 +25,105 @@
19
  border-right-color: var(--border-dark, #374151);
20
  }
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  /* Header Section */
23
  .sidebar-header {
24
  padding: 20px;
25
  border-bottom: 1px solid var(--border-light, #e5e7eb);
26
  flex-shrink: 0;
 
 
 
 
 
27
  }
28
 
29
  .dark .sidebar-header {
30
  border-bottom-color: var(--border-dark, #374151);
31
  }
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  .user-section {
34
  display: flex;
35
  align-items: center;
@@ -198,6 +292,7 @@
198
  padding: 16px 20px;
199
  border-bottom: 1px solid var(--border-light, #e5e7eb);
200
  flex-shrink: 0;
 
201
  }
202
 
203
  .dark .sidebar-search {
@@ -252,6 +347,11 @@
252
  flex: 1;
253
  overflow-y: auto;
254
  padding: 0 20px 20px;
 
 
 
 
 
255
  }
256
 
257
  .loading-sessions {
@@ -264,6 +364,10 @@
264
  gap: 12px;
265
  }
266
 
 
 
 
 
267
  .dark .loading-sessions {
268
  color: var(--text-secondary-dark, #9ca3af);
269
  }
@@ -294,6 +398,10 @@
294
  font-size: 14px;
295
  }
296
 
 
 
 
 
297
  .dark .no-sessions {
298
  color: var(--text-secondary-dark, #9ca3af);
299
  }
@@ -318,6 +426,11 @@
318
  group: true;
319
  }
320
 
 
 
 
 
 
321
  .session-item:hover {
322
  background: var(--bg-secondary, #f3f4f6);
323
  border-color: var(--border-light, #e5e7eb);
@@ -346,6 +459,11 @@
346
  min-width: 0;
347
  }
348
 
 
 
 
 
 
349
  .session-icon {
350
  color: var(--text-secondary, #6b7280);
351
  flex-shrink: 0;
@@ -420,15 +538,56 @@
420
  background: var(--bg-tertiary-dark, #4b5563);
421
  }
422
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  /* Responsive Design */
424
  @media (max-width: 768px) {
425
  .sidebar {
426
  width: 280px;
427
  transform: translateX(-100%);
428
- transition: transform 0.3s ease;
 
 
 
 
429
  }
430
 
431
  .sidebar.open {
432
  transform: translateX(0);
433
  }
 
 
 
 
434
  }
 
11
  top: 0;
12
  z-index: 100;
13
  overflow: hidden;
14
+ transition: width 0.3s ease;
15
+ }
16
+
17
+ /* Collapsed state */
18
+ .sidebar.collapsed {
19
+ width: 70px;
20
  }
21
 
22
  /* Dark theme support */
 
25
  border-right-color: var(--border-dark, #374151);
26
  }
27
 
28
+ /* Toggle Button - Matches User Menu Style */
29
+ .sidebar-toggle {
30
+ padding: 6px;
31
+ border: none;
32
+ background: none;
33
+ border-radius: 6px;
34
+ cursor: pointer;
35
+ color: var(--text-secondary, #6b7280);
36
+ transition: all 0.2s ease;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ }
41
+
42
+ .sidebar-toggle:hover {
43
+ background: var(--bg-secondary, #f3f4f6);
44
+ color: var(--text-primary, #111827);
45
+ }
46
+
47
+ .dark .sidebar-toggle:hover {
48
+ background: var(--bg-secondary-dark, #374151);
49
+ color: var(--text-primary-dark, #f9fafb);
50
+ }
51
+
52
+ /* Toggle button as collapsed user avatar replacement */
53
+ .collapsed-toggle-avatar {
54
+ width: 40px;
55
+ height: 40px;
56
+ background: var(--primary-color, #3b82f6);
57
+ border-radius: 50%;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ color: white;
62
+ flex-shrink: 0;
63
+ cursor: pointer;
64
+ transition: all 0.2s ease;
65
+ }
66
+
67
+ .collapsed-toggle-avatar:hover {
68
+ background: var(--primary-color-hover, #2563eb);
69
+ transform: scale(1.05);
70
+ }
71
+
72
  /* Header Section */
73
  .sidebar-header {
74
  padding: 20px;
75
  border-bottom: 1px solid var(--border-light, #e5e7eb);
76
  flex-shrink: 0;
77
+ transition: all 0.3s ease;
78
+ }
79
+
80
+ .sidebar.collapsed .sidebar-header {
81
+ padding: 20px 10px;
82
  }
83
 
84
  .dark .sidebar-header {
85
  border-bottom-color: var(--border-dark, #374151);
86
  }
87
 
88
+ /* Collapsed Header */
89
+ .collapsed-header {
90
+ display: flex;
91
+ flex-direction: column;
92
+ align-items: center;
93
+ gap: 16px;
94
+ }
95
+
96
+ .collapsed-user-avatar {
97
+ width: 40px;
98
+ height: 40px;
99
+ background: var(--primary-color, #3b82f6);
100
+ border-radius: 50%;
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ color: white;
105
+ flex-shrink: 0;
106
+ }
107
+
108
+ .collapsed-new-chat {
109
+ width: 40px;
110
+ height: 40px;
111
+ background: var(--primary-color, #3b82f6);
112
+ color: white;
113
+ border: none;
114
+ border-radius: 50%;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ cursor: pointer;
119
+ transition: all 0.2s ease;
120
+ }
121
+
122
+ .collapsed-new-chat:hover {
123
+ background: var(--primary-color-hover, #2563eb);
124
+ transform: scale(1.1);
125
+ }
126
+
127
  .user-section {
128
  display: flex;
129
  align-items: center;
 
292
  padding: 16px 20px;
293
  border-bottom: 1px solid var(--border-light, #e5e7eb);
294
  flex-shrink: 0;
295
+ transition: all 0.3s ease;
296
  }
297
 
298
  .dark .sidebar-search {
 
347
  flex: 1;
348
  overflow-y: auto;
349
  padding: 0 20px 20px;
350
+ transition: padding 0.3s ease;
351
+ }
352
+
353
+ .sidebar.collapsed .chat-sessions {
354
+ padding: 0 10px 20px;
355
  }
356
 
357
  .loading-sessions {
 
364
  gap: 12px;
365
  }
366
 
367
+ .sidebar.collapsed .loading-sessions {
368
+ padding: 40px 5px;
369
+ }
370
+
371
  .dark .loading-sessions {
372
  color: var(--text-secondary-dark, #9ca3af);
373
  }
 
398
  font-size: 14px;
399
  }
400
 
401
+ .sidebar.collapsed .no-sessions {
402
+ padding: 40px 5px;
403
+ }
404
+
405
  .dark .no-sessions {
406
  color: var(--text-secondary-dark, #9ca3af);
407
  }
 
426
  group: true;
427
  }
428
 
429
+ .session-item.collapsed {
430
+ justify-content: center;
431
+ padding: 12px 8px;
432
+ }
433
+
434
  .session-item:hover {
435
  background: var(--bg-secondary, #f3f4f6);
436
  border-color: var(--border-light, #e5e7eb);
 
459
  min-width: 0;
460
  }
461
 
462
+ .session-item.collapsed .session-content {
463
+ justify-content: center;
464
+ flex: none;
465
+ }
466
+
467
  .session-icon {
468
  color: var(--text-secondary, #6b7280);
469
  flex-shrink: 0;
 
538
  background: var(--bg-tertiary-dark, #4b5563);
539
  }
540
 
541
+ /* Tooltip for collapsed state */
542
+ .session-item.collapsed {
543
+ position: relative;
544
+ }
545
+
546
+ .session-item.collapsed:hover::after {
547
+ content: attr(title);
548
+ position: absolute;
549
+ left: 100%;
550
+ top: 50%;
551
+ transform: translateY(-50%);
552
+ background: var(--bg-primary, #ffffff);
553
+ border: 1px solid var(--border-light, #e5e7eb);
554
+ border-radius: 6px;
555
+ padding: 8px 12px;
556
+ margin-left: 8px;
557
+ white-space: nowrap;
558
+ font-size: 12px;
559
+ color: var(--text-primary, #111827);
560
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
561
+ z-index: 1000;
562
+ max-width: 200px;
563
+ overflow: hidden;
564
+ text-overflow: ellipsis;
565
+ }
566
+
567
+ .dark .session-item.collapsed:hover::after {
568
+ background: var(--bg-primary-dark, #1f2937);
569
+ border-color: var(--border-dark, #374151);
570
+ color: var(--text-primary-dark, #f9fafb);
571
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
572
+ }
573
+
574
  /* Responsive Design */
575
  @media (max-width: 768px) {
576
  .sidebar {
577
  width: 280px;
578
  transform: translateX(-100%);
579
+ transition: transform 0.3s ease, width 0.3s ease;
580
+ }
581
+
582
+ .sidebar.collapsed {
583
+ width: 70px;
584
  }
585
 
586
  .sidebar.open {
587
  transform: translateX(0);
588
  }
589
+
590
+ .sidebar-toggle {
591
+ display: none; /* Hide toggle on mobile, use overlay instead */
592
+ }
593
  }