Skip to content

Commit 290cfc9

Browse files
committed
feat: add 3-way dark mode toggle (dark/system/light)
Add a ThemeToggle component with moon/computer/sun icons that appears in both the Settings page (under a new Appearance section) and the "More" menu in the sidebar. The toggle persists as themeMode in CeptSettings. Convert all dark mode CSS from @media (prefers-color-scheme: dark) to .dark class-based selectors so the theme can be controlled programmatically via a useTheme hook that manages the dark class on <html>. https://claude.ai/code/session_01QddASAFJgFoPPTDS88nkq2
1 parent 5835875 commit 290cfc9

12 files changed

Lines changed: 533 additions & 301 deletions

File tree

packages/ui/src/components/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { SearchResult } from './search/SearchPanel.js';
1212
import { PageHeader } from './page-header/PageHeader.js';
1313
import { SettingsModal, DEFAULT_SETTINGS } from './settings/SettingsModal.js';
1414
import type { CeptSettings, SpaceInfo } from './settings/SettingsModal.js';
15+
import { useTheme } from './settings/useTheme.js';
1516
import { DOCS_PAGES, DOCS_CONTENT, DOCS_SPACE_INFO, getDocsSourceUrl, resolveDocsContent } from './docs/docs-content.js';
1617
import {
1718
useStorage,
@@ -104,6 +105,7 @@ export function App() {
104105
const [favorites, setFavorites] = useState<SidebarPageRef[]>([]);
105106
const [recentPages, setRecentPages] = useState<SidebarPageRef[]>([]);
106107
const [settings, setSettings] = useState<CeptSettings>({ ...DEFAULT_SETTINGS });
108+
useTheme(settings.themeMode);
107109
const [settingsOpen, setSettingsOpen] = useState(false);
108110
const [settingsTab, setSettingsTab] = useState<'settings' | 'about' | 'spaces'>('settings');
109111
const [addSpaceWizardOpen, setAddSpaceWizardOpen] = useState(false);
@@ -1253,6 +1255,8 @@ export function App() {
12531255
handleSwitchSpace(id);
12541256
}
12551257
}}
1258+
themeMode={settings.themeMode}
1259+
onThemeModeChange={(mode) => handleSettingsChange({ ...settings, themeMode: mode })}
12561260
/>
12571261
)}
12581262
{sidebarOpen && isDocsActive && (
@@ -1288,6 +1292,8 @@ export function App() {
12881292
handleSwitchSpace(id);
12891293
}
12901294
}}
1295+
themeMode={settings.themeMode}
1296+
onThemeModeChange={(mode) => handleSettingsChange({ ...settings, themeMode: mode })}
12911297
/>
12921298
)}
12931299
<section className="flex-1 min-w-0 p-4 md:p-8 overflow-y-auto">

packages/ui/src/components/app-menu/app-menu-styles.css

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,18 @@
143143
}
144144

145145
/* Dark mode */
146-
@media (prefers-color-scheme: dark) {
147-
.cept-app-menu-trigger { color: #71717a; }
148-
.cept-app-menu-trigger:hover { background-color: rgba(255, 255, 255, 0.06); color: #a1a1aa; }
149-
.cept-app-menu { background: #262626; border-color: #404040; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); }
150-
.cept-app-menu-item { color: #d4d4d8; }
151-
.cept-app-menu-item:hover:not(:disabled) { background-color: rgba(255, 255, 255, 0.06); }
152-
.cept-app-menu-item:disabled { color: #71717a; }
153-
.cept-app-menu-badge { background: #333; color: #71717a; }
154-
.cept-app-menu-item--danger { color: #ef4444; }
155-
.cept-app-menu-item--danger:hover { background-color: rgba(239, 68, 68, 0.1) !important; }
156-
.cept-app-menu-divider { background: #404040; }
157-
.cept-about-dialog { background: #262626; }
158-
.cept-about-dialog h2 { color: #e5e5e5; }
159-
.cept-about-dialog p { color: #a1a1aa; }
160-
.cept-about-close { background: #333; border-color: #404040; color: #d4d4d8; }
161-
.cept-about-close:hover { background: #404040; }
162-
}
146+
.dark .cept-app-menu-trigger { color: #71717a; }
147+
.dark .cept-app-menu-trigger:hover { background-color: rgba(255, 255, 255, 0.06); color: #a1a1aa; }
148+
.dark .cept-app-menu { background: #262626; border-color: #404040; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); }
149+
.dark .cept-app-menu-item { color: #d4d4d8; }
150+
.dark .cept-app-menu-item:hover:not(:disabled) { background-color: rgba(255, 255, 255, 0.06); }
151+
.dark .cept-app-menu-item:disabled { color: #71717a; }
152+
.dark .cept-app-menu-badge { background: #333; color: #71717a; }
153+
.dark .cept-app-menu-item--danger { color: #ef4444; }
154+
.dark .cept-app-menu-item--danger:hover { background-color: rgba(239, 68, 68, 0.1) !important; }
155+
.dark .cept-app-menu-divider { background: #404040; }
156+
.dark .cept-about-dialog { background: #262626; }
157+
.dark .cept-about-dialog h2 { color: #e5e5e5; }
158+
.dark .cept-about-dialog p { color: #a1a1aa; }
159+
.dark .cept-about-close { background: #333; border-color: #404040; color: #d4d4d8; }
160+
.dark .cept-about-close:hover { background: #404040; }

packages/ui/src/components/editor/editor-styles.css

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -931,51 +931,49 @@
931931
}
932932

933933
/* Dark mode */
934-
@media (prefers-color-scheme: dark) {
935-
.cept-editor .tiptap p.is-editor-empty:first-child::before { color: #52525b; }
936-
.cept-editor .tiptap code { background-color: rgba(255, 255, 255, 0.1); }
937-
.cept-editor .tiptap hr, .cept-editor .tiptap hr.cept-divider { border-top-color: #404040; }
938-
.cept-editor .tiptap blockquote.cept-blockquote { border-left-color: #404040; color: #a1a1aa; }
939-
.cept-editor .tiptap pre.cept-code-block { background-color: #1e1e1e; }
940-
.cept-editor .tiptap .cept-mermaid { border-color: #404040; }
941-
.cept-editor .tiptap .cept-mermaid-source { background-color: #1e1e1e; border-bottom-color: #404040; }
942-
.cept-editor .tiptap .cept-mermaid-preview { background: #262626; }
943-
.cept-editor .tiptap .cept-math-block { background-color: #1e1e1e; }
944-
.cept-editor .tiptap figure.cept-image-block figcaption.cept-image-caption { color: #71717a; }
945-
.cept-editor .tiptap figure.cept-image-block img:hover { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 2px 8px rgba(0, 0, 0, 0.3); }
946-
.cept-editor .tiptap aside.cept-bookmark a.cept-bookmark-link { border-color: #404040; }
947-
.cept-editor .tiptap aside.cept-bookmark a.cept-bookmark-link:hover { background-color: rgba(255, 255, 255, 0.03); }
948-
.cept-editor .tiptap .cept-bookmark-description { color: #a1a1aa; }
949-
.cept-editor .tiptap .cept-bookmark-url { color: #71717a; }
950-
.cept-editor .tiptap li.cept-task-item[data-checked="true"] > div > p { color: #52525b; }
951-
.cept-editor .tiptap a.cept-link { color: #60a5fa; }
952-
.cept-editor .tiptap a.cept-link:hover { color: #93bbfd; }
953-
.cept-editor .tiptap mark { background-color: rgba(255, 212, 0, 0.25); }
954-
.cept-slash-menu { background: #262626; border-color: #404040; }
955-
.cept-slash-menu-empty { color: #71717a; }
956-
.cept-slash-menu-group + .cept-slash-menu-group { border-top-color: #333; }
957-
.cept-slash-menu-category { color: #71717a; }
958-
.cept-slash-menu-item:hover, .cept-slash-menu-item.is-selected { background-color: #333; }
959-
.cept-slash-menu-icon { background: #1e1e1e; border-color: #404040; }
960-
.cept-slash-menu-title { color: #e5e5e5; }
961-
.cept-slash-menu-description { color: #71717a; }
962-
.cept-inline-toolbar { background: #262626; border-color: #404040; }
963-
.cept-inline-toolbar-btn { color: #d4d4d8; }
964-
.cept-inline-toolbar-btn:hover { background-color: #333; }
965-
.cept-inline-toolbar-btn.is-active { background-color: #312e81; color: #a5b4fc; }
966-
.cept-inline-toolbar-divider { background-color: #404040; }
967-
.cept-inline-toolbar-link-input { background: #333; border-color: #525252; color: #e5e5e5; }
968-
.cept-block-actions { background: #262626; border-color: #404040; }
969-
.cept-block-action-item { color: #d4d4d8; }
970-
.cept-block-action-item:hover { background-color: #333; }
971-
.cept-block-action-divider { background-color: #404040; }
972-
.cept-editor .tiptap table.cept-table td.cept-table-cell,
973-
.cept-editor .tiptap table.cept-table th.cept-table-header-cell { border-color: #404040; color: #d4d4d8; }
974-
.cept-editor .tiptap table.cept-table th.cept-table-header-cell { background-color: #1e1e1e; color: #e5e5e5; }
975-
.cept-editor .tiptap table.cept-table .selectedCell::after { background: rgba(99, 102, 241, 0.2); }
976-
.cept-folder-view-item { color: #d4d4d8; }
977-
.cept-folder-view-item:hover { background-color: #333; }
978-
.cept-folder-view-count { color: #71717a; background: #333; }
979-
.cept-editor .drag-handle::after { color: #4a4a4a; }
980-
.cept-editor .drag-handle:hover::after { color: #71717a; }
981-
}
934+
.dark .cept-editor .tiptap p.is-editor-empty:first-child::before { color: #52525b; }
935+
.dark .cept-editor .tiptap code { background-color: rgba(255, 255, 255, 0.1); }
936+
.dark .cept-editor .tiptap hr, .dark .cept-editor .tiptap hr.cept-divider { border-top-color: #404040; }
937+
.dark .cept-editor .tiptap blockquote.cept-blockquote { border-left-color: #404040; color: #a1a1aa; }
938+
.dark .cept-editor .tiptap pre.cept-code-block { background-color: #1e1e1e; }
939+
.dark .cept-editor .tiptap .cept-mermaid { border-color: #404040; }
940+
.dark .cept-editor .tiptap .cept-mermaid-source { background-color: #1e1e1e; border-bottom-color: #404040; }
941+
.dark .cept-editor .tiptap .cept-mermaid-preview { background: #262626; }
942+
.dark .cept-editor .tiptap .cept-math-block { background-color: #1e1e1e; }
943+
.dark .cept-editor .tiptap figure.cept-image-block figcaption.cept-image-caption { color: #71717a; }
944+
.dark .cept-editor .tiptap figure.cept-image-block img:hover { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 2px 8px rgba(0, 0, 0, 0.3); }
945+
.dark .cept-editor .tiptap aside.cept-bookmark a.cept-bookmark-link { border-color: #404040; }
946+
.dark .cept-editor .tiptap aside.cept-bookmark a.cept-bookmark-link:hover { background-color: rgba(255, 255, 255, 0.03); }
947+
.dark .cept-editor .tiptap .cept-bookmark-description { color: #a1a1aa; }
948+
.dark .cept-editor .tiptap .cept-bookmark-url { color: #71717a; }
949+
.dark .cept-editor .tiptap li.cept-task-item[data-checked="true"] > div > p { color: #52525b; }
950+
.dark .cept-editor .tiptap a.cept-link { color: #60a5fa; }
951+
.dark .cept-editor .tiptap a.cept-link:hover { color: #93bbfd; }
952+
.dark .cept-editor .tiptap mark { background-color: rgba(255, 212, 0, 0.25); }
953+
.dark .cept-slash-menu { background: #262626; border-color: #404040; }
954+
.dark .cept-slash-menu-empty { color: #71717a; }
955+
.dark .cept-slash-menu-group + .dark .cept-slash-menu-group { border-top-color: #333; }
956+
.dark .cept-slash-menu-category { color: #71717a; }
957+
.dark .cept-slash-menu-item:hover, .dark .cept-slash-menu-item.is-selected { background-color: #333; }
958+
.dark .cept-slash-menu-icon { background: #1e1e1e; border-color: #404040; }
959+
.dark .cept-slash-menu-title { color: #e5e5e5; }
960+
.dark .cept-slash-menu-description { color: #71717a; }
961+
.dark .cept-inline-toolbar { background: #262626; border-color: #404040; }
962+
.dark .cept-inline-toolbar-btn { color: #d4d4d8; }
963+
.dark .cept-inline-toolbar-btn:hover { background-color: #333; }
964+
.dark .cept-inline-toolbar-btn.is-active { background-color: #312e81; color: #a5b4fc; }
965+
.dark .cept-inline-toolbar-divider { background-color: #404040; }
966+
.dark .cept-inline-toolbar-link-input { background: #333; border-color: #525252; color: #e5e5e5; }
967+
.dark .cept-block-actions { background: #262626; border-color: #404040; }
968+
.dark .cept-block-action-item { color: #d4d4d8; }
969+
.dark .cept-block-action-item:hover { background-color: #333; }
970+
.dark .cept-block-action-divider { background-color: #404040; }
971+
.dark .cept-editor .tiptap table.cept-table td.cept-table-cell,
972+
.dark .cept-editor .tiptap table.cept-table th.cept-table-header-cell { border-color: #404040; color: #d4d4d8; }
973+
.dark .cept-editor .tiptap table.cept-table th.cept-table-header-cell { background-color: #1e1e1e; color: #e5e5e5; }
974+
.dark .cept-editor .tiptap table.cept-table .selectedCell::after { background: rgba(99, 102, 241, 0.2); }
975+
.dark .cept-folder-view-item { color: #d4d4d8; }
976+
.dark .cept-folder-view-item:hover { background-color: #333; }
977+
.dark .cept-folder-view-count { color: #71717a; background: #333; }
978+
.dark .cept-editor .drag-handle::after { color: #4a4a4a; }
979+
.dark .cept-editor .drag-handle:hover::after { color: #71717a; }

packages/ui/src/components/page-header/page-header-styles.css

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,17 @@
168168
}
169169

170170
/* Dark mode */
171-
@media (prefers-color-scheme: dark) {
172-
.cept-page-header-title { color: #e5e5e5; background: transparent; border-color: transparent; }
173-
.cept-page-header-title:hover { background: #262626; border-color: #404040; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); }
174-
.cept-page-header-title-input { color: #e5e5e5; background: #262626; border-color: #3b82f6; }
175-
.cept-page-header-menu-btn { color: #71717a; }
176-
.cept-page-header-menu-btn:hover { background-color: rgba(255, 255, 255, 0.06); color: #a1a1aa; }
177-
.cept-page-header-menu { background: #262626; border-color: #404040; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); }
178-
.cept-page-header-menu-item { color: #d4d4d8; }
179-
.cept-page-header-menu-item:hover { background-color: rgba(255, 255, 255, 0.06); }
180-
.cept-page-header-menu-item--danger { color: #ef4444; }
181-
.cept-page-header-menu-item--danger:hover { background-color: rgba(239, 68, 68, 0.1); }
182-
.cept-page-header-menu-divider { background: #404040; }
183-
}
171+
.dark .cept-page-header-title { color: #e5e5e5; background: transparent; border-color: transparent; }
172+
.dark .cept-page-header-title:hover { background: #262626; border-color: #404040; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); }
173+
.dark .cept-page-header-title-input { color: #e5e5e5; background: #262626; border-color: #3b82f6; }
174+
.dark .cept-page-header-menu-btn { color: #71717a; }
175+
.dark .cept-page-header-menu-btn:hover { background-color: rgba(255, 255, 255, 0.06); color: #a1a1aa; }
176+
.dark .cept-page-header-menu { background: #262626; border-color: #404040; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); }
177+
.dark .cept-page-header-menu-item { color: #d4d4d8; }
178+
.dark .cept-page-header-menu-item:hover { background-color: rgba(255, 255, 255, 0.06); }
179+
.dark .cept-page-header-menu-item--danger { color: #ef4444; }
180+
.dark .cept-page-header-menu-item--danger:hover { background-color: rgba(239, 68, 68, 0.1); }
181+
.dark .cept-page-header-menu-divider { background: #404040; }
184182

185183
/* Empty state */
186184
.cept-empty-state {
@@ -306,18 +304,16 @@
306304
}
307305

308306
/* Dark mode for empty state & trash */
309-
@media (prefers-color-scheme: dark) {
310-
.cept-empty-state { color: #71717a; }
311-
.cept-empty-state-link { color: #60a5fa; }
312-
.cept-empty-state-link:hover { color: #93bbfd; }
313-
.cept-trash-view-title { color: #e5e5e5; }
314-
.cept-trash-view-empty { color: #71717a; }
315-
.cept-trash-view-item:hover { background-color: rgba(255, 255, 255, 0.04); }
316-
.cept-trash-view-item-title { color: #d4d4d8; }
317-
.cept-trash-view-action { background: #333; border-color: #404040; color: #d4d4d8; }
318-
.cept-trash-view-action:hover { background: #404040; }
319-
.cept-trash-view-action--danger { color: #ef4444; border-color: rgba(239, 68, 68, 0.3); }
320-
.cept-trash-view-action--danger:hover { background: rgba(239, 68, 68, 0.1); }
321-
.cept-trash-view-empty-btn { border-color: rgba(239, 68, 68, 0.3); color: #ef4444; }
322-
.cept-trash-view-empty-btn:hover { background: rgba(239, 68, 68, 0.1); }
323-
}
307+
.dark .cept-empty-state { color: #71717a; }
308+
.dark .cept-empty-state-link { color: #60a5fa; }
309+
.dark .cept-empty-state-link:hover { color: #93bbfd; }
310+
.dark .cept-trash-view-title { color: #e5e5e5; }
311+
.dark .cept-trash-view-empty { color: #71717a; }
312+
.dark .cept-trash-view-item:hover { background-color: rgba(255, 255, 255, 0.04); }
313+
.dark .cept-trash-view-item-title { color: #d4d4d8; }
314+
.dark .cept-trash-view-action { background: #333; border-color: #404040; color: #d4d4d8; }
315+
.dark .cept-trash-view-action:hover { background: #404040; }
316+
.dark .cept-trash-view-action--danger { color: #ef4444; border-color: rgba(239, 68, 68, 0.3); }
317+
.dark .cept-trash-view-action--danger:hover { background: rgba(239, 68, 68, 0.1); }
318+
.dark .cept-trash-view-empty-btn { border-color: rgba(239, 68, 68, 0.3); color: #ef4444; }
319+
.dark .cept-trash-view-empty-btn:hover { background: rgba(239, 68, 68, 0.1); }

packages/ui/src/components/settings/SettingsModal.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { useState, useCallback, useEffect, useRef } from 'react';
22
import type { StorageBackend } from '@cept/core';
33
import { FileBrowser } from './FileBrowser.js';
4+
import { ThemeToggle } from './ThemeToggle.js';
5+
6+
export type ThemeMode = 'dark' | 'system' | 'light';
47

58
export interface CeptSettings {
69
autoSave: boolean;
710
showDemoContent: boolean;
811
/** When true, URLs for git-backed spaces use the shareable /g/ prefix instead of /s/. Default: true */
912
redirectToGitUrl: boolean;
13+
themeMode: ThemeMode;
1014
}
1115

1216
function isNsheapsDeployment(): boolean {
@@ -21,6 +25,7 @@ export const DEFAULT_SETTINGS: CeptSettings = {
2125
autoSave: true,
2226
showDemoContent: isNsheapsDeployment(),
2327
redirectToGitUrl: true,
28+
themeMode: 'system',
2429
};
2530

2631
const SETTINGS_KEY = 'cept-settings';
@@ -222,6 +227,21 @@ export function SettingsModal({
222227
<div className="cept-settings-content">
223228
{activeTab === 'settings' && (
224229
<div data-testid="settings-panel-settings">
230+
<h3 className="cept-settings-section-title">Appearance</h3>
231+
<div className="cept-settings-toggle-row" data-testid="setting-theme-mode">
232+
<div className="cept-settings-toggle-label">
233+
<span className="cept-settings-toggle-name">Theme</span>
234+
<span className="cept-settings-toggle-desc">
235+
Choose dark, light, or match your system
236+
</span>
237+
</div>
238+
<ThemeToggle
239+
value={settings.themeMode}
240+
onChange={(mode) => handleSettingChange('themeMode', mode)}
241+
/>
242+
</div>
243+
244+
<div className="cept-settings-section-divider" />
225245
<h3 className="cept-settings-section-title">Editor</h3>
226246
<label className="cept-settings-toggle-row" data-testid="setting-auto-save">
227247
<div className="cept-settings-toggle-label">
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type React from 'react';
2+
import type { ThemeMode } from './SettingsModal.js';
3+
4+
const MODES: ThemeMode[] = ['dark', 'system', 'light'];
5+
6+
const LABELS: Record<ThemeMode, string> = {
7+
dark: 'Dark',
8+
system: 'System',
9+
light: 'Light',
10+
};
11+
12+
// Moon icon
13+
function MoonIcon({ active }: { active: boolean }) {
14+
return (
15+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"
16+
style={{ opacity: active ? 1 : 0.4 }}>
17+
<path d="M13.5 8.5a5.5 5.5 0 01-7-7A5.5 5.5 0 1013.5 8.5z" />
18+
</svg>
19+
);
20+
}
21+
22+
// Computer/monitor icon
23+
function MonitorIcon({ active }: { active: boolean }) {
24+
return (
25+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"
26+
style={{ opacity: active ? 1 : 0.4 }}>
27+
<rect x="1.5" y="2" width="13" height="9" rx="1.5" />
28+
<path d="M5.5 14h5M8 11v3" />
29+
</svg>
30+
);
31+
}
32+
33+
// Sun icon
34+
function SunIcon({ active }: { active: boolean }) {
35+
return (
36+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"
37+
style={{ opacity: active ? 1 : 0.4 }}>
38+
<circle cx="8" cy="8" r="3" />
39+
<path d="M8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2M3.4 3.4l1.4 1.4M11.2 11.2l1.4 1.4M3.4 12.6l1.4-1.4M11.2 4.8l1.4-1.4" />
40+
</svg>
41+
);
42+
}
43+
44+
const ICONS: Record<ThemeMode, (active: boolean) => React.JSX.Element> = {
45+
dark: (a) => <MoonIcon active={a} />,
46+
system: (a) => <MonitorIcon active={a} />,
47+
light: (a) => <SunIcon active={a} />,
48+
};
49+
50+
export interface ThemeToggleProps {
51+
value: ThemeMode;
52+
onChange: (mode: ThemeMode) => void;
53+
/** Compact variant for use in menus */
54+
compact?: boolean;
55+
}
56+
57+
export function ThemeToggle({ value, onChange, compact }: ThemeToggleProps) {
58+
const idx = MODES.indexOf(value);
59+
60+
return (
61+
<div
62+
className={`cept-theme-toggle ${compact ? 'cept-theme-toggle--compact' : ''}`}
63+
role="radiogroup"
64+
aria-label="Theme mode"
65+
data-testid="theme-toggle"
66+
>
67+
<div className="cept-theme-toggle-track">
68+
<div
69+
className="cept-theme-toggle-thumb"
70+
style={{ transform: `translateX(${idx * 100}%)` }}
71+
/>
72+
{MODES.map((mode) => (
73+
<button
74+
key={mode}
75+
className={`cept-theme-toggle-option ${value === mode ? 'is-active' : ''}`}
76+
onClick={() => onChange(mode)}
77+
role="radio"
78+
aria-checked={value === mode}
79+
aria-label={LABELS[mode]}
80+
title={LABELS[mode]}
81+
data-testid={`theme-toggle-${mode}`}
82+
>
83+
{ICONS[mode](value === mode)}
84+
</button>
85+
))}
86+
</div>
87+
</div>
88+
);
89+
}

0 commit comments

Comments
 (0)