Add light/dark theme switching
This commit is contained in:
parent
cd22b904ee
commit
5f41608e92
13 changed files with 133 additions and 65 deletions
21
src/app.tsx
21
src/app.tsx
|
@ -1,4 +1,4 @@
|
||||||
import { createGlobalStyle } from "styled-components";
|
import { createGlobalStyle, ThemeProvider } from "styled-components";
|
||||||
|
|
||||||
import SearchBar from "./components/searchBar";
|
import SearchBar from "./components/searchBar";
|
||||||
import Greeter from "./components/greeter";
|
import Greeter from "./components/greeter";
|
||||||
|
@ -7,12 +7,13 @@ import BookmarkList from "./components/bookmarks";
|
||||||
import Settings from "./components/settings";
|
import Settings from "./components/settings";
|
||||||
import Imprint from "./components/imprint";
|
import Imprint from "./components/imprint";
|
||||||
|
|
||||||
import selectedTheme from "./lib/theme";
|
import { IThemeProps, getTheme, setScheme } from "./lib/theme";
|
||||||
import useFetcher from "./lib/fetcher";
|
import useFetcher from "./lib/fetcher";
|
||||||
|
import useMediaQuery from "./lib/useMediaQuery";
|
||||||
|
|
||||||
export const GlobalStyle = createGlobalStyle`
|
export const GlobalStyle = createGlobalStyle<{ theme: IThemeProps }>`
|
||||||
body {
|
body {
|
||||||
background-color: ${selectedTheme.backgroundColor};
|
background-color: ${(props) => props.theme.backgroundColor};
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
@ -38,8 +39,16 @@ const App = () => {
|
||||||
greeterData,
|
greeterData,
|
||||||
} = useFetcher();
|
} = useFetcher();
|
||||||
|
|
||||||
|
const theme = getTheme();
|
||||||
|
let isDark = useMediaQuery("(prefers-color-scheme: dark");
|
||||||
|
if (isDark) {
|
||||||
|
setScheme("dark-theme");
|
||||||
|
} else {
|
||||||
|
setScheme("light-theme");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ThemeProvider theme={theme}>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<div>
|
<div>
|
||||||
<SearchBar search={searchProviderData?.search} />
|
<SearchBar search={searchProviderData?.search} />
|
||||||
|
@ -56,7 +65,7 @@ const App = () => {
|
||||||
{!bookmarkData.error && <BookmarkList groups={bookmarkData.groups} />}
|
{!bookmarkData.error && <BookmarkList groups={bookmarkData.groups} />}
|
||||||
{!imprintData.error && <Imprint imprint={imprintData.imprint} />}
|
{!imprintData.error && <Imprint imprint={imprintData.imprint} />}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import Icon from "./icon";
|
import Icon from "./icon";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
|
|
||||||
const AppContainer = styled.a`
|
const AppContainer = styled.a`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -37,7 +36,7 @@ const AppDescription = styled.p`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${(props) => props.theme.accentColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface IAppProps {
|
export interface IAppProps {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {
|
||||||
ListContainer,
|
ListContainer,
|
||||||
SubHeadline,
|
SubHeadline,
|
||||||
} from "./elements";
|
} from "./elements";
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
|
|
||||||
const GroupContainer = styled.div`
|
const GroupContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -18,7 +17,7 @@ const GroupContainer = styled.div`
|
||||||
const Bookmark = styled.a`
|
const Bookmark = styled.a`
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${(props) => props.theme.accentColor};
|
||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
|
|
||||||
export const ListContainer = styled.div`
|
export const ListContainer = styled.div`
|
||||||
padding: 2rem 0;
|
padding: 2rem 0;
|
||||||
|
@ -11,7 +10,7 @@ export const Headline = styled.h2`
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SubHeadline = styled.h3`
|
export const SubHeadline = styled.h3`
|
||||||
|
@ -19,7 +18,7 @@ export const SubHeadline = styled.h3`
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ItemList = styled.ul`
|
export const ItemList = styled.ul`
|
||||||
|
@ -44,8 +43,8 @@ export const Button = styled.button`
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border: 1px solid ${selectedTheme.mainColor};
|
border: 1px solid ${(props) => props.theme.mainColor};
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
background: none;
|
background: none;
|
||||||
|
|
||||||
min-height: 2rem;
|
min-height: 2rem;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
|
|
||||||
const GreeterContainer = styled.div`
|
const GreeterContainer = styled.div`
|
||||||
padding: 2rem 0;
|
padding: 2rem 0;
|
||||||
|
@ -9,7 +8,7 @@ const GreetText = styled.h1`
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const DateText = styled.h3`
|
const DateText = styled.h3`
|
||||||
|
@ -17,7 +16,7 @@ const DateText = styled.h3`
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${(props) => props.theme.accentColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface IGreeterComponentProps {
|
export interface IGreeterComponentProps {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
|
|
||||||
interface IIconProps {
|
interface IIconProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -40,7 +39,7 @@ const IconContainer = styled.i`
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
font-feature-settings: "liga";
|
font-feature-settings: "liga";
|
||||||
font-size: ${(props) => props.about};
|
font-size: ${(props) => props.about};
|
||||||
color: ${(props) => props.color};
|
color: ${(props) => props.theme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +48,7 @@ const IconContainer = styled.i`
|
||||||
* @returns {React.ReactNode} the icon node
|
* @returns {React.ReactNode} the icon node
|
||||||
*/
|
*/
|
||||||
export const Icon = ({ name, size }: IIconProps) => (
|
export const Icon = ({ name, size }: IIconProps) => (
|
||||||
<IconContainer color={selectedTheme.mainColor} about={size}>
|
<IconContainer about={size}>
|
||||||
{name}
|
{name}
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
import { ListContainer, ItemList, Headline, SubHeadline } from "./elements";
|
import { ListContainer, ItemList, Headline, SubHeadline } from "./elements";
|
||||||
|
|
||||||
const ModalSubHeadline = styled(SubHeadline)`
|
const ModalSubHeadline = styled(SubHeadline)`
|
||||||
|
@ -12,14 +11,14 @@ const Text = styled.p`
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Link = styled.a`
|
const Link = styled.a`
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
|
|
||||||
import { Headline } from "./elements";
|
import { Headline } from "./elements";
|
||||||
import { IconButton } from "./icon";
|
import { IconButton } from "./icon";
|
||||||
|
@ -12,8 +11,8 @@ const ModalContainer = styled.div`
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
border: 1px solid ${selectedTheme.mainColor};
|
border: 1px solid ${(props) => props.theme.mainColor};
|
||||||
background-color: ${selectedTheme.backgroundColor};
|
background-color: ${(props) => props.theme.backgroundColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Text = styled.p`
|
const Text = styled.p`
|
||||||
|
@ -22,7 +21,7 @@ const Text = styled.p`
|
||||||
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${(props) => props.theme.accentColor};
|
||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import selectedTheme from "../lib/theme";
|
|
||||||
|
|
||||||
import { Button } from "./elements";
|
import { Button } from "./elements";
|
||||||
|
|
||||||
const Search = styled.form`
|
const Search = styled.form`
|
||||||
|
@ -21,11 +19,11 @@ const SearchInput = styled.input`
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid ${selectedTheme.accentColor};
|
border-bottom: 1px solid ${(props) => props.theme.accentColor};
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
background: none;
|
background: none;
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -8,6 +8,7 @@ export interface IItemProps {
|
||||||
export interface ISelectProps {
|
export interface ISelectProps {
|
||||||
items: Array<IItemProps>;
|
items: Array<IItemProps>;
|
||||||
onChange: (item: any) => void;
|
onChange: (item: any) => void;
|
||||||
|
current: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ const update = (
|
||||||
onChange(items.find((item) => item.value.toString() === e.target.value));
|
onChange(items.find((item) => item.value.toString() === e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
const Select = ({ items, onChange, className }: ISelectProps) => (
|
const Select = ({ items, onChange, current, className }: ISelectProps) => (
|
||||||
<select
|
<select
|
||||||
data-testid="select"
|
data-testid="select"
|
||||||
onChange={(e) => update(items, onChange, e)}
|
onChange={(e) => update(items, onChange, e)}
|
||||||
|
@ -30,6 +31,7 @@ const Select = ({ items, onChange, className }: ISelectProps) => (
|
||||||
data-testid={"option-" + index}
|
data-testid={"option-" + index}
|
||||||
key={[label, index].join("")}
|
key={[label, index].join("")}
|
||||||
value={value.toString()}
|
value={value.toString()}
|
||||||
|
selected={current === label}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
|
|
|
@ -4,25 +4,24 @@ import styled from "styled-components";
|
||||||
import Select from "./select";
|
import Select from "./select";
|
||||||
|
|
||||||
import { ISearchProps } from "./searchBar";
|
import { ISearchProps } from "./searchBar";
|
||||||
import selectedTheme, { setTheme, IThemeProps } from "../lib/theme";
|
import { setTheme, IThemeProps, getTheme } from "../lib/theme";
|
||||||
import { Button, SubHeadline } from "./elements";
|
import { Button, SubHeadline } from "./elements";
|
||||||
|
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
|
|
||||||
export const FormContainer = styled.div`
|
export const FormContainer = styled.div`
|
||||||
display: grid;
|
margin-bottom: 1em;
|
||||||
grid-template-columns: auto auto auto;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Table = styled.table`
|
export const Table = styled.table`
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
background: none;
|
background: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TableRow = styled.tr`
|
export const TableRow = styled.tr`
|
||||||
border-bottom: 1px solid ${selectedTheme.mainColor};
|
border-bottom: 1px solid ${(props) => props.theme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TableCell = styled.td`
|
export const TableCell = styled.td`
|
||||||
|
@ -41,18 +40,23 @@ export const Section = styled.div`
|
||||||
|
|
||||||
export const SectionHeadline = styled(SubHeadline)`
|
export const SectionHeadline = styled(SubHeadline)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid ${selectedTheme.accentColor};
|
border-bottom: 1px solid ${(props) => props.theme.accentColor};
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Text = styled.p`
|
const Text = styled.p`
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${(props) => props.theme.accentColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Code = styled.p`
|
const Code = styled.p`
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${(props) => props.theme.accentColor};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ThemeHeader = styled.p`
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
color: ${(props) => props.theme.accentColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ThemeSelect = styled(Select)`
|
const ThemeSelect = styled(Select)`
|
||||||
|
@ -62,12 +66,12 @@ const ThemeSelect = styled(Select)`
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border: 1px solid ${selectedTheme.mainColor};
|
border: 1px solid ${(props) => props.theme.mainColor};
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${(props) => props.theme.mainColor};
|
||||||
background: none;
|
background: none;
|
||||||
|
|
||||||
& > option {
|
& > option {
|
||||||
background-color: ${selectedTheme.backgroundColor};
|
background-color: ${(props) => props.theme.backgroundColor};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -82,7 +86,12 @@ interface ISettingsProps {
|
||||||
* @param {ISearchProps} search - the list of search providers
|
* @param {ISearchProps} search - the list of search providers
|
||||||
*/
|
*/
|
||||||
const Settings = ({ themes, search }: ISettingsProps) => {
|
const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
const [newTheme, setNewTheme] = useState<IThemeProps>();
|
const [newLightTheme, setNewLightTheme] = useState<IThemeProps>();
|
||||||
|
const [newDarkTheme, setNewDarkTheme] = useState<IThemeProps>();
|
||||||
|
|
||||||
|
const currentLightTheme = getTheme("light").label;
|
||||||
|
const currentDarkTheme = getTheme("dark").label;
|
||||||
|
console.log(currentLightTheme, currentDarkTheme);
|
||||||
|
|
||||||
if (themes || search) {
|
if (themes || search) {
|
||||||
return (
|
return (
|
||||||
|
@ -91,25 +100,34 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
<Section>
|
<Section>
|
||||||
<SectionHeadline>Theme:</SectionHeadline>
|
<SectionHeadline>Theme:</SectionHeadline>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
|
<ThemeHeader>Light</ThemeHeader>
|
||||||
<ThemeSelect
|
<ThemeSelect
|
||||||
items={themes}
|
items={themes}
|
||||||
onChange={(theme: IThemeProps) => setNewTheme(theme)}
|
onChange={(theme: IThemeProps) => setNewLightTheme(theme)}
|
||||||
|
current={currentLightTheme}
|
||||||
|
></ThemeSelect>
|
||||||
|
<ThemeHeader>Dark</ThemeHeader>
|
||||||
|
<ThemeSelect
|
||||||
|
items={themes}
|
||||||
|
onChange={(theme: IThemeProps) => setNewDarkTheme(theme)}
|
||||||
|
current={currentDarkTheme}
|
||||||
></ThemeSelect>
|
></ThemeSelect>
|
||||||
<Button
|
|
||||||
data-testid="button-submit"
|
|
||||||
onClick={() => {
|
|
||||||
if (newTheme) setTheme(newTheme);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Apply
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
data-testid="button-refresh"
|
|
||||||
onClick={() => window.location.reload()}
|
|
||||||
>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
|
<Button
|
||||||
|
data-testid="button-submit"
|
||||||
|
onClick={() => {
|
||||||
|
if (newLightTheme) setTheme("light", newLightTheme);
|
||||||
|
if (newDarkTheme) setTheme("dark", newDarkTheme);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Apply
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
data-testid="button-refresh"
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
{search && (
|
{search && (
|
||||||
|
|
|
@ -14,23 +14,47 @@ export const defaultTheme: IThemeProps = {
|
||||||
backgroundColor: "#ffffff",
|
backgroundColor: "#ffffff",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the color scheme into localStorage
|
||||||
|
* @param {string} scheme
|
||||||
|
*/
|
||||||
|
export const setScheme = (scheme: string) => {
|
||||||
|
localStorage.setItem("theme", scheme);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a given theme into localStorage
|
* Writes a given theme into localStorage
|
||||||
|
* @param {string} scheme - the color scheme (light or dark) to save the theme to
|
||||||
* @param {string} theme - the theme that shall be saved (in stringified JSON)
|
* @param {string} theme - the theme that shall be saved (in stringified JSON)
|
||||||
*/
|
*/
|
||||||
export const setTheme = (theme: IThemeProps) => {
|
export const setTheme = (scheme: string, theme: IThemeProps) => {
|
||||||
localStorage.setItem("theme", JSON.stringify(theme));
|
if (scheme === "light") {
|
||||||
|
localStorage.setItem("light-theme", JSON.stringify(theme));
|
||||||
|
localStorage.setItem("theme", "light-theme");
|
||||||
|
}
|
||||||
|
if (scheme === "dark") {
|
||||||
|
localStorage.setItem("dark-theme", JSON.stringify(theme));
|
||||||
|
localStorage.setItem("theme", "dark-theme");
|
||||||
|
}
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function that gets the saved theme from localStorage or returns the default
|
* Function that gets the saved theme from localStorage or returns the default
|
||||||
|
* @param {string} [scheme] the color scheme to retrieve the theme for
|
||||||
* @returns {IThemeProps} the saved theme or the default theme
|
* @returns {IThemeProps} the saved theme or the default theme
|
||||||
*/
|
*/
|
||||||
export const getTheme = (): IThemeProps => {
|
export const getTheme = (scheme?: string): IThemeProps => {
|
||||||
|
let currentScheme = localStorage.getItem("theme");
|
||||||
let selectedTheme = defaultTheme;
|
let selectedTheme = defaultTheme;
|
||||||
|
|
||||||
let theme = localStorage.getItem("theme");
|
if (scheme === "light") {
|
||||||
|
currentScheme = "light-theme";
|
||||||
|
} else if (scheme === "dark") {
|
||||||
|
currentScheme = "dark-theme";
|
||||||
|
}
|
||||||
|
|
||||||
|
let theme = currentScheme === "dark-theme" ? localStorage.getItem("dark-theme") : localStorage.getItem("light-theme");
|
||||||
if (theme !== null) selectedTheme = JSON.parse(theme || "{}");
|
if (theme !== null) selectedTheme = JSON.parse(theme || "{}");
|
||||||
|
|
||||||
return selectedTheme;
|
return selectedTheme;
|
||||||
|
|
24
src/lib/useMediaQuery.tsx
Normal file
24
src/lib/useMediaQuery.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Credit: https://www.netlify.com/blog/2020/12/05/building-a-custom-react-media-query-hook-for-more-responsive-apps/
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const useMediaQuery = (query: string) => {
|
||||||
|
const [matches, setMatches] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const media = window.matchMedia(query);
|
||||||
|
if (media.matches !== matches) {
|
||||||
|
setMatches(media.matches);
|
||||||
|
}
|
||||||
|
const listener = () => {
|
||||||
|
setMatches(media.matches);
|
||||||
|
};
|
||||||
|
media.addEventListener("change", listener);
|
||||||
|
return () => media.removeEventListener("change", listener);
|
||||||
|
}, [matches, query]);
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IsDark = () => useMediaQuery("(prefers-color-scheme: dark");
|
||||||
|
|
||||||
|
export default useMediaQuery;
|
Loading…
Reference in a new issue