Skip to content

Commit 7aab55f

Browse files
authored
feat: add black colour theme (#36)
1 parent 5a7ae53 commit 7aab55f

File tree

7 files changed

+149
-62
lines changed

7 files changed

+149
-62
lines changed

src/app/storage/dashboards.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ describe('dashboards', () => {
9797

9898
it('should update an existing dashboard', () => {
9999
const existingDashboards = { default: defaultDashboard };
100-
const updatedDashboard: DashboardLayout = { widgets: [], generalSettings: { fontSize: 'lg' }};
100+
const updatedDashboard: DashboardLayout = { widgets: [], generalSettings: { fontSize: 'lg', colorPalette: 'black' }};
101101
mockReadData.mockReturnValue(existingDashboards);
102102

103103
saveDashboard('default', updatedDashboard);
@@ -247,7 +247,7 @@ describe('dashboards', () => {
247247

248248
const updatedDashboard = getOrCreateDefaultDashboard();
249249

250-
expect(updatedDashboard.generalSettings).toEqual({ fontSize: 'sm' });
250+
expect(updatedDashboard.generalSettings).toEqual({ fontSize: 'sm', colorPalette: 'default' });
251251
});
252252

253253
it('should preserve general settings from the existing dashboard', () => {
@@ -258,7 +258,7 @@ describe('dashboards', () => {
258258

259259
const updatedDashboard = getOrCreateDefaultDashboard();
260260

261-
expect(updatedDashboard.generalSettings).toEqual({ fontSize: 'lg' });
261+
expect(updatedDashboard.generalSettings).toEqual({ fontSize: 'lg', colorPalette: 'default' });
262262
});
263263
});
264264
});

src/app/storage/defaultDashboard.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,6 @@ export const defaultDashboard: DashboardLayout = {
9292
],
9393
generalSettings: {
9494
fontSize: 'sm',
95+
colorPalette: 'default',
9596
},
9697
};

src/frontend/components/Settings/sections/GeneralSettings.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ const FONT_SIZE_PRESETS = {
99
xl: 'Extra Large',
1010
};
1111

12+
const COLOR_THEME_PRESETS: Record<string, string> = {
13+
default: 'Slate (default)',
14+
black: 'Black',
15+
};
16+
1217
export const GeneralSettings = () => {
1318
const { currentDashboard, onDashboardUpdated } = useDashboard();
1419
const [settings, setSettings] = useState<GeneralSettingsType>({
1520
fontSize: currentDashboard?.generalSettings?.fontSize ?? 'sm',
21+
colorPalette: currentDashboard?.generalSettings?.colorPalette ?? 'default',
1622
});
1723

1824
if (!currentDashboard || !onDashboardUpdated) {
@@ -33,6 +39,12 @@ export const GeneralSettings = () => {
3339
updateDashboard(newSettings);
3440
};
3541

42+
const handleColorThemeChange = (newTheme: 'default' | 'black') => {
43+
const newSettings = { ...settings, colorPalette: newTheme };
44+
setSettings(newSettings);
45+
updateDashboard(newSettings);
46+
};
47+
3648
return (
3749
<div className="flex flex-col h-full space-y-6">
3850
<div>
@@ -93,6 +105,28 @@ export const GeneralSettings = () => {
93105
</button>
94106
</div>
95107
</div>
108+
109+
{/* Color Theme Settings */}
110+
<div className="space-y-4">
111+
<div className="flex items-center justify-between">
112+
<h3 className="text-lg font-medium text-slate-200">Color Theme</h3>
113+
<div className="flex items-center gap-2">
114+
<span className="text-sm text-slate-300">{COLOR_THEME_PRESETS[settings.colorPalette ?? 'default']}</span>
115+
</div>
116+
</div>
117+
118+
{/* Color Theme Dropdown */}
119+
<div className="mt-4">
120+
<select
121+
value={settings.colorPalette ?? 'default'}
122+
onChange={(e) => handleColorThemeChange(e.target.value as 'default' | 'black')}
123+
className="w-full px-3 py-2 bg-slate-700 text-slate-300 rounded border border-slate-600 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
124+
>
125+
<option value="default">{COLOR_THEME_PRESETS.default}</option>
126+
<option value="black">{COLOR_THEME_PRESETS.black}</option>
127+
</select>
128+
</div>
129+
</div>
96130
</div>
97131
);
98132
};

src/frontend/components/ThemeManager/ThemeManager.stories.tsx

Lines changed: 83 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ export default meta;
1919
const createMockBridge = (
2020
fontSize: 'xs' | 'sm' | 'lg' | 'xl',
2121
setFontSize: (size: 'xs' | 'sm' | 'lg' | 'xl') => void,
22+
colorPalette: 'default' | string,
23+
setColorPalette: (palette: 'default' | string) => void,
2224
widgets: DashboardLayout['widgets'] = []
2325
): DashboardBridge => ({
2426
reloadDashboard: () => {
2527
// noop
2628
},
2729
saveDashboard: (dashboard: DashboardLayout) => {
28-
// Update the font size in the dashboard
30+
// Update the font size and color palette in the dashboard
2931
setFontSize(dashboard.generalSettings?.fontSize || 'sm');
32+
setColorPalette(dashboard.generalSettings?.colorPalette || 'default');
3033
},
3134
dashboardUpdated: (callback) => {
32-
// Initialize with current font size
35+
// Initialize with current font size and color palette
3336
callback({
3437
widgets,
35-
generalSettings: { fontSize },
38+
generalSettings: { fontSize, colorPalette },
3639
});
3740
return () => {
3841
// noop
@@ -49,13 +52,14 @@ const createMockBridge = (
4952
resetDashboard: () =>
5053
Promise.resolve({
5154
widgets: [],
52-
generalSettings: { fontSize },
55+
generalSettings: { fontSize, colorPalette },
5356
}),
5457
});
5558

56-
// Helper function to create font size buttons
57-
const createFontSizeButtons = (
59+
// Helper function to create theme controls (font size buttons and color palette dropdown)
60+
const createThemeControls = (
5861
fontSize: 'xs' | 'sm' | 'lg' | 'xl',
62+
colorPalette: 'default' | string,
5963
mockBridge: DashboardBridge
6064
) => {
6165
const getButtonClass = (size: 'xs' | 'sm' | 'lg' | 'xl') => {
@@ -66,51 +70,72 @@ const createFontSizeButtons = (
6670
};
6771

6872
return (
69-
<div className="flex gap-2">
70-
<button
71-
className={getButtonClass('xs')}
72-
onClick={() =>
73-
mockBridge.saveDashboard({
74-
widgets: [],
75-
generalSettings: { fontSize: 'xs' },
76-
})
77-
}
78-
>
79-
Extra Small
80-
</button>
81-
<button
82-
className={getButtonClass('sm')}
83-
onClick={() =>
84-
mockBridge.saveDashboard({
85-
widgets: [],
86-
generalSettings: { fontSize: 'sm' },
87-
})
88-
}
89-
>
90-
Small
91-
</button>
92-
<button
93-
className={getButtonClass('lg')}
94-
onClick={() =>
95-
mockBridge.saveDashboard({
96-
widgets: [],
97-
generalSettings: { fontSize: 'lg' },
98-
})
99-
}
100-
>
101-
Large
102-
</button>
103-
<button
104-
className={getButtonClass('xl')}
105-
onClick={() =>
106-
mockBridge.saveDashboard({
107-
widgets: [],
108-
generalSettings: { fontSize: 'xl' },
109-
})
110-
}
111-
>
112-
Extra Large
113-
</button>
73+
<div className="flex flex-col gap-4">
74+
<div className="flex gap-2">
75+
<button
76+
className={getButtonClass('xs')}
77+
onClick={() =>
78+
mockBridge.saveDashboard({
79+
widgets: [],
80+
generalSettings: { fontSize: 'xs', colorPalette },
81+
})
82+
}
83+
>
84+
Extra Small
85+
</button>
86+
<button
87+
className={getButtonClass('sm')}
88+
onClick={() =>
89+
mockBridge.saveDashboard({
90+
widgets: [],
91+
generalSettings: { fontSize: 'sm', colorPalette },
92+
})
93+
}
94+
>
95+
Small
96+
</button>
97+
<button
98+
className={getButtonClass('lg')}
99+
onClick={() =>
100+
mockBridge.saveDashboard({
101+
widgets: [],
102+
generalSettings: { fontSize: 'lg', colorPalette },
103+
})
104+
}
105+
>
106+
Large
107+
</button>
108+
<button
109+
className={getButtonClass('xl')}
110+
onClick={() =>
111+
mockBridge.saveDashboard({
112+
widgets: [],
113+
generalSettings: { fontSize: 'xl', colorPalette },
114+
})
115+
}
116+
>
117+
Extra Large
118+
</button>
119+
</div>
120+
<div className="flex items-center gap-2">
121+
<label htmlFor="colorPalette" className="text-[12px]">
122+
Color Palette:
123+
</label>
124+
<select
125+
id="colorPalette"
126+
value={colorPalette}
127+
onChange={(e) =>
128+
mockBridge.saveDashboard({
129+
widgets: [],
130+
generalSettings: { fontSize, colorPalette: e.target.value as 'default' | string },
131+
})
132+
}
133+
className="px-2 py-1 rounded border text-[12px]"
134+
>
135+
<option value="default">Default</option>
136+
<option value="black">Black</option>
137+
</select>
138+
</div>
114139
</div>
115140
);
116141
};
@@ -143,15 +168,16 @@ export const Primary = {
143168
export const WithFontSizeControls = {
144169
render: () => {
145170
const [fontSize, setFontSize] = useState<'xs' | 'sm' | 'lg' | 'xl'>('sm');
146-
const mockBridge = createMockBridge(fontSize, setFontSize);
171+
const [colorPalette, setColorPalette] = useState<'default' | string>('default');
172+
const mockBridge = createMockBridge(fontSize, setFontSize, colorPalette, setColorPalette);
147173

148174
return (
149175
<DashboardProvider bridge={mockBridge}>
150176
<MemoryRouter initialEntries={['/']}>
151177
<ThemeManager>
152178
<div className="p-4 space-y-4">
153-
{createFontSizeButtons(fontSize, mockBridge)}
154-
<div className="space-y-2">
179+
{createThemeControls(fontSize, colorPalette, mockBridge)}
180+
<div className="space-y-2 bg-slate-800/25 rounded-sm p-2">
155181
<div className="text-xs">This is extra small text</div>
156182
<div className="text-sm">This is small text</div>
157183
<div className="text-base">This is base text</div>
@@ -169,14 +195,15 @@ export const WithFontSizeControls = {
169195
export const WithAllAvailableWidgets = {
170196
render: () => {
171197
const [fontSize, setFontSize] = useState<'xs' | 'sm' | 'lg' | 'xl'>('sm');
172-
const mockBridge = createMockBridge(fontSize, setFontSize, defaultDashboard.widgets);
198+
const [colorPalette, setColorPalette] = useState<'default' | string>('default');
199+
const mockBridge = createMockBridge(fontSize, setFontSize, colorPalette, setColorPalette, defaultDashboard.widgets);
173200

174201
return (
175202
<DashboardProvider bridge={mockBridge}>
176203
<MemoryRouter initialEntries={['/']}>
177204
<ThemeManager>
178205
<div className="p-4 space-y-4">
179-
{createFontSizeButtons(fontSize, mockBridge)}
206+
{createThemeControls(fontSize, colorPalette, mockBridge)}
180207
</div>
181208
<hr className="my-4" />
182209
<Routes>

src/frontend/components/ThemeManager/ThemeManager.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@ import { useGeneralSettings } from '@irdashies/context';
33
import { useLocation } from 'react-router-dom';
44

55
export const ThemeManager = ({ children }: PropsWithChildren) => {
6-
const { fontSize } = useGeneralSettings() || {};
6+
const { fontSize, colorPalette } = useGeneralSettings() || {};
77
const location = useLocation();
88

9-
// Don't apply theme changes to the settings page since
9+
// Don't apply theme changes to the settings page since
1010
// they share the same theme as the rest of the overlays
1111
if (location.pathname.startsWith('/settings')) {
1212
return <>{children}</>;
1313
}
1414

15-
return <div className={`relative w-full h-full overlay-window overlay-theme-${fontSize ?? 'sm'}`}>{children}</div>;
15+
return (
16+
<div
17+
className={`
18+
relative w-full h-full overlay-window
19+
overlay-theme-${fontSize ?? 'sm'}
20+
overlay-theme-color-${colorPalette ?? 'default'}
21+
`}
22+
>
23+
{children}
24+
</div>
25+
);
1626
};

src/frontend/theme.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,17 @@ html {
6161
--text-lg: 20px;
6262
--text-xl: 22px;
6363
}
64+
65+
.overlay-window.overlay-theme-color-black {
66+
--color-slate-50: oklch(0.98 0 0);
67+
--color-slate-100: oklch(0.95 0 0);
68+
--color-slate-200: oklch(0.9 0 0);
69+
--color-slate-300: oklch(0.8 0 0);
70+
--color-slate-400: oklch(0.65 0 0);
71+
--color-slate-500: oklch(0.5 0 0);
72+
--color-slate-600: oklch(0.4 0 0);
73+
--color-slate-700: oklch(0.3 0 0);
74+
--color-slate-800: oklch(0.2 0 0);
75+
--color-slate-900: oklch(0.1 0 0);
76+
--color-slate-950: oklch(0.05 0 0);
77+
}

src/types/dashboardLayout.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface DashboardWidget {
2525

2626
export interface GeneralSettingsType {
2727
fontSize?: 'xs' | 'sm' | 'lg' | 'xl';
28+
colorPalette?: 'default' | string;
2829
}
2930

3031
export interface DashboardLayout {

0 commit comments

Comments
 (0)