Skip to content

Commit 0e54dfd

Browse files
committed
Add light/dark theme toggle
1 parent 8c21601 commit 0e54dfd

6 files changed

Lines changed: 120 additions & 55 deletions

File tree

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
rel="stylesheet"
1717
/>
1818
</head>
19-
<body class="bg-gray-950 text-gray-100 antialiased">
19+
<body class="bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100 antialiased">
2020
<div id="root"></div>
2121
<script type="module" src="/src/main.tsx"></script>
2222
</body>

src/components/canvas/DashboardCanvas.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,25 +97,25 @@ function WidgetContainer({
9797

9898
return (
9999
<div
100-
className={`h-full flex flex-col bg-gray-900 rounded-lg border transition-colors ${
101-
isSelected ? 'border-ray-500' : 'border-gray-800 hover:border-gray-700'
100+
className={`h-full flex flex-col bg-white dark:bg-gray-900 rounded-lg border shadow-sm dark:shadow-none transition-colors ${
101+
isSelected ? 'border-ray-500' : 'border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700'
102102
}`}
103103
onClick={(e) => {
104104
e.stopPropagation();
105105
onSelect();
106106
}}
107107
>
108108
{/* Widget header */}
109-
<div className="flex items-center justify-between px-3 py-2 border-b border-gray-800 cursor-move drag-handle">
110-
<h3 className="text-sm font-medium text-gray-300 truncate">
109+
<div className="flex items-center justify-between px-3 py-2 border-b border-gray-100 dark:border-gray-800 cursor-move drag-handle">
110+
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300 truncate">
111111
{widget.title || widget.component.name}
112112
</h3>
113113
<div className="flex items-center gap-1">
114114
<button
115115
onClick={(e) => {
116116
e.stopPropagation();
117117
}}
118-
className="p-1 text-gray-500 hover:text-gray-300 rounded hover:bg-gray-800"
118+
className="p-1 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 rounded hover:bg-gray-100 dark:hover:bg-gray-800"
119119
>
120120
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
121121
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
@@ -126,7 +126,7 @@ function WidgetContainer({
126126
e.stopPropagation();
127127
onRemove();
128128
}}
129-
className="p-1 text-gray-500 hover:text-red-400 rounded hover:bg-gray-800"
129+
className="p-1 text-gray-400 dark:text-gray-500 hover:text-red-500 dark:hover:text-red-400 rounded hover:bg-gray-100 dark:hover:bg-gray-800"
130130
>
131131
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
132132
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -148,9 +148,9 @@ function ControlPlaceholder({ component }: { component: ComponentDefinition }) {
148148
const fullComponent = getComponentById(component.id);
149149

150150
return (
151-
<div className="h-full flex items-center justify-center bg-gray-800/30 rounded">
151+
<div className="h-full flex items-center justify-center bg-gray-100 dark:bg-gray-800/30 rounded">
152152
<div className="text-center">
153-
<div className="text-gray-600 mb-2">{fullComponent?.icon ?? '📦'}</div>
153+
<div className="text-gray-400 dark:text-gray-600 mb-2">{fullComponent?.icon ?? '📦'}</div>
154154
<span className="text-xs text-gray-500">{component.name}</span>
155155
</div>
156156
</div>
@@ -243,13 +243,13 @@ export function DashboardCanvas({
243243
onDragLeave={() => setIsDragOver(false)}
244244
onDrop={handleDrop}
245245
className={`h-full flex items-center justify-center border-2 border-dashed rounded-lg transition-colors ${
246-
isDragOver ? 'border-ray-500 bg-ray-500/5' : 'border-gray-800'
246+
isDragOver ? 'border-ray-500 bg-ray-500/5' : 'border-gray-300 dark:border-gray-800'
247247
}`}
248248
>
249249
<div className="text-center max-w-md px-4">
250-
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-800 flex items-center justify-center">
250+
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
251251
<svg
252-
className="w-8 h-8 text-gray-600"
252+
className="w-8 h-8 text-gray-400 dark:text-gray-600"
253253
fill="none"
254254
viewBox="0 0 24 24"
255255
stroke="currentColor"
@@ -262,7 +262,7 @@ export function DashboardCanvas({
262262
/>
263263
</svg>
264264
</div>
265-
<h3 className="text-lg font-medium text-gray-300 mb-2">
265+
<h3 className="text-lg font-medium text-gray-700 dark:text-gray-300 mb-2">
266266
Build Your Dashboard
267267
</h3>
268268
<p className="text-sm text-gray-500">

src/components/layout/AppShell.tsx

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export function AppShell() {
2323
dataset,
2424
loadSampleData,
2525
loadCSVData,
26+
theme,
27+
toggleTheme,
2628
} = useRayLensStore();
2729

2830
// Panel visibility
@@ -128,14 +130,14 @@ export function AppShell() {
128130
const availableFields = dataset?.schema.map((s) => s.name) ?? [];
129131

130132
return (
131-
<div className="flex h-screen flex-col bg-gray-950">
133+
<div className="flex h-screen flex-col bg-gray-50 dark:bg-gray-950">
132134
{/* Top toolbar */}
133-
<header className="flex h-12 items-center justify-between border-b border-gray-800 px-4 shrink-0">
135+
<header className="flex h-12 items-center justify-between border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 px-4 shrink-0">
134136
<div className="flex items-center gap-4">
135137
{/* Logo */}
136138
<div className="flex items-center gap-2">
137139
<svg
138-
className="h-6 w-6 text-ray-500"
140+
className="h-6 w-6 text-ray-600 dark:text-ray-500"
139141
viewBox="0 0 24 24"
140142
fill="none"
141143
stroke="currentColor"
@@ -145,14 +147,14 @@ export function AppShell() {
145147
<path d="M2 17l10 5 10-5" />
146148
<path d="M2 12l10 5 10-5" />
147149
</svg>
148-
<span className="font-semibold text-white">RayLens</span>
150+
<span className="font-semibold text-gray-900 dark:text-white">RayLens</span>
149151
</div>
150152

151153
{/* Menu items */}
152154
<nav className="flex items-center gap-1">
153155
<button
154156
onClick={() => fileInputRef.current?.click()}
155-
className="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors"
157+
className="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-colors"
156158
>
157159
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
158160
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
@@ -171,17 +173,17 @@ export function AppShell() {
171173
loadSampleData();
172174
setLeftPanelTab('data');
173175
}}
174-
className="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors"
176+
className="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-colors"
175177
>
176178
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
177179
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
178180
</svg>
179181
Sample Data
180182
</button>
181-
<div className="w-px h-6 bg-gray-800 mx-2" />
183+
<div className="w-px h-6 bg-gray-200 dark:bg-gray-800 mx-2" />
182184
<button
183185
onClick={() => setWidgets([])}
184-
className="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors"
186+
className="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-colors"
185187
>
186188
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
187189
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
@@ -192,13 +194,31 @@ export function AppShell() {
192194
</div>
193195

194196
<div className="flex items-center gap-4">
195-
<span className="text-xs text-gray-500">Rayforce {version ?? '...'}</span>
197+
<span className="text-xs text-gray-500 dark:text-gray-500">Rayforce {version ?? '...'}</span>
198+
199+
{/* Theme toggle */}
200+
<button
201+
onClick={toggleTheme}
202+
className="p-1.5 rounded transition-colors text-gray-500 hover:text-gray-300 dark:text-gray-400 dark:hover:text-white"
203+
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
204+
>
205+
{theme === 'dark' ? (
206+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
207+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
208+
</svg>
209+
) : (
210+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
211+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
212+
</svg>
213+
)}
214+
</button>
215+
196216
{/* Panel toggles */}
197217
<div className="flex items-center gap-1">
198218
<button
199219
onClick={() => setLeftPanelOpen(!leftPanelOpen)}
200220
className={`p-1.5 rounded transition-colors ${
201-
leftPanelOpen ? 'bg-gray-800 text-ray-400' : 'text-gray-500 hover:text-gray-300'
221+
leftPanelOpen ? 'bg-gray-200 dark:bg-gray-800 text-ray-600 dark:text-ray-400' : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
202222
}`}
203223
title="Toggle left panel"
204224
>
@@ -209,7 +229,7 @@ export function AppShell() {
209229
<button
210230
onClick={() => setRightPanelOpen(!rightPanelOpen)}
211231
className={`p-1.5 rounded transition-colors ${
212-
rightPanelOpen ? 'bg-gray-800 text-ray-400' : 'text-gray-500 hover:text-gray-300'
232+
rightPanelOpen ? 'bg-gray-200 dark:bg-gray-800 text-ray-600 dark:text-ray-400' : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
213233
}`}
214234
title="Toggle right panel"
215235
>
@@ -225,15 +245,15 @@ export function AppShell() {
225245
<div className="flex flex-1 overflow-hidden">
226246
{/* Left panel - Components & Data */}
227247
{leftPanelOpen && (
228-
<aside className="w-64 border-r border-gray-800 bg-gray-900/50 flex flex-col shrink-0">
248+
<aside className="w-64 border-r border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-900/50 flex flex-col shrink-0">
229249
{/* Tab buttons */}
230-
<div className="flex border-b border-gray-800">
250+
<div className="flex border-b border-gray-200 dark:border-gray-800">
231251
<button
232252
onClick={() => setLeftPanelTab('components')}
233253
className={`flex-1 px-3 py-2 text-xs font-medium transition-colors ${
234254
leftPanelTab === 'components'
235-
? 'text-white bg-gray-800/50 border-b-2 border-ray-500'
236-
: 'text-gray-400 hover:text-gray-300'
255+
? 'text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-800/50 border-b-2 border-ray-500'
256+
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
237257
}`}
238258
>
239259
Components
@@ -242,11 +262,11 @@ export function AppShell() {
242262
onClick={() => setLeftPanelTab('data')}
243263
className={`flex-1 px-3 py-2 text-xs font-medium transition-colors ${
244264
leftPanelTab === 'data'
245-
? 'text-white bg-gray-800/50 border-b-2 border-ray-500'
246-
: 'text-gray-400 hover:text-gray-300'
265+
? 'text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-800/50 border-b-2 border-ray-500'
266+
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
247267
}`}
248268
>
249-
Data {dataset && <span className="ml-1 text-ray-400"></span>}
269+
Data {dataset && <span className="ml-1 text-ray-500 dark:text-ray-400"></span>}
250270
</button>
251271
</div>
252272

@@ -265,7 +285,7 @@ export function AppShell() {
265285
)}
266286

267287
{/* Center - Dashboard Canvas */}
268-
<main ref={canvasRef} className="flex-1 overflow-hidden p-4">
288+
<main ref={canvasRef} className="flex-1 overflow-hidden p-4 bg-gray-100/50 dark:bg-transparent">
269289
<DashboardCanvas
270290
widgets={widgets}
271291
onWidgetsChange={setWidgets}
@@ -280,7 +300,7 @@ export function AppShell() {
280300

281301
{/* Right panel - Property Inspector */}
282302
{rightPanelOpen && (
283-
<aside className="w-72 border-l border-gray-800 bg-gray-900/50 shrink-0">
303+
<aside className="w-72 border-l border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-900/50 shrink-0">
284304
<PropertyInspector
285305
selectedComponent={selectedWidget?.component ?? null}
286306
title={selectedWidget?.title ?? ''}
@@ -300,7 +320,7 @@ export function AppShell() {
300320
</div>
301321

302322
{/* Status bar */}
303-
<footer className="flex h-6 items-center justify-between border-t border-gray-800 bg-gray-900 px-4 text-2xs text-gray-500 shrink-0">
323+
<footer className="flex h-6 items-center justify-between border-t border-gray-200 dark:border-gray-800 bg-gray-100 dark:bg-gray-900 px-4 text-2xs text-gray-500 shrink-0">
304324
<div className="flex items-center gap-4">
305325
{dataset ? (
306326
<>

src/components/palette/ComponentPalette.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -250,16 +250,16 @@ function CategorySection({
250250
const [isOpen, setIsOpen] = useState(true);
251251

252252
return (
253-
<div className="border-b border-gray-800 last:border-0">
253+
<div className="border-b border-gray-200 dark:border-gray-800 last:border-0">
254254
<button
255255
onClick={() => setIsOpen(!isOpen)}
256-
className="flex items-center justify-between w-full px-3 py-2 hover:bg-gray-800/30 text-left"
256+
className="flex items-center justify-between w-full px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-800/30 text-left"
257257
>
258-
<span className="text-xs font-semibold text-gray-400 uppercase tracking-wide">
258+
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
259259
{title}
260260
</span>
261261
<svg
262-
className={`w-4 h-4 text-gray-500 transition-transform ${isOpen ? 'rotate-0' : '-rotate-90'}`}
262+
className={`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${isOpen ? 'rotate-0' : '-rotate-90'}`}
263263
fill="none"
264264
viewBox="0 0 24 24"
265265
stroke="currentColor"
@@ -280,13 +280,13 @@ function CategorySection({
280280
onDragStart(component);
281281
}}
282282
onClick={() => onComponentClick(component)}
283-
className="flex flex-col items-center gap-1 p-2 rounded cursor-grab active:cursor-grabbing hover:bg-gray-800 transition-colors group"
283+
className="flex flex-col items-center gap-1 p-2 rounded cursor-grab active:cursor-grabbing hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors group"
284284
title={component.description}
285285
>
286-
<div className="text-gray-500 group-hover:text-ray-400 transition-colors">
286+
<div className="text-gray-400 dark:text-gray-500 group-hover:text-ray-500 dark:group-hover:text-ray-400 transition-colors">
287287
{component.icon}
288288
</div>
289-
<span className="text-2xs text-gray-400 group-hover:text-gray-300 text-center leading-tight">
289+
<span className="text-2xs text-gray-500 dark:text-gray-400 group-hover:text-gray-700 dark:group-hover:text-gray-300 text-center leading-tight">
290290
{component.name}
291291
</span>
292292
</div>
@@ -305,9 +305,9 @@ export function ComponentPalette({ onDragStart, onComponentClick }: ComponentPal
305305

306306
return (
307307
<div className="h-full flex flex-col">
308-
<div className="p-3 border-b border-gray-800">
309-
<h2 className="text-sm font-semibold text-gray-300 flex items-center gap-2">
310-
<svg className="w-4 h-4 text-ray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
308+
<div className="p-3 border-b border-gray-200 dark:border-gray-800">
309+
<h2 className="text-sm font-semibold text-gray-700 dark:text-gray-300 flex items-center gap-2">
310+
<svg className="w-4 h-4 text-ray-600 dark:text-ray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
311311
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
312312
</svg>
313313
Components

src/core/store/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ export interface RayLensStore {
5555
chartType: ChartType;
5656
chartTypeAuto: boolean;
5757
sidebarOpen: boolean;
58+
59+
// Theme
60+
theme: 'light' | 'dark';
5861

5962
// Actions
63+
toggleTheme: () => void;
6064
init: () => Promise<void>;
6165
eval: (expression: string) => Promise<unknown>;
6266
loadSampleData: () => Promise<void>;
@@ -192,6 +196,23 @@ export const useRayLensStore = create<RayLensStore>()(
192196
chartType: 'table',
193197
chartTypeAuto: true,
194198
sidebarOpen: true,
199+
theme: (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: light)').matches) ? 'light' : 'dark',
200+
201+
// ====================================================================
202+
// Theme Actions
203+
// ====================================================================
204+
205+
toggleTheme: () => {
206+
set((state) => {
207+
const newTheme = state.theme === 'dark' ? 'light' : 'dark';
208+
// Update document class
209+
if (typeof document !== 'undefined') {
210+
document.documentElement.classList.remove('dark', 'light');
211+
document.documentElement.classList.add(newTheme);
212+
}
213+
state.theme = newTheme;
214+
});
215+
},
195216

196217
// ====================================================================
197218
// Rayforce Actions

0 commit comments

Comments
 (0)