add theme option

This commit is contained in:
hzrd149 2023-10-05 10:58:50 -05:00
parent 184884d5d2
commit bcc842770a
13 changed files with 167 additions and 25 deletions
.changeset
package.json
src
components/embed-event/event-types
providers
services/settings
theme
views/settings
yarn.lock

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add theme option and better dark theme

@ -13,8 +13,10 @@
"analyze": "npx vite-bundle-visualizer -o ./stats.html"
},
"dependencies": {
"@chakra-ui/anatomy": "^2.2.1",
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/react": "^2.8.1",
"@chakra-ui/styled-system": "^2.9.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@getalby/bitcoin-connect-react": "^1.1.0",
@ -22,6 +24,7 @@
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
"bech32": "^2.0.0",
"cheerio": "^1.0.0-rc.12",
"chroma-js": "^2.4.2",
"dayjs": "^1.11.9",
"debug": "^4.3.4",
"emojilib": "2",
@ -59,6 +62,7 @@
"devDependencies": {
"@changesets/cli": "^2.26.2",
"@testing-library/cypress": "^9.0.0",
"@types/chroma-js": "^2.4.1",
"@types/debug": "^4.1.8",
"@types/identicon.js": "^2.3.1",
"@types/leaflet": "^1.9.3",

@ -1,5 +1,5 @@
import { MouseEventHandler, useCallback } from "react";
import { Card, CardProps, Flex, LinkBox, LinkOverlay, Spacer } from "@chakra-ui/react";
import { Card, CardProps, Flex, LinkBox, LinkOverlay, Spacer, cssVar } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { NostrEvent } from "../../../types/nostr-event";
@ -19,7 +19,7 @@ import styled from "@emotion/styled";
const HoverLinkOverlay = styled(LinkOverlay)`
&:hover:before {
background-color: rgba(0, 0, 0, 0.15);
background-color: var(--chakra-colors-card-hover-overlay);
}
`;

@ -14,10 +14,10 @@ import MuteModalProvider from "./mute-modal-provider";
// Top level providers, should be render as close to the root as possible
export const GlobalProviders = ({ children }: { children: React.ReactNode }) => {
const { primaryColor, maxPageWidth } = useAppSettings();
const { theme: themeName, primaryColor, maxPageWidth } = useAppSettings();
const theme = useMemo(
() => createTheme(primaryColor, maxPageWidth !== "none" ? maxPageWidth : undefined),
[primaryColor, maxPageWidth],
() => createTheme(themeName, primaryColor, maxPageWidth !== "none" ? maxPageWidth : undefined),
[themeName, primaryColor, maxPageWidth],
);
return (

@ -27,17 +27,26 @@ export type AppSettingsV1 = Omit<AppSettingsV0, "version"> & {
mutedWords?: string;
maxPageWidth: "none" | "md" | "lg" | "xl";
};
export type AppSettingsV2 = Omit<AppSettingsV1, "version"> & {
version: 2;
theme: string;
};
export function isV0(settings: { version: number }): settings is AppSettingsV0 {
return settings.version === undefined || settings.version === 0;
}
export function isV1(settings: { version: number }): settings is AppSettingsV1 {
return settings.version === 1;
}
export function isV2(settings: { version: number }): settings is AppSettingsV2 {
return settings.version === 2;
}
export type AppSettings = AppSettingsV1;
export type AppSettings = AppSettingsV2;
export const defaultSettings: AppSettings = {
version: 1,
version: 2,
theme: "default",
colorMode: "system",
maxPageWidth: "none",
blurImages: true,
@ -59,8 +68,9 @@ export const defaultSettings: AppSettings = {
};
export function upgradeSettings(settings: { version: number }): AppSettings | null {
if (isV0(settings)) return { ...settings, version: 1, maxPageWidth: "none" };
if (isV1(settings)) return settings;
if (isV0(settings)) return { ...settings, version: 2, maxPageWidth: "none", theme: "default" };
if (isV1(settings)) return { ...settings, version: 2, theme: "default" };
if (isV2(settings)) return settings;
return null;
}

6
src/theme/helpers.ts Normal file

@ -0,0 +1,6 @@
export function pallet(colors: string[]) {
return [50, 100, 200, 300, 400, 500, 600, 700, 800, 900].reduce(
(pallet, key, i) => ({ ...pallet, [key]: colors[i] }),
{},
);
}

@ -1,27 +1,43 @@
import { extendTheme, Theme, DeepPartial } from "@chakra-ui/react";
import { containerTheme } from "./container";
import chroma from "chroma-js";
import midnightTheme from "./midnight";
function pallet(colors: string[]) {
return [50, 100, 200, 300, 400, 500, 600, 700, 800, 900].reduce(
(pallet, key, i) => ({ ...pallet, [key]: colors[i] }),
{},
);
}
function getTheme(name: string) {
if (name === "midnight") return midnightTheme;
return {};
}
const breakpoints = ["sm", "md", "lg", "xl", "2xl"] as const;
export default function createTheme(primaryColor: string = "#8DB600", maxBreakpoint?: (typeof breakpoints)[number]) {
const theme = extendTheme({
export default function createTheme(
themeName: string,
primaryColor: string = "#8DB600",
maxBreakpoint?: (typeof breakpoints)[number],
) {
const theme = extendTheme(getTheme(themeName), {
colors: {
primary: {
50: primaryColor,
100: primaryColor,
200: primaryColor,
300: primaryColor,
400: primaryColor,
500: primaryColor,
600: primaryColor,
700: primaryColor,
800: primaryColor,
900: primaryColor,
},
primary: pallet(chroma.scale([chroma(primaryColor).brighten(1), chroma(primaryColor).darken(1)]).colors(10)),
},
components: {
Container: containerTheme,
},
semanticTokens: {
colors: {
"card-hover-overlay": {
_light: "blackAlpha.100",
_dark: "whiteAlpha.100",
},
},
},
} as DeepPartial<Theme>);
// if maxBreakpoint is set, set all breakpoints above it to a large number so they are never reached

@ -0,0 +1,9 @@
import { defineStyleConfig } from "@chakra-ui/styled-system";
export const buttonTheme = defineStyleConfig({
variants: {
link: {
color: "colors.chakra-body-text",
},
},
});

@ -0,0 +1,29 @@
import { cardAnatomy as parts } from "@chakra-ui/anatomy";
import { createMultiStyleConfigHelpers, cssVar } from "@chakra-ui/styled-system";
const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(parts.keys);
const $bg = cssVar("card-bg");
const $padding = cssVar("card-padding");
const $shadow = cssVar("card-shadow");
const $radius = cssVar("card-radius");
const $border = cssVar("card-border-width", "0");
const $borderColor = cssVar("card-border-color");
export const cardTheme = defineMultiStyleConfig({
baseStyle: {},
variants: {
elevated: definePartsStyle({
container: {
_dark: {
[$bg.variable]: "colors.chakra-subtle-bg",
},
},
}),
filled: definePartsStyle({
container: {
[$bg.variable]: "colors.chakra-subtle-bg",
},
}),
},
});

@ -0,0 +1,16 @@
import { drawerAnatomy as parts } from "@chakra-ui/anatomy";
import { createMultiStyleConfigHelpers, cssVar, defineStyle } from "@chakra-ui/styled-system";
const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(parts.keys);
const $bg = cssVar("drawer-bg");
export const drawerTheme = defineMultiStyleConfig({
baseStyle: definePartsStyle({
dialog: {
_dark: {
[$bg.variable]: "colors.gray.800",
},
},
}),
});

@ -0,0 +1,28 @@
import chroma from "chroma-js";
import { DeepPartial, Theme, extendTheme } from "@chakra-ui/react";
import { cardTheme } from "./components/card";
import { pallet } from "../helpers";
import { buttonTheme } from "./components/button";
import { drawerTheme } from "./components/drawer";
const midnightTheme = extendTheme({
colors: {
gray: pallet(chroma.scale(["#d5d5d5", "#0e0e0e"]).colors(10)),
},
components: {
Card: cardTheme,
Button: buttonTheme,
Drawer: drawerTheme,
},
semanticTokens: {
colors: {
"chakra-body-text": { _light: "gray.800", _dark: "white" },
"chakra-body-bg": { _light: "white", _dark: "gray.900" },
"chakra-subtle-bg": { _light: "gray.100", _dark: "gray.800" },
"chakra-subtle-text": { _light: "gray.600", _dark: "gray.400" },
},
},
} as DeepPartial<Theme>);
export default midnightTheme;

@ -33,6 +33,15 @@ export default function DisplaySettings() {
</h2>
<AccordionPanel>
<Flex direction="column" gap="4">
<FormControl>
<FormLabel htmlFor="theme" mb="0">
Theme
</FormLabel>
<Select id="theme" {...register("theme")}>
<option value="default">Default</option>
<option value="midnight">Midnight</option>
</Select>
</FormControl>
<FormControl>
<FormLabel htmlFor="colorMode" mb="0">
Color Mode

@ -998,7 +998,7 @@
"@chakra-ui/shared-utils" "2.0.5"
"@chakra-ui/spinner" "2.1.0"
"@chakra-ui/anatomy@2.2.1":
"@chakra-ui/anatomy@2.2.1", "@chakra-ui/anatomy@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/anatomy/-/anatomy-2.2.1.tgz#f7ef088dcb8be4f1d075f37101830199fb93f763"
integrity sha512-bbmyWTGwQo+aHYDMtLIj7k7hcWvwE7GFVDViLFArrrPhfUTDdQTNqhiDp1N7eh2HLyjNhc2MKXV8s2KTQqkmTg==
@ -1640,7 +1640,7 @@
"@chakra-ui/react-context" "2.1.0"
"@chakra-ui/shared-utils" "2.0.5"
"@chakra-ui/styled-system@2.9.1":
"@chakra-ui/styled-system@2.9.1", "@chakra-ui/styled-system@^2.9.1":
version "2.9.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/styled-system/-/styled-system-2.9.1.tgz#888a4901b2afa174461259a8875379adb0363934"
integrity sha512-jhYKBLxwOPi9/bQt9kqV3ELa/4CjmNNruTyXlPp5M0v0+pDMUngPp48mVLoskm9RKZGE0h1qpvj/jZ3K7c7t8w==
@ -2527,6 +2527,11 @@
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.2.tgz#6f1225829d89794fd9f891989c9ce667422d7f64"
integrity sha512-PHKZuMN+K5qgKIWhBodXzQslTo5P+K/6LqeKXS6O/4liIDdZqaX5RXrCK++LAw+y/nptN48YmUMFiQHRSWYwtQ==
"@types/chroma-js@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.4.1.tgz#f76bbd7c760c1db18734b9d8f473ced7275a374e"
integrity sha512-YIm3RLfWdDU0/3rsNnu/Hh4uKCLQ9p/w1NvnFfBOBL/BGqOvuLNuhXwkJMUKMscmFJdan25lYqv2+qh1l7tZLg==
"@types/chrome@^0.0.74":
version "0.0.74"
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.74.tgz#f69827c48fcf7fecc90c96089807661749a5a5e3"
@ -3217,6 +3222,11 @@ cheerio@^1.0.0-rc.12:
parse5 "^7.0.0"
parse5-htmlparser2-tree-adapter "^7.0.0"
chroma-js@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0"
integrity sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==
ci-info@^3.1.0, ci-info@^3.2.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"