|
1 | | -import { useState } from 'react'; |
| 1 | +import { useState, useEffect } from 'react'; |
2 | 2 | import { motion } from 'framer-motion'; |
3 | 3 | import { useStore } from '@nanostores/react'; |
4 | | -import { Switch } from '@radix-ui/react-switch'; |
| 4 | +import { Switch } from '~/components/ui/Switch'; |
5 | 5 | import { classNames } from '~/utils/classNames'; |
6 | 6 | import { tabConfigurationStore } from '~/lib/stores/settings'; |
7 | 7 | import { TAB_LABELS } from '~/components/@settings/core/constants'; |
8 | 8 | import type { TabType } from '~/components/@settings/core/types'; |
9 | 9 | import { toast } from 'react-toastify'; |
10 | 10 | import { TbLayoutGrid } from 'react-icons/tb'; |
| 11 | +import { useSettingsStore } from '~/lib/stores/settings'; |
11 | 12 |
|
12 | 13 | // Define tab icons mapping |
13 | 14 | const TAB_ICONS: Record<TabType, string> = { |
@@ -55,6 +56,7 @@ const BetaLabel = () => ( |
55 | 56 | export const TabManagement = () => { |
56 | 57 | const [searchQuery, setSearchQuery] = useState(''); |
57 | 58 | const tabConfiguration = useStore(tabConfigurationStore); |
| 59 | + const { setSelectedTab } = useSettingsStore(); |
58 | 60 |
|
59 | 61 | const handleTabVisibilityChange = (tabId: TabType, checked: boolean) => { |
60 | 62 | // Get current tab configuration |
@@ -126,6 +128,13 @@ export const TabManagement = () => { |
126 | 128 | // Filter tabs based on search query |
127 | 129 | const filteredTabs = allTabs.filter((tab) => TAB_LABELS[tab.id].toLowerCase().includes(searchQuery.toLowerCase())); |
128 | 130 |
|
| 131 | + useEffect(() => { |
| 132 | + // Reset to first tab when component unmounts |
| 133 | + return () => { |
| 134 | + setSelectedTab('user'); // Reset to user tab when unmounting |
| 135 | + }; |
| 136 | + }, [setSelectedTab]); |
| 137 | + |
129 | 138 | return ( |
130 | 139 | <div className="space-y-6"> |
131 | 140 | <motion.div |
@@ -177,92 +186,193 @@ export const TabManagement = () => { |
177 | 186 |
|
178 | 187 | {/* Tab Grid */} |
179 | 188 | <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> |
180 | | - {filteredTabs.map((tab, index) => ( |
181 | | - <motion.div |
182 | | - key={tab.id} |
183 | | - className={classNames( |
184 | | - 'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary', |
185 | | - 'bg-bolt-elements-background-depth-2', |
186 | | - 'hover:bg-bolt-elements-background-depth-3', |
187 | | - 'transition-all duration-200', |
188 | | - 'relative overflow-hidden group', |
189 | | - )} |
190 | | - initial={{ opacity: 0, y: 20 }} |
191 | | - animate={{ opacity: 1, y: 0 }} |
192 | | - transition={{ delay: index * 0.1 }} |
193 | | - whileHover={{ scale: 1.02 }} |
194 | | - > |
195 | | - {/* Status Badges */} |
196 | | - <div className="absolute top-2 right-2 flex gap-1"> |
197 | | - {DEFAULT_USER_TABS.includes(tab.id) && ( |
198 | | - <span className="px-2 py-0.5 text-xs rounded-full bg-purple-500/10 text-purple-500 font-medium"> |
| 189 | + {/* Default Section Header */} |
| 190 | + {filteredTabs.some((tab) => DEFAULT_USER_TABS.includes(tab.id)) && ( |
| 191 | + <div className="col-span-full flex items-center gap-2 mt-4 mb-2"> |
| 192 | + <div className="i-ph:star-fill w-4 h-4 text-purple-500" /> |
| 193 | + <span className="text-sm font-medium text-bolt-elements-textPrimary">Default Tabs</span> |
| 194 | + </div> |
| 195 | + )} |
| 196 | + |
| 197 | + {/* Default Tabs */} |
| 198 | + {filteredTabs |
| 199 | + .filter((tab) => DEFAULT_USER_TABS.includes(tab.id)) |
| 200 | + .map((tab, index) => ( |
| 201 | + <motion.div |
| 202 | + key={tab.id} |
| 203 | + className={classNames( |
| 204 | + 'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary', |
| 205 | + 'bg-bolt-elements-background-depth-2', |
| 206 | + 'hover:bg-bolt-elements-background-depth-3', |
| 207 | + 'transition-all duration-200', |
| 208 | + 'relative overflow-hidden group', |
| 209 | + )} |
| 210 | + initial={{ opacity: 0, y: 20 }} |
| 211 | + animate={{ opacity: 1, y: 0 }} |
| 212 | + transition={{ delay: index * 0.1 }} |
| 213 | + whileHover={{ scale: 1.02 }} |
| 214 | + > |
| 215 | + {/* Status Badges */} |
| 216 | + <div className="absolute top-1 right-1.5 flex gap-1"> |
| 217 | + <span className="px-1.5 py-0.25 text-xs rounded-full bg-purple-500/10 text-purple-500 font-medium mr-2"> |
199 | 218 | Default |
200 | 219 | </span> |
| 220 | + </div> |
| 221 | + |
| 222 | + <div className="flex items-start gap-4 p-4"> |
| 223 | + <motion.div |
| 224 | + className={classNames( |
| 225 | + 'w-10 h-10 flex items-center justify-center rounded-xl', |
| 226 | + 'bg-bolt-elements-background-depth-3 group-hover:bg-bolt-elements-background-depth-4', |
| 227 | + 'transition-all duration-200', |
| 228 | + tab.visible ? 'text-purple-500' : 'text-bolt-elements-textSecondary', |
| 229 | + )} |
| 230 | + whileHover={{ scale: 1.1 }} |
| 231 | + whileTap={{ scale: 0.9 }} |
| 232 | + > |
| 233 | + <div |
| 234 | + className={classNames('w-6 h-6', 'transition-transform duration-200', 'group-hover:rotate-12')} |
| 235 | + > |
| 236 | + <div className={classNames(TAB_ICONS[tab.id], 'w-full h-full')} /> |
| 237 | + </div> |
| 238 | + </motion.div> |
| 239 | + |
| 240 | + <div className="flex-1 min-w-0"> |
| 241 | + <div className="flex items-center justify-between gap-4"> |
| 242 | + <div> |
| 243 | + <div className="flex items-center gap-2"> |
| 244 | + <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors"> |
| 245 | + {TAB_LABELS[tab.id]} |
| 246 | + </h4> |
| 247 | + {BETA_TABS.has(tab.id) && <BetaLabel />} |
| 248 | + </div> |
| 249 | + <p className="text-xs text-bolt-elements-textSecondary mt-0.5"> |
| 250 | + {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} |
| 251 | + </p> |
| 252 | + </div> |
| 253 | + <Switch |
| 254 | + checked={tab.visible} |
| 255 | + onCheckedChange={(checked) => { |
| 256 | + const isDisabled = |
| 257 | + !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id); |
| 258 | + |
| 259 | + if (!isDisabled) { |
| 260 | + handleTabVisibilityChange(tab.id, checked); |
| 261 | + } |
| 262 | + }} |
| 263 | + className={classNames('data-[state=checked]:bg-purple-500 ml-4', { |
| 264 | + 'opacity-50 pointer-events-none': |
| 265 | + !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id), |
| 266 | + })} |
| 267 | + /> |
| 268 | + </div> |
| 269 | + </div> |
| 270 | + </div> |
| 271 | + |
| 272 | + <motion.div |
| 273 | + className="absolute inset-0 border-2 border-purple-500/0 rounded-lg pointer-events-none" |
| 274 | + animate={{ |
| 275 | + borderColor: tab.visible ? 'rgba(168, 85, 247, 0.2)' : 'rgba(168, 85, 247, 0)', |
| 276 | + scale: tab.visible ? 1 : 0.98, |
| 277 | + }} |
| 278 | + transition={{ duration: 0.2 }} |
| 279 | + /> |
| 280 | + </motion.div> |
| 281 | + ))} |
| 282 | + |
| 283 | + {/* Optional Section Header */} |
| 284 | + {filteredTabs.some((tab) => OPTIONAL_USER_TABS.includes(tab.id)) && ( |
| 285 | + <div className="col-span-full flex items-center gap-2 mt-8 mb-2"> |
| 286 | + <div className="i-ph:plus-circle-fill w-4 h-4 text-blue-500" /> |
| 287 | + <span className="text-sm font-medium text-bolt-elements-textPrimary">Optional Tabs</span> |
| 288 | + </div> |
| 289 | + )} |
| 290 | + |
| 291 | + {/* Optional Tabs */} |
| 292 | + {filteredTabs |
| 293 | + .filter((tab) => OPTIONAL_USER_TABS.includes(tab.id)) |
| 294 | + .map((tab, index) => ( |
| 295 | + <motion.div |
| 296 | + key={tab.id} |
| 297 | + className={classNames( |
| 298 | + 'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary', |
| 299 | + 'bg-bolt-elements-background-depth-2', |
| 300 | + 'hover:bg-bolt-elements-background-depth-3', |
| 301 | + 'transition-all duration-200', |
| 302 | + 'relative overflow-hidden group', |
201 | 303 | )} |
202 | | - {OPTIONAL_USER_TABS.includes(tab.id) && ( |
203 | | - <span className="px-2 py-0.5 text-xs rounded-full bg-blue-500/10 text-blue-500 font-medium"> |
| 304 | + initial={{ opacity: 0, y: 20 }} |
| 305 | + animate={{ opacity: 1, y: 0 }} |
| 306 | + transition={{ delay: index * 0.1 }} |
| 307 | + whileHover={{ scale: 1.02 }} |
| 308 | + > |
| 309 | + {/* Status Badges */} |
| 310 | + <div className="absolute top-1 right-1.5 flex gap-1"> |
| 311 | + <span className="px-1.5 py-0.25 text-xs rounded-full bg-blue-500/10 text-blue-500 font-medium mr-2"> |
204 | 312 | Optional |
205 | 313 | </span> |
206 | | - )} |
207 | | - </div> |
| 314 | + </div> |
208 | 315 |
|
209 | | - <div className="flex items-start gap-4 p-4"> |
210 | | - <motion.div |
211 | | - className={classNames( |
212 | | - 'w-10 h-10 flex items-center justify-center rounded-xl', |
213 | | - 'bg-bolt-elements-background-depth-3 group-hover:bg-bolt-elements-background-depth-4', |
214 | | - 'transition-all duration-200', |
215 | | - tab.visible ? 'text-purple-500' : 'text-bolt-elements-textSecondary', |
216 | | - )} |
217 | | - whileHover={{ scale: 1.1 }} |
218 | | - whileTap={{ scale: 0.9 }} |
219 | | - > |
220 | | - <div className={classNames('w-6 h-6', 'transition-transform duration-200', 'group-hover:rotate-12')}> |
221 | | - <div className={classNames(TAB_ICONS[tab.id], 'w-full h-full')} /> |
222 | | - </div> |
223 | | - </motion.div> |
224 | | - |
225 | | - <div className="flex-1 min-w-0"> |
226 | | - <div className="flex items-center justify-between gap-4"> |
227 | | - <div> |
228 | | - <div className="flex items-center gap-2"> |
229 | | - <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors"> |
230 | | - {TAB_LABELS[tab.id]} |
231 | | - </h4> |
232 | | - {BETA_TABS.has(tab.id) && <BetaLabel />} |
233 | | - </div> |
234 | | - <p className="text-xs text-bolt-elements-textSecondary mt-0.5"> |
235 | | - {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} |
236 | | - </p> |
| 316 | + <div className="flex items-start gap-4 p-4"> |
| 317 | + <motion.div |
| 318 | + className={classNames( |
| 319 | + 'w-10 h-10 flex items-center justify-center rounded-xl', |
| 320 | + 'bg-bolt-elements-background-depth-3 group-hover:bg-bolt-elements-background-depth-4', |
| 321 | + 'transition-all duration-200', |
| 322 | + tab.visible ? 'text-purple-500' : 'text-bolt-elements-textSecondary', |
| 323 | + )} |
| 324 | + whileHover={{ scale: 1.1 }} |
| 325 | + whileTap={{ scale: 0.9 }} |
| 326 | + > |
| 327 | + <div |
| 328 | + className={classNames('w-6 h-6', 'transition-transform duration-200', 'group-hover:rotate-12')} |
| 329 | + > |
| 330 | + <div className={classNames(TAB_ICONS[tab.id], 'w-full h-full')} /> |
237 | 331 | </div> |
238 | | - <Switch |
239 | | - checked={tab.visible} |
240 | | - onCheckedChange={(checked) => handleTabVisibilityChange(tab.id, checked)} |
241 | | - disabled={!DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id)} |
242 | | - className={classNames( |
243 | | - 'relative inline-flex h-5 w-9 items-center rounded-full', |
244 | | - 'transition-colors duration-200', |
245 | | - tab.visible ? 'bg-purple-500' : 'bg-bolt-elements-background-depth-4', |
246 | | - { |
247 | | - 'opacity-50 cursor-not-allowed': |
| 332 | + </motion.div> |
| 333 | + |
| 334 | + <div className="flex-1 min-w-0"> |
| 335 | + <div className="flex items-center justify-between gap-4"> |
| 336 | + <div> |
| 337 | + <div className="flex items-center gap-2"> |
| 338 | + <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors"> |
| 339 | + {TAB_LABELS[tab.id]} |
| 340 | + </h4> |
| 341 | + {BETA_TABS.has(tab.id) && <BetaLabel />} |
| 342 | + </div> |
| 343 | + <p className="text-xs text-bolt-elements-textSecondary mt-0.5"> |
| 344 | + {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} |
| 345 | + </p> |
| 346 | + </div> |
| 347 | + <Switch |
| 348 | + checked={tab.visible} |
| 349 | + onCheckedChange={(checked) => { |
| 350 | + const isDisabled = |
| 351 | + !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id); |
| 352 | + |
| 353 | + if (!isDisabled) { |
| 354 | + handleTabVisibilityChange(tab.id, checked); |
| 355 | + } |
| 356 | + }} |
| 357 | + className={classNames('data-[state=checked]:bg-purple-500 ml-4', { |
| 358 | + 'opacity-50 pointer-events-none': |
248 | 359 | !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id), |
249 | | - }, |
250 | | - )} |
251 | | - /> |
| 360 | + })} |
| 361 | + /> |
| 362 | + </div> |
252 | 363 | </div> |
253 | 364 | </div> |
254 | | - </div> |
255 | 365 |
|
256 | | - <motion.div |
257 | | - className="absolute inset-0 border-2 border-purple-500/0 rounded-lg pointer-events-none" |
258 | | - animate={{ |
259 | | - borderColor: tab.visible ? 'rgba(168, 85, 247, 0.2)' : 'rgba(168, 85, 247, 0)', |
260 | | - scale: tab.visible ? 1 : 0.98, |
261 | | - }} |
262 | | - transition={{ duration: 0.2 }} |
263 | | - /> |
264 | | - </motion.div> |
265 | | - ))} |
| 366 | + <motion.div |
| 367 | + className="absolute inset-0 border-2 border-purple-500/0 rounded-lg pointer-events-none" |
| 368 | + animate={{ |
| 369 | + borderColor: tab.visible ? 'rgba(168, 85, 247, 0.2)' : 'rgba(168, 85, 247, 0)', |
| 370 | + scale: tab.visible ? 1 : 0.98, |
| 371 | + }} |
| 372 | + transition={{ duration: 0.2 }} |
| 373 | + /> |
| 374 | + </motion.div> |
| 375 | + ))} |
266 | 376 | </div> |
267 | 377 | </motion.div> |
268 | 378 | </div> |
|
0 commit comments