added themes

This commit is contained in:
Fedor Katurov 2023-04-29 13:50:41 +06:00
parent 51576b0309
commit 44d903b6e2
17 changed files with 368 additions and 31 deletions

View file

@ -9,5 +9,12 @@
"Colors": "Colors", "Colors": "Colors",
"Inline code": "Inline code", "Inline code": "Inline code",
"Ok": "Ok", "Ok": "Ok",
"Settings": "Settings" "Settings": "Settings",
"Color theme": "Color theme",
"Custom theme": "Custom theme",
"Dark theme": "Dark theme",
"Light theme": "Light theme",
"Blueberry boar": "Blueberry boar",
"User defined theme": "User defined",
"Built-in theme": "Built-in"
} }

View file

@ -9,5 +9,12 @@
"Colors": "Цвета", "Colors": "Цвета",
"Inline code": "Код в строке", "Inline code": "Код в строке",
"Ok": "Ok", "Ok": "Ok",
"Settings": "Настройки" "Settings": "Настройки",
"Color theme": "Цветовая схема",
"Custom theme": "Своя тема",
"Dark theme": "Тёмная",
"Light theme": "Светлая",
"Blueberry boar": "Черничный кабанчик",
"User defined theme": "Пользовательская",
"Built-in theme": "Встроенная"
} }

View file

@ -1,8 +1,8 @@
{ {
"name": "Markdown Home Tab", "name": "Markdown Home Tab",
"short_name": "Markdown New Tab", "short_name": "Markdown New Tab",
"version": "0.0.1", "version": "0.0.2",
"description": "Makdown editor for your new tab", "description": "Markdown right in your home tab! Paste links, pictures, lists and more. You can also customize colors to match your needs.",
"manifest_version": 2, "manifest_version": 2,
"permissions": ["storage"], "permissions": ["storage"],
"chrome_url_overrides": { "chrome_url_overrides": {

View file

@ -3,11 +3,20 @@ import styles from "./styles.module.scss";
type SettingsRowProps = PropsWithChildren & { type SettingsRowProps = PropsWithChildren & {
title: string; title: string;
subTitle?: string;
}; };
const SettingsRow: FC<SettingsRowProps> = ({ title, children }) => ( const SettingsRow: FC<SettingsRowProps> = ({ title, children, subTitle }) => (
<div className={styles.row}> <div className={styles.row}>
<div className={styles.legend}>
<div className={styles.title}>{title}</div> <div className={styles.title}>{title}</div>
{!!subTitle && (
<div
className={styles.subTitle}
dangerouslySetInnerHTML={{ __html: subTitle }}
/>
)}
</div>
<div className={styles.item}>{children}</div> <div className={styles.item}>{children}</div>
</div> </div>
); );

View file

@ -6,6 +6,18 @@
gap: 20px; gap: 20px;
} }
.title { .legend {
flex: 1; flex: 1;
min-width: 0;
}
.title {
font-weight: bold;
}
.subTitle {
font-size: 0.8em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
} }

View file

@ -13,5 +13,5 @@
.title { .title {
flex: 1; flex: 1;
font-size: 1.4em; font-weight: bold;
} }

View file

@ -0,0 +1,46 @@
import { ChangeEvent, FC, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { CustomTheme } from "~/modules/theme/types/theme";
import { ColorSettings } from "../../context/SettingsContext";
interface ThemeSelectProps {
themes: CustomTheme[];
value?: CustomTheme;
onChange: (val: ColorSettings) => void;
}
const ThemeSelect: FC<ThemeSelectProps> = ({ themes, value, onChange }) => {
const { t } = useTranslation();
const onChangeTrigger = useCallback(
(event: ChangeEvent<HTMLSelectElement>) => {
const themeTitle = event.target.value;
if (!themeTitle) {
return;
}
const theme = themes.find((it) => it.title === themeTitle);
if (!theme) {
return;
}
onChange(theme.colors);
},
[themes, onChange]
);
return (
<select onChange={onChangeTrigger} value={value?.title}>
{!value && <option selected>{t("Custom theme")}</option>}
{themes.map((theme) => (
<option key={theme.title} value={theme.title}>
{theme.title}
</option>
))}
</select>
);
};
export { ThemeSelect };

View file

@ -1,15 +1,20 @@
import { ChangeEvent, FC, useCallback } from "react"; import { ChangeEvent, FC, useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { RowGroup } from "~/components/containers/RowGroup"; import { RowGroup } from "~/components/containers/RowGroup";
import { SettingsRow } from "~/components/containers/SettingsRow"; import { SettingsRow } from "~/components/containers/SettingsRow";
import { import {
ColorSettings,
SettingsValue, SettingsValue,
useSettings, useSettings,
} from "~/modules/settings/context/SettingsContext"; } from "~/modules/settings/context/SettingsContext";
import { ThemeSelect } from "../../components/ThemeSelect";
import { useBuiltinThemes } from "~/modules/theme/constants/themes";
import { useDefaultTheme } from "~/modules/theme/hooks/useDefaultTheme";
const ColorSettings: FC = () => { const ColorSettings: FC = () => {
const { update, settings } = useSettings(); const { update, settings } = useSettings();
const { t } = useTranslation(); const { t } = useTranslation();
const defaultColors = useDefaultTheme();
const setString = useCallback( const setString = useCallback(
(field: keyof SettingsValue) => (event: ChangeEvent<HTMLInputElement>) => { (field: keyof SettingsValue) => (event: ChangeEvent<HTMLInputElement>) => {
@ -18,46 +23,88 @@ const ColorSettings: FC = () => {
[update] [update]
); );
const themes = useBuiltinThemes();
const currentTheme = useMemo(
() =>
themes.find((it) =>
Object.entries({ ...defaultColors, ...it.colors }).every(
([key, value]) =>
(settings as unknown as Record<string, string>)[key] === value
)
),
[themes, defaultColors, settings]
);
const setThemeColors = useCallback(
(val: ColorSettings) => {
update({ ...defaultColors, ...val });
},
[defaultColors, update]
);
const themeSubtitle = useMemo(() => {
if (!currentTheme) {
return t("User defined theme");
}
if (!currentTheme.url) {
return t("Built-in theme");
}
return `<a href="${currentTheme.url}" target="__blank">${currentTheme.url}</a>`;
}, [currentTheme, t]);
return ( return (
<RowGroup> <RowGroup>
<label htmlFor="color"> <label htmlFor="theme">
<SettingsRow title={t("Color theme")} subTitle={themeSubtitle}>
<ThemeSelect
value={currentTheme}
onChange={setThemeColors}
themes={themes}
/>
</SettingsRow>
</label>
<label htmlFor="backgroundColor">
<SettingsRow title={t("Background")}> <SettingsRow title={t("Background")}>
<input <input
type="color" type="color"
id="color" id="backgroundColor"
onChange={setString("backgroundColor")} onChange={setString("backgroundColor")}
value={settings.backgroundColor} value={settings.backgroundColor}
/> />
</SettingsRow> </SettingsRow>
</label> </label>
<label htmlFor="color"> <label htmlFor="textColor">
<SettingsRow title={t("Text")}> <SettingsRow title={t("Text")}>
<input <input
type="color" type="color"
id="color" id="textColor"
onChange={setString("textColor")} onChange={setString("textColor")}
value={settings.textColor} value={settings.textColor}
/> />
</SettingsRow> </SettingsRow>
</label> </label>
<label htmlFor="color"> <label htmlFor="linkColor">
<SettingsRow title={t("Links")}> <SettingsRow title={t("Links")}>
<input <input
type="color" type="color"
id="color" id="linkColor"
onChange={setString("linkColor")} onChange={setString("linkColor")}
value={settings.linkColor} value={settings.linkColor}
/> />
</SettingsRow> </SettingsRow>
</label> </label>
<label htmlFor="color"> <label htmlFor="codeColor">
<SettingsRow title={t("Inline code")}> <SettingsRow title={t("Inline code")}>
<input <input
type="color" type="color"
id="color" id="codeColor"
onChange={setString("codeColor")} onChange={setString("codeColor")}
value={settings.codeColor} value={settings.codeColor}
/> />

View file

@ -5,6 +5,11 @@ export interface ColorSettings {
textColor: string; textColor: string;
linkColor: string; linkColor: string;
codeColor: string; codeColor: string;
h1Color?: string;
h2Color?: string;
h3Color?: string;
h4Color?: string;
h5Color?: string;
} }
export type SettingsValue = ColorSettings & { export type SettingsValue = ColorSettings & {

View file

@ -1,12 +1,7 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { defaultSettings } from "../context/SettingsContext";
import {
Theme,
defaultDarkTheme,
defaultLightTheme,
} from "~/modules/theme/constants/theme";
import { useDetectTheme } from "~/modules/theme/hooks/useDetectTheme";
import { SettingsValue } from "~/modules/settings/context/SettingsContext"; import { SettingsValue } from "~/modules/settings/context/SettingsContext";
import { useDefaultTheme } from "~/modules/theme/hooks/useDefaultTheme";
import { defaultSettings } from "../context/SettingsContext";
const getLocalStorageSettings = (defaultValue: SettingsValue) => { const getLocalStorageSettings = (defaultValue: SettingsValue) => {
try { try {
@ -20,9 +15,7 @@ const getLocalStorageSettings = (defaultValue: SettingsValue) => {
}; };
export const usePersistSettings = () => { export const usePersistSettings = () => {
const theme = useDetectTheme(); const defaultColors = useDefaultTheme();
const defaultColors =
theme === Theme.Dark ? defaultDarkTheme : defaultLightTheme;
const [settings, setSettings] = useState<SettingsValue>( const [settings, setSettings] = useState<SettingsValue>(
getLocalStorageSettings({ ...defaultSettings, ...defaultColors }) getLocalStorageSettings({ ...defaultSettings, ...defaultColors })

View file

@ -10,6 +10,11 @@ export const defaultDarkTheme: ColorSettings = {
textColor: "#eeeeee", textColor: "#eeeeee",
linkColor: "#25bfe6", linkColor: "#25bfe6",
codeColor: "#ff3344", codeColor: "#ff3344",
h1Color: "",
h2Color: "",
h3Color: "",
h4Color: "",
h5Color: "",
}; };
export const defaultLightTheme: ColorSettings = { export const defaultLightTheme: ColorSettings = {
@ -17,4 +22,9 @@ export const defaultLightTheme: ColorSettings = {
textColor: "#2e2e2e", textColor: "#2e2e2e",
linkColor: "#25bfe6", linkColor: "#25bfe6",
codeColor: "#ff3344", codeColor: "#ff3344",
h1Color: "",
h2Color: "",
h3Color: "",
h4Color: "",
h5Color: "",
}; };

View file

@ -0,0 +1,146 @@
import { useMemo } from "react";
import { CustomTheme } from "../types/theme";
import { defaultDarkTheme, defaultLightTheme } from "./theme";
import { useTranslation } from "react-i18next";
export const useBuiltinThemes = () => {
const { t } = useTranslation();
return useMemo<CustomTheme[]>(
() => [
{
title: t("Dark theme"),
colors: defaultDarkTheme,
},
{
title: t("Light theme"),
colors: defaultLightTheme,
},
{
title: t("Blueberry boar"),
colors: {
backgroundColor: "#201d26",
textColor: "#eeeeee",
linkColor: "#40bfbf",
codeColor: "#d24276",
h1Color: "#f1b8b8",
h2Color: "#e2c79e",
h3Color: "#a6d6c2",
h4Color: "#a4b8cb",
h5Color: "#c0a1da",
},
},
{
title: "Cattpucin Macchiato",
url: "https://github.com/catppuccin/catppuccin",
colors: {
backgroundColor: "#24273a",
textColor: "#cad3f5",
linkColor: "#ee99a0",
codeColor: "#f5a97f",
},
},
{
title: "Cattpucin Mocha",
url: "https://github.com/catppuccin/catppuccin",
colors: {
backgroundColor: "#1e1e2e",
textColor: "#cdd6f4",
linkColor: "#74c7ec",
codeColor: "#f38ba8",
},
},
{
title: "Cattpucin Frappe",
url: "https://github.com/catppuccin/catppuccin",
colors: {
backgroundColor: "#303446",
textColor: "#c6d0f5",
linkColor: "#a6d189",
codeColor: "#ef9f76",
},
},
{
title: "Cattpucin Latte",
url: "https://github.com/catppuccin/catppuccin",
colors: {
backgroundColor: "#eff1f5",
textColor: "#4c4f69",
linkColor: "#1e66f5",
codeColor: "#e64553",
},
},
{
title: "Dracula",
url: "https://draculatheme.com/",
colors: {
backgroundColor: "#282a36",
textColor: "#f8f8f2",
linkColor: "#ff79c6",
codeColor: "#6272a4",
},
},
{
title: "Tokyo Night",
url: "https://github.com/enkia/tokyo-night-vscode-theme",
colors: {
backgroundColor: "#1a1b26",
textColor: "#a9b1d6",
linkColor: "#7aa2f7",
codeColor: "#f7768e",
},
},
{
title: "Tokyo Night Storm",
url: "https://github.com/enkia/tokyo-night-vscode-theme",
colors: {
backgroundColor: "#24283b",
textColor: "#a9b1d6",
linkColor: "#7aa2f7",
codeColor: "#f7768e",
},
},
{
title: "Tokyo Night Light",
url: "https://github.com/enkia/tokyo-night-vscode-theme",
colors: {
backgroundColor: "#d5d6db",
textColor: "#343b58",
linkColor: "#34548a",
codeColor: "#8c4351",
},
},
{
title: "Solarized",
url: "https://ethanschoonover.com/solarized/",
colors: {
backgroundColor: "#073642",
textColor: "#eee8d5",
linkColor: "#8a8a8a",
codeColor: "#b58900",
},
},
{
title: "Horizon Dark",
url: "https://horizontheme.netlify.app/",
colors: {
backgroundColor: "#232530",
textColor: "#FAC29A",
linkColor: "#21BFC2",
codeColor: "#E95379",
},
},
{
title: "Horizon Bright",
url: "https://horizontheme.netlify.app/",
colors: {
backgroundColor: "#FDF0ED",
textColor: "#2E303E",
linkColor: "#1EAEAE",
codeColor: "#E84A72",
},
},
],
[]
);
};

View file

@ -0,0 +1,11 @@
import {
Theme,
defaultDarkTheme,
defaultLightTheme,
} from "~/modules/theme/constants/theme";
import { useDetectTheme } from "~/modules/theme/hooks/useDetectTheme";
export const useDefaultTheme = () => {
const theme = useDetectTheme();
return theme === Theme.Dark ? defaultDarkTheme : defaultLightTheme;
};

View file

@ -13,8 +13,11 @@ export const useThemeColors = (settings: ColorSettings) => {
const code = new Color(settings.codeColor); const code = new Color(settings.codeColor);
console.log(code.hex());
// Backgrounds and text
document.body.style.setProperty("--color-background", background.hex()); document.body.style.setProperty("--color-background", background.hex());
document.body.style.setProperty("--color-code", code.hex()); document.body.style.setProperty("--color-code-text", code.hex());
document.body.style.setProperty( document.body.style.setProperty(
"--color-code-background", "--color-code-background",
code.fade(isDark ? 0.9 : 0.7).toString() code.fade(isDark ? 0.9 : 0.7).toString()
@ -24,7 +27,16 @@ export const useThemeColors = (settings: ColorSettings) => {
document.body.style.setProperty("--color-border", border.hex()); document.body.style.setProperty("--color-border", border.hex());
document.body.style.setProperty( document.body.style.setProperty(
"--color-pre-background", "--color-pre-background",
isDark ? background.lighten(0.2).hex() : background.darken(0.2).hex() isDark ? background.lighten(0.2).hex() : background.darken(0.1).hex()
); );
// Headings
[...new Array(5)].forEach((_, i) => {
document.body.style.setProperty(
`--color-h${i + 1}`,
(settings as unknown as Record<string, string>)[`h${i + 1}Color`] ||
settings.textColor
);
});
}, [settings]); }, [settings]);
}; };

View file

@ -0,0 +1,7 @@
import { ColorSettings } from "~/modules/settings/context/SettingsContext";
export interface CustomTheme {
title: string;
url?: string;
colors: ColorSettings;
}

View file

@ -6,4 +6,9 @@
--color-code-text: #ff3344; --color-code-text: #ff3344;
--color-code-background: #{transparentize(#ff3344, 0.9)}; --color-code-background: #{transparentize(#ff3344, 0.9)};
--color-pre-background: #{lighten(#111111, 2%)}; --color-pre-background: #{lighten(#111111, 2%)};
--color-h1: #ffffff;
--color-h2: #ffffff;
--color-h3: #ffffff;
--color-h4: #ffffff;
--color-h5: #ffffff;
} }

View file

@ -28,10 +28,14 @@ a {
ul { ul {
padding-left: 1em; padding-left: 1em;
li p { li {
margin: 5px 0;
p {
margin: 0.5em 0; margin: 0.5em 0;
} }
} }
}
code { code {
background: var(--color-code-background); background: var(--color-code-background);
@ -72,3 +76,19 @@ img {
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
} }
h1 {
color: var(--color-h1);
}
h2 {
color: var(--color-h2);
}
h3 {
color: var(--color-h3);
}
h4 {
color: var(--color-h4);
}
h5 {
color: var(--color-h5);
}