diff --git a/client/src/components/ColorPicker.tsx b/client/src/components/ColorPicker.tsx new file mode 100644 index 0000000..dc8468c --- /dev/null +++ b/client/src/components/ColorPicker.tsx @@ -0,0 +1,151 @@ +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + TextField, +} from '@mui/material'; +import { Palette } from '@mui/icons-material'; +import { useThemeStore } from '../stores/themeStore'; + +interface ColorPickerProps { + open: boolean; + onClose: () => void; +} + +const predefinedColors = [ + // Purples (3 shades as requested) + { name: 'Deep Purple', value: '#673ab7' }, + { name: 'Medium Purple', value: '#9c27b0' }, + { name: 'Light Purple', value: '#e1bee7' }, + + // Blues + { name: 'Deep Blue', value: '#1976d2' }, + { name: 'Light Blue', value: '#03dac6' }, + { name: 'Indigo', value: '#3f51b5' }, + + // Greens + { name: 'Forest Green', value: '#2e7d32' }, + { name: 'Mint Green', value: '#4caf50' }, + { name: 'Teal', value: '#009688' }, + + // Reds/Oranges + { name: 'Deep Red', value: '#d32f2f' }, + { name: 'Coral', value: '#ff5722' }, + { name: 'Amber', value: '#ff9800' }, + + // Grays + { name: 'Charcoal', value: '#424242' }, + { name: 'Slate', value: '#607d8b' }, + { name: 'Warm Gray', value: '#795548' }, +]; + +export function ColorPicker({ open, onClose }: ColorPickerProps) { + const { primaryColor, setPrimaryColor } = useThemeStore(); + const [customColor, setCustomColor] = useState(primaryColor); + + const handleColorSelect = (color: string) => { + setPrimaryColor(color); + onClose(); + }; + + const handleCustomColorSubmit = () => { + if (customColor && /^#[0-9A-F]{6}$/i.test(customColor)) { + setPrimaryColor(customColor); + onClose(); + } + }; + + return ( + + + + + Choose Primary Color + + + + + + Predefined Colors + + + {predefinedColors.map((color) => ( + handleColorSelect(color.value)} + > + + + {color.name} + + + ))} + + + + + + Custom Color + + + setCustomColor(e.target.value)} + placeholder="#000000" + size="small" + sx={{ flex: 1 }} + /> + + + + + + + + + + ); +} \ No newline at end of file diff --git a/client/src/components/ColorPickerButton.tsx b/client/src/components/ColorPickerButton.tsx new file mode 100644 index 0000000..bf24907 --- /dev/null +++ b/client/src/components/ColorPickerButton.tsx @@ -0,0 +1,38 @@ +import { useState } from 'react'; +import { IconButton, Tooltip } from '@mui/material'; +import { Palette } from '@mui/icons-material'; +import { useThemeStore } from '../stores/themeStore'; +import { ColorPicker } from './ColorPicker'; + +export function ColorPickerButton() { + const [open, setOpen] = useState(false); + const { primaryColor } = useThemeStore(); + + return ( + <> + + setOpen(true)} + sx={{ + position: 'relative', + '&::after': { + content: '""', + position: 'absolute', + bottom: 2, + right: 2, + width: 12, + height: 12, + borderRadius: '50%', + bgcolor: primaryColor, + border: 1, + borderColor: 'background.paper', + }, + }} + > + + + + setOpen(false)} /> + + ); +} \ No newline at end of file diff --git a/client/src/components/ThemeProvider.tsx b/client/src/components/ThemeProvider.tsx new file mode 100644 index 0000000..95758ba --- /dev/null +++ b/client/src/components/ThemeProvider.tsx @@ -0,0 +1,25 @@ +import { createTheme, ThemeProvider as MuiThemeProvider } from '@mui/material/styles'; +import { useThemeStore } from '../stores/themeStore'; +import { ReactNode } from 'react'; + +interface ThemeProviderProps { + children: ReactNode; +} + +export function ThemeProvider({ children }: ThemeProviderProps) { + const { primaryColor } = useThemeStore(); + + const theme = createTheme({ + palette: { + primary: { + main: primaryColor, + }, + }, + }); + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/client/src/layouts/DesktopLayout.tsx b/client/src/layouts/DesktopLayout.tsx index 4556f3a..7039bcb 100644 --- a/client/src/layouts/DesktopLayout.tsx +++ b/client/src/layouts/DesktopLayout.tsx @@ -2,6 +2,7 @@ import { Box, Typography, IconButton, TextField, Button } from '@mui/material'; import PrintIcon from '@mui/icons-material/Print'; import type { GroupWithTasks, TaskWithSteps, StepWithNotes } from '../types'; import { CreateButtons } from '../components/CreateButtons'; +import { ColorPickerButton } from '../components/ColorPickerButton'; import { useUserSelection } from '../hooks/useUserSelection'; interface DesktopLayoutProps { @@ -45,7 +46,10 @@ export function DesktopLayout({ Groups - + + + + {/* Groups List */} diff --git a/client/src/layouts/MobileLayout.tsx b/client/src/layouts/MobileLayout.tsx index c540481..d8d9c64 100644 --- a/client/src/layouts/MobileLayout.tsx +++ b/client/src/layouts/MobileLayout.tsx @@ -3,6 +3,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import PrintIcon from '@mui/icons-material/Print'; import type { GroupWithTasks, TaskWithSteps, StepWithNotes } from '../types'; import { CreateButtons } from '../components/CreateButtons'; +import { ColorPickerButton } from '../components/ColorPickerButton'; import { useUserSelection } from '../hooks/useUserSelection'; function isGroupView(selectedGroup?: GroupWithTasks) { @@ -103,7 +104,10 @@ export function MobileLayout({ <> Groups - + + + + {/* Groups List */} diff --git a/client/src/main.tsx b/client/src/main.tsx index c6257cc..bdff80d 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -10,12 +10,15 @@ if (process.env.NODE_ENV !== "production") { import React from 'react'; import ReactDOM from 'react-dom/client'; import { App } from './App'; +import { ThemeProvider } from './components/ThemeProvider'; import './index.css' ReactDOM.createRoot(document.getElementById('root')!).render( - + + + ); diff --git a/client/src/stores/themeStore.ts b/client/src/stores/themeStore.ts new file mode 100644 index 0000000..7dac9a6 --- /dev/null +++ b/client/src/stores/themeStore.ts @@ -0,0 +1,21 @@ +import { create } from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; + +interface ThemeState { + primaryColor: string; + setPrimaryColor: (color: string) => void; +} + +export const useThemeStore = create()( + devtools( + persist( + (set) => ({ + primaryColor: '#1976d2', // MUI default blue + setPrimaryColor: (color) => set({ primaryColor: color }), + }), + { + name: 'theme-storage', + } + ) + ) +); \ No newline at end of file