diff options
Diffstat (limited to 'src/components/ActivityBar/ActivityBar.tsx')
| -rw-r--r-- | src/components/ActivityBar/ActivityBar.tsx | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/src/components/ActivityBar/ActivityBar.tsx b/src/components/ActivityBar/ActivityBar.tsx new file mode 100644 index 0000000..7f96b5c --- /dev/null +++ b/src/components/ActivityBar/ActivityBar.tsx @@ -0,0 +1,147 @@ +import { useLayoutStore, type ActivityView } from '@/stores/useLayoutStore' +import { useThemeStore } from '@/stores/useThemeStore' + +interface ActivityItem { + view: ActivityView + label: string +} + +function ExplorerIcon() { + return ( + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /> + </svg> + ) +} + +function SearchIcon() { + return ( + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <circle cx="11" cy="11" r="8" /> + <line x1="21" y1="21" x2="16.65" y2="16.65" /> + </svg> + ) +} + +function ExtensionsIcon() { + return ( + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <path d="M4 11a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2z" /> + <path d="M15 11a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2z" /> + <path d="M15 22a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2z" /> + <path d="M4 22a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2z" /> + </svg> + ) +} + +function SettingsIcon() { + return ( + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <circle cx="12" cy="12" r="3" /> + <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" /> + </svg> + ) +} + +function MoonIcon() { + return ( + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"> + <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /> + </svg> + ) +} + +function SunIcon() { + return ( + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"> + <circle cx="12" cy="12" r="5" /> + <line x1="12" y1="1" x2="12" y2="3" /> + <line x1="12" y1="21" x2="12" y2="23" /> + <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /> + <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" /> + <line x1="1" y1="12" x2="3" y2="12" /> + <line x1="21" y1="12" x2="23" y2="12" /> + <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /> + <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" /> + </svg> + ) +} + +const activities: ActivityItem[] = [ + { view: 'explorer', label: 'Explorer' }, + { view: 'search', label: 'Search' }, + { view: 'extensions', label: 'Extensions' }, +] + +const bottomActivities: ActivityItem[] = [ + { view: 'settings', label: 'Settings' }, +] + +const iconMap: Record<string, typeof ExplorerIcon> = { + explorer: ExplorerIcon, + search: SearchIcon, + extensions: ExtensionsIcon, + settings: SettingsIcon, +} + +function ActivityButton({ item, isActive, setActiveView }: { item: ActivityItem; isActive: boolean; setActiveView: (view: ActivityView | null) => void }) { + const IconComponent = iconMap[item.view] || ExplorerIcon + + return ( + <button + role="tab" + aria-label={item.label} + aria-current={isActive ? 'true' : undefined} + className="relative flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-fast hover:bg-panel-hover" + style={{ color: isActive ? 'var(--color-text-primary)' : 'var(--color-text-muted)' }} + onClick={() => setActiveView(item.view)} + title={item.label} + > + {isActive && ( + <span className="absolute left-0 w-[3px] h-6 rounded-r-sm bg-accent" style={{ left: '-4px' }} /> + )} + <span className={isActive ? '' : 'opacity-50 group-hover:opacity-80 transition-opacity duration-fast'}> + <IconComponent /> + </span> + </button> + ) +} + +export default function ActivityBar() { + const activeView = useLayoutStore((s) => s.activeView) + const setActiveView = useLayoutStore((s) => s.setActiveView) + const theme = useThemeStore((s) => s.theme) + const toggleTheme = useThemeStore((s) => s.toggleTheme) + + return ( + <div className="flex flex-col items-center pt-2 gap-1 w-12 shrink-0 bg-activity-bg select-none group" role="tablist" aria-label="Main navigation"> + {activities.map((item) => ( + <ActivityButton + key={item.view} + item={item} + isActive={activeView === item.view} + setActiveView={setActiveView} + /> + ))} + + <div className="flex-1" /> + + {bottomActivities.map((item) => ( + <ActivityButton + key={item.view} + item={item} + isActive={activeView === item.view} + setActiveView={setActiveView} + /> + ))} + + <button + className="flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-fast hover:bg-panel-hover text-text-muted hover:text-text-primary mb-2" + onClick={toggleTheme} + title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`} + > + {theme === 'dark' ? <SunIcon /> : <MoonIcon />} + </button> + </div> + ) +} |