From 6045200ac460ca4ff0f48a66f8e5f20888b5aa39 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Wed, 23 Mar 2022 18:21:46 -0700 Subject: [PATCH] client: add next-themes support for CSS variables --- client/components/app/index.tsx | 25 +++++++ client/components/button/button.module.css | 59 +++++++--------- client/components/header/controls.tsx | 16 +++-- client/components/header/header.tsx | 68 +++---------------- client/components/preview/index.tsx | 4 -- client/components/preview/preview.module.css | 4 +- .../preview/react-markdown-preview.tsx | 62 ----------------- client/lib/hooks/use-debounce.ts | 22 +++--- client/lib/hooks/use-theme.ts | 27 -------- client/lib/render-markdown.tsx | 31 ++++----- client/lib/types.d.ts | 5 -- client/package.json | 1 + client/pages/_app.tsx | 21 +++--- client/pages/api/render-markdown.ts | 1 - client/styles/globals.css | 48 +++++++++---- client/yarn.lock | 5 ++ server/src/routes/files.ts | 1 - 17 files changed, 146 insertions(+), 254 deletions(-) create mode 100644 client/components/app/index.tsx delete mode 100644 client/components/preview/react-markdown-preview.tsx delete mode 100644 client/lib/hooks/use-theme.ts diff --git a/client/components/app/index.tsx b/client/components/app/index.tsx new file mode 100644 index 00000000..77b4f1b1 --- /dev/null +++ b/client/components/app/index.tsx @@ -0,0 +1,25 @@ +import { GeistProvider, CssBaseline } from "@geist-ui/core" +import type { NextComponentType, NextPageContext } from "next" +import { SkeletonTheme } from "react-loading-skeleton" +import { useTheme } from 'next-themes' +const App = ({ + Component, + pageProps, +}: { + Component: NextComponentType + pageProps: any +}) => { + const skeletonBaseColor = 'var(--light-gray)' + const skeletonHighlightColor = 'var(--lighter-gray)' + + const { theme } = useTheme(); + + return ( + + + + + ) +} + +export default App \ No newline at end of file diff --git a/client/components/button/button.module.css b/client/components/button/button.module.css index 381f3962..35690899 100644 --- a/client/components/button/button.module.css +++ b/client/components/button/button.module.css @@ -1,46 +1,37 @@ .button { - user-select: none; - cursor: pointer; - border-radius: var(--radius); - color: var(--input-fg); - font-weight: 400; - font-size: 1.1rem; - background: var(--input-bg); - border: var(--input-border); - height: 2rem; - display: flex; - align-items: center; - padding: var(--gap-quarter) var(--gap-half); - transition: background-color var(--transition), color var(--transition); - width: 100%; - height: var(--input-height); + user-select: none; + cursor: pointer; + border-radius: var(--radius); + color: var(--input-fg); + font-weight: 400; + font-size: 1.1rem; + background: var(--input-bg); + border: var(--input-border); + height: 2rem; + display: flex; + align-items: center; + padding: var(--gap-quarter) var(--gap-half); + transition: background-color var(--transition), color var(--transition); + width: 100%; + height: var(--input-height); } -/* ---input-height: 2.5rem; ---input-border: 1px solid var(--light-gray); ---input-border-focus: 1px solid var(--gray); ---input-border-error: 1px solid var(--red); ---input-bg: var(--bg); ---input-fg: var(--fg); ---input-placeholder-fg: var(--light-gray); */ - .button:hover, .button:focus { - outline: none; - background: var(--input-bg-hover); - border: var(--input-border-focus); + outline: none; + background: var(--input-bg-hover); + border: var(--input-border-focus); } .button[disabled] { - cursor: not-allowed; - background: var(--lighter-gray); - color: var(--gray); + cursor: not-allowed; + background: var(--lighter-gray); + color: var(--gray); } .secondary { - background: var(--bg); - color: var(--fg); + background: var(--bg); + color: var(--fg); } /* @@ -57,6 +48,6 @@ */ .primary { - background: var(--fg); - color: var(--bg); + background: var(--fg); + color: var(--bg); } diff --git a/client/components/header/controls.tsx b/client/components/header/controls.tsx index 09b4afac..19416ce2 100644 --- a/client/components/header/controls.tsx +++ b/client/components/header/controls.tsx @@ -1,14 +1,22 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import MoonIcon from '@geist-ui/icons/moon' import SunIcon from '@geist-ui/icons/sun' // import { useAllThemes, useTheme } from '@geist-ui/core' import styles from './header.module.css' -import { ThemeProps } from '@lib/types' import { Select } from '@geist-ui/core' +import { useTheme } from 'next-themes' -const Controls = ({ changeTheme, theme }: ThemeProps) => { +const Controls = () => { + const [mounted, setMounted] = useState(false) + const { theme, setTheme } = useTheme() + useEffect(() => setMounted(true), []) + if (!mounted) return null const switchThemes = () => { - changeTheme() + if (theme === 'dark') { + setTheme('light') + } else { + setTheme('dark') + } } return ( diff --git a/client/components/header/header.tsx b/client/components/header/header.tsx index 0e89b226..9a9aae95 100644 --- a/client/components/header/header.tsx +++ b/client/components/header/header.tsx @@ -16,7 +16,7 @@ import NewIcon from '@geist-ui/icons/plusCircle'; import YourIcon from '@geist-ui/icons/list' import MoonIcon from '@geist-ui/icons/moon'; import SunIcon from '@geist-ui/icons/sun'; -import useTheme from "@lib/hooks/use-theme"; +import { useTheme } from "next-themes" import { Button } from "@geist-ui/core"; type Tab = { @@ -37,7 +37,7 @@ const Header = () => { const isMobile = useMediaQuery('xs', { match: 'down' }) const { signedIn: isSignedIn, signout } = useSignedIn() const [pages, setPages] = useState([]) - const { changeTheme, theme } = useTheme() + const { setTheme, theme } = useTheme() useEffect(() => { setBodyHidden(expanded) }, [expanded, setBodyHidden]) @@ -60,9 +60,8 @@ const Header = () => { { name: isMobile ? "Change theme" : "", onClick: function () { - if (typeof window !== 'undefined') { - changeTheme(); - } + if (typeof window !== 'undefined') + setTheme(theme === 'light' ? 'dark' : 'light'); }, icon: theme === 'light' ? : , condition: true, @@ -73,9 +72,9 @@ const Header = () => { if (isSignedIn) setPages([ { - name: 'home', - icon: , - value: 'home', + name: 'new', + icon: , + value: 'new', href: '/' }, { @@ -116,58 +115,7 @@ const Header = () => { ]) // TODO: investigate deps causing infinite loop // eslint-disable-next-line react-hooks/exhaustive-deps - }, [changeTheme, isMobile, isSignedIn, theme]) - - // useEffect(() => { - // const pageList: Tab[] = [ - // { - // name: "Home", - // href: "/", - // icon: , - // condition: !isSignedIn, - // value: "home" - // }, - // { - // name: "New", - // href: "/new", - // icon: , - // condition: isSignedIn, - // value: "new" - // }, - // { - // name: "Yours", - // href: "/mine", - // icon: , - // condition: isSignedIn, - // value: "mine" - // }, - // { - // name: "Sign out", - // href: "/signout", - // icon: , - // condition: isSignedIn, - // value: "signout" - // }, - // { - // name: "Sign in", - // href: "/signin", - // icon: , - // condition: !isSignedIn, - // value: "signin" - // }, - // { - // name: "Sign up", - // href: "/signup", - // icon: , - // condition: !isSignedIn, - // value: "signup" - // }, - - // ] - - // setPages(pageList.filter(page => page.condition)) - // }, [changeTheme, isMobile, isSignedIn, theme]) - + }, [isMobile, isSignedIn, theme]) const onTabChange = useCallback((tab: string) => { if (typeof window === 'undefined') return diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index f301264d..2b817b6a 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -1,4 +1,3 @@ -import useTheme from "@lib/hooks/use-theme" import { memo, useEffect, useState } from "react" import styles from './preview.module.css' @@ -13,17 +12,14 @@ type Props = { const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { const [preview, setPreview] = useState(content || "") const [isLoading, setIsLoading] = useState(true) - const { theme } = useTheme() useEffect(() => { async function fetchPost() { if (fileId) { const resp = await fetch(`/server-api/files/html/${fileId}`, { method: "GET", }) - console.log(resp) if (resp.ok) { const res = await resp.text() - console.log(res) setPreview(res) setIsLoading(false) } diff --git a/client/components/preview/preview.module.css b/client/components/preview/preview.module.css index a930c324..57d4d7d0 100644 --- a/client/components/preview/preview.module.css +++ b/client/components/preview/preview.module.css @@ -75,11 +75,11 @@ } .markdownPreview h5 { - font-size: 0.875rem; + font-size: 1rem; } .markdownPreview h6 { - font-size: 0.75rem; + font-size: 0.875rem; } .markdownPreview ul { diff --git a/client/components/preview/react-markdown-preview.tsx b/client/components/preview/react-markdown-preview.tsx deleted file mode 100644 index cb2cde40..00000000 --- a/client/components/preview/react-markdown-preview.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import remarkGfm from "remark-gfm" -import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/prism-async-light'; -import rehypeSlug from 'rehype-slug' -import rehypeAutolinkHeadings from 'rehype-autolink-headings' -import rehypeRaw from 'rehype-raw' - -// @ts-ignore because of no types in remark-a11y-emoji -// import a11yEmoji from '@fec/remark-a11y-emoji'; - -import styles from './preview.module.css' -import dark from 'react-syntax-highlighter/dist/cjs/styles/prism/vsc-dark-plus' -import light from 'react-syntax-highlighter/dist/cjs/styles/prism/vs' -import useSharedState from "@lib/hooks/use-shared-state"; -import ReactMarkdown from "react-markdown"; - -type Props = { - content: string | undefined - height: number | string -} - -const ReactMarkdownPreview = ({ content, height }: Props) => { - const [themeType] = useSharedState('theme') - - return (
- {String(children).replace(/\n$/, '')} - ) : ( - - {children} - - ) - } - }}> - {content || ""} -
) -} - - -export default ReactMarkdownPreview diff --git a/client/lib/hooks/use-debounce.ts b/client/lib/hooks/use-debounce.ts index ed3e5225..432edc0f 100644 --- a/client/lib/hooks/use-debounce.ts +++ b/client/lib/hooks/use-debounce.ts @@ -1,18 +1,18 @@ // useDebounce.js -import { useState, useEffect } from 'react'; +import { useState, useEffect } from "react" export default function useDebounce(value: any, delay: number) { - const [debouncedValue, setDebouncedValue] = useState(value); + const [debouncedValue, setDebouncedValue] = useState(value) - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value) + }, delay) - return () => { - clearTimeout(handler); - }; - }, [value, delay]); + return () => { + clearTimeout(handler) + } + }, [value, delay]) - return debouncedValue; + return debouncedValue } diff --git a/client/lib/hooks/use-theme.ts b/client/lib/hooks/use-theme.ts deleted file mode 100644 index 9f08a145..00000000 --- a/client/lib/hooks/use-theme.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback, useEffect } from "react" -import useSharedState from "./use-shared-state" - -const useTheme = () => { - const isClient = typeof window === "object" - const [themeType, setThemeType] = useSharedState("theme", "light") - - useEffect(() => { - if (!isClient) return - const storedTheme = localStorage.getItem("drift-theme") - if (storedTheme) { - setThemeType(storedTheme) - } - }, [isClient, setThemeType]) - - const changeTheme = useCallback(() => { - setThemeType((last) => { - const newTheme = last === "dark" ? "light" : "dark" - localStorage.setItem("drift-theme", newTheme) - return newTheme - }) - }, [setThemeType]) - - return { theme: themeType, changeTheme } -} - -export default useTheme diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index 082d7966..fda487f2 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -60,20 +60,20 @@ renderer.listitem = (text, task, checked) => { return `
  • ${text}
  • ` } -renderer.code = (code: string, language: string) => { - return renderToStaticMarkup( -
    -            {/* {title && {title} } */}
    -            {/* {language && title &&  {language} } */}
    -            
    -        
    - ) -} +// renderer.code = (code: string, language: string) => { +// return renderToStaticMarkup( +//
    +//             {/* {title && {title} } */}
    +//             {/* {language && title &&  {language} } */}
    +//             
    +//         
    +// ) +// } marked.setOptions({ gfm: true, @@ -95,8 +95,7 @@ const Code = ({ code, language, highlight, title, ...props }: { if (!language) return ( <> - + ) diff --git a/client/lib/types.d.ts b/client/lib/types.d.ts index 462c8803..a1c14907 100644 --- a/client/lib/types.d.ts +++ b/client/lib/types.d.ts @@ -1,10 +1,5 @@ export type PostVisibility = "unlisted" | "private" | "public" | "protected" -export type ThemeProps = { - theme: "light" | "dark" | string - changeTheme: () => void -} - export type Document = { title: string content: string diff --git a/client/package.json b/client/package.json index 42b93032..cd567a29 100644 --- a/client/package.json +++ b/client/package.json @@ -22,6 +22,7 @@ "lodash.debounce": "^4.0.8", "marked": "^4.0.12", "next": "^12.1.1-canary.15", + "next-themes": "^0.1.1", "postcss": "^8.4.12", "postcss-flexbugs-fixes": "^5.0.2", "postcss-hover-media-feature": "^1.0.2", diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 2013345e..0c887e17 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -4,8 +4,10 @@ import type { AppProps as NextAppProps } from "next/app"; import 'react-loading-skeleton/dist/skeleton.css' import { SkeletonTheme } from 'react-loading-skeleton'; import Head from 'next/head'; -import useTheme from '@lib/hooks/use-theme'; -import { CssBaseline, GeistProvider } from '@geist-ui/core'; +import { CssBaseline, GeistProvider, Themes } from '@geist-ui/core'; +import { useTheme, ThemeProvider } from 'next-themes' +import { useEffect } from 'react'; +import App from '@components/app'; type AppProps

    = { pageProps: P; @@ -13,12 +15,8 @@ type AppProps

    = { function MyApp({ Component, pageProps }: AppProps) { - const { theme } = useTheme() - const skeletonBaseColor = 'var(--light-gray)' - const skeletonHighlightColor = 'var(--lighter-gray)' - return ( -

    +
    @@ -33,12 +31,9 @@ function MyApp({ Component, pageProps }: AppProps) { Drift - - - - - - + + +
    ) } diff --git a/client/pages/api/render-markdown.ts b/client/pages/api/render-markdown.ts index 9b02b60b..74aaee46 100644 --- a/client/pages/api/render-markdown.ts +++ b/client/pages/api/render-markdown.ts @@ -23,7 +23,6 @@ const renderMarkdown: NextApiHandler = async (req, res) => { } const type = fileType() let contentToRender: string = content || "" - if (!renderAsMarkdown.includes(type)) { contentToRender = `~~~${type} ${content} diff --git a/client/styles/globals.css b/client/styles/globals.css index 281fb13d..b68a3cac 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -32,7 +32,7 @@ --highlight: #2e2e2e; /* Dark Mode Colors */ - --bg: #131415; + --bg: #0e0e0e; --fg: #fafbfc; --gray: #666; --light-gray: #444; @@ -94,10 +94,34 @@ body { flex-direction: column; } -p, -li { - letter-spacing: -0.33px; - font-size: 1.125rem; +p { + overflow-wrap: break-word; + hyphens: auto; +} + +input, +button, +textarea, +select { + font-size: 1rem; +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; } blockquote { @@ -117,15 +141,6 @@ code { font-family: var(--font-mono) !important; } -kbd { - font-family: var(--font-sans); - font-size: 1rem; - padding: 2px 7px; - font-weight: 600; - background: var(--lighter-gray); - border-radius: 5px; -} - @media print { :root { --bg: #fff; @@ -150,3 +165,8 @@ kbd { text-shadow: none !important; } } + +#root, +#__next { + isolation: isolate; +} diff --git a/client/yarn.lock b/client/yarn.lock index 145dc566..71a90bed 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2893,6 +2893,11 @@ needle@^2.5.2: iconv-lite "^0.4.4" sax "^1.2.4" +next-themes@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.1.1.tgz#122113a458bf1d1be5ffed66778ab924c106f82a" + integrity sha512-Iqxt6rhS/KfK/iHJ0tfFjTcdLEAI0AgwFuAFrMwLOPK5e+MI3I+fzyvBoS+VaOS+NldUiazurhgwYhrfV0VXsQ== + next-unused@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/next-unused/-/next-unused-0.0.6.tgz#dbefa300bf5586e33d5bfde909130fb19ab04a64" diff --git a/server/src/routes/files.ts b/server/src/routes/files.ts index acaaf9f2..50ffc55b 100644 --- a/server/src/routes/files.ts +++ b/server/src/routes/files.ts @@ -42,7 +42,6 @@ files.get("/html/:id", async (req, res, next) => { return res.status(404).json({ error: "File not found" }) } - console.log(file.html) res.setHeader('Content-Type', 'text/plain') res.setHeader('Cache-Control', 'public, max-age=4800') res.status(200).write(file.html)