Skip to content

Commit c617e08

Browse files
Add Workspace Hub theme presets
1 parent 16e5d56 commit c617e08

6 files changed

Lines changed: 1047 additions & 236 deletions

File tree

repos/workspace-hub/src/app/App.tsx

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ import { SectionCard } from '../components/SectionCard.tsx'
55
import { RepoSnapshot } from '../features/repos/RepoSnapshot.tsx'
66
import { SettingsPanel } from '../features/settings/SettingsPanel.tsx'
77
import { StatusStrip } from '../features/status/StatusStrip.tsx'
8+
import { ThemeControls } from '../features/theme/ThemeControls.tsx'
9+
import {
10+
applyThemePreference,
11+
persistThemePreference,
12+
themePresets,
13+
type ThemeMode,
14+
type ThemePreference,
15+
type ThemePreset,
16+
} from '../features/theme/theme.ts'
817
import {
918
fetchWorkspaceSummary,
1019
generateRepoCover,
@@ -22,6 +31,9 @@ import type { RepoType, WorkspaceRepo, WorkspaceSummary } from '../types/workspa
2231
import './app.css'
2332

2433
type RepoFilterValue = RepoType | 'all' | 'external' | 'runnable'
34+
type AppProps = {
35+
initialThemePreference: ThemePreference
36+
}
2537

2638
function formatGeneratedAt(value: string) {
2739
return new Intl.DateTimeFormat(undefined, {
@@ -110,7 +122,7 @@ function filterRepos(
110122
})
111123
}
112124

113-
export function App() {
125+
export function App({ initialThemePreference }: AppProps) {
114126
const [summary, setSummary] = useState<WorkspaceSummary | null>(null)
115127
const [loading, setLoading] = useState(true)
116128
const [error, setError] = useState<string | null>(null)
@@ -119,6 +131,7 @@ export function App() {
119131
const [searchTerm, setSearchTerm] = useState('')
120132
const [selectedPath, setSelectedPath] = useState<string | null>(null)
121133
const [selectedFilter, setSelectedFilter] = useState<RepoFilterValue>('all')
134+
const [themePreference, setThemePreference] = useState(initialThemePreference)
122135
const deferredSearchTerm = useDeferredValue(searchTerm)
123136
const lastRecordedSelectionRef = useRef<string | null>(null)
124137

@@ -357,13 +370,20 @@ export function App() {
357370
}
358371
}, [])
359372

373+
useEffect(() => {
374+
applyThemePreference(themePreference)
375+
persistThemePreference(themePreference)
376+
}, [themePreference])
377+
360378
const availableTypes = summary
361379
? [...new Set(summary.repos.map((repo) => repo.type))].sort()
362380
: []
363381
const normalizedSearch = deferredSearchTerm.trim().toLowerCase()
364382
const filteredRepos = summary
365383
? filterRepos(summary.repos, normalizedSearch, selectedFilter)
366384
: []
385+
const activeThemePreset =
386+
themePresets.find((preset) => preset.id === themePreference.preset) ?? themePresets[0]
367387

368388
useEffect(() => {
369389
const nextFilteredRepos = summary
@@ -419,38 +439,64 @@ export function App() {
419439

420440
const generatedAt = summary ? formatGeneratedAt(summary.generatedAt) : 'Waiting for API'
421441

442+
function handleThemePresetChange(preset: ThemePreset) {
443+
setThemePreference((currentPreference) => ({
444+
...currentPreference,
445+
preset,
446+
}))
447+
}
448+
449+
function handleThemeModeChange(mode: ThemeMode) {
450+
setThemePreference((currentPreference) => ({
451+
...currentPreference,
452+
mode,
453+
}))
454+
}
455+
422456
return (
423457
<div className="app-shell">
424458
<header className="hero-panel reveal">
425-
<div className="hero-copy">
426-
<p className="eyebrow">Codex Workspace Control Centre</p>
427-
<h1>Workspace Hub</h1>
428-
<p className="hero-text">
429-
A local dashboard for discovering repositories, tracking runtime
430-
state, and keeping direct previews as the default path.
431-
</p>
432-
</div>
433-
434-
<div className="hero-actions">
435-
<button
436-
className="primary-button"
437-
disabled={loading}
438-
onClick={() => {
439-
void loadSummary()
440-
}}
441-
type="button"
442-
>
443-
{loading ? 'Refreshing snapshot...' : 'Refresh snapshot'}
444-
</button>
445-
446-
<a className="secondary-link" href="/api/health" rel="noreferrer" target="_blank">
447-
API health
448-
</a>
449-
</div>
459+
<div className="hero-grid">
460+
<div className="hero-main">
461+
<div className="hero-copy">
462+
<p className="eyebrow">Codex Workspace Control Centre</p>
463+
<h1>Workspace Hub</h1>
464+
<p className="hero-text">
465+
A local dashboard for discovering repositories, tracking runtime
466+
state, and keeping direct previews as the default path.
467+
</p>
468+
<p className="hero-subtext">{activeThemePreset.description}</p>
469+
</div>
470+
471+
<div className="hero-actions">
472+
<button
473+
className="primary-button"
474+
disabled={loading}
475+
onClick={() => {
476+
void loadSummary()
477+
}}
478+
type="button"
479+
>
480+
{loading ? 'Refreshing snapshot...' : 'Refresh snapshot'}
481+
</button>
482+
483+
<a className="secondary-link" href="/api/health" rel="noreferrer" target="_blank">
484+
API health
485+
</a>
486+
</div>
487+
488+
<div className="hero-meta">
489+
<span>Workspace root: {summary?.workspaceRoot ?? 'Loading...'}</span>
490+
<span>Last sync: {generatedAt}</span>
491+
</div>
492+
</div>
450493

451-
<div className="hero-meta">
452-
<span>Workspace root: {summary?.workspaceRoot ?? 'Loading...'}</span>
453-
<span>Last sync: {generatedAt}</span>
494+
<ThemeControls
495+
mode={themePreference.mode}
496+
onModeChange={handleThemeModeChange}
497+
onPresetChange={handleThemePresetChange}
498+
preset={themePreference.preset}
499+
/>
454500
</div>
455501
</header>
456502

0 commit comments

Comments
 (0)