Files
JohnRiceMLandClaude Opus 4.6 32f5b403b0 feat: complete Apple-quality UI/UX remake
Design System:
- Apple HIG typography scale (caption2 → large-title)
- 4px spacing grid (space-1 → space-16)
- 7 new CSS tokens across all 5 themes
- Interactive state CSS classes (hover-lift, btn-primary, focus-ring, etc.)
- Shimmer skeleton, slideDown, bounce-dot animations
- Light theme polish: deeper shadows, better accent contrast

Navigation:
- Lucide icons replacing emoji in sidebar
- Cmd+K global search command palette (agents, pages, crons)
- Breadcrumbs component for page context
- Sidebar wrapper orchestrating desktop + mobile
- Mobile hamburger menu with spring-animated slide-out
- Cron error dot badge on nav item

Manor Map + Agent Detail:
- Polished agent nodes: tinted squircle emoji, status dots with border ring
- Smooth bezier edges, selected agent edge highlighting
- Map legend (top-right), skeleton loading state
- Detail panel: hierarchy navigation, cron status, dual CTAs
- Agent detail: single-column scrollable layout, card sections
- SOUL.md viewer with copy button, voice ID copy

Chat:
- iMessage-style agent list with gradient avatars, online dots
- Desktop + mobile variants with responsive master/detail
- Sticky header with agent info, profile link, clear button
- Time gap timestamps between messages
- Typing indicator (bouncing dots) before first stream token
- Thin blinking cursor replacing block cursor
- Code blocks with copy button, URL auto-linking
- Rounded input capsule with SVG icons

Cron + Memory:
- Sticky header with summary counts, spinning refresh, auto-update timer
- Filter pills with ARIA tablist, keyboard cycling
- Expandable cron rows with colored borders, error tinting, copy
- Memory: 260px sidebar with search, file icons, arrow key nav
- Content header with breadcrumb path, copy + download buttons
- Mobile master/detail pattern with back button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:08:52 -06:00

109 lines
3.2 KiB
TypeScript

'use client';
import { useRef, useCallback } from 'react';
import { THEMES } from '@/lib/themes';
import { useTheme } from '@/app/providers';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const containerRef = useRef<HTMLDivElement>(null);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
const buttons =
containerRef.current?.querySelectorAll<HTMLButtonElement>('button');
if (!buttons || buttons.length === 0) return;
const currentIndex = THEMES.findIndex((t) => t.id === theme);
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
e.preventDefault();
const nextIndex = (currentIndex + 1) % THEMES.length;
setTheme(THEMES[nextIndex].id);
buttons[nextIndex].focus();
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
e.preventDefault();
const prevIndex =
(currentIndex - 1 + THEMES.length) % THEMES.length;
setTheme(THEMES[prevIndex].id);
buttons[prevIndex].focus();
}
},
[theme, setTheme]
);
return (
<div style={{ padding: '8px 16px 12px' }}>
<div
style={{
fontSize: '11px',
fontWeight: 600,
letterSpacing: '0.06em',
color: 'var(--text-tertiary)',
textTransform: 'uppercase',
marginBottom: '6px',
paddingLeft: '4px',
}}
>
Theme
</div>
<div
ref={containerRef}
className="flex flex-wrap gap-1.5"
role="radiogroup"
aria-label="Theme selection"
onKeyDown={handleKeyDown}
>
{THEMES.map((t) => {
const isActive = theme === t.id;
return (
<button
key={t.id}
onClick={() => setTheme(t.id)}
title={t.label}
role="radio"
aria-checked={isActive}
aria-label={`${t.label} theme`}
tabIndex={isActive ? 0 : -1}
className="focus-ring"
style={{
display: 'flex',
alignItems: 'center',
gap: '4px',
height: '28px',
padding: isActive ? '0 10px' : '0 6px',
borderRadius: '14px',
fontSize: '12px',
fontWeight: isActive ? 600 : 500,
border: 'none',
cursor: 'pointer',
transition: 'all 150ms var(--ease-spring)',
background: isActive
? 'var(--accent-fill)'
: 'var(--fill-quaternary)',
color: isActive ? 'var(--accent)' : 'var(--text-tertiary)',
outline: 'none',
}}
>
<span style={{ fontSize: '13px', lineHeight: 1 }}>
{t.emoji}
</span>
{isActive && (
<span
style={{
fontSize: '11px',
fontWeight: 600,
letterSpacing: '-0.01em',
}}
>
{t.label}
</span>
)}
</button>
);
})}
</div>
</div>
);
}