mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-05 22:17:42 +00:00
Feat: Refine design language and use theme tokens across most pages.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Settings, Sparkles } from 'lucide-react';
|
||||
import { Settings, Sparkles, PanelLeftOpen } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
|
||||
type SidebarCollapsedProps = {
|
||||
@@ -17,41 +17,39 @@ export default function SidebarCollapsed({
|
||||
t,
|
||||
}: SidebarCollapsedProps) {
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center py-4 gap-4 bg-card">
|
||||
<div className="h-full flex flex-col items-center py-3 gap-1 bg-background/80 backdrop-blur-sm w-12">
|
||||
{/* Expand button with brand logo */}
|
||||
<button
|
||||
onClick={onExpand}
|
||||
className="p-2 hover:bg-accent rounded-md transition-colors duration-200 group"
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center hover:bg-accent/80 transition-colors group"
|
||||
aria-label={t('common:versionUpdate.ariaLabels.showSidebar')}
|
||||
title={t('common:versionUpdate.ariaLabels.showSidebar')}
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5 text-foreground group-hover:scale-110 transition-transform"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
||||
</svg>
|
||||
<PanelLeftOpen className="w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||
</button>
|
||||
|
||||
<div className="nav-divider w-6 my-1" />
|
||||
|
||||
{/* Settings */}
|
||||
<button
|
||||
onClick={onShowSettings}
|
||||
className="p-2 hover:bg-accent rounded-md transition-colors duration-200"
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center hover:bg-accent/80 transition-colors group"
|
||||
aria-label={t('actions.settings')}
|
||||
title={t('actions.settings')}
|
||||
>
|
||||
<Settings className="w-5 h-5 text-muted-foreground hover:text-foreground transition-colors" />
|
||||
<Settings className="w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||
</button>
|
||||
|
||||
{/* Update indicator */}
|
||||
{updateAvailable && (
|
||||
<button
|
||||
onClick={onShowVersionModal}
|
||||
className="relative p-2 hover:bg-accent rounded-md transition-colors duration-200"
|
||||
className="relative w-8 h-8 rounded-lg flex items-center justify-center hover:bg-accent/80 transition-colors"
|
||||
aria-label={t('common:versionUpdate.ariaLabels.updateAvailable')}
|
||||
title={t('common:versionUpdate.ariaLabels.updateAvailable')}
|
||||
>
|
||||
<Sparkles className="w-5 h-5 text-blue-500" />
|
||||
<span className="absolute top-1 right-1 w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
|
||||
<Sparkles className="w-4 h-4 text-blue-500" />
|
||||
<span className="absolute top-1.5 right-1.5 w-1.5 h-1.5 bg-blue-500 rounded-full animate-pulse" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function SidebarContent({
|
||||
}: SidebarContentProps) {
|
||||
return (
|
||||
<div
|
||||
className="h-full flex flex-col bg-card md:select-none md:w-80"
|
||||
className="h-full flex flex-col bg-background/80 backdrop-blur-sm md:select-none md:w-72"
|
||||
style={isPWA && isMobile ? { paddingTop: '44px' } : {}}
|
||||
>
|
||||
<SidebarHeader
|
||||
@@ -67,7 +67,7 @@ export default function SidebarContent({
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<ScrollArea className="flex-1 md:px-2 md:py-3 overflow-y-auto overscroll-contain">
|
||||
<ScrollArea className="flex-1 md:px-1.5 md:py-2 overflow-y-auto overscroll-contain">
|
||||
<SidebarProjectList {...projectListProps} />
|
||||
</ScrollArea>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Settings } from 'lucide-react';
|
||||
import { Settings, ArrowUpCircle } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { ReleaseInfo } from '../../../../types/sharedTypes';
|
||||
import { Button } from '../../../ui/button';
|
||||
|
||||
type SidebarFooterProps = {
|
||||
updateAvailable: boolean;
|
||||
@@ -21,74 +20,81 @@ export default function SidebarFooter({
|
||||
t,
|
||||
}: SidebarFooterProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex-shrink-0">
|
||||
{/* Update banner */}
|
||||
{updateAvailable && (
|
||||
<div className="md:p-2 border-t border-border/50 flex-shrink-0">
|
||||
<div className="hidden md:block">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-start gap-3 p-3 h-auto font-normal text-left hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors duration-200 border border-blue-200 dark:border-blue-700 rounded-lg mb-2"
|
||||
<>
|
||||
<div className="nav-divider" />
|
||||
{/* Desktop update */}
|
||||
<div className="hidden md:block px-2 py-1.5">
|
||||
<button
|
||||
className="w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-left hover:bg-blue-50/80 dark:hover:bg-blue-900/15 transition-colors group"
|
||||
onClick={onShowVersionModal}
|
||||
>
|
||||
<div className="relative">
|
||||
<svg className="w-4 h-4 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
|
||||
</svg>
|
||||
<div className="absolute -top-1 -right-1 w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
|
||||
<div className="relative flex-shrink-0">
|
||||
<ArrowUpCircle className="w-4 h-4 text-blue-500 dark:text-blue-400" />
|
||||
<span className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 bg-blue-500 rounded-full animate-pulse" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
{releaseInfo?.title || `Version ${latestVersion}`}
|
||||
</div>
|
||||
<div className="text-xs text-blue-600 dark:text-blue-400">{t('version.updateAvailable')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="md:hidden p-3 pb-2">
|
||||
<button
|
||||
className="w-full h-12 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-700 rounded-xl flex items-center justify-start gap-3 px-4 active:scale-[0.98] transition-all duration-150"
|
||||
onClick={onShowVersionModal}
|
||||
>
|
||||
<div className="relative">
|
||||
<svg className="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
|
||||
</svg>
|
||||
<div className="absolute -top-1 -right-1 w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 text-left">
|
||||
<div className="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
{releaseInfo?.title || `Version ${latestVersion}`}
|
||||
</div>
|
||||
<div className="text-xs text-blue-600 dark:text-blue-400">{t('version.updateAvailable')}</div>
|
||||
<span className="text-xs font-medium text-blue-600 dark:text-blue-300 truncate block">
|
||||
{releaseInfo?.title || `v${latestVersion}`}
|
||||
</span>
|
||||
<span className="text-[10px] text-blue-500/70 dark:text-blue-400/60">
|
||||
{t('version.updateAvailable')}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile update */}
|
||||
<div className="md:hidden px-3 py-2">
|
||||
<button
|
||||
className="w-full h-11 bg-blue-50/80 dark:bg-blue-900/15 border border-blue-200/60 dark:border-blue-700/40 rounded-xl flex items-center gap-3 px-3.5 active:scale-[0.98] transition-all"
|
||||
onClick={onShowVersionModal}
|
||||
>
|
||||
<div className="relative flex-shrink-0">
|
||||
<ArrowUpCircle className="w-4.5 h-4.5 text-blue-500 dark:text-blue-400" />
|
||||
<span className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 bg-blue-500 rounded-full animate-pulse" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 text-left">
|
||||
<span className="text-sm font-medium text-blue-600 dark:text-blue-300 truncate block">
|
||||
{releaseInfo?.title || `v${latestVersion}`}
|
||||
</span>
|
||||
<span className="text-xs text-blue-500/70 dark:text-blue-400/60">
|
||||
{t('version.updateAvailable')}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="md:p-2 md:border-t md:border-border flex-shrink-0">
|
||||
<div className="md:hidden p-4 pb-20 border-t border-border/50">
|
||||
<button
|
||||
className="w-full h-14 bg-muted/50 hover:bg-muted/70 rounded-2xl flex items-center justify-start gap-4 px-4 active:scale-[0.98] transition-all duration-150"
|
||||
onClick={onShowSettings}
|
||||
>
|
||||
<div className="w-10 h-10 rounded-2xl bg-background/80 flex items-center justify-center">
|
||||
<Settings className="w-5 h-5 text-muted-foreground" />
|
||||
</div>
|
||||
<span className="text-lg font-medium text-foreground">{t('actions.settings')}</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* Settings */}
|
||||
<div className="nav-divider" />
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden md:flex w-full justify-start gap-2 p-2 h-auto font-normal text-muted-foreground hover:text-foreground hover:bg-accent transition-colors duration-200"
|
||||
{/* Desktop settings */}
|
||||
<div className="hidden md:block px-2 py-1.5">
|
||||
<button
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent/60 transition-colors"
|
||||
onClick={onShowSettings}
|
||||
>
|
||||
<Settings className="w-3 h-3" />
|
||||
<Settings className="w-3.5 h-3.5" />
|
||||
<span className="text-xs">{t('actions.settings')}</span>
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
{/* Mobile settings */}
|
||||
<div className="md:hidden p-3 pb-20">
|
||||
<button
|
||||
className="w-full h-12 bg-muted/40 hover:bg-muted/60 rounded-xl flex items-center gap-3.5 px-4 active:scale-[0.98] transition-all"
|
||||
onClick={onShowSettings}
|
||||
>
|
||||
<div className="w-8 h-8 rounded-xl bg-background/80 flex items-center justify-center">
|
||||
<Settings className="w-4.5 h-4.5 text-muted-foreground" />
|
||||
</div>
|
||||
<span className="text-base font-medium text-foreground">{t('actions.settings')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FolderPlus, MessageSquare, RefreshCw, Search, X } from 'lucide-react';
|
||||
import { FolderPlus, Plus, RefreshCw, Search, X, PanelLeftClose } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { Button } from '../../../ui/button';
|
||||
import { Input } from '../../../ui/input';
|
||||
@@ -33,155 +33,159 @@ export default function SidebarHeader({
|
||||
onCollapseSidebar,
|
||||
t,
|
||||
}: SidebarHeaderProps) {
|
||||
const LogoBlock = () => (
|
||||
<div className="flex items-center gap-2.5 min-w-0">
|
||||
<div className="w-7 h-7 bg-primary/90 rounded-lg flex items-center justify-center shadow-sm flex-shrink-0">
|
||||
<svg className="w-3.5 h-3.5 text-primary-foreground" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-sm font-semibold text-foreground tracking-tight truncate">{t('app.title')}</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex-shrink-0">
|
||||
{/* Desktop header */}
|
||||
<div
|
||||
className="md:p-4 md:border-b md:border-border"
|
||||
className="hidden md:block px-3 pt-3 pb-2"
|
||||
style={isPWA && isMobile ? { paddingTop: '44px' } : {}}
|
||||
>
|
||||
<div className="hidden md:flex items-center justify-between">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{IS_PLATFORM ? (
|
||||
<a
|
||||
href="https://cloudcli.ai/dashboard"
|
||||
className="flex items-center gap-3 hover:opacity-80 transition-opacity group"
|
||||
className="flex items-center gap-2.5 min-w-0 hover:opacity-80 transition-opacity"
|
||||
title={t('tooltips.viewEnvironments')}
|
||||
>
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center shadow-sm group-hover:shadow-md transition-shadow">
|
||||
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-bold text-foreground">{t('app.title')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t('app.subtitle')}</p>
|
||||
</div>
|
||||
<LogoBlock />
|
||||
</a>
|
||||
) : (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center shadow-sm">
|
||||
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-bold text-foreground">{t('app.title')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t('app.subtitle')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<LogoBlock />
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 px-0 hover:bg-accent transition-colors duration-200"
|
||||
onClick={onCollapseSidebar}
|
||||
title={t('tooltips.hideSidebar')}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="md:hidden p-3 border-b border-border"
|
||||
style={isPWA && isMobile ? { paddingTop: '16px' } : {}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{IS_PLATFORM ? (
|
||||
<a
|
||||
href="https://cloudcli.ai/dashboard"
|
||||
className="flex items-center gap-3 active:opacity-70 transition-opacity"
|
||||
title={t('tooltips.viewEnvironments')}
|
||||
>
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
||||
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-foreground">{t('app.title')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t('projects.title')}</p>
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
||||
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-foreground">{t('app.title')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t('projects.title')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className="w-8 h-8 rounded-md bg-background border border-border flex items-center justify-center active:scale-95 transition-all duration-150"
|
||||
onClick={onRefresh}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 text-foreground ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
</button>
|
||||
<button
|
||||
className="w-8 h-8 rounded-md bg-primary text-primary-foreground flex items-center justify-center active:scale-95 transition-all duration-150"
|
||||
onClick={onCreateProject}
|
||||
>
|
||||
<FolderPlus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isLoading && !isMobile && (
|
||||
<div className="px-3 md:px-4 py-2 border-b border-border">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center gap-0.5 flex-shrink-0">
|
||||
<Button
|
||||
variant="default"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex-1 h-8 text-xs bg-primary hover:bg-primary/90 transition-all duration-200"
|
||||
onClick={onCreateProject}
|
||||
title={t('tooltips.createProject')}
|
||||
>
|
||||
<FolderPlus className="w-3.5 h-3.5 mr-1.5" />
|
||||
{t('projects.newProject')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 w-8 px-0 hover:bg-accent transition-colors duration-200 group"
|
||||
className="h-7 w-7 p-0 text-muted-foreground hover:text-foreground hover:bg-accent/80 rounded-lg"
|
||||
onClick={onRefresh}
|
||||
disabled={isRefreshing}
|
||||
title={t('tooltips.refresh')}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`w-3.5 h-3.5 ${
|
||||
isRefreshing ? 'animate-spin' : 'group-hover:rotate-180 transition-transform duration-300'
|
||||
isRefreshing ? 'animate-spin' : ''
|
||||
}`}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0 text-muted-foreground hover:text-foreground hover:bg-accent/80 rounded-lg"
|
||||
onClick={onCreateProject}
|
||||
title={t('tooltips.createProject')}
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0 text-muted-foreground hover:text-foreground hover:bg-accent/80 rounded-lg"
|
||||
onClick={onCollapseSidebar}
|
||||
title={t('tooltips.hideSidebar')}
|
||||
>
|
||||
<PanelLeftClose className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{projectsCount > 0 && !isLoading && (
|
||||
<div className="px-3 md:px-4 py-2 border-b border-border">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
{/* Search bar */}
|
||||
{projectsCount > 0 && !isLoading && (
|
||||
<div className="relative mt-2.5">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground/50 pointer-events-none" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t('projects.searchPlaceholder')}
|
||||
value={searchFilter}
|
||||
onChange={(event) => onSearchFilterChange(event.target.value)}
|
||||
className="pl-9 h-9 text-sm bg-muted/50 border-0 focus:bg-background focus:ring-1 focus:ring-primary/20"
|
||||
className="nav-search-input pl-9 pr-8 h-9 text-xs rounded-xl border-0 placeholder:text-muted-foreground/40 focus-visible:ring-0 focus-visible:ring-offset-0 transition-all duration-200"
|
||||
/>
|
||||
{searchFilter && (
|
||||
<button
|
||||
onClick={onClearSearchFilter}
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2 p-1 hover:bg-accent rounded"
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 p-0.5 hover:bg-accent rounded-md"
|
||||
>
|
||||
<X className="w-3 h-3 text-muted-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Desktop divider */}
|
||||
<div className="hidden md:block nav-divider" />
|
||||
|
||||
{/* Mobile header */}
|
||||
<div
|
||||
className="md:hidden p-3 pb-2"
|
||||
style={isPWA && isMobile ? { paddingTop: '16px' } : {}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{IS_PLATFORM ? (
|
||||
<a
|
||||
href="https://cloudcli.ai/dashboard"
|
||||
className="flex items-center gap-2.5 active:opacity-70 transition-opacity min-w-0"
|
||||
title={t('tooltips.viewEnvironments')}
|
||||
>
|
||||
<LogoBlock />
|
||||
</a>
|
||||
) : (
|
||||
<LogoBlock />
|
||||
)}
|
||||
|
||||
<div className="flex gap-1.5 flex-shrink-0">
|
||||
<button
|
||||
className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center active:scale-95 transition-all"
|
||||
onClick={onRefresh}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 text-muted-foreground ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
</button>
|
||||
<button
|
||||
className="w-8 h-8 rounded-lg bg-primary/90 text-primary-foreground flex items-center justify-center active:scale-95 transition-all"
|
||||
onClick={onCreateProject}
|
||||
>
|
||||
<FolderPlus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* Mobile search */}
|
||||
{projectsCount > 0 && !isLoading && (
|
||||
<div className="relative mt-2.5">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground/50 pointer-events-none" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t('projects.searchPlaceholder')}
|
||||
value={searchFilter}
|
||||
onChange={(event) => onSearchFilterChange(event.target.value)}
|
||||
className="nav-search-input pl-10 pr-9 h-10 text-sm rounded-xl border-0 placeholder:text-muted-foreground/40 focus-visible:ring-0 focus-visible:ring-offset-0 transition-all duration-200"
|
||||
/>
|
||||
{searchFilter && (
|
||||
<button
|
||||
onClick={onClearSearchFilter}
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 p-1 hover:bg-accent rounded-md"
|
||||
>
|
||||
<X className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile divider */}
|
||||
<div className="md:hidden nav-divider" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,12 +84,16 @@ export default function SidebarModals({
|
||||
document.body,
|
||||
)}
|
||||
|
||||
<TypedSettings
|
||||
isOpen={showSettings}
|
||||
onClose={onCloseSettings}
|
||||
projects={settingsProjects}
|
||||
initialTab={settingsInitialTab}
|
||||
/>
|
||||
{showSettings &&
|
||||
ReactDOM.createPortal(
|
||||
<TypedSettings
|
||||
isOpen={showSettings}
|
||||
onClose={onCloseSettings}
|
||||
projects={settingsProjects}
|
||||
initialTab={settingsInitialTab}
|
||||
/>,
|
||||
document.body,
|
||||
)}
|
||||
|
||||
{deleteConfirmation &&
|
||||
ReactDOM.createPortal(
|
||||
|
||||
Reference in New Issue
Block a user