Major UI refresh: sidebar redesign, theme expansion, and chat performance optimizations (#706)
## Summary Complete sidebar redesign and comprehensive UI polish pass with performance optimizations, theme system refinements, and desktop integration improvements. ## Key Changes **Sidebar & Navigation Redesign** - Redesigned sessions sidebar layout with unified button primitives - Added activity sections with project grouping and improved session organization - Refined sidebar corners, spacing, and visual hierarchy - Removed NavRail component in favor of streamlined sidebar - Stabilized sessions bar toggle position in fullscreen mode **Performance Optimizations** - Reduced chat streaming CPU usage and storage churn - Optimized task tool polling and live timers with debouncing - Prevented chat state races and reduced background request load - Debounced draft writes and coalesced session reloads - Optimized message store updates and turn tracking **Theme & Visual System** - Added theme-aware window corners (desktop) and border radius tokens - Introduced glassmorphism effects on desktop sidebar - Added backdrop blur to UI elements **Chat Experience** - Added session-based permission auto-accept toggle in chat input - Polished permission shield UX with improved icon sizing and spacing - Fixed chat scroll-to-bottom behavior and timeline tracking - Enhanced tool output display with better path label detection - Removed duplicate draft context details in chat header - Added text selection menu to chat messages **Git Improvements** - Refreshed git history visual design with cleaner dividers - Added remote removal action in sync selector - Stabilized git polling to prevent excessive requests - Improved tool output rendering for git operations **Settings & Panels** - Fixed mobile scrolling on settings pages - Made outside-click settings close instantly - Reduced settings load churn and CPU spikes - Improved services dropdown layout and spacing - Softened panel resize handles **Desktop Integration** - Synced macOS window theme with app theme - Restored window dragging in sidebar header zones - Fixed system window corners on macOS - Improved header session metadata and action controls **Button & Component Standardization** - Unified button primitives across all components - Standardized destructive action patterns - Removed unused button variants (button-large, button-small) - Aligned context tab close hit areas --------- Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Iuliia Ivashko
parent
359879153a
commit
321cc7252a
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ButtonSmall } from '@/components/ui/button-small';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { toast } from '@/components/ui';
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { ButtonLarge } from '@/components/ui/button-large';
|
||||
import { SkillsCatalogPage } from './catalog/SkillsCatalogPage';
|
||||
import {
|
||||
SKILL_LOCATION_OPTIONS,
|
||||
@@ -459,9 +458,9 @@ const SkillsInstalledPage: React.FC = () => {
|
||||
<h3 className="typography-ui-header font-medium text-foreground">
|
||||
Supporting Files
|
||||
</h3>
|
||||
<ButtonSmall variant="outline" size="xs" className="!font-normal gap-1" onClick={handleAddFile}>
|
||||
<Button variant="outline" size="xs" className="!font-normal gap-1" onClick={handleAddFile}>
|
||||
<RiAddLine className="h-3.5 w-3.5" /> Add File
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<section className="px-2 pb-2 pt-0">
|
||||
@@ -491,7 +490,7 @@ const SkillsInstalledPage: React.FC = () => {
|
||||
pending
|
||||
</span>
|
||||
)}
|
||||
<ButtonSmall
|
||||
<Button size="sm"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 px-0 flex-shrink-0 text-muted-foreground hover:text-[var(--status-error)] opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
onClick={(e) => {
|
||||
@@ -500,7 +499,7 @@ const SkillsInstalledPage: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<RiDeleteBinLine className="h-3 w-3" />
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -511,14 +510,14 @@ const SkillsInstalledPage: React.FC = () => {
|
||||
|
||||
{/* Save action */}
|
||||
<div className="px-2 py-1">
|
||||
<ButtonSmall
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || !hasSkillChanges}
|
||||
size="xs"
|
||||
className="!font-normal"
|
||||
>
|
||||
{isSaving ? 'Saving...' : isNewSkill ? 'Create Skill' : 'Save Changes'}
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -540,16 +539,17 @@ const SkillsInstalledPage: React.FC = () => {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<ButtonLarge
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setDeleteFilePath(null)}
|
||||
disabled={isDeletingFile}
|
||||
>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge onClick={handleConfirmDeleteFile} disabled={isDeletingFile} className="bg-[var(--status-error)] hover:bg-[var(--status-error)]/90 text-white border-0">
|
||||
</Button>
|
||||
<Button size="sm" variant="destructive" onClick={handleConfirmDeleteFile} disabled={isDeletingFile}>
|
||||
Delete
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -603,7 +603,8 @@ const SkillsInstalledPage: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter className="mt-4">
|
||||
<ButtonLarge
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setIsFileDialogOpen(false);
|
||||
@@ -611,10 +612,10 @@ const SkillsInstalledPage: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge onClick={handleSaveFile} disabled={isLoadingFile || !hasFileChanges}>
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSaveFile} disabled={isLoadingFile || !hasFileChanges}>
|
||||
{editingFilePath ? 'Save Changes' : 'Create File'}
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ButtonSmall } from '@/components/ui/button-small';
|
||||
import { ButtonLarge } from '@/components/ui/button-large';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { toast } from '@/components/ui';
|
||||
import { isMobileDeviceViaCSS } from '@/lib/device';
|
||||
@@ -207,13 +206,13 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
|
||||
<SettingsProjectSelector className="mb-3" />
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="typography-meta text-muted-foreground">Total {skills.length}</span>
|
||||
<ButtonSmall
|
||||
<Button size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
|
||||
onClick={handleCreateNew}
|
||||
>
|
||||
<RiAddLine className="h-3.5 w-3.5" />
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -347,17 +346,18 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<ButtonLarge
|
||||
<Button
|
||||
size="sm"
|
||||
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||
variant="ghost"
|
||||
onClick={() => setDeleteDialogSkill(null)}
|
||||
disabled={isDeletePending}
|
||||
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||
>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge onClick={handleConfirmDeleteSkill} disabled={isDeletePending}>
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleConfirmDeleteSkill} disabled={isDeletePending}>
|
||||
Delete
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -383,16 +383,17 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
|
||||
}}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<ButtonLarge
|
||||
<Button
|
||||
size="sm"
|
||||
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||
variant="ghost"
|
||||
onClick={() => setRenameDialogSkill(null)}
|
||||
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||
>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge onClick={handleRenameSkill}>
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleRenameSkill}>
|
||||
Rename
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -461,12 +462,12 @@ const SkillListItem: React.FC<SkillListItemProps> = ({
|
||||
|
||||
<DropdownMenu open={isMenuOpen} onOpenChange={onMenuOpenChange}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<ButtonSmall
|
||||
<Button size="sm"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 px-0 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
|
||||
>
|
||||
<RiMore2Line className="h-3.5 w-3.5" />
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-fit min-w-20">
|
||||
<DropdownMenuItem
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { ButtonLarge } from '@/components/ui/button-large';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
@@ -311,24 +311,26 @@ export const AddCatalogDialog: React.FC<AddCatalogDialogProps> = ({ open, onOpen
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<ButtonLarge variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
<Button size="sm" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
variant="ghost"
|
||||
onClick={() => void handleScan()}
|
||||
disabled={isScanning || !source.trim()}
|
||||
className="gap-2"
|
||||
>
|
||||
<RiGitRepositoryLine className="h-4 w-4" />
|
||||
{isScanning ? 'Scanning...' : 'Scan'}
|
||||
</ButtonLarge>
|
||||
<ButtonLarge
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => void handleAdd()}
|
||||
disabled={!scanOk || isDuplicate || !label.trim() || !source.trim()}
|
||||
>
|
||||
Add catalog
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { ButtonLarge } from '@/components/ui/button-large';
|
||||
import { ButtonSmall } from '@/components/ui/button-small';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -73,8 +72,8 @@ export const InstallConflictsDialog: React.FC<InstallConflictsDialogProps> = ({
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="typography-meta text-muted-foreground">{conflicts.length} conflict(s)</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonSmall variant="outline" size="xs" className="!font-normal" onClick={() => setAll('skip')}>Skip all</ButtonSmall>
|
||||
<ButtonSmall variant="outline" size="xs" className="!font-normal" onClick={() => setAll('overwrite')}>Overwrite all</ButtonSmall>
|
||||
<Button variant="outline" size="xs" className="!font-normal" onClick={() => setAll('skip')}>Skip all</Button>
|
||||
<Button variant="outline" size="xs" className="!font-normal" onClick={() => setAll('overwrite')}>Overwrite all</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -113,15 +112,16 @@ export const InstallConflictsDialog: React.FC<InstallConflictsDialogProps> = ({
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<ButtonLarge variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
<Button size="sm" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => onConfirm(decisions)}
|
||||
disabled={!canConfirm}
|
||||
>
|
||||
Continue
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ButtonLarge } from '@/components/ui/button-large';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -487,15 +486,16 @@ export const InstallFromRepoDialog: React.FC<InstallFromRepoDialogProps> = ({ op
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex-shrink-0">
|
||||
<ButtonLarge variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
<Button size="sm" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={isInstalling || selectedDirs.length === 0 || !source.trim() || (scope === 'project' && !directoryOverride)}
|
||||
onClick={() => void doInstall({})}
|
||||
>
|
||||
{isInstalling ? 'Installing…' : 'Install selected'}
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { ButtonLarge } from '@/components/ui/button-large';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -231,13 +231,15 @@ export const InstallSkillDialog: React.FC<InstallSkillDialogProps> = ({ open, on
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<ButtonLarge
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={isInstalling || !item.installable || (scope === 'project' && !directoryOverride)}
|
||||
onClick={() =>
|
||||
void doInstall({
|
||||
@@ -251,7 +253,7 @@ export const InstallSkillDialog: React.FC<InstallSkillDialogProps> = ({ open, on
|
||||
}
|
||||
>
|
||||
{isInstalling ? 'Installing...' : 'Install'}
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ButtonSmall } from '@/components/ui/button-small';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { ButtonLarge } from '@/components/ui/button-large';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -195,7 +194,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<ButtonSmall
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
className="!font-normal h-6 w-6 px-0"
|
||||
@@ -210,10 +209,10 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
title="Refresh"
|
||||
>
|
||||
<RiRefreshLine className={cn("h-3.5 w-3.5", (isLoadingCatalog || isLoadingSource) && "animate-spin")} />
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
|
||||
{isCustomSource && (
|
||||
<ButtonSmall
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="!font-normal h-6 w-6 px-0 text-[var(--status-error)] hover:text-[var(--status-error)]"
|
||||
@@ -222,16 +221,16 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
title="Remove Catalog"
|
||||
>
|
||||
<RiDeleteBinLine className="h-3.5 w-3.5" />
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<ButtonSmall
|
||||
<Button
|
||||
size="xs"
|
||||
className="!font-normal gap-1"
|
||||
onClick={() => setAddCatalogOpen(true)}
|
||||
>
|
||||
<RiAddLine className="h-3.5 w-3.5" /> Add Catalog
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="py-1.5">
|
||||
@@ -328,7 +327,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<ButtonSmall
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
className="!font-normal shrink-0"
|
||||
@@ -339,7 +338,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
}}
|
||||
>
|
||||
Install
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -350,7 +349,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
|
||||
{isClawdHubSource && hasMoreClawdHub && !isLoadingSource && filtered.length > 0 && (
|
||||
<div className="flex justify-center mt-2 px-2">
|
||||
<ButtonSmall
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
className="!font-normal"
|
||||
@@ -358,7 +357,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
disabled={isLoadingMore}
|
||||
>
|
||||
{isLoadingMore ? 'Loading...' : 'Load More Skills'}
|
||||
</ButtonSmall>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -381,16 +380,17 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
<DialogDescription>Are you sure you want to remove this catalog?</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<ButtonLarge
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setIsRemoveCatalogDialogOpen(false)}
|
||||
disabled={isRemovingCatalog}
|
||||
>
|
||||
Cancel
|
||||
</ButtonLarge>
|
||||
<ButtonLarge className="bg-[var(--status-error)] hover:bg-[var(--status-error)]/90 text-white" onClick={() => void removeSelectedCatalog()} disabled={isRemovingCatalog}>
|
||||
</Button>
|
||||
<Button size="sm" variant="destructive" onClick={() => void removeSelectedCatalog()} disabled={isRemovingCatalog}>
|
||||
Remove Catalog
|
||||
</ButtonLarge>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user