mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-02-21 16:17:34 +00:00
Feat: Refine design language and use theme tokens across most pages.
This commit is contained in:
@@ -13,9 +13,9 @@ export default function MainContentHeader({
|
||||
onMenuClick,
|
||||
}: MainContentHeaderProps) {
|
||||
return (
|
||||
<div className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0">
|
||||
<div className="flex items-center justify-between relative">
|
||||
<div className="flex items-center space-x-2 min-w-0 flex-1">
|
||||
<div className="bg-background border-b border-border/60 px-3 py-1.5 sm:px-4 sm:py-2 pwa-header-safe flex-shrink-0">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
{isMobile && <MobileMenuButton onMenuClick={onMenuClick} />}
|
||||
<MainContentTitle
|
||||
activeTab={activeTab}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Folder } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MobileMenuButton from './MobileMenuButton';
|
||||
import type { MainContentStateViewProps } from '../../types/types';
|
||||
@@ -10,17 +11,17 @@ export default function MainContentStateView({ mode, isMobile, onMenuClick }: Ma
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
{isMobile && (
|
||||
<div className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0">
|
||||
<div className="bg-background/80 backdrop-blur-sm border-b border-border/50 p-2 sm:p-3 pwa-header-safe flex-shrink-0">
|
||||
<MobileMenuButton onMenuClick={onMenuClick} compact />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center text-gray-500 dark:text-gray-400">
|
||||
<div className="w-12 h-12 mx-auto mb-4">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<div className="w-10 h-10 mx-auto mb-4">
|
||||
<div
|
||||
className="w-full h-full rounded-full border-4 border-gray-200 border-t-blue-500"
|
||||
className="w-full h-full rounded-full border-[3px] border-muted border-t-primary"
|
||||
style={{
|
||||
animation: 'spin 1s linear infinite',
|
||||
WebkitAnimation: 'spin 1s linear infinite',
|
||||
@@ -28,22 +29,20 @@ export default function MainContentStateView({ mode, isMobile, onMenuClick }: Ma
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold mb-2">{t('mainContent.loading')}</h2>
|
||||
<p>{t('mainContent.settingUpWorkspace')}</p>
|
||||
<h2 className="text-lg font-semibold text-foreground mb-1">{t('mainContent.loading')}</h2>
|
||||
<p className="text-sm">{t('mainContent.settingUpWorkspace')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center text-gray-500 dark:text-gray-400 max-w-md mx-auto px-6">
|
||||
<div className="w-16 h-16 mx-auto mb-6 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center">
|
||||
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-5l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
<div className="w-14 h-14 mx-auto mb-5 bg-muted/50 rounded-2xl flex items-center justify-center">
|
||||
<Folder className="w-7 h-7 text-muted-foreground" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold mb-3 text-gray-900 dark:text-white">{t('mainContent.chooseProject')}</h2>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">{t('mainContent.selectProjectDescription')}</p>
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 border border-blue-200 dark:border-blue-800">
|
||||
<p className="text-sm text-blue-700 dark:text-blue-300">
|
||||
<h2 className="text-xl font-semibold mb-2 text-foreground">{t('mainContent.chooseProject')}</h2>
|
||||
<p className="text-sm text-muted-foreground mb-5 leading-relaxed">{t('mainContent.selectProjectDescription')}</p>
|
||||
<div className="bg-primary/5 rounded-xl p-3.5 border border-primary/10">
|
||||
<p className="text-sm text-primary">
|
||||
<strong>{t('mainContent.tip')}:</strong> {isMobile ? t('mainContent.createProjectMobile') : t('mainContent.createProjectDesktop')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MessageSquare, Terminal, Folder, GitBranch, ClipboardCheck, type LucideIcon } from 'lucide-react';
|
||||
import Tooltip from '../../../Tooltip';
|
||||
import type { AppTab } from '../../../../types/app';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
@@ -12,50 +13,22 @@ type MainContentTabSwitcherProps = {
|
||||
type TabDefinition = {
|
||||
id: AppTab;
|
||||
labelKey: string;
|
||||
iconPath: string;
|
||||
icon: LucideIcon;
|
||||
};
|
||||
|
||||
const BASE_TABS: TabDefinition[] = [
|
||||
{
|
||||
id: 'chat',
|
||||
labelKey: 'tabs.chat',
|
||||
iconPath:
|
||||
'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z',
|
||||
},
|
||||
{
|
||||
id: 'shell',
|
||||
labelKey: 'tabs.shell',
|
||||
iconPath: 'M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2z',
|
||||
},
|
||||
{
|
||||
id: 'files',
|
||||
labelKey: 'tabs.files',
|
||||
iconPath: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-5l-2-2H5a2 2 0 00-2 2z',
|
||||
},
|
||||
{
|
||||
id: 'git',
|
||||
labelKey: 'tabs.git',
|
||||
iconPath: 'M13 10V3L4 14h7v7l9-11h-7z',
|
||||
},
|
||||
{ id: 'chat', labelKey: 'tabs.chat', icon: MessageSquare },
|
||||
{ id: 'shell', labelKey: 'tabs.shell', icon: Terminal },
|
||||
{ id: 'files', labelKey: 'tabs.files', icon: Folder },
|
||||
{ id: 'git', labelKey: 'tabs.git', icon: GitBranch },
|
||||
];
|
||||
|
||||
const TASKS_TAB: TabDefinition = {
|
||||
id: 'tasks',
|
||||
labelKey: 'tabs.tasks',
|
||||
iconPath:
|
||||
'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4',
|
||||
icon: ClipboardCheck,
|
||||
};
|
||||
|
||||
function getButtonClasses(tabId: AppTab, activeTab: AppTab) {
|
||||
const base = 'relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200';
|
||||
|
||||
if (tabId === activeTab) {
|
||||
return `${base} bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm`;
|
||||
}
|
||||
|
||||
return `${base} text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700`;
|
||||
}
|
||||
|
||||
export default function MainContentTabSwitcher({
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
@@ -66,19 +39,27 @@ export default function MainContentTabSwitcher({
|
||||
const tabs = shouldShowTasksTab ? [...BASE_TABS, TASKS_TAB] : BASE_TABS;
|
||||
|
||||
return (
|
||||
<div className="relative flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1">
|
||||
{tabs.map((tab) => (
|
||||
<Tooltip key={tab.id} content={t(tab.labelKey)} position="bottom">
|
||||
<button onClick={() => setActiveTab(tab.id)} className={getButtonClasses(tab.id, activeTab)}>
|
||||
<span className="flex items-center gap-1 sm:gap-1.5">
|
||||
<svg className="w-3 sm:w-3.5 h-3 sm:h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={tab.iconPath} />
|
||||
</svg>
|
||||
<span className="hidden md:hidden lg:inline">{t(tab.labelKey)}</span>
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
<div className="inline-flex items-center bg-muted/60 rounded-lg p-[3px] gap-[2px]">
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
const isActive = tab.id === activeTab;
|
||||
|
||||
return (
|
||||
<Tooltip key={tab.id} content={t(tab.labelKey)} position="bottom">
|
||||
<button
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`relative flex items-center gap-1.5 px-2.5 py-[5px] text-xs font-medium rounded-md transition-all duration-150 ${
|
||||
isActive
|
||||
? 'bg-background text-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-3.5 h-3.5" strokeWidth={isActive ? 2.2 : 1.8} />
|
||||
<span className="hidden lg:inline">{t(tab.labelKey)}</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,22 +55,22 @@ export default function MainContentTitle({
|
||||
<div className="min-w-0 flex-1">
|
||||
{activeTab === 'chat' && selectedSession ? (
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white whitespace-nowrap overflow-x-auto scrollbar-hide">
|
||||
<h2 className="text-sm font-semibold text-foreground whitespace-nowrap overflow-x-auto scrollbar-hide leading-tight">
|
||||
{getSessionTitle(selectedSession)}
|
||||
</h2>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 truncate">{selectedProject.displayName}</div>
|
||||
<div className="text-[11px] text-muted-foreground truncate leading-tight">{selectedProject.displayName}</div>
|
||||
</div>
|
||||
) : showChatNewSession ? (
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white">{t('mainContent.newSession')}</h2>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 truncate">{selectedProject.displayName}</div>
|
||||
<h2 className="text-sm font-semibold text-foreground leading-tight">{t('mainContent.newSession')}</h2>
|
||||
<div className="text-[11px] text-muted-foreground truncate leading-tight">{selectedProject.displayName}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white">
|
||||
<h2 className="text-sm font-semibold text-foreground leading-tight">
|
||||
{getTabTitle(activeTab, shouldShowTasksTab, t)}
|
||||
</h2>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 truncate">{selectedProject.displayName}</div>
|
||||
<div className="text-[11px] text-muted-foreground truncate leading-tight">{selectedProject.displayName}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,8 @@ export default function MobileMenuButton({ onMenuClick, compact = false }: Mobil
|
||||
const { handleMobileMenuClick, handleMobileMenuTouchEnd } = useMobileMenuHandlers(onMenuClick);
|
||||
|
||||
const buttonClasses = compact
|
||||
? 'p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 pwa-menu-button'
|
||||
: 'p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 touch-manipulation active:scale-95 pwa-menu-button flex-shrink-0';
|
||||
? 'p-1.5 text-muted-foreground hover:text-foreground rounded-lg hover:bg-accent/60 pwa-menu-button'
|
||||
: 'p-1.5 text-muted-foreground hover:text-foreground rounded-lg hover:bg-accent/60 touch-manipulation active:scale-95 pwa-menu-button flex-shrink-0';
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user