gaialive commited on
Commit
affeae1
·
verified ·
1 Parent(s): c63497b

Upload 3 files

Browse files
web/src/components/layout/AppFooter.css ADDED
File without changes
web/src/components/layout/AppFooter.js ADDED
File without changes
web/src/components/layout/Sidebar.js ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import './Sidebar.css';
4
+
5
+ const Sidebar = ({
6
+ isOpen,
7
+ onToggle,
8
+ activePage,
9
+ onPageChange,
10
+ pages = [],
11
+ userStats = {}
12
+ }) => {
13
+ const { t, i18n } = useTranslation();
14
+ const [searchTerm, setSearchTerm] = useState('');
15
+ const [filteredPages, setFilteredPages] = useState(pages);
16
+
17
+ useEffect(() => {
18
+ if (searchTerm) {
19
+ const lowercasedSearchTerm = searchTerm.toLowerCase();
20
+ const filtered = pages.filter(page =>
21
+ t(page.name).toLowerCase().includes(lowercasedSearchTerm) ||
22
+ t(page.description).toLowerCase().includes(lowercasedSearchTerm) ||
23
+ (page.category && t(`sidebar.categories.${page.category}`, page.category).toLowerCase().includes(lowercasedSearchTerm))
24
+ );
25
+ setFilteredPages(filtered);
26
+ } else {
27
+ setFilteredPages(pages);
28
+ }
29
+ }, [searchTerm, pages, t]);
30
+
31
+ const groupedPages = filteredPages.reduce((groups, page) => {
32
+ const category = page.category || 'Tools';
33
+ if (!groups[category]) {
34
+ groups[category] = [];
35
+ }
36
+ groups[category].push(page);
37
+ return groups;
38
+ }, {});
39
+
40
+ const handlePageClick = (pageId) => {
41
+ onPageChange(pageId);
42
+ };
43
+
44
+ const clearSearch = () => {
45
+ setSearchTerm('');
46
+ };
47
+
48
+ const handleLanguageChange = (e) => {
49
+ i18n.changeLanguage(e.target.value);
50
+ };
51
+
52
+ return (
53
+ <div className={`terra-sidebar ${isOpen ? 'terra-sidebar--open' : 'terra-sidebar--closed'}`}>
54
+ {/* Logo Section */}
55
+ <div className="terra-sidebar__header">
56
+ <div className="terra-sidebar__logo">
57
+ <div className="terra-sidebar__logo-icon">
58
+ 🌿
59
+ </div>
60
+ {isOpen && (
61
+ <div className="terra-sidebar__logo-text">
62
+ <h1 className="terra-sidebar__logo-title">{t('sidebar.title')}</h1>
63
+ <p className="terra-sidebar__logo-subtitle">{t('sidebar.subtitle')}</p>
64
+ </div>
65
+ )}
66
+ </div>
67
+ <button
68
+ className="terra-sidebar__toggle"
69
+ onClick={onToggle}
70
+ aria-label={isOpen ? t('sidebar.collapse') : t('sidebar.expand')}
71
+ >
72
+ <span className={`terra-sidebar__toggle-icon ${isOpen ? 'terra-sidebar__toggle-icon--open' : ''}`}>
73
+
74
+ </span>
75
+ </button>
76
+ </div>
77
+
78
+ {/* Search Section */}
79
+ {isOpen && (
80
+ <div className="terra-sidebar__search">
81
+ <div className="terra-sidebar__search-container">
82
+ <input
83
+ type="text"
84
+ placeholder={t('sidebar.searchPlaceholder')}
85
+ value={searchTerm}
86
+ onChange={(e) => setSearchTerm(e.target.value)}
87
+ className="terra-sidebar__search-input"
88
+ />
89
+ <div className="terra-sidebar__search-icon">
90
+ {searchTerm ? (
91
+ <button
92
+ onClick={clearSearch}
93
+ className="terra-sidebar__search-clear"
94
+ aria-label={t('sidebar.clearSearch')}
95
+ >
96
+
97
+ </button>
98
+ ) : (
99
+ <span>🔍</span>
100
+ )}
101
+ </div>
102
+ </div>
103
+ </div>
104
+ )}
105
+
106
+ {/* Navigation Section */}
107
+ <nav className="terra-sidebar__nav">
108
+ <div className="terra-sidebar__nav-content">
109
+ {Object.entries(groupedPages).map(([category, categoryPages]) => (
110
+ <div key={category} className="terra-sidebar__category">
111
+ {isOpen && (
112
+ <h3 className="terra-sidebar__category-title">{t(`sidebar.categories.${category}`, category)}</h3>
113
+ )}
114
+ <div className="terra-sidebar__category-items">
115
+ {categoryPages.map(page => (
116
+ <div
117
+ key={page.id}
118
+ className={`terra-sidebar__nav-item ${
119
+ activePage === page.id ? 'terra-sidebar__nav-item--active' : ''
120
+ }`}
121
+ onClick={() => handlePageClick(page.id)}
122
+ role="button"
123
+ tabIndex={0}
124
+ onKeyDown={(e) => {
125
+ if (e.key === 'Enter' || e.key === ' ') {
126
+ e.preventDefault();
127
+ handlePageClick(page.id);
128
+ }
129
+ }}
130
+ >
131
+ <div className="terra-sidebar__nav-icon">
132
+ {page.icon}
133
+ </div>
134
+ {isOpen && (
135
+ <div className="terra-sidebar__nav-content">
136
+ <span className="terra-sidebar__nav-name">{t(page.name)}</span>
137
+ <span className="terra-sidebar__nav-desc">{t(page.description)}</span>
138
+ </div>
139
+ )}
140
+ {activePage === page.id && (
141
+ <div className="terra-sidebar__nav-indicator" />
142
+ )}
143
+ </div>
144
+ ))}
145
+ </div>
146
+ </div>
147
+ ))}
148
+ </div>
149
+ </nav>
150
+
151
+ {/* Stats Footer */}
152
+ {isOpen && (
153
+ <div className="terra-sidebar__footer">
154
+ <div className="terra-sidebar__stats">
155
+ <div className="terra-sidebar__stat">
156
+ <span className="terra-sidebar__stat-icon">🌱</span>
157
+ <div className="terra-sidebar__stat-content">
158
+ <div className="terra-sidebar__stat-value">
159
+ {userStats.toolsUsed || pages.length}
160
+ </div>
161
+ <div className="terra-sidebar__stat-label">{t('sidebar.tools')}</div>
162
+ </div>
163
+ </div>
164
+ <div className="terra-sidebar__stat">
165
+ <span className="terra-sidebar__stat-icon">🌍</span>
166
+ <div className="terra-sidebar__stat-content">
167
+ <div className="terra-sidebar__stat-value">
168
+ {userStats.impactScore || '2.5M+'}
169
+ </div>
170
+ <div className="terra-sidebar__stat-label">{t('sidebar.impact')}</div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ <div className="terra-sidebar__language-selector-container">
176
+ <label htmlFor="language-select" className="terra-sidebar__language-label">{t('sidebar.language')}</label>
177
+ <select id="language-select" onChange={handleLanguageChange} value={i18n.language} className="terra-sidebar__language-select">
178
+ <option value="vi">Tiếng Việt</option>
179
+ <option value="en">English</option>
180
+ </select>
181
+ </div>
182
+
183
+ <div className="terra-sidebar__version">
184
+ <span>{t('sidebar.version')}</span>
185
+ </div>
186
+ </div>
187
+ )}
188
+
189
+ {/* Tooltip for collapsed state */}
190
+ {!isOpen && (
191
+ <div className="terra-sidebar__tooltip" id="sidebar-tooltip" role="tooltip">
192
+ <div className="terra-sidebar__tooltip-content"></div>
193
+ <div className="terra-sidebar__tooltip-arrow"></div>
194
+ </div>
195
+ )}
196
+ </div>
197
+ );
198
+ };
199
+
200
+ export default Sidebar;