Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/frontend/components/Settings/sections/GeneralSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ const FONT_SIZE_PRESETS = {
xl: 'Extra Large',
};

const COLOR_THEME_PRESETS = {
default: 'Slate (default)',
black: 'Black',
};

export const GeneralSettings = () => {
const { currentDashboard, onDashboardUpdated } = useDashboard();
const [settings, setSettings] = useState<GeneralSettingsType>({
fontSize: currentDashboard?.generalSettings?.fontSize ?? 'sm',
colorPalette: currentDashboard?.generalSettings?.colorPalette ?? 'default',
});

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

const handleColorThemeChange = (newTheme: 'default' | 'black') => {
const newSettings = { ...settings, colorPalette: newTheme };
setSettings(newSettings);
updateDashboard(newSettings);
};

return (
<div className="flex flex-col h-full space-y-6">
<div>
Expand Down Expand Up @@ -93,6 +105,28 @@ export const GeneralSettings = () => {
</button>
</div>
</div>

{/* Color Theme Settings */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-slate-200">Color Theme</h3>
<div className="flex items-center gap-2">
<span className="text-sm text-slate-300">{COLOR_THEME_PRESETS[settings.colorPalette as keyof typeof COLOR_THEME_PRESETS ?? 'default']}</span>
</div>
</div>

{/* Color Theme Dropdown */}
<div className="mt-4">
<select
value={settings.colorPalette ?? 'default'}
onChange={(e) => handleColorThemeChange(e.target.value as 'default' | 'black')}
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"
>
<option value="default">{COLOR_THEME_PRESETS.default}</option>
<option value="black">{COLOR_THEME_PRESETS.black}</option>
</select>
</div>
</div>
</div>
);
};
139 changes: 83 additions & 56 deletions src/frontend/components/ThemeManager/ThemeManager.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@ export default meta;
const createMockBridge = (
fontSize: 'xs' | 'sm' | 'lg' | 'xl',
setFontSize: (size: 'xs' | 'sm' | 'lg' | 'xl') => void,
colorPalette: 'default' | string,
setColorPalette: (palette: 'default' | string) => void,
widgets: DashboardLayout['widgets'] = []
): DashboardBridge => ({
reloadDashboard: () => {
// noop
},
saveDashboard: (dashboard: DashboardLayout) => {
// Update the font size in the dashboard
// Update the font size and color palette in the dashboard
setFontSize(dashboard.generalSettings?.fontSize || 'sm');
setColorPalette(dashboard.generalSettings?.colorPalette || 'default');
},
dashboardUpdated: (callback) => {
// Initialize with current font size
// Initialize with current font size and color palette
callback({
widgets,
generalSettings: { fontSize },
generalSettings: { fontSize, colorPalette },
});
return () => {
// noop
Expand All @@ -49,13 +52,14 @@ const createMockBridge = (
resetDashboard: () =>
Promise.resolve({
widgets: [],
generalSettings: { fontSize },
generalSettings: { fontSize, colorPalette },
}),
});

// Helper function to create font size buttons
const createFontSizeButtons = (
// Helper function to create theme controls (font size buttons and color palette dropdown)
const createThemeControls = (
fontSize: 'xs' | 'sm' | 'lg' | 'xl',
colorPalette: 'default' | string,
mockBridge: DashboardBridge
) => {
const getButtonClass = (size: 'xs' | 'sm' | 'lg' | 'xl') => {
Expand All @@ -66,51 +70,72 @@ const createFontSizeButtons = (
};

return (
<div className="flex gap-2">
<button
className={getButtonClass('xs')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'xs' },
})
}
>
Extra Small
</button>
<button
className={getButtonClass('sm')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'sm' },
})
}
>
Small
</button>
<button
className={getButtonClass('lg')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'lg' },
})
}
>
Large
</button>
<button
className={getButtonClass('xl')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'xl' },
})
}
>
Extra Large
</button>
<div className="flex flex-col gap-4">
<div className="flex gap-2">
<button
className={getButtonClass('xs')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'xs', colorPalette },
})
}
>
Extra Small
</button>
<button
className={getButtonClass('sm')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'sm', colorPalette },
})
}
>
Small
</button>
<button
className={getButtonClass('lg')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'lg', colorPalette },
})
}
>
Large
</button>
<button
className={getButtonClass('xl')}
onClick={() =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize: 'xl', colorPalette },
})
}
>
Extra Large
</button>
</div>
<div className="flex items-center gap-2">
<label htmlFor="colorPalette" className="text-[12px]">
Color Palette:
</label>
<select
id="colorPalette"
value={colorPalette}
onChange={(e) =>
mockBridge.saveDashboard({
widgets: [],
generalSettings: { fontSize, colorPalette: e.target.value as 'default' | string },
})
}
className="px-2 py-1 rounded border text-[12px]"
>
<option value="default">Default</option>
<option value="black">Black</option>
</select>
</div>
</div>
);
};
Expand Down Expand Up @@ -143,15 +168,16 @@ export const Primary = {
export const WithFontSizeControls = {
render: () => {
const [fontSize, setFontSize] = useState<'xs' | 'sm' | 'lg' | 'xl'>('sm');
const mockBridge = createMockBridge(fontSize, setFontSize);
const [colorPalette, setColorPalette] = useState<'default' | string>('default');
const mockBridge = createMockBridge(fontSize, setFontSize, colorPalette, setColorPalette);

return (
<DashboardProvider bridge={mockBridge}>
<MemoryRouter initialEntries={['/']}>
<ThemeManager>
<div className="p-4 space-y-4">
{createFontSizeButtons(fontSize, mockBridge)}
<div className="space-y-2">
{createThemeControls(fontSize, colorPalette, mockBridge)}
<div className="space-y-2 bg-slate-800/25 rounded-sm p-2">
<div className="text-xs">This is extra small text</div>
<div className="text-sm">This is small text</div>
<div className="text-base">This is base text</div>
Expand All @@ -169,14 +195,15 @@ export const WithFontSizeControls = {
export const WithAllAvailableWidgets = {
render: () => {
const [fontSize, setFontSize] = useState<'xs' | 'sm' | 'lg' | 'xl'>('sm');
const mockBridge = createMockBridge(fontSize, setFontSize, defaultDashboard.widgets);
const [colorPalette, setColorPalette] = useState<'default' | string>('default');
const mockBridge = createMockBridge(fontSize, setFontSize, colorPalette, setColorPalette, defaultDashboard.widgets);

return (
<DashboardProvider bridge={mockBridge}>
<MemoryRouter initialEntries={['/']}>
<ThemeManager>
<div className="p-4 space-y-4">
{createFontSizeButtons(fontSize, mockBridge)}
{createThemeControls(fontSize, colorPalette, mockBridge)}
</div>
<hr className="my-4" />
<Routes>
Expand Down
16 changes: 13 additions & 3 deletions src/frontend/components/ThemeManager/ThemeManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@ import { useGeneralSettings } from '@irdashies/context';
import { useLocation } from 'react-router-dom';

export const ThemeManager = ({ children }: PropsWithChildren) => {
const { fontSize } = useGeneralSettings() || {};
const { fontSize, colorPalette } = useGeneralSettings() || {};
const location = useLocation();

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

return <div className={`relative w-full h-full overlay-window overlay-theme-${fontSize ?? 'sm'}`}>{children}</div>;
return (
<div
className={`
relative w-full h-full overlay-window
overlay-theme-${fontSize ?? 'sm'}
overlay-theme-color-${colorPalette ?? 'default'}
`}
>
{children}
</div>
);
};
14 changes: 14 additions & 0 deletions src/frontend/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,17 @@ html {
--text-lg: 20px;
--text-xl: 22px;
}

.overlay-window.overlay-theme-color-black {
--color-slate-50: oklch(0.98 0 0);
--color-slate-100: oklch(0.95 0 0);
--color-slate-200: oklch(0.9 0 0);
--color-slate-300: oklch(0.8 0 0);
--color-slate-400: oklch(0.65 0 0);
--color-slate-500: oklch(0.5 0 0);
--color-slate-600: oklch(0.4 0 0);
--color-slate-700: oklch(0.3 0 0);
--color-slate-800: oklch(0.2 0 0);
--color-slate-900: oklch(0.1 0 0);
--color-slate-950: oklch(0.05 0 0);
}
1 change: 1 addition & 0 deletions src/types/dashboardLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface DashboardWidget {

export interface GeneralSettingsType {
fontSize?: 'xs' | 'sm' | 'lg' | 'xl';
colorPalette?: 'default' | string;
}

export interface DashboardLayout {
Expand Down