diff --git a/src/components/buttons/action-button-menu.js b/src/components/buttons/action-button-menu.js index a5b075be..a564e330 100644 --- a/src/components/buttons/action-button-menu.js +++ b/src/components/buttons/action-button-menu.js @@ -12,7 +12,7 @@ export const ActionButtonMenu = ({ children }) => { className="action-button-menu" sx={{ p: 1, - backgroundColor: 'neutral.softActiveBg', + backgroundColor: 'background.surface', }} > { children } diff --git a/src/components/map/map.js b/src/components/map/map.js index d54bf614..1fe8a53c 100644 --- a/src/components/map/map.js +++ b/src/components/map/map.js @@ -1,12 +1,16 @@ import React from 'react'; import { MapContainer, TileLayer } from 'react-leaflet'; import { DefaultLayers } from './default-layers'; -import { useLayers } from '@context'; +import { + useLayers, + useSettings, +} from '@context'; import 'leaflet/dist/leaflet.css'; const DEFAULT_CENTER = [30.0, -73.0]; export const Map = () => { + const { darkMode } = useSettings(); const { setMap } = useLayers(); @@ -19,10 +23,10 @@ export const Map = () => { scrollWheelZoom={true} whenCreated={setMap} style={{ height: '100vh', width:'100wh' }}> - + { darkMode.enabled + ? + : + } ); diff --git a/src/components/sidebar/sidebar.js b/src/components/sidebar/sidebar.js index ddc8a92b..ebd34f1f 100644 --- a/src/components/sidebar/sidebar.js +++ b/src/components/sidebar/sidebar.js @@ -3,12 +3,14 @@ import { Fragment, useCallback, useState } from 'react'; import { List, Sheet, + useTheme, } from '@mui/joy'; import { Tray } from './tray'; import { MenuItem } from './menu-item'; import SidebarTrays from '../trays'; export const Sidebar = () => { + const theme = useTheme(); const [activeIndex, setActiveIndex] = useState(0); const handleClickMenuItem = useCallback(newIndex => { @@ -34,13 +36,15 @@ export const Sidebar = () => { maxWidth: '68px', overflow: 'hidden', p: 0, - backgroundColor: activeIndex === -1 ? '#f0f4f899' : '#f0f4f8', + backgroundColor: activeIndex === -1 + ? `rgba(${ theme.palette.mainChannel } / 0.2)` + : `rgba(${ theme.palette.mainChannel } / 1.0)`, // a drop shadow looks nice. we'll remove it if a tray is open, // as they should appear on the same plane. filter: activeIndex === -1 ? 'drop-shadow(0 0 8px rgba(0, 0, 0, 0.2))' : 'drop-shadow(1px 0 0 rgba(0, 0, 0, 0.2))', // similarly, here. '&:hover': { - backgroundColor: '#f0f4f8', + backgroundColor: `rgba(${ theme.palette.mainChannel } / 1.0)`, transition: 'max-width 250ms, filter 250ms, background-color 150ms', }, // we'll add a delay to this exit animation to give ample time diff --git a/src/components/trays/layers/layer-card.js b/src/components/trays/layers/layer-card.js index 2ecdb34b..0247da06 100644 --- a/src/components/trays/layers/layer-card.js +++ b/src/components/trays/layers/layer-card.js @@ -48,7 +48,12 @@ export const LayerCard = ({ index, layer }) => { sx={{ p: 1, borderLeft: '6px solid', - borderLeftColor: isVisible ? 'primary.400' : 'primary.100', + // borderLeftColor: isVisible + // ? `rgba(${ theme.palette.primary.mainChannel }) / 1.0` + // : `rgba(${ theme.palette.primary.mainChannel }) / 0.2`, + borderLeftColor: isVisible + ? `primary.plainColor` + : `primary.plainDisabledColor`, '.action-button': { filter: 'opacity(0.1)', transition: 'filter 250ms' }, '&:hover .action-button': { filter: 'opacity(0.5)' }, '& .action-button:hover': { filter: 'opacity(1.0)' }, @@ -119,7 +124,8 @@ export const LayerCard = ({ index, layer }) => { /> - { { JSON.stringify(layer.properties, null, 2) } diff --git a/src/components/trays/settings/dark-mode.js b/src/components/trays/settings/dark-mode.js new file mode 100644 index 00000000..f611cb42 --- /dev/null +++ b/src/components/trays/settings/dark-mode.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { IconButton, Stack, Typography } from '@mui/joy'; +import { + DarkMode as OnIcon, + LightMode as OffIcon, +} from '@mui/icons-material'; +import { useSettings } from '@context'; + +export const DarkModeToggle = () => { + const { darkMode } = useSettings(); + + return ( + + + + + Dark Mode + + + { darkMode.enabled ? 'Enabled' : 'Disabled' } + + + + ); +}; + +export const Toggler = () => { + const { darkMode } = useSettings(); + + return ( + + { + darkMode.enabled + ? + : + } + + ); +}; diff --git a/src/components/trays/settings/index.js b/src/components/trays/settings/index.js index 464bf421..1cb45672 100644 --- a/src/components/trays/settings/index.js +++ b/src/components/trays/settings/index.js @@ -2,7 +2,7 @@ import React from 'react'; import { Stack } from '@mui/joy'; import { Tune as SettingsIcon } from '@mui/icons-material'; -import { SampleToggle } from './sample'; +import { DarkModeToggle } from './dark-mode'; export const icon = ; @@ -10,6 +10,6 @@ export const title = 'Settings'; export const trayContents = () => ( - + ); diff --git a/src/context/settings.js b/src/context/settings.js index a3bdd0a2..d4018f85 100644 --- a/src/context/settings.js +++ b/src/context/settings.js @@ -1,5 +1,11 @@ -import React, { createContext, useContext } from "react"; +import React, { + createContext, + useCallback, + useContext, + useMemo, +} from "react"; import PropTypes from "prop-types"; +import { useColorScheme } from '@mui/joy/styles'; import { // useToggleLocalStorage, useToggleState, @@ -9,13 +15,24 @@ export const SettingsContext = createContext({}); export const useSettings = () => useContext(SettingsContext); export const SettingsProvider = ({ children }) => { + const { mode, setMode } = useColorScheme(); const booleanValue = useToggleState(); // to persist the value in the device's local // storage, use `useToggleLocalStorage` instead: // const booleanValue = useToggleLocalStorage('boolean-value') + const darkMode = useMemo(() => mode === 'dark', [mode]); + const toggleDarkMode = useCallback(() => { + setMode(darkMode ? 'light' : 'dark'); + }, [mode]); return ( - + { children } ); diff --git a/src/index.js b/src/index.js index d5ea0a3f..42df9d84 100644 --- a/src/index.js +++ b/src/index.js @@ -1,18 +1,23 @@ import React from 'react'; -import { App } from './app'; +import { CssVarsProvider } from '@mui/joy/styles'; import { createRoot } from 'react-dom/client'; +import { App } from './app'; import { LayersProvider, SettingsProvider } from '@context'; import './index.css'; +import '@fontsource/inter'; +import theme from './theme'; const container = document.getElementById('root'); const root = createRoot(container); const ProvisionedApp = () => ( - - - - - + + + + + + + ); root.render(); diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 00000000..ee635cf8 --- /dev/null +++ b/src/theme.js @@ -0,0 +1,153 @@ +import { extendTheme } from '@mui/joy/styles'; + +const theme = extendTheme({ + breakpoints: { + keys: ['xs', 'sm', 'md', 'lg', 'xl'], + values: { xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536 }, + unit: 'px', + }, + defaultMode: 'system', + direction: 'ltr', + palette: { + mode: 'light', + primary: { + 50: '#f0f7ff', + 100: '#c2e0ff', + 200: '#99ccf3', + 300: '#66b2ff', + 400: '#3399ff', + 500: '#007fff', + 600: '#0072e5', + 700: '#0059b2', + 800: '#004c99', + 900: '#003a75', + main: '#007fff', + light: '#66b2ff', + dark: '#0059b2', + contrastText: '#fff' + }, + secondary: { + 50: '#f3f6f9', + 100: '#e5eaf2', + 200: '#dae2ed', + 300: '#c7d0dd', + 400: '#b0b8c4', + 500: '#9da8b7', + 600: '#6b7a90', + 700: '#434d5b', + 800: '#303740', + 900: '#1c2025', + main: '#dae0e7', + contrastText: '#2f3a46', + light: 'rgb(225, 230, 235)', + dark: 'rgb(152, 156, 161)' + }, + divider: '#e5eaf2', + primaryDark: { + 50: '#eaedf1', + 100: '#dae0e7', + 200: '#acbac8', + 300: '#7b91a7', + 400: '#4b5e71', + 500: '#3b4a59', + 600: '#2f3a46', + 700: '#1f262e', + 800: '#141a1f', + 900: '#101418', + main: '#7b91a7' + }, + common: { + black: '#0b0d0e', + white: '#fff' + }, + text: { + primary: '#1c2025', + secondary: '#434d5b', + tertiary: '#6b7a90', + disabled: 'rgba(0, 0, 0, 0.38)' + }, + grey: { + 50: '#f3f6f9', + 100: '#e5eaf2', + 200: '#dae2ed', + 300: '#c7d0dd', + 400: '#b0b8c4', + 500: '#9da8b7', + 600: '#6b7a90', + 700: '#434d5b', + 800: '#303740', + 900: '#1c2025', + main: '#e5eaf2', + contrastText: '#6b7a90', + A100: '#f5f5f5', + A200: '#eeeeee', + A400: '#bdbdbd', + A700: '#616161' + }, + error: { + 50: '#fff0f1', + 100: '#ffdbde', + 200: '#ffbdc2', + 300: '#ff99a2', + 400: '#ff7a86', + 500: '#ff505f', + 600: '#eb0014', + 700: '#c70011', + 800: '#94000d', + 900: '#570007', + main: '#eb0014', + light: '#ff99a2', + dark: '#c70011', + contrastText: '#fff' + }, + success: { + 50: '#e9fbf0', + 100: '#c6f6d9', + 200: '#9aefbc', + 300: '#6ae79c', + 400: '#3ee07f', + 500: '#21cc66', + 600: '#1db45a', + 700: '#1aa251', + 800: '#178d46', + 900: '#0f5c2e', + main: '#1aa251', + light: '#6ae79c', + dark: '#1aa251', + contrastText: '#fff' + }, + warning: { + 50: '#fff9eb', + 100: '#fff3c1', + 200: '#ffeca1', + 300: '#ffdc48', + 400: '#f4c000', + 500: '#dea500', + 600: '#d18e00', + 700: '#ab6800', + 800: '#8c5800', + 900: '#5a3600', + main: '#dea500', + light: '#ffdc48', + dark: '#ab6800', + contrastText: 'rgba(0, 0, 0, 0.87)' + }, + info: { + main: '#0288d1', + light: '#03a9f4', + dark: '#01579b', + contrastText: '#fff' + }, + contrastThreshold: 3, + tonalOffset: 0.2, + background: { + paper: '#fff', + default: '#fff' + }, + }, + shape: { + borderRadius: 12 + }, +}); + +export default theme;