diff --git a/external/lang b/external/lang index 24766f8f..2a9ef2c8 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 24766f8f5c2147ba866af922e3f5f42ad3ea44e4 +Subproject commit 2a9ef2c8025dd71a20fddca7c94cb7af30978c4b diff --git a/package.json b/package.json index 595b73b7..2b793266 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,23 @@ "preact": "^10.5.13" }, "devDependencies": { - "@fontsource/fira-mono": "^4.4.5", + "@fontsource/atkinson-hyperlegible": "^4.4.5", + "@fontsource/bree-serif": "^4.4.5", + "@fontsource/comic-neue": "^4.4.5", + "@fontsource/fira-code": "^4.4.5", + "@fontsource/inter": "^4.4.5", + "@fontsource/lato": "^4.4.5", + "@fontsource/montserrat": "^4.4.5", + "@fontsource/noto-sans": "^4.4.5", "@fontsource/open-sans": "^4.4.5", + "@fontsource/poppins": "^4.4.5", + "@fontsource/raleway": "^4.4.5", + "@fontsource/roboto": "^4.4.5", + "@fontsource/roboto-mono": "^4.4.5", + "@fontsource/source-code-pro": "^4.4.5", + "@fontsource/space-mono": "^4.4.5", + "@fontsource/ubuntu": "^4.4.5", + "@fontsource/ubuntu-mono": "^4.4.5", "@hcaptcha/react-hcaptcha": "^0.3.6", "@preact/preset-vite": "^2.0.0", "@rollup/plugin-replace": "^2.4.2", @@ -86,7 +101,7 @@ "typescript": "^4.3.2", "ulid": "^2.3.0", "use-resize-observer": "^7.0.0", - "vite": "^2.3.7", + "vite": "npm:@insertish/vite@2.2.4-dynamic-import-css-f428476", "vite-plugin-pwa": "^0.8.1", "workbox-precaching": "^6.1.5" } diff --git a/src/components/common/CollapsibleSection.tsx b/src/components/common/CollapsibleSection.tsx new file mode 100644 index 00000000..3b6409fb --- /dev/null +++ b/src/components/common/CollapsibleSection.tsx @@ -0,0 +1,50 @@ +import Details from "../ui/Details"; +import { State, store } from "../../redux"; +import { Action } from "../../redux/reducers"; +import { Children } from "../../types/Preact"; +import { ChevronDown } from "@styled-icons/boxicons-regular"; + +interface Props { + id: string; + defaultValue: boolean; + + sticky?: boolean; + large?: boolean; + + summary: Children; + children: Children; +} + +export default function CollapsibleSection({ id, defaultValue, summary, children, ...detailsProps }: Props) { + const state: State = store.getState(); + + function setState(state: boolean) { + if (state === defaultValue) { + store.dispatch({ + type: 'SECTION_TOGGLE_UNSET', + id + } as Action); + } else { + store.dispatch({ + type: 'SECTION_TOGGLE_SET', + id, + state + } as Action); + } + } + + return ( +
setState(e.currentTarget.open)} + {...detailsProps}> + +
+ + { summary } +
+
+ { children } +
+ ) +} diff --git a/src/components/common/Tooltip.tsx b/src/components/common/Tooltip.tsx index c8497e40..7a9957f4 100644 --- a/src/components/common/Tooltip.tsx +++ b/src/components/common/Tooltip.tsx @@ -24,14 +24,16 @@ const PermissionTooltipBase = styled.div` display: flex; align-items: center; flex-direction: column; + span { font-weight: 700; text-transform: uppercase; color: var(--secondary-foreground); font-size: 11px; } + code { - font-family: 'Fira Mono'; + font-family: var(--monoscape-font); } `; diff --git a/src/components/common/messaging/attachments/Attachment.module.scss b/src/components/common/messaging/attachments/Attachment.module.scss index 4f23a898..5aa89744 100644 --- a/src/components/common/messaging/attachments/Attachment.module.scss +++ b/src/components/common/messaging/attachments/Attachment.module.scss @@ -2,6 +2,13 @@ border-radius: 6px; margin: .125rem 0 .125rem; + height: auto; + + max-height: 640px; + max-width: min(480px, 100%); + + object-fit: contain; + &[data-spoiler="true"] { filter: blur(30px); pointer-events: none; @@ -71,7 +78,7 @@ } pre code { - font-family: "Fira Mono", sans-serif; + font-family: var(--monoscape-font), sans-serif; } &[data-loading="true"] { diff --git a/src/components/common/messaging/attachments/Attachment.tsx b/src/components/common/messaging/attachments/Attachment.tsx index eb144ebe..15c42536 100644 --- a/src/components/common/messaging/attachments/Attachment.tsx +++ b/src/components/common/messaging/attachments/Attachment.tsx @@ -15,60 +15,33 @@ interface Props { } const MAX_ATTACHMENT_WIDTH = 480; -const MAX_ATTACHMENT_HEIGHT = 640; export default function Attachment({ attachment, hasContent }: Props) { const client = useContext(AppContext); const { openScreen } = useIntermediate(); const { filename, metadata } = attachment; const [ spoiler, setSpoiler ] = useState(filename.startsWith("SPOILER_")); - const maxWidth = Math.min(useContext(MessageAreaWidthContext), MAX_ATTACHMENT_WIDTH); const url = client.generateFileURL(attachment, { width: MAX_ATTACHMENT_WIDTH * 1.5 }, true); - let width = 0, - height = 0; - - if (metadata.type === 'Image' || metadata.type === 'Video') { - let limitingWidth = Math.min( - maxWidth, - metadata.width - ); - let limitingHeight = Math.min( - MAX_ATTACHMENT_HEIGHT, - metadata.height - ); - - // Calculate smallest possible WxH. - width = Math.min( - limitingWidth, - limitingHeight * (metadata.width / metadata.height) - ); - - height = Math.min( - limitingHeight, - limitingWidth * (metadata.height / metadata.width) - ); - } switch (metadata.type) { case "Image": { return (
spoiler && setSpoiler(false)} > {spoiler && (
-
- -
+
)} {filename}
); @@ -102,13 +74,10 @@ export default function Attachment({ attachment, hasContent }: Props) { onClick={() => spoiler && setSpoiler(false)}> {spoiler && (
-
- -
+
)}
ev.button === 1 && window.open(url, "_blank") diff --git a/src/components/common/messaging/bars/FilePreview.tsx b/src/components/common/messaging/bars/FilePreview.tsx index 9f0a98c4..80f9fae5 100644 --- a/src/components/common/messaging/bars/FilePreview.tsx +++ b/src/components/common/messaging/bars/FilePreview.tsx @@ -101,6 +101,7 @@ const PreviewBox = styled.div` .icon { height: 100px; + width: 100%; margin-bottom: 4px; object-fit: contain; } diff --git a/src/components/markdown/Markdown.module.scss b/src/components/markdown/Markdown.module.scss index de439a34..202ce12e 100644 --- a/src/components/markdown/Markdown.module.scss +++ b/src/components/markdown/Markdown.module.scss @@ -1,5 +1,3 @@ -@import "@fontsource/fira-mono/400.css"; - .markdown { :global(.emoji) { height: 1.25em; @@ -89,7 +87,7 @@ font-size: 90%; border-radius: 4px; background: var(--block); - font-family: "Fira Mono", monospace; + font-family: var(--monoscape-font), monospace; } input[type="checkbox"] { @@ -136,7 +134,7 @@ } :global(.code) { - font-family: "Fira Mono", monospace; + font-family: var(--monoscape-font), monospace; :global(.lang) { // height: 8px; diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index cd98bc7e..16e586bb 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -1,4 +1,4 @@ -import { Localizer, Text } from "preact-i18n"; +import { Text } from "preact-i18n"; import { useContext, useEffect } from "preact/hooks"; import { Home, UserDetail, Wrench, Notepad } from "@styled-icons/boxicons-solid"; @@ -105,13 +105,9 @@ function HomeSidebar(props: Props) { )} - - } - /** @ts-ignore : ignored due to conflicting naming between the Category property name and the existing JSX attribute */ - action={() => openScreen({ id: "special_input", type: "create_group" })} - /> - + } + action={() => openScreen({ id: "special_input", type: "create_group" })} /> {channelsArr.length === 0 && } {channelsArr.map(x => { let user; diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index 49e57959..3dd7aff9 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -14,6 +14,7 @@ import ServerHeader from "../../common/ServerHeader"; import { useEffect } from "preact/hooks"; import Category from "../../ui/Category"; import ConditionalLink from "../../../lib/ConditionalLink"; +import CollapsibleSection from "../../common/CollapsibleSection"; interface Props { unreads: Unreads; @@ -69,6 +70,7 @@ function ServerSidebar(props: Props & WithDispatcher) { let uncategorised = new Set(server.channels); let elements = []; + function addChannel(id: string) { const entry = channels.find(x => x._id === id); if (!entry) return; @@ -76,9 +78,8 @@ function ServerSidebar(props: Props & WithDispatcher) { const active = channel?._id === entry._id; return ( - + ); - + let channels = []; for (let id of category.channels) { uncategorised.delete(id); - elements.push(addChannel(id)); + channels.push(addChannel(id)); } + + elements.push( + }> + { channels } + + ); } } - for (let id of uncategorised) { + for (let id of Array.from(uncategorised).reverse()) { elements.unshift(addChannel(id)); } diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index c5bcaf67..03287b15 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -2,6 +2,7 @@ import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; import { User } from "revolt.js"; +import Details from "../../../components/ui/Details"; import Category from "../../ui/Category"; import { useParams } from "react-router"; import { UserButton } from "../items/ButtonItem"; diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 8582a193..870e7ed2 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -10,7 +10,7 @@ export default styled.button` padding: 8px; font-size: 16px; text-align: center; - font-family: 'Open Sans', sans-serif; + font-family: inherit; transition: 0.2s ease opacity; transition: 0.2s ease background-color; diff --git a/src/components/ui/Category.tsx b/src/components/ui/Category.tsx index 79f1d0bc..38980b19 100644 --- a/src/components/ui/Category.tsx +++ b/src/components/ui/Category.tsx @@ -31,7 +31,7 @@ const CategoryBase = styled.div>` ` } `; -type Props = Omit, 'children' | 'as'> & { +type Props = Omit, 'children' | 'as' | 'action'> & { text: Children; // TODO: rename from action to prevent type conflicts with the dom action?: () => void; diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx index bc87c5cc..7fad94d8 100644 --- a/src/components/ui/Checkbox.tsx +++ b/src/components/ui/Checkbox.tsx @@ -3,9 +3,9 @@ import { Children } from "../../types/Preact"; import styled, { css } from "styled-components"; const CheckboxBase = styled.label` + margin-top: 20px; gap: 4px; z-index: 1; - padding: 4px; display: flex; border-radius: 4px; align-items: center; @@ -16,25 +16,19 @@ const CheckboxBase = styled.label` transition: 0.2s ease all; - p { - margin: 0; - } - input { display: none; } &:hover { - background: var(--secondary-background); - .check { background: var(--background); } } &[disabled] { - opacity: 0.5; - cursor: unset; + opacity: .5; + cursor: not-allowed; &:hover { background: unset; @@ -43,15 +37,15 @@ const CheckboxBase = styled.label` `; const CheckboxContent = styled.span` - flex-grow: 1; display: flex; + flex-grow: 1; font-size: 1rem; font-weight: 600; flex-direction: column; `; const CheckboxDescription = styled.span` - font-size: 0.8em; + font-size: .75rem; font-weight: 400; color: var(--secondary-foreground); `; diff --git a/src/components/ui/ColourSwatches.tsx b/src/components/ui/ColourSwatches.tsx index cbc13bd2..46a473fc 100644 --- a/src/components/ui/ColourSwatches.tsx +++ b/src/components/ui/ColourSwatches.tsx @@ -1,5 +1,6 @@ import { useRef } from "preact/hooks"; -import { Check, Pencil } from "@styled-icons/boxicons-regular"; +import { Check } from "@styled-icons/boxicons-regular"; +import { Palette } from "@styled-icons/boxicons-solid"; import styled, { css } from "styled-components"; interface Props { @@ -98,7 +99,7 @@ export default function ColourSwatches({ value, onChange }: Props) { type="large" onClick={() => ref.current.click()} > - + ` + summary { + ${ props => props.sticky && css` + top: -1px; + z-index: 10; + position: sticky; + ` } + + ${ props => props.large && css` + /*padding: 5px 0;*/ + background: var(--primary-background); + color: var(--secondary-foreground); + + .padding { /*TOFIX: make this applicable only for the friends list menu, DO NOT REMOVE.*/ + display: flex; + align-items: center; + padding: 5px 0; + margin: 0.8em 0px 0.4em; + cursor: pointer; + } + ` } + + outline: none; + cursor: pointer; + list-style: none; + align-items: center; + transition: .2s opacity; + + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + + &::marker, &::-webkit-details-marker { + display: none; + } + + .title { + flex-grow: 1; + margin-top: 1px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .padding { + display: flex; + align-items: center; + + > svg { + flex-shrink: 0; + margin-inline-end: 4px; + transition: .2s ease transform; + } + } + } + + &:not([open]) { + summary { + opacity: .7; + } + + summary svg { + transform: rotateZ(-90deg); + } + } +`; diff --git a/src/components/ui/Header.tsx b/src/components/ui/Header.tsx index 37652397..d4e0e3f9 100644 --- a/src/components/ui/Header.tsx +++ b/src/components/ui/Header.tsx @@ -46,6 +46,6 @@ export default styled.div` ` } ${ props => props.borders && css` - border-end-start-radius: 8px; + border-start-start-radius: 8px; ` } `; diff --git a/src/components/ui/InputBox.tsx b/src/components/ui/InputBox.tsx index ac374646..75e3510b 100644 --- a/src/components/ui/InputBox.tsx +++ b/src/components/ui/InputBox.tsx @@ -9,6 +9,7 @@ export default styled.input` padding: 8px 16px; border-radius: 6px; + font-family: inherit; color: var(--foreground); background: var(--primary-background); transition: 0.2s ease background-color; diff --git a/src/components/ui/Overline.tsx b/src/components/ui/Overline.tsx index 5a9f9d04..17159077 100644 --- a/src/components/ui/Overline.tsx +++ b/src/components/ui/Overline.tsx @@ -5,6 +5,7 @@ import { Text } from 'preact-i18n'; type Props = Omit, 'children' | 'as'> & { error?: string; block?: boolean; + spaced?: boolean; children?: Children; type?: "default" | "subtle" | "error"; } @@ -12,7 +13,10 @@ type Props = Omit, 'children' | 'as'> & { const OverlineBase = styled.div>` display: inline; margin: 0.4em 0; - margin-top: 0.8em; + + ${ props => props.spaced && css` + margin-top: 0.8em; + ` } font-size: 14px; font-weight: 600; diff --git a/src/components/ui/TextArea.tsx b/src/components/ui/TextArea.tsx index d61e43c7..88f5959d 100644 --- a/src/components/ui/TextArea.tsx +++ b/src/components/ui/TextArea.tsx @@ -39,8 +39,10 @@ export default styled.textarea` } ${ props => props.code ? css` - font-family: 'Fira Mono', 'Courier New', Courier, monospace; + font-family: var(--monoscape-font-font), monospace; ` : css` - font-family: 'Open Sans', sans-serif; + font-family: inherit; ` } + + font-variant-ligatures: var(--ligatures); `; diff --git a/src/components/ui/Tip.tsx b/src/components/ui/Tip.tsx index 489572b9..f664a58c 100644 --- a/src/components/ui/Tip.tsx +++ b/src/components/ui/Tip.tsx @@ -7,6 +7,13 @@ interface Props { error?: boolean } +export const Separator = styled.div` + height: 1px; + width: calc(100% - 10px); + background: var(--secondary-header); + margin: 18px auto; +`; + export const TipBase = styled.div` display: flex; padding: 12px; @@ -46,9 +53,13 @@ export const TipBase = styled.div` export default function Tip(props: Props & { children: Children }) { const { children, ...tipProps } = props; return ( - - - {props.children} - + <> + + + + {props.children} + + + ); } diff --git a/src/context/Locale.tsx b/src/context/Locale.tsx index af894b40..6fdff9e2 100644 --- a/src/context/Locale.tsx +++ b/src/context/Locale.tsx @@ -43,7 +43,6 @@ export enum Language { PIRATE = "pr", BOTTOM = "bottom", PIGLATIN = "piglatin", - HARDCORE = "hardcore", } export interface LanguageEntry { @@ -107,13 +106,6 @@ export const Languages: { [key in Language]: LanguageEntry } = { dayjs: "en-gb", alt: true }, - hardcore: { - display: "Hardcore Mode", - emoji: "🔥", - i18n: "hardcore", - dayjs: "en-gb", - alt: true - }, }; interface Props { @@ -126,8 +118,10 @@ function Locale({ children, locale }: Props) { const [defns, setDefinition] = useState>(definition); const lang = Languages[locale]; - // TOOD: clean this up and use the built in Intl API - function transformLanguage(obj: { [key: string]: any }) { + // TODO: clean this up and use the built in Intl API + function transformLanguage(source: { [key: string]: any }) { + const obj = defaultsDeep(source, definition); + const dayjs = obj.dayjs; const defaults = dayjs.defaults; @@ -151,23 +145,16 @@ function Locale({ children, locale }: Props) { useEffect(() => { if (locale === "en") { - transformLanguage(definition); - setDefinition(definition); + const defn = transformLanguage(definition); + setDefinition(defn); dayjs.locale("en"); - dayjs.updateLocale('en', { calendar: definition.dayjs }); - return; - } - - if (lang.i18n === "hardcore") { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - setDefinition({}); + dayjs.updateLocale('en', { calendar: defn.dayjs }); return; } import(`../../external/lang/${lang.i18n}.json`).then( async (lang_file) => { - const defn = lang_file.default; - transformLanguage(defn); + const defn = transformLanguage(lang_file.default); const target = lang.dayjs ?? lang.i18n; const dayjs_locale = await import(`../../node_modules/dayjs/esm/locale/${target}.js`); @@ -176,7 +163,7 @@ function Locale({ children, locale }: Props) { } dayjs.locale(dayjs_locale.default); - setDefinition(defaultsDeep(defn, definition)); + setDefinition(defn); } ); }, [locale, lang]); diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx index b6131c6f..02f5ca66 100644 --- a/src/context/Theme.tsx +++ b/src/context/Theme.tsx @@ -33,18 +33,161 @@ export type Variables = | "status-invisible" | "sidebar-active"; +export type Fonts = 'Open Sans' | 'Inter' | 'Atkinson Hyperlegible' | 'Roboto' | 'Noto Sans' | 'Lato' | 'Bree Serif' | 'Montserrat' | 'Poppins' | 'Raleway' | 'Ubuntu' | 'Comic Neue'; +export type MonoscapeFonts = 'Fira Code' | 'Roboto Mono' | 'Source Code Pro' | 'Space Mono' | 'Ubuntu Mono'; + export type Theme = { [variable in Variables]: string; } & { light?: boolean; + font?: Fonts; css?: string; + monoscapeFont?: MonoscapeFonts; }; export interface ThemeOptions { preset?: string; + ligatures?: boolean; custom?: Partial; } +// import aaa from "@fontsource/open-sans/300.css?raw"; +// console.info(aaa); + +export const FONTS: Record void }> = { + "Open Sans": { + name: "Open Sans", + load: async () => { + await import("@fontsource/open-sans/300.css"); + await import("@fontsource/open-sans/400.css"); + await import("@fontsource/open-sans/600.css"); + await import("@fontsource/open-sans/700.css"); + await import("@fontsource/open-sans/400-italic.css"); + } + }, + Inter: { + name: "Inter", + load: async () => { + await import("@fontsource/inter/300.css"); + await import("@fontsource/inter/400.css"); + await import("@fontsource/inter/600.css"); + await import("@fontsource/inter/700.css"); + } + }, + "Atkinson Hyperlegible": { + name: "Atkinson Hyperlegible", + load: async () => { + await import("@fontsource/atkinson-hyperlegible/400.css"); + await import("@fontsource/atkinson-hyperlegible/700.css"); + await import("@fontsource/atkinson-hyperlegible/400-italic.css"); + } + }, + "Roboto": { + name: "Roboto", + load: async () => { + await import("@fontsource/roboto/400.css"); + await import("@fontsource/roboto/700.css"); + await import("@fontsource/roboto/400-italic.css"); + } + }, + "Noto Sans": { + name: "Noto Sans", + load: async () => { + await import("@fontsource/noto-sans/400.css"); + await import("@fontsource/noto-sans/700.css"); + await import("@fontsource/noto-sans/400-italic.css"); + } + }, + "Bree Serif": { + name: "Bree Serif", + load: () => import("@fontsource/bree-serif/400.css") + }, + "Lato": { + name: "Lato", + load: async () => { + await import("@fontsource/lato/300.css"); + await import("@fontsource/lato/400.css"); + await import("@fontsource/lato/700.css"); + await import("@fontsource/lato/400-italic.css"); + } + }, + "Montserrat": { + name: "Montserrat", + load: async () => { + await import("@fontsource/montserrat/300.css"); + await import("@fontsource/montserrat/400.css"); + await import("@fontsource/montserrat/600.css"); + await import("@fontsource/montserrat/700.css"); + await import("@fontsource/montserrat/400-italic.css"); + } + }, + "Poppins": { + name: "Poppins", + load: async () => { + await import("@fontsource/poppins/300.css"); + await import("@fontsource/poppins/400.css"); + await import("@fontsource/poppins/600.css"); + await import("@fontsource/poppins/700.css"); + await import("@fontsource/poppins/400-italic.css"); + } + }, + "Raleway": { + name: "Raleway", + load: async () => { + await import("@fontsource/raleway/300.css"); + await import("@fontsource/raleway/400.css"); + await import("@fontsource/raleway/600.css"); + await import("@fontsource/raleway/700.css"); + await import("@fontsource/raleway/400-italic.css"); + } + }, + "Ubuntu": { + name: "Ubuntu", + load: async () => { + await import("@fontsource/ubuntu/300.css"); + await import("@fontsource/ubuntu/400.css"); + await import("@fontsource/ubuntu/500.css"); + await import("@fontsource/ubuntu/700.css"); + await import("@fontsource/ubuntu/400-italic.css"); + } + }, + "Comic Neue": { + name: "Comic Neue", + load: async () => { + await import("@fontsource/comic-neue/300.css"); + await import("@fontsource/comic-neue/400.css"); + await import("@fontsource/comic-neue/700.css"); + await import("@fontsource/comic-neue/400-italic.css"); + } + } +}; + +export const MONOSCAPE_FONTS: Record void }> = { + "Fira Code": { + name: "Fira Code", + load: () => import("@fontsource/fira-code/400.css") + }, + "Roboto Mono": { + name: "Roboto Mono", + load: () => import("@fontsource/roboto-mono/400.css") + }, + "Source Code Pro": { + name: "Source Code Pro", + load: () => import("@fontsource/source-code-pro/400.css") + }, + "Space Mono": { + name: "Space Mono", + load: () => import("@fontsource/space-mono/400.css") + }, + "Ubuntu Mono": { + name: "Ubuntu Mono", + load: () => import("@fontsource/ubuntu-mono/400.css") + } +}; + +export const FONT_KEYS = Object.keys(FONTS).sort(); +export const MONOSCAPE_FONT_KEYS = Object.keys(MONOSCAPE_FONTS).sort(); + // Generated from https://gitlab.insrt.uk/revolt/community/themes export const PRESETS: Record = { light: { @@ -124,15 +267,32 @@ interface Props { options?: ThemeOptions; } -function Theme(props: Props) { +function Theme({ children, options }: Props) { const theme: Theme = { ...PRESETS["dark"], ...PRESETS[props.options?.preset ?? ''], ...props.options?.custom }; + const root = document.documentElement.style; useEffect(() => { - const resize = () => document.documentElement.style.setProperty('--app-height', `${window.innerHeight}px`); + const font = theme.font ?? 'Inter'; + root.setProperty('--font', `"${font}"`); + FONTS[font].load(); + }, [ theme.font ]); + + useEffect(() => { + const font = theme.monoscapeFont ?? 'Fira Code'; + root.setProperty('--monoscape-font', `"${font}"`); + MONOSCAPE_FONTS[font].load(); + }, [ theme.monoscapeFont ]); + + useEffect(() => { + root.setProperty('--ligatures', options?.ligatures ? 'normal' : 'none'); + }, [ options?.ligatures ]); + + useEffect(() => { + const resize = () => root.setProperty('--app-height', `${window.innerHeight}px`); resize(); window.addEventListener('resize', resize); @@ -155,7 +315,7 @@ function Theme(props: Props) { {theme.css && (