move theme related data from blog
to theme
This commit is contained in:
parent
2965ca04b0
commit
7b7be55499
68 changed files with 1393 additions and 1251 deletions
|
@ -24,7 +24,6 @@
|
|||
"rules": {
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react/react-in-jsx-scope": ["off"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@developomp-site/theme": "workspace:*",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||
|
@ -33,8 +34,8 @@
|
|||
"styled-components": "^5.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@developomp-site/tsconfig": "workspace:0.0.0",
|
||||
"@developomp-site/eslint-config": "workspace:0.0.0",
|
||||
"@developomp-site/eslint-config": "workspace:*",
|
||||
"@developomp-site/tsconfig": "workspace:*",
|
||||
"@types/ejs": "^3.1.1",
|
||||
"@types/elasticlunr": "^0.9.5",
|
||||
"@types/highlight.js": "^10.1.0",
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import darkTheme from "@developomp-site/theme/dist/dark.json"
|
||||
import lightTheme from "@developomp-site/theme/dist/light.json"
|
||||
|
||||
import { useContext, useEffect, useState } from "react"
|
||||
import { Routes, Route, useNavigate, useLocation } from "react-router-dom"
|
||||
import styled, { ThemeProvider } from "styled-components"
|
||||
|
@ -5,7 +8,7 @@ import { Helmet } from "react-helmet-async"
|
|||
import { isIE } from "react-device-detect"
|
||||
|
||||
import Loading from "./components/Loading"
|
||||
import Navbar from "./components/Navbar"
|
||||
import Header from "./components/Header"
|
||||
import Footer from "./components/Footer"
|
||||
|
||||
import Home from "./pages/Home"
|
||||
|
@ -14,17 +17,16 @@ import Page from "./pages/Page"
|
|||
import NotFound from "./pages/NotFound"
|
||||
import Portfolio from "./pages/Portfolio"
|
||||
|
||||
import theming from "./styles/theming"
|
||||
import GlobalStyle from "./styles/globalStyle"
|
||||
|
||||
import { ActionsEnum, globalContext } from "./globalContext"
|
||||
import { globalContext } from "./globalContext"
|
||||
|
||||
const IENotSupported = styled.p`
|
||||
margin: auto;
|
||||
font-size: 2rem;
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
font-family: ${theming.font.regular};
|
||||
font-family: ${(props) => props.theme.theme.font.sansSerif};
|
||||
`
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
|
@ -34,7 +36,7 @@ const StyledContentContainer = styled.div`
|
|||
`
|
||||
|
||||
export default function App() {
|
||||
const { globalState, dispatch } = useContext(globalContext)
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
@ -73,10 +75,8 @@ export default function App() {
|
|||
return (
|
||||
<ThemeProvider
|
||||
theme={{
|
||||
currentTheme: globalState.theme,
|
||||
setTheme(setThemeTo) {
|
||||
dispatch({ type: ActionsEnum.UPDATE_THEME, payload: setThemeTo })
|
||||
},
|
||||
currentTheme: globalState.currentTheme,
|
||||
theme: globalState.currentTheme === "dark" ? darkTheme : lightTheme,
|
||||
}}
|
||||
>
|
||||
<Helmet>
|
||||
|
@ -87,7 +87,7 @@ export default function App() {
|
|||
|
||||
<GlobalStyle />
|
||||
|
||||
<Navbar />
|
||||
<Header />
|
||||
<StyledContentContainer>
|
||||
{isLoading ? (
|
||||
<Loading />
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import dark from "@developomp-site/theme/dist/dark.json"
|
||||
import light from "@developomp-site/theme/dist/light.json"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
|
||||
import theming from "../styles/theming"
|
||||
|
||||
const StyledBadge = styled.div<{ color: string; isDark: boolean }>`
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
|
@ -15,7 +16,7 @@ const StyledBadge = styled.div<{ color: string; isDark: boolean }>`
|
|||
|
||||
background-color: ${(props) => props.color};
|
||||
color: ${(props) =>
|
||||
props.isDark ? theming.dark.color1 : theming.light.color1};
|
||||
props.isDark ? dark.color.text.default : light.color.text.default};
|
||||
`
|
||||
|
||||
const StyledSVG = styled.div<{ isDark: boolean }>`
|
||||
|
@ -27,7 +28,9 @@ const StyledSVG = styled.div<{ isDark: boolean }>`
|
|||
svg {
|
||||
height: 16px;
|
||||
fill: ${(props) =>
|
||||
props.isDark ? theming.dark.color1 : theming.light.color1} !important;
|
||||
props.isDark
|
||||
? dark.color.text.default
|
||||
: light.color.text.default} !important;
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
import styled, { css } from "styled-components"
|
||||
|
||||
import theming from "../styles/theming"
|
||||
|
||||
export const cardCSS = css`
|
||||
margin: auto;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "white",
|
||||
dark: theming.dark.backgroundColor2,
|
||||
})};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.currentTheme ? theme.theme.component.card.color.background : "white"};
|
||||
padding: 2rem;
|
||||
border-radius: 6px;
|
||||
box-shadow: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "0 4px 10px rgb(0 0 0 / 5%), 0 0 1px rgb(0 0 0 / 10%);",
|
||||
dark: "0 4px 10px rgb(0 0 0 / 30%), 0 0 1px rgb(0 0 0 / 30%);",
|
||||
})};
|
||||
box-shadow: ${({ theme }) =>
|
||||
theme.currentTheme === "dark"
|
||||
? "0 4px 10px rgb(0 0 0 / 30%), 0 0 1px rgb(0 0 0 / 30%)"
|
||||
: "0 4px 10px rgb(0 0 0 / 5%), 0 0 1px rgb(0 0 0 / 10%)"};
|
||||
|
||||
@media screen and (max-width: ${theming.size.screen_size1}) {
|
||||
@media screen and (max-width: ${({ theme }) =>
|
||||
theme.theme.maxDisplayWidth.mobile}) {
|
||||
padding: 1rem;
|
||||
}
|
||||
`
|
||||
|
||||
const Card = styled.div`
|
||||
export default styled.div`
|
||||
${cardCSS}
|
||||
`
|
||||
|
||||
export default Card
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import styled from "styled-components"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
import GithubLinkIcon from "../GithubLinkIcon"
|
||||
|
||||
const StyledFooter = styled.footer`
|
||||
|
@ -13,17 +12,8 @@ const StyledFooter = styled.footer`
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "white",
|
||||
dark: "black",
|
||||
})};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.footer.color.background};
|
||||
`
|
||||
|
||||
const StyledFooterContainer = styled.div`
|
||||
|
@ -35,10 +25,10 @@ const StyledFooterContainer = styled.div`
|
|||
color: gray;
|
||||
|
||||
width: 100%;
|
||||
max-width: ${theming.size.screen_size2};
|
||||
max-width: ${({ theme }) => theme.theme.maxDisplayWidth.desktop};
|
||||
`
|
||||
|
||||
const Footer = () => {
|
||||
export default () => {
|
||||
return (
|
||||
<StyledFooter>
|
||||
<StyledFooterContainer>
|
||||
|
@ -51,5 +41,3 @@ const Footer = () => {
|
|||
</StyledFooter>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
|
|
|
@ -4,39 +4,27 @@ import styled from "styled-components"
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faGithub } from "@fortawesome/free-brands-svg-icons"
|
||||
|
||||
import theming from "../styles/theming"
|
||||
|
||||
const StyledGithubLink = styled.a<{ size?: string }>`
|
||||
font-size: ${(props) => props.size || "2.5rem"};
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "lightgrey",
|
||||
dark: "grey",
|
||||
})};
|
||||
color: ${({ theme }) =>
|
||||
theme.currentTheme === "dark" ? "grey" : "lightgrey"};
|
||||
|
||||
:hover {
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color0,
|
||||
})};
|
||||
color: ${({ theme }) => theme.theme.color.text.highContrast};
|
||||
}
|
||||
`
|
||||
|
||||
interface GithubLinkIconProps {
|
||||
interface Props {
|
||||
link: string
|
||||
size?: string
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
const GithubLinkIcon = (props: GithubLinkIconProps) => {
|
||||
export default ({ link, size, children }: Props) => {
|
||||
return (
|
||||
<StyledGithubLink size={props.size} href={props.link} target="_blank">
|
||||
<StyledGithubLink size={size} href={link} target="_blank">
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
{props.children}
|
||||
{children}
|
||||
</StyledGithubLink>
|
||||
)
|
||||
}
|
||||
|
||||
export default GithubLinkIcon
|
||||
|
|
21
apps/blog/src/components/Header/Buttons/Buttons.tsx
Normal file
21
apps/blog/src/components/Header/Buttons/Buttons.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import styled from "styled-components"
|
||||
|
||||
import LocaleToggleButton from "./LocaleToggleButton"
|
||||
import ThemeToggleButton from "./ThemeToggleButton"
|
||||
import SearchButton from "./SearchButton"
|
||||
|
||||
const RightButtons = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
`
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<RightButtons>
|
||||
<LocaleToggleButton />
|
||||
<ThemeToggleButton />
|
||||
<SearchButton />
|
||||
</RightButtons>
|
||||
)
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import type { SiteLocale } from "../../../globalContext"
|
||||
|
||||
import { useContext } from "react"
|
||||
import styled from "styled-components"
|
||||
import ReactTooltip from "react-tooltip"
|
||||
|
@ -5,29 +7,28 @@ import ReactTooltip from "react-tooltip"
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faLanguage } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import { ActionsEnum, globalContext } from "../../globalContext"
|
||||
import theming from "../../styles/theming"
|
||||
import { ActionsEnum, globalContext } from "../../../globalContext"
|
||||
import { HeaderButtonCSS } from "../HeaderButton"
|
||||
|
||||
import type { SiteLocale } from "../../globalContext"
|
||||
|
||||
interface StyledLocaleToggleButtonProps {
|
||||
interface Props {
|
||||
locale: SiteLocale
|
||||
}
|
||||
|
||||
const StyledLocaleToggleButton = styled.button<StyledLocaleToggleButtonProps>`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
const LocaleToggleButton = styled.button<Props>`
|
||||
${HeaderButtonCSS}
|
||||
|
||||
border: none;
|
||||
width: 72px;
|
||||
|
||||
${(props) => (props.locale == "en" ? "" : "transform: scaleX(-1);")};
|
||||
`
|
||||
|
||||
function LocaleToggleButton() {
|
||||
export default () => {
|
||||
const { globalState, dispatch } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledLocaleToggleButton
|
||||
<LocaleToggleButton
|
||||
data-tip
|
||||
data-for="locale"
|
||||
onClick={() => {
|
||||
|
@ -39,12 +40,10 @@ function LocaleToggleButton() {
|
|||
locale={globalState.locale}
|
||||
>
|
||||
<FontAwesomeIcon icon={faLanguage} />
|
||||
</StyledLocaleToggleButton>
|
||||
</LocaleToggleButton>
|
||||
<ReactTooltip id="locale" type="dark" effect="solid">
|
||||
<span>{globalState.locale == "en" ? "English" : "한국어"} </span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LocaleToggleButton
|
|
@ -2,12 +2,11 @@ import { useContext } from "react"
|
|||
import { Link } from "react-router-dom"
|
||||
import ReactTooltip from "react-tooltip"
|
||||
|
||||
import HeaderButton from "../HeaderButton"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import { StyledLink } from "./Navbar"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
import { globalContext } from "../../../globalContext"
|
||||
|
||||
const SearchButton = () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
@ -17,9 +16,9 @@ const SearchButton = () => {
|
|||
<>
|
||||
<div>
|
||||
<Link data-tip data-for="search" to={`/${locale}/search`}>
|
||||
<StyledLink>
|
||||
<HeaderButton>
|
||||
<FontAwesomeIcon icon={faSearch} />
|
||||
</StyledLink>
|
||||
</HeaderButton>
|
||||
</Link>
|
||||
</div>
|
||||
<ReactTooltip id="search" type="dark" effect="solid">
|
|
@ -6,24 +6,21 @@ import ReactTooltip from "react-tooltip"
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faMoon, faSun } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
import { ActionsEnum, globalContext } from "../../globalContext"
|
||||
import { ActionsEnum, globalContext } from "../../../globalContext"
|
||||
import { HeaderButtonCSS } from "../HeaderButton"
|
||||
|
||||
const StyledThemeButton = styled.button`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
${HeaderButtonCSS}
|
||||
border: none;
|
||||
width: 72px;
|
||||
|
||||
${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "",
|
||||
dark: "transform: scaleX(-1);",
|
||||
})};
|
||||
${({ theme }) =>
|
||||
theme.currentTheme === "dark" ? "transform: scaleX(-1)" : ""};
|
||||
`
|
||||
|
||||
const ThemeToggleButton = () => {
|
||||
const { globalState, dispatch } = useContext(globalContext)
|
||||
const theme = globalState.theme
|
||||
const theme = globalState.currentTheme
|
||||
|
||||
return (
|
||||
<>
|
3
apps/blog/src/components/Header/Buttons/index.ts
Normal file
3
apps/blog/src/components/Header/Buttons/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Buttons from "./Buttons"
|
||||
|
||||
export default Buttons
|
63
apps/blog/src/components/Header/Header.tsx
Normal file
63
apps/blog/src/components/Header/Header.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { useContext } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
import ReadProgress from "./ReadProgress"
|
||||
|
||||
import Nav from "./Nav"
|
||||
|
||||
import Sidebar from "../Sidebar"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
import Buttons from "./Buttons"
|
||||
|
||||
const Header = styled.header`
|
||||
/* set z index to arbitrarily high value to prevent other components from drawing over it */
|
||||
z-index: 9999;
|
||||
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.ui.color.background.default};
|
||||
color: ${({ theme }) => theme.theme.color.text.default};
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 5%);
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 4rem;
|
||||
|
||||
/* account for 20px scrollbar width */
|
||||
@media only screen and (min-width: calc(${({ theme }) =>
|
||||
theme.theme.maxDisplayWidth.desktop} + 20px)) {
|
||||
width: calc(${({ theme }) => theme.theme.maxDisplayWidth.desktop} - 20px);
|
||||
}
|
||||
`
|
||||
|
||||
const Icon = styled.img`
|
||||
height: 2.5rem;
|
||||
|
||||
display: block;
|
||||
margin: 1rem;
|
||||
`
|
||||
|
||||
export default () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<Container>
|
||||
<Link to={`/${locale}`}>
|
||||
<Icon src="/icon/icon_circle.svg" />
|
||||
</Link>
|
||||
<Nav />
|
||||
<Buttons />
|
||||
<Sidebar />
|
||||
</Container>
|
||||
<ReadProgress />
|
||||
</Header>
|
||||
)
|
||||
}
|
43
apps/blog/src/components/Header/HeaderButton.tsx
Normal file
43
apps/blog/src/components/Header/HeaderButton.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @file Manages the configuration settings for the widget.
|
||||
*/
|
||||
|
||||
import styled, { css } from "styled-components"
|
||||
|
||||
export const HeaderButtonCSS = css`
|
||||
/* style */
|
||||
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* size */
|
||||
|
||||
height: 100%;
|
||||
min-width: 2.5rem;
|
||||
margin: 0;
|
||||
padding: 0 1rem 0 1rem;
|
||||
|
||||
/* text */
|
||||
|
||||
text-decoration: none;
|
||||
|
||||
/* color */
|
||||
|
||||
color: ${({ theme }) => theme.theme.color.text.default};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.ui.color.background.default};
|
||||
|
||||
/* animation */
|
||||
|
||||
transition: transform 0.1s linear;
|
||||
&:hover {
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.ui.color.background.hover};
|
||||
}
|
||||
`
|
||||
|
||||
export default styled.div`
|
||||
${HeaderButtonCSS}
|
||||
`
|
|
@ -2,35 +2,33 @@ import { useContext } from "react"
|
|||
import styled from "styled-components"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { StyledLink } from "./Navbar"
|
||||
import HeaderButton from "./HeaderButton"
|
||||
|
||||
import NavbarData from "../../data/NavbarData"
|
||||
import theming from "../../styles/theming"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const StyledNavLinks = styled.div`
|
||||
const Nav = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
@media only screen and (max-width: ${theming.size.screen_size1}) {
|
||||
@media only screen and (max-width: ${({ theme }) =>
|
||||
theme.theme.maxDisplayWidth.mobile}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const NavLinks = () => {
|
||||
export default () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
<StyledNavLinks>
|
||||
<Nav>
|
||||
{NavbarData.map((item, index) => (
|
||||
<Link key={index} to={globalState.locale + item.path}>
|
||||
<StyledLink>
|
||||
<HeaderButton>
|
||||
{globalState.locale == "en" ? item.title_en : item.title_kr}
|
||||
</StyledLink>
|
||||
</HeaderButton>
|
||||
</Link>
|
||||
))}
|
||||
</StyledNavLinks>
|
||||
</Nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default NavLinks
|
|
@ -2,24 +2,16 @@ import { useCallback, useEffect, useState } from "react"
|
|||
import { useLocation } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
const StyledReadProgressBackground = styled.div`
|
||||
const Background = styled.div`
|
||||
height: 0.2rem;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "silver",
|
||||
dark: "darkslategrey",
|
||||
})};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.scrollProgressBar.color.background};
|
||||
`
|
||||
|
||||
const StyledReadProgress = styled.div`
|
||||
const ProgressBar = styled.div`
|
||||
height: 100%;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color2,
|
||||
})};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.scrollProgressBar.color.foreground};
|
||||
`
|
||||
|
||||
const st = "scrollTop"
|
||||
|
@ -58,9 +50,9 @@ const ReadProgress = () => {
|
|||
}, [location])
|
||||
|
||||
return (
|
||||
<StyledReadProgressBackground>
|
||||
<StyledReadProgress style={{ width: `${scroll}%` }} />
|
||||
</StyledReadProgressBackground>
|
||||
<Background>
|
||||
<ProgressBar style={{ width: `${scroll}%` }} />
|
||||
</Background>
|
||||
)
|
||||
}
|
||||
|
3
apps/blog/src/components/Header/index.ts
Normal file
3
apps/blog/src/components/Header/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Navbar from "./Header"
|
||||
|
||||
export default Navbar
|
|
@ -6,8 +6,6 @@ import styled from "styled-components"
|
|||
|
||||
import MainContent from "./MainContent"
|
||||
|
||||
import theming from "../styles/theming"
|
||||
|
||||
const StyledContainer = styled(MainContent)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -30,11 +28,7 @@ const StyledContainer = styled(MainContent)`
|
|||
`
|
||||
|
||||
const StyledSVG = styled.svg`
|
||||
--color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color1,
|
||||
dark: theming.dark.color1,
|
||||
})};
|
||||
--color: ${({ theme }) => theme.theme.color.text.default};
|
||||
|
||||
display: block;
|
||||
margin: 1rem;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import styled, { css } from "styled-components"
|
||||
import theming from "../styles/theming"
|
||||
|
||||
import Card from "./Card"
|
||||
|
||||
|
@ -15,7 +14,8 @@ export const mainContentCSS = css`
|
|||
max-width: fit-content;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${theming.size.screen_size1}) {
|
||||
@media screen and (max-width: ${({ theme }) =>
|
||||
theme.theme.maxDisplayWidth.mobile}) {
|
||||
width: auto;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
import { useContext } from "react"
|
||||
import styled from "styled-components"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import NavLinks from "./NavLinks"
|
||||
import LocaleToggleButton from "./LocaleToggleButton"
|
||||
import ThemeToggleButton from "./ThemeToggleButton"
|
||||
import SearchButton from "./SearchButton"
|
||||
import ReadProgress from "./ReadProgress"
|
||||
import Sidebar from "../Sidebar"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const StyledNav = styled.nav`
|
||||
/* set z index to arbitrarily high value to prevent other components from drawing over the navbar */
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color0,
|
||||
})};
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 5%);
|
||||
`
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 4rem;
|
||||
|
||||
/* account for 20px scrollbar width */
|
||||
@media only screen and (min-width: calc(${theming.size
|
||||
.screen_size2} + 20px)) {
|
||||
width: calc(${theming.size.screen_size2} - 20px);
|
||||
}
|
||||
`
|
||||
|
||||
const RightButtons = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
`
|
||||
|
||||
const StyledImg = styled.img`
|
||||
height: 2.5rem;
|
||||
|
||||
display: block;
|
||||
margin: 1rem;
|
||||
`
|
||||
|
||||
export const StyledLink = styled.div`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
`
|
||||
|
||||
const Navbar = () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
return (
|
||||
<StyledNav>
|
||||
<StyledContainer>
|
||||
{/* icon */}
|
||||
<Link to={`/${locale}`}>
|
||||
<StyledImg src="/icon/icon_circle.svg" />
|
||||
</Link>
|
||||
|
||||
{/* nav links */}
|
||||
<NavLinks />
|
||||
|
||||
{/* right buttons */}
|
||||
<RightButtons>
|
||||
<LocaleToggleButton />
|
||||
<ThemeToggleButton />
|
||||
<SearchButton />
|
||||
</RightButtons>
|
||||
|
||||
{/* etc */}
|
||||
<Sidebar />
|
||||
</StyledContainer>
|
||||
<ReadProgress />
|
||||
</StyledNav>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
|
@ -1,3 +0,0 @@
|
|||
import Navbar from "./Navbar"
|
||||
|
||||
export default Navbar
|
|
@ -15,57 +15,41 @@ import Tag from "./Tag"
|
|||
import TagList from "./TagList"
|
||||
import MainContent from "./MainContent"
|
||||
|
||||
import theming from "../styles/theming"
|
||||
import { globalContext } from "../globalContext"
|
||||
|
||||
const PostCard = styled(MainContent)`
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
|
||||
text-align: left;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 10px
|
||||
${({ theme }) => theme.theme.component.card.color.hoverGlow};
|
||||
}
|
||||
`
|
||||
|
||||
const PostCardContainer = styled(Link)`
|
||||
display: block;
|
||||
padding: 2rem;
|
||||
text-decoration: none;
|
||||
padding: 0;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color2,
|
||||
dark: theming.dark.color2,
|
||||
})};
|
||||
|
||||
/* override link color */
|
||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||
&:hover {
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color2,
|
||||
dark: theming.dark.color2,
|
||||
})};
|
||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledPostCard = styled(MainContent)`
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
|
||||
text-align: left;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color1,
|
||||
dark: theming.dark.color1,
|
||||
})};
|
||||
|
||||
${theming.styles.hoverCard}
|
||||
`
|
||||
|
||||
const StyledTitle = styled.h1`
|
||||
const Title = styled.h1`
|
||||
font-size: 2rem;
|
||||
font-style: bold;
|
||||
margin: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color2,
|
||||
dark: theming.dark.color2,
|
||||
})};
|
||||
`
|
||||
|
||||
const StyledMetaContainer = styled.small``
|
||||
const MetaContainer = styled.small``
|
||||
|
||||
interface PostCardData extends PostData {
|
||||
content_id: string
|
||||
|
@ -75,26 +59,26 @@ interface Props {
|
|||
postData: PostCardData
|
||||
}
|
||||
|
||||
const PostCard = (props: Props) => {
|
||||
export default (props: Props) => {
|
||||
const { postData } = props
|
||||
const { content_id, wordCount, date, readTime, title, tags } = postData
|
||||
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
<StyledPostCard>
|
||||
<PostCard>
|
||||
<PostCardContainer
|
||||
to={`/${globalState.locale}${content_id.replace(/(.kr)$/g, "")}`}
|
||||
>
|
||||
<StyledTitle>
|
||||
<Title>
|
||||
{title || "No title"}
|
||||
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
||||
{/\/series\/[^/]*$/.test(content_id) && " (series)"}
|
||||
</StyledTitle>
|
||||
</Title>
|
||||
|
||||
<br />
|
||||
|
||||
<StyledMetaContainer>
|
||||
<MetaContainer>
|
||||
<TagList direction="left">
|
||||
{tags &&
|
||||
tags.map((tag) => {
|
||||
|
@ -115,10 +99,8 @@ const PostCard = (props: Props) => {
|
|||
{typeof wordCount === "number"
|
||||
? wordCount + " words"
|
||||
: "unknown length"}
|
||||
</StyledMetaContainer>
|
||||
</MetaContainer>
|
||||
</PostCardContainer>
|
||||
</StyledPostCard>
|
||||
</PostCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default PostCard
|
||||
|
|
|
@ -9,14 +9,15 @@ import { faEllipsisV, faTimes } from "@fortawesome/free-solid-svg-icons"
|
|||
import SubMenu from "./SubMenu"
|
||||
|
||||
import NavbarData from "../../data/NavbarData"
|
||||
import theming from "../../styles/theming"
|
||||
import HeaderButton from "../Header/HeaderButton"
|
||||
|
||||
const CommonSidebarToggleButtonStyle = css`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
${HeaderButton}
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
@media only screen and (min-width: ${theming.size.screen_size1}) {
|
||||
|
||||
@media only screen and (min-width: ${({ theme }) =>
|
||||
theme.theme.maxDisplayWidth.mobile}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
@ -65,16 +66,9 @@ const SidebarNav = styled.nav<{ isSidebarOpen: boolean }>`
|
|||
z-index: 30;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color0,
|
||||
})};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.header.color.background};
|
||||
color: ${({ theme }) => theme.theme.component.header.color.text};
|
||||
`
|
||||
|
||||
const SidebarWrap = styled.div`
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
* @file Submenu item for sidebar
|
||||
*/
|
||||
|
||||
import type { Item } from "../../data/NavbarData"
|
||||
|
||||
import { useCallback, useContext, useState } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
import type { Item } from "../../data/NavbarData"
|
||||
import button from "../../styles/button"
|
||||
|
||||
const SidebarLink = styled(Link)`
|
||||
${theming.styles.navbarButtonStyle};
|
||||
${button};
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
|
|
@ -4,19 +4,13 @@ import styled from "styled-components"
|
|||
import { faHashtag } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
||||
import theming from "../styles/theming"
|
||||
|
||||
const StyledTag = styled.div`
|
||||
const Tag = styled.div`
|
||||
text-align: center;
|
||||
|
||||
margin-right: 0.8rem;
|
||||
border-radius: 10px;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color2,
|
||||
dark: theming.dark.color2,
|
||||
})};
|
||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||
`
|
||||
|
||||
interface Props {
|
||||
|
@ -24,12 +18,10 @@ interface Props {
|
|||
onClick?: (event: MouseEvent<never>) => void
|
||||
}
|
||||
|
||||
const Tag = (props: Props) => {
|
||||
export default (props: Props) => {
|
||||
return (
|
||||
<StyledTag onClick={props.onClick || undefined}>
|
||||
<Tag onClick={props.onClick || undefined}>
|
||||
<FontAwesomeIcon icon={faHashtag} /> {props.text}
|
||||
</StyledTag>
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tag
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import type { Dispatch, ReactNode, ReactElement } from "react"
|
||||
import type { Theme } from "@developomp-site/theme"
|
||||
|
||||
import darkTheme from "@developomp-site/theme/dist/dark.json"
|
||||
import lightTheme from "@developomp-site/theme/dist/light.json"
|
||||
import { createContext, useEffect, useReducer } from "react"
|
||||
import storage from "local-storage-fallback"
|
||||
|
||||
import type { Dispatch, ReactNode, ReactElement } from "react"
|
||||
|
||||
export type SiteLocale = "en" | "kr"
|
||||
export type SiteTheme = "dark" | "light"
|
||||
|
||||
|
@ -11,11 +14,6 @@ export enum ActionsEnum {
|
|||
UPDATE_LOCALE,
|
||||
}
|
||||
|
||||
export interface IGlobalState {
|
||||
locale: SiteLocale
|
||||
theme: SiteTheme
|
||||
}
|
||||
|
||||
export type GlobalAction =
|
||||
| {
|
||||
type: ActionsEnum.UPDATE_THEME
|
||||
|
@ -26,6 +24,12 @@ export type GlobalAction =
|
|||
payload: SiteLocale
|
||||
}
|
||||
|
||||
export interface IGlobalState {
|
||||
locale: SiteLocale
|
||||
currentTheme: SiteTheme
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
export interface IGlobalContext {
|
||||
globalState: IGlobalState
|
||||
dispatch: Dispatch<GlobalAction>
|
||||
|
@ -40,7 +44,11 @@ function getDefaultLocale(): SiteLocale {
|
|||
|
||||
const defaultState: IGlobalState = {
|
||||
locale: getDefaultLocale(),
|
||||
theme: (storage.getItem("theme") || "dark") as SiteTheme,
|
||||
currentTheme: (storage.getItem("theme") || "dark") as SiteTheme,
|
||||
theme:
|
||||
((storage.getItem("theme") || "dark") as SiteTheme) === "dark"
|
||||
? darkTheme
|
||||
: lightTheme,
|
||||
}
|
||||
|
||||
export const globalContext = createContext({} as IGlobalContext)
|
||||
|
@ -48,7 +56,8 @@ export const globalContext = createContext({} as IGlobalContext)
|
|||
function reducer(state = defaultState, action: GlobalAction): IGlobalState {
|
||||
switch (action.type) {
|
||||
case ActionsEnum.UPDATE_THEME:
|
||||
state.theme = action.payload
|
||||
state.currentTheme = action.payload
|
||||
state.theme = state.currentTheme === "dark" ? darkTheme : lightTheme
|
||||
break
|
||||
|
||||
case ActionsEnum.UPDATE_LOCALE:
|
||||
|
@ -67,8 +76,8 @@ export function GlobalStore(props: { children: ReactNode }): ReactElement {
|
|||
|
||||
// save theme when it is changed
|
||||
useEffect(() => {
|
||||
storage.setItem("theme", globalState.theme)
|
||||
}, [globalState.theme])
|
||||
storage.setItem("theme", globalState.currentTheme)
|
||||
}, [globalState.currentTheme])
|
||||
|
||||
// save locale when it is changed
|
||||
useEffect(() => {
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
* PostList.tsx
|
||||
* show posts in recent order
|
||||
*/
|
||||
import type { Map } from "../../../types/types"
|
||||
|
||||
import { useContext, useEffect, useState } from "react"
|
||||
import { useCallback, useContext, useEffect, useState } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import styled from "styled-components"
|
||||
|
||||
|
@ -11,25 +12,20 @@ import PostCard from "../../components/PostCard"
|
|||
import ShowMoreButton from "./ShowMoreButton"
|
||||
|
||||
import _map from "../../data/map.json"
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
import type { Map } from "../../../types/types"
|
||||
|
||||
const map: Map = _map
|
||||
|
||||
const StyledPostList = styled.div`
|
||||
const PostList = styled.div`
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#111111",
|
||||
dark: "#EEEEEE",
|
||||
})};
|
||||
color: ${({ theme }) => theme.theme.color.text.default};
|
||||
`
|
||||
|
||||
const Home = () => {
|
||||
export default () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
|
@ -37,7 +33,7 @@ const Home = () => {
|
|||
const [postsLength, setPostsLength] = useState(0)
|
||||
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
||||
|
||||
function loadPostCards() {
|
||||
const loadPostCards = useCallback(() => {
|
||||
let postCount = 0
|
||||
const postCards = [] as JSX.Element[]
|
||||
|
||||
|
@ -65,7 +61,7 @@ const Home = () => {
|
|||
}
|
||||
|
||||
setPostCards(postCards)
|
||||
}
|
||||
}, [howMany, postCards])
|
||||
|
||||
useEffect(() => {
|
||||
loadPostCards()
|
||||
|
@ -81,9 +77,11 @@ const Home = () => {
|
|||
<meta property="og:image" content="/icon/icon.svg" />
|
||||
</Helmet>
|
||||
|
||||
<StyledPostList>
|
||||
<PostList>
|
||||
<h1>{locale == "en" ? "Recent Posts" : "최근 포스트"}</h1>
|
||||
|
||||
{postCards}
|
||||
|
||||
{postsLength > howMany && (
|
||||
<ShowMoreButton
|
||||
action={() => {
|
||||
|
@ -91,9 +89,7 @@ const Home = () => {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledPostList>
|
||||
</PostList>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
|
|
|
@ -1,54 +1,21 @@
|
|||
import { useContext } from "react"
|
||||
import styled from "styled-components"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
import { globalContext } from "../../globalContext"
|
||||
import buttonStyle from "../../styles/button"
|
||||
|
||||
const Button = styled.button`
|
||||
/* size */
|
||||
${buttonStyle}
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
/* styling */
|
||||
|
||||
display: inline-block;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
/* text */
|
||||
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
|
||||
/* colors */
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "#CFD0D0",
|
||||
})};
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor2,
|
||||
dark: theming.dark.backgroundColor2,
|
||||
})};
|
||||
|
||||
:hover {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
}
|
||||
/* center div */
|
||||
margin: 0 auto;
|
||||
`
|
||||
|
||||
interface Props {
|
||||
action(): void
|
||||
}
|
||||
|
||||
const ShowMoreButton = (props: Props) => {
|
||||
export default (props: Props) => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
|
@ -57,5 +24,3 @@ const ShowMoreButton = (props: Props) => {
|
|||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowMoreButton
|
||||
|
|
|
@ -8,14 +8,9 @@ import {
|
|||
} from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import { PageData } from "../../../types/types"
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
const StyledMetaContainer = styled.div`
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#555",
|
||||
dark: "#CCC",
|
||||
})};
|
||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||
`
|
||||
|
||||
const Meta = (props: { fetchedPage: PageData }) => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext } from "react"
|
||||
import styled, { css } from "styled-components"
|
||||
import styled from "styled-components"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
@ -9,8 +9,7 @@ import {
|
|||
faListUl,
|
||||
} from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
import buttonStyle from "../../styles/button"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const Container = styled.div`
|
||||
|
@ -18,26 +17,6 @@ const Container = styled.div`
|
|||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const buttonStyle = css`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
dark: "#202225",
|
||||
light: "#EEEEEE",
|
||||
})};
|
||||
border-radius: 0.5rem;
|
||||
height: 3rem;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
dark: theming.dark.backgroundColor1,
|
||||
light: theming.light.backgroundColor2,
|
||||
})};
|
||||
}
|
||||
`
|
||||
|
||||
const Button = styled.div`
|
||||
${buttonStyle}
|
||||
`
|
||||
|
@ -47,14 +26,6 @@ const DisabledButton = styled.div`
|
|||
|
||||
color: grey;
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
dark: "#202225",
|
||||
light: "#EEEEEE",
|
||||
})};
|
||||
}
|
||||
`
|
||||
|
||||
interface Props {
|
||||
|
|
|
@ -6,7 +6,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|||
import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"
|
||||
import styled from "styled-components"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const StyledTocToggleButton = styled.button`
|
||||
|
@ -16,11 +15,7 @@ const StyledTocToggleButton = styled.button`
|
|||
background-color: rgba(0, 0, 0, 0);
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
color: ${({ theme }) => theme.theme.color.text.highContrast};
|
||||
`
|
||||
|
||||
const StyledCollapseContainer = styled.div`
|
||||
|
|
|
@ -6,21 +6,21 @@ import Badge from "../../components/Badge"
|
|||
import { cardCSS } from "../../components/Card"
|
||||
|
||||
import { PortfolioProject } from "../../../types/types"
|
||||
import theming from "../../styles/theming"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const StyledProjectCard = styled.div`
|
||||
${cardCSS}
|
||||
${theming.styles.hoverCard}
|
||||
|
||||
color: ${(props) => props.theme.theme.color.text.default};
|
||||
margin-bottom: 2rem;
|
||||
word-wrap: break-word;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color1,
|
||||
dark: theming.dark.color1,
|
||||
})};
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
|
||||
box-shadow: 0 4px 10px
|
||||
${(props) => props.theme.theme.component.card.color.hoverGlow};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledImg = styled.img`
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { DateRange } from "react-date-range"
|
||||
import styled from "styled-components"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
export const DateRangeControl = styled.div`
|
||||
width: 350px;
|
||||
|
||||
@media screen and (max-width: ${theming.size.screen_size2}) {
|
||||
@media screen and (max-width: ${(props) =>
|
||||
props.theme.theme.maxDisplayWidth.mobile}) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* eslint-disable react/prop-types */
|
||||
|
||||
import { useContext, useEffect, useState } from "react"
|
||||
import { useCallback, useContext, useEffect, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
@ -10,7 +8,6 @@ import elasticlunr from "elasticlunr" // search engine
|
|||
|
||||
import _map from "../../data/map.json"
|
||||
import searchData from "../../data/search.json"
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
import Loading from "../../components/Loading"
|
||||
import PostCard from "../../components/PostCard"
|
||||
|
@ -54,7 +51,8 @@ const StyledSearchContainer = styled.div`
|
|||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
@media screen and (max-width: ${theming.size.screen_size2}) {
|
||||
@media screen and (max-width: ${(props) =>
|
||||
props.theme.theme.maxDisplayWidth.mobile}) {
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -64,7 +62,8 @@ const StyledSearchControlContainer = styled.div`
|
|||
width: 100%;
|
||||
margin-left: 1rem;
|
||||
|
||||
@media screen and (max-width: ${theming.size.screen_size2}) {
|
||||
@media screen and (max-width: ${(props) =>
|
||||
props.theme.theme.maxDisplayWidth.mobile}) {
|
||||
margin-top: 2rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
@ -113,6 +112,38 @@ const Search = () => {
|
|||
|
||||
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
||||
|
||||
const doSearch = useCallback(() => {
|
||||
try {
|
||||
const _postCards: JSX.Element[] = []
|
||||
for (const res of searchIndex.search(searchInput)) {
|
||||
const postData = map.posts[res.ref]
|
||||
|
||||
if (
|
||||
postData && // if post data exists
|
||||
isDateInRange(postData.date, dateRange[0]) && // date is within range
|
||||
isSelectedTagsInPost(selectedTags, postData.tags) // if post include tags
|
||||
) {
|
||||
_postCards.push(
|
||||
<PostCard
|
||||
key={res.ref}
|
||||
postData={{
|
||||
content_id: res.ref,
|
||||
...postData,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// apply search result
|
||||
setPostCards(_postCards)
|
||||
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}, [dateRange, selectedTags, searchInput])
|
||||
|
||||
// parse search parameters
|
||||
useEffect(() => {
|
||||
for (const [key, value] of URLSearchParams.entries()) {
|
||||
|
@ -186,38 +217,6 @@ const Search = () => {
|
|||
return () => clearTimeout(delayDebounceFn)
|
||||
}, [searchInput])
|
||||
|
||||
function doSearch() {
|
||||
try {
|
||||
const _postCards: JSX.Element[] = []
|
||||
for (const res of searchIndex.search(searchInput)) {
|
||||
const postData = map.posts[res.ref]
|
||||
|
||||
if (
|
||||
postData && // if post data exists
|
||||
isDateInRange(postData.date, dateRange[0]) && // date is within range
|
||||
isSelectedTagsInPost(selectedTags, postData.tags) // if post include tags
|
||||
) {
|
||||
_postCards.push(
|
||||
<PostCard
|
||||
key={res.ref}
|
||||
postData={{
|
||||
content_id: res.ref,
|
||||
...postData,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// apply search result
|
||||
setPostCards(_postCards)
|
||||
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized) return <Loading />
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
import styled from "styled-components"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
const StyledSearchBar = styled.input`
|
||||
export default styled.input`
|
||||
width: 100%;
|
||||
border-radius: 100px; /* arbitrarily large value */
|
||||
height: 2.5rem;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
outline: none;
|
||||
|
||||
border: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "1px solid #ccc",
|
||||
dark: "1px solid #555",
|
||||
})};
|
||||
|
||||
color: ${({ theme }) => theme.theme.color.text.default};
|
||||
border: 1px solid
|
||||
${(props) => props.theme.theme.component.input.color.border.default};
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.dark.color1,
|
||||
dark: theming.dark.backgroundColor1,
|
||||
})};
|
||||
props.theme.theme.component.input.color.background.default};
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color1,
|
||||
dark: theming.dark.color1,
|
||||
})};
|
||||
::placeholder {
|
||||
color: ${(props) => props.theme.theme.component.input.color.placeHolder};
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid
|
||||
${({ theme }) => theme.theme.component.input.color.border.hover};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 1px solid
|
||||
${({ theme }) => theme.theme.component.input.color.border.focus};
|
||||
}
|
||||
`
|
||||
|
||||
export default StyledSearchBar
|
||||
|
|
|
@ -2,8 +2,6 @@ import { useContext } from "react"
|
|||
import styled from "styled-components"
|
||||
import Select from "react-select"
|
||||
|
||||
import theming from "../../styles/theming"
|
||||
|
||||
import _map from "../../data/map.json"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
|
@ -33,24 +31,19 @@ interface TagSelectProps {
|
|||
|
||||
const TagSelect = (props: TagSelectProps) => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { theme } = globalState
|
||||
const locale = globalState.locale
|
||||
const currentTheme = globalState.theme
|
||||
const { onChange, defaultValue: selectedTags } = props
|
||||
|
||||
return (
|
||||
<StyledReactTagsContainer>
|
||||
<Select
|
||||
placeholder={locale == "en" ? "Select tags..." : "태그를 선택하세요"}
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
theme={(reactSelectTheme) => ({
|
||||
...reactSelectTheme,
|
||||
colors: {
|
||||
...theme.colors,
|
||||
neutral0: theming
|
||||
.theme(currentTheme, {
|
||||
light: theming.light.backgroundColor1,
|
||||
dark: theming.dark.backgroundColor1,
|
||||
})
|
||||
.toString(),
|
||||
...reactSelectTheme.colors,
|
||||
neutral0: theme.component.input.color.background.default,
|
||||
neutral5: "hsl(0, 0%, 20%)",
|
||||
neutral10: "hsl(0, 0%, 30%)",
|
||||
neutral20: "hsl(0, 0%, 40%)",
|
||||
|
@ -68,59 +61,42 @@ const TagSelect = (props: TagSelectProps) => {
|
|||
styles={{
|
||||
option: (styles) => ({
|
||||
...styles,
|
||||
backgroundColor: theming
|
||||
.theme(currentTheme, {
|
||||
light: theming.light.backgroundColor1,
|
||||
dark: theming.dark.backgroundColor1,
|
||||
})
|
||||
.toString(),
|
||||
color: theming
|
||||
.theme(currentTheme, {
|
||||
light: theming.light.color1,
|
||||
dark: theming.dark.color1,
|
||||
})
|
||||
.toString(),
|
||||
backgroundColor: theme.component.input.color.background.default,
|
||||
color: theme.color.text.default,
|
||||
cursor: "pointer",
|
||||
padding: 10,
|
||||
":hover": {
|
||||
backgroundColor: theming
|
||||
.theme(currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})
|
||||
.toString(),
|
||||
backgroundColor: theme.component.input.color.background.itemHover,
|
||||
},
|
||||
}),
|
||||
control: (styles) => ({
|
||||
...styles,
|
||||
backgroundColor: theming
|
||||
.theme(currentTheme, {
|
||||
light: theming.light.backgroundColor1,
|
||||
dark: theming.dark.backgroundColor1,
|
||||
})
|
||||
.toString(),
|
||||
border: theming.theme(currentTheme, {
|
||||
light: "1px solid #ccc",
|
||||
dark: "1px solid #555",
|
||||
}),
|
||||
backgroundColor: theme.component.input.color.background.default,
|
||||
border: `1px solid ${theme.component.input.color.border.default}`,
|
||||
":hover": {
|
||||
border: `1px solid ${theme.component.input.color.border.hover}`,
|
||||
},
|
||||
":focus": {
|
||||
border: `1px solid ${theme.component.input.color.border.focus}`,
|
||||
},
|
||||
}),
|
||||
multiValue: (styles) => ({
|
||||
...styles,
|
||||
color: "white",
|
||||
backgroundColor: theming.light.linkColor,
|
||||
borderRadius: "5px",
|
||||
color: theme.color.text.default,
|
||||
backgroundColor: theme.component.ui.color.background.default,
|
||||
borderRadius: "10px",
|
||||
}),
|
||||
multiValueLabel: (styles) => ({
|
||||
...styles,
|
||||
color: theme.color.text.default,
|
||||
marginLeft: "0.2rem",
|
||||
marginRight: "0.2rem",
|
||||
}),
|
||||
multiValueRemove: (styles) => ({
|
||||
...styles,
|
||||
marginLeft: "0.2rem",
|
||||
marginRight: "0.3rem",
|
||||
cursor: "pointer",
|
||||
color: theme.component.input.color.placeHolder,
|
||||
":hover": {
|
||||
backgroundColor: "white",
|
||||
color: theming.light.linkColor,
|
||||
color: theme.color.text.default,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
|
|
32
apps/blog/src/styles/anchor.ts
Normal file
32
apps/blog/src/styles/anchor.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
color: ${(props) => props.theme.theme.component.anchor.color.default};
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.theme.component.anchor.color.hover};
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: ${(props) => props.theme.theme.component.anchor.color.active};
|
||||
}
|
||||
}
|
||||
|
||||
/* The "#" thingy used beside headers */
|
||||
a.header-anchor {
|
||||
/* compensate for navbar height*/
|
||||
display: inline-block;
|
||||
margin-top: 4.5rem;
|
||||
|
||||
color: ${(props) => props.theme.theme.component.anchor.color.header};
|
||||
}
|
||||
|
||||
/* footnote anchors */
|
||||
a[id^="fnref"] {
|
||||
display: inline;
|
||||
padding-top: 4.5rem;
|
||||
}
|
||||
`
|
19
apps/blog/src/styles/blockQuote.ts
Normal file
19
apps/blog/src/styles/blockQuote.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
blockquote {
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.blockQuote.color.background};
|
||||
border-left: 0.4rem solid
|
||||
${({ theme }) => theme.theme.component.blockQuote.color.borderLeft};
|
||||
padding-top: 0.1rem;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: 0.1rem;
|
||||
padding-left: 1.5rem;
|
||||
|
||||
@media screen and (max-width: ${({ theme }) =>
|
||||
theme.theme.maxDisplayWidth.mobile}) {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
}
|
||||
`
|
37
apps/blog/src/styles/button.tsx
Normal file
37
apps/blog/src/styles/button.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
/* style */
|
||||
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
/* size */
|
||||
|
||||
height: 3rem;
|
||||
min-width: 2.5rem;
|
||||
margin: 0;
|
||||
padding: 0 1rem 0 1rem;
|
||||
|
||||
/* text */
|
||||
|
||||
text-decoration: none;
|
||||
|
||||
/* color */
|
||||
|
||||
color: ${({ theme }) => theme.theme.color.text.default};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.ui.color.background.default};
|
||||
&:hover {
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.ui.color.background.hover};
|
||||
}
|
||||
|
||||
/* animation */
|
||||
|
||||
transition: transform 0.1s linear;
|
||||
`
|
37
apps/blog/src/styles/code.ts
Normal file
37
apps/blog/src/styles/code.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
/* highlight.js code style */
|
||||
${({ theme }) => theme.theme.component.code.block.style}
|
||||
|
||||
/* inline code */
|
||||
:not(pre) > code {
|
||||
font-family: ${({ theme }) => theme.theme.font.monospace};
|
||||
word-wrap: break-word;
|
||||
color: ${({ theme }) => theme.theme.component.code.inline.color.text};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.code.inline.color.background};
|
||||
border: 1px solid
|
||||
${({ theme }) => theme.theme.component.code.inline.color.border};
|
||||
border-radius: 3px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
/* code block */
|
||||
pre > code {
|
||||
font-family: ${(props) => props.theme.theme.font.monospace};
|
||||
border: 1px solid
|
||||
${({ theme }) => theme.theme.component.code.block.color.border};
|
||||
}
|
||||
|
||||
/* // todo: fix highlight not working properly when scrolled horizontally // */
|
||||
.highlighted-line {
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.code.block.color.highlight};
|
||||
|
||||
display: block;
|
||||
min-width: min-content;
|
||||
margin: 0 -1rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
`
|
|
@ -1,102 +0,0 @@
|
|||
/**
|
||||
* from node_modules/highlight.js/styles/atom-one-dark-reasonable.css
|
||||
*/
|
||||
|
||||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
.hljs {
|
||||
color: #abb2bf;
|
||||
background: #282c34;
|
||||
}
|
||||
.hljs-keyword,
|
||||
.hljs-operator,
|
||||
.hljs-pattern-match {
|
||||
color: #f92672;
|
||||
}
|
||||
.hljs-function,
|
||||
.hljs-pattern-match .hljs-constructor {
|
||||
color: #61aeee;
|
||||
}
|
||||
.hljs-function .hljs-params {
|
||||
color: #a6e22e;
|
||||
}
|
||||
.hljs-function .hljs-params .hljs-typing {
|
||||
color: #fd971f;
|
||||
}
|
||||
.hljs-module-access .hljs-module {
|
||||
color: #7e57c2;
|
||||
}
|
||||
.hljs-constructor {
|
||||
color: #e2b93d;
|
||||
}
|
||||
.hljs-constructor .hljs-string {
|
||||
color: #9ccc65;
|
||||
}
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #b18eb1;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-doctag,
|
||||
.hljs-formula {
|
||||
color: #c678dd;
|
||||
}
|
||||
.hljs-deletion,
|
||||
.hljs-name,
|
||||
.hljs-section,
|
||||
.hljs-selector-tag,
|
||||
.hljs-subst {
|
||||
color: #e06c75;
|
||||
}
|
||||
.hljs-literal {
|
||||
color: #56b6c2;
|
||||
}
|
||||
.hljs-addition,
|
||||
.hljs-attribute,
|
||||
.hljs-meta .hljs-string,
|
||||
.hljs-regexp,
|
||||
.hljs-string {
|
||||
color: #98c379;
|
||||
}
|
||||
.hljs-built_in,
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-title.class_ {
|
||||
color: #e6c07b;
|
||||
}
|
||||
.hljs-attr,
|
||||
.hljs-number,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable {
|
||||
color: #d19a66;
|
||||
}
|
||||
.hljs-bullet,
|
||||
.hljs-link,
|
||||
.hljs-meta,
|
||||
.hljs-selector-id,
|
||||
.hljs-symbol,
|
||||
.hljs-title {
|
||||
color: #61aeee;
|
||||
}
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* from node_modules/highlight.js/styles/default.css
|
||||
*/
|
||||
|
||||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
.hljs {
|
||||
background: #f0f0f0;
|
||||
color: #444;
|
||||
}
|
||||
.hljs-comment {
|
||||
color: #888;
|
||||
}
|
||||
.hljs-punctuation,
|
||||
.hljs-tag {
|
||||
color: #444a;
|
||||
}
|
||||
.hljs-tag .hljs-attr,
|
||||
.hljs-tag .hljs-name {
|
||||
color: #444;
|
||||
}
|
||||
.hljs-attribute,
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-name,
|
||||
.hljs-selector-tag {
|
||||
font-weight: 700;
|
||||
}
|
||||
.hljs-deletion,
|
||||
.hljs-number,
|
||||
.hljs-quote,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id,
|
||||
.hljs-string,
|
||||
.hljs-template-tag,
|
||||
.hljs-type {
|
||||
color: #800;
|
||||
}
|
||||
.hljs-section,
|
||||
.hljs-title {
|
||||
color: #800;
|
||||
font-weight: 700;
|
||||
}
|
||||
.hljs-link,
|
||||
.hljs-operator,
|
||||
.hljs-regexp,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-symbol,
|
||||
.hljs-template-variable,
|
||||
.hljs-variable {
|
||||
color: #bc6060;
|
||||
}
|
||||
.hljs-literal {
|
||||
color: #78a960;
|
||||
}
|
||||
.hljs-addition,
|
||||
.hljs-built_in,
|
||||
.hljs-bullet,
|
||||
.hljs-code {
|
||||
color: #397300;
|
||||
}
|
||||
.hljs-meta {
|
||||
color: #1f7199;
|
||||
}
|
||||
.hljs-meta .hljs-string {
|
||||
color: #4d99bf;
|
||||
}
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
|
@ -1,285 +1,16 @@
|
|||
import { createGlobalStyle, css } from "styled-components"
|
||||
|
||||
import codeblockLightCSS from "./codeblock-light"
|
||||
import codeblockDarkCSS from "./codeblock-dark"
|
||||
|
||||
import "katex/dist/katex.min.css"
|
||||
|
||||
import theming from "./theming"
|
||||
|
||||
const anchorCSS = css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.linkColor,
|
||||
dark: theming.dark.linkColor,
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.dark.linkColor,
|
||||
dark: theming.light.linkColor,
|
||||
})};
|
||||
}
|
||||
}
|
||||
|
||||
/* The "#" thingy used beside headers */
|
||||
a.header-anchor {
|
||||
/* compensate for navbar height*/
|
||||
display: inline-block;
|
||||
margin-top: 4.5rem;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "lightgray",
|
||||
dark: "lightslategray",
|
||||
})};
|
||||
}
|
||||
|
||||
/* footnote anchors */
|
||||
a[id^="fnref"] {
|
||||
display: inline;
|
||||
padding-top: 4.5rem;
|
||||
}
|
||||
`
|
||||
|
||||
const scrollbarCSS = css`
|
||||
body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-track {
|
||||
border-radius: ${theming.size.x2_small};
|
||||
|
||||
background: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
box-shadow: inset 0 0 5px rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb {
|
||||
border-radius: ${theming.size.x2_small};
|
||||
background: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor2,
|
||||
dark: "#888888",
|
||||
})};
|
||||
box-shadow: inset 0 0 10px rgb(0 0 0 / 20%);
|
||||
}
|
||||
`
|
||||
|
||||
const codeCSS = css`
|
||||
${(props) => {
|
||||
switch (props.theme.currentTheme) {
|
||||
case "dark":
|
||||
return codeblockDarkCSS
|
||||
case "light":
|
||||
return codeblockLightCSS
|
||||
default:
|
||||
return codeblockDarkCSS
|
||||
}
|
||||
}}
|
||||
|
||||
/* line code */
|
||||
:not(pre) > code {
|
||||
font-family: ${theming.font.code};
|
||||
word-wrap: break-word;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color1,
|
||||
dark: theming.dark.color1,
|
||||
})};
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#eee",
|
||||
dark: "#444", // I hope no hardcore christian finds this code
|
||||
})};
|
||||
border: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "1px solid #BBB",
|
||||
dark: "1px solid #666", // especially this
|
||||
})};
|
||||
border-radius: 3px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
/* code block */
|
||||
pre > code {
|
||||
font-family: ${theming.font.code};
|
||||
border: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "1px solid #BBB",
|
||||
dark: "1px solid #555",
|
||||
})};
|
||||
}
|
||||
|
||||
/* // todo: fix highlight not working properly when scrolled horizontally // */
|
||||
.highlighted-line {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#dddddd",
|
||||
dark: "#14161a",
|
||||
})};
|
||||
|
||||
display: block;
|
||||
min-width: min-content;
|
||||
margin: 0 -1rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
`
|
||||
|
||||
const kbdCSS = css`
|
||||
/* https://www.rgagnon.com/jsdetails/js-nice-effect-the-KBD-tag.html */
|
||||
kbd {
|
||||
margin: 0px 0.1em;
|
||||
padding: 0.1em 0.6em;
|
||||
border-radius: 3px;
|
||||
border: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "1px solid #CCCCCC",
|
||||
dark: "1px solid #555555",
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#333333",
|
||||
dark: "white",
|
||||
})};
|
||||
line-height: 1.4;
|
||||
font-size: 13.5px;
|
||||
display: inline-block;
|
||||
box-shadow: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "0px 1px 0px rgba(0,0,0,0.2), inset 0px 0px 0px 2px white",
|
||||
dark: "0px 1px 0px rgba(255,255,255,0.3), inset 0px 0px 0px 2px black",
|
||||
})};
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#F7F7F7",
|
||||
dark: "black",
|
||||
})};
|
||||
}
|
||||
`
|
||||
|
||||
const tableCSS = css`
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 8px;
|
||||
border: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "1px solid #ddd",
|
||||
dark: "1px solid #777777",
|
||||
})};
|
||||
}
|
||||
|
||||
/* table alternating color */
|
||||
tr:nth-child(even) {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#f2f2f2",
|
||||
dark: "#21272E",
|
||||
})};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const blockquoteCSS = css`
|
||||
blockquote {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "rgba(0, 0, 0, 5%)",
|
||||
dark: "rgba(255, 255, 255, 7%)",
|
||||
})};
|
||||
border-left: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "0.4rem solid rgba(0, 0, 0, 10%)",
|
||||
dark: "0.4rem solid rgba(255, 255, 255, 30%)",
|
||||
})};
|
||||
padding-top: 0.1rem;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: 0.1rem;
|
||||
padding-left: 1.5rem;
|
||||
|
||||
@media screen and (max-width: ${theming.size.screen_size1}) {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const hrCSS = css`
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
`
|
||||
|
||||
const headerCSS = css`
|
||||
/* intentionally left out h1 */
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-top: -2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1rem;
|
||||
text-indent: 0.5rem;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
text-indent: 1rem;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
text-indent: 1.5rem;
|
||||
}
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
text-indent: 2rem;
|
||||
}
|
||||
`
|
||||
|
||||
const markCSS = css`
|
||||
mark {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "rgba(255, 255, 0, 75%)",
|
||||
dark: "rgba(255, 255, 0, 50%)",
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
}
|
||||
`
|
||||
|
||||
const katexCSS = css`
|
||||
// prevent overflowing on small displays
|
||||
.katex-html {
|
||||
overflow: auto;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
`
|
||||
import anchorCSS from "./anchor"
|
||||
import scrollbarCSS from "./scrollbar"
|
||||
import codeCSS from "./code"
|
||||
import kbdCSS from "./kbd"
|
||||
import tableCSS from "./table"
|
||||
import blockquoteCSS from "./blockQuote"
|
||||
import hrCSS from "./hr"
|
||||
import headerCSS from "./header"
|
||||
import markCSS from "./mark"
|
||||
import katexCSS from "./katex"
|
||||
|
||||
const globalCSS = css`
|
||||
body {
|
||||
|
@ -303,24 +34,16 @@ const globalCSS = css`
|
|||
/* text */
|
||||
|
||||
line-height: 2rem;
|
||||
font-size: ${theming.size.medium};
|
||||
font-family: ${theming.font.regular};
|
||||
font-size: 1rem;
|
||||
font-family: ${({ theme }) => theme.theme.font.sansSerif};
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* color */
|
||||
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor1,
|
||||
dark: theming.dark.backgroundColor1,
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color1,
|
||||
dark: theming.dark.color1,
|
||||
})};
|
||||
background-color: ${({ theme }) => theme.theme.color.background};
|
||||
color: ${({ theme }) => theme.theme.color.text.default};
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
37
apps/blog/src/styles/header.ts
Normal file
37
apps/blog/src/styles/header.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
/* intentionally left out h1 */
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-top: -2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1rem;
|
||||
text-indent: 0.5rem;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
text-indent: 1rem;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
text-indent: 1.5rem;
|
||||
}
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
text-indent: 2rem;
|
||||
}
|
||||
`
|
8
apps/blog/src/styles/hr.ts
Normal file
8
apps/blog/src/styles/hr.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
`
|
9
apps/blog/src/styles/katex.ts
Normal file
9
apps/blog/src/styles/katex.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
// prevent overflowing on small displays
|
||||
.katex-html {
|
||||
overflow: auto;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
`
|
21
apps/blog/src/styles/kbd.ts
Normal file
21
apps/blog/src/styles/kbd.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
/* https://www.rgagnon.com/jsdetails/js-nice-effect-the-KBD-tag.html */
|
||||
kbd {
|
||||
margin: 0px 0.1em;
|
||||
padding: 0.1em 0.6em;
|
||||
border-radius: 3px;
|
||||
border: 1px solid ${({ theme }) => theme.theme.component.kbd.color.border};
|
||||
color: ${({ theme }) => theme.theme.component.kbd.color.text};
|
||||
line-height: 1.4;
|
||||
font-size: 13.5px;
|
||||
display: inline-block;
|
||||
box-shadow: 0px 1px 0px
|
||||
${({ theme }) => theme.theme.component.kbd.color.outerShadow},
|
||||
inset 0px 0px 0px 2px
|
||||
${({ theme }) => theme.theme.component.kbd.color.innerShadow};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.kbd.color.background};
|
||||
}
|
||||
`
|
9
apps/blog/src/styles/mark.ts
Normal file
9
apps/blog/src/styles/mark.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
mark {
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.mark.color.background};
|
||||
color: ${({ theme }) => theme.theme.component.mark.color.text};
|
||||
}
|
||||
`
|
21
apps/blog/src/styles/scrollbar.ts
Normal file
21
apps/blog/src/styles/scrollbar.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
body::-webkit-scrollbar {
|
||||
width: ${(props) => props.theme.theme.component.scrollbar.width};
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-track {
|
||||
border-radius: ${(props) =>
|
||||
props.theme.theme.component.scrollbar.borderRadius};
|
||||
background: ${(props) => props.theme.theme.component.scrollbar.color.track};
|
||||
box-shadow: inset 0 0 5px rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb {
|
||||
border-radius: ${(props) =>
|
||||
props.theme.theme.component.scrollbar.borderRadius};
|
||||
background: ${(props) => props.theme.theme.component.scrollbar.color.thumb};
|
||||
box-shadow: inset 0 0 10px rgb(0 0 0 / 20%);
|
||||
}
|
||||
`
|
22
apps/blog/src/styles/table.ts
Normal file
22
apps/blog/src/styles/table.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { css } from "styled-components"
|
||||
|
||||
export default css`
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 8px;
|
||||
border: 1px solid
|
||||
${({ theme }) => theme.theme.component.table.color.border};
|
||||
}
|
||||
|
||||
/* table alternating color */
|
||||
tr:nth-child(even) {
|
||||
background-color: ${({ theme }) =>
|
||||
theme.theme.component.table.color.even};
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,107 +0,0 @@
|
|||
/** theming.ts
|
||||
* stores values that are used across
|
||||
* It makes changing values easier
|
||||
*/
|
||||
|
||||
import { css } from "styled-components"
|
||||
|
||||
// not declared in the export object so the export object can refer to it
|
||||
function theme(
|
||||
currentTheme: string,
|
||||
values: { [key: string]: string | number }
|
||||
) {
|
||||
return values[currentTheme]
|
||||
}
|
||||
|
||||
const theming = {
|
||||
theme: theme,
|
||||
font: {
|
||||
regular: "'Noto Sans KR', sans-serif",
|
||||
code: "'Source Code Pro', monospace",
|
||||
},
|
||||
size: {
|
||||
x2_small: "3px",
|
||||
small: 0,
|
||||
medium: "1rem",
|
||||
large: 0,
|
||||
x_large: 0,
|
||||
screen_size1: "1000px",
|
||||
screen_size2: "1500px",
|
||||
},
|
||||
dark: {
|
||||
backgroundColor0: "#202225",
|
||||
backgroundColor1: "#36393F",
|
||||
backgroundColor2: "#2F3136",
|
||||
color0: "#FFFFFF",
|
||||
color1: "#EEEEEE",
|
||||
color2: "#CCC",
|
||||
linkColor: "#66AAFF",
|
||||
},
|
||||
light: {
|
||||
backgroundColor0: "#FFFFFF",
|
||||
backgroundColor1: "#F7F7F7",
|
||||
backgroundColor2: "#DDDDDD",
|
||||
color0: "#000000",
|
||||
color1: "#111111",
|
||||
color2: "#555",
|
||||
linkColor: "#4592F7",
|
||||
},
|
||||
styles: {
|
||||
hoverCard: css`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
|
||||
box-shadow: ${(props) =>
|
||||
theme(props.theme.currentTheme, {
|
||||
light: "0 4px 10px rgb(0 0 0 / 25%)",
|
||||
dark: "0 4px 10px rgb(255 255 255 / 20%)",
|
||||
})};
|
||||
}
|
||||
`,
|
||||
navbarButtonStyle: css`
|
||||
/* style */
|
||||
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* size */
|
||||
|
||||
height: 100%;
|
||||
min-width: 2.5rem;
|
||||
margin: 0;
|
||||
padding: 0 1rem 0 1rem;
|
||||
|
||||
/* text */
|
||||
|
||||
text-decoration: none;
|
||||
|
||||
/* color */
|
||||
|
||||
color: ${(props) =>
|
||||
theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "#CFD0D0",
|
||||
})};
|
||||
background-color: ${(props) =>
|
||||
theme(props.theme.currentTheme, {
|
||||
light: "white",
|
||||
dark: "#202225",
|
||||
})};
|
||||
|
||||
/* animation */
|
||||
|
||||
transition: transform 0.1s linear;
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
theme(props.theme.currentTheme, {
|
||||
light: "#EEEEEE",
|
||||
dark: "#36393F",
|
||||
})};
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
export default theming
|
55
apps/blog/src/theme.tsx
Normal file
55
apps/blog/src/theme.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
// import "./DarkMode.css"
|
||||
import { ChangeEventHandler } from "react"
|
||||
|
||||
/* NEW (START) */
|
||||
const setDark = () => {
|
||||
localStorage.setItem("theme", "dark")
|
||||
document.documentElement.setAttribute("data-theme", "dark")
|
||||
}
|
||||
|
||||
const setLight = () => {
|
||||
localStorage.setItem("theme", "light")
|
||||
document.documentElement.setAttribute("data-theme", "light")
|
||||
}
|
||||
|
||||
const storedTheme = localStorage.getItem("theme")
|
||||
|
||||
const prefersDark =
|
||||
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
|
||||
const defaultDark =
|
||||
storedTheme === "dark" || (storedTheme === null && prefersDark)
|
||||
|
||||
if (defaultDark) {
|
||||
setDark()
|
||||
}
|
||||
|
||||
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
if (e.target.checked) {
|
||||
setDark()
|
||||
} else {
|
||||
setLight()
|
||||
}
|
||||
}
|
||||
/* NEW (END) */
|
||||
|
||||
const DarkMode = () => {
|
||||
return (
|
||||
<div className="toggle-theme-wrapper">
|
||||
<span>☀️</span>
|
||||
<label className="toggle-theme" htmlFor="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checkbox"
|
||||
// NEW
|
||||
onChange={toggleTheme}
|
||||
defaultChecked={defaultDark}
|
||||
/>
|
||||
<div className="slider round"></div>
|
||||
</label>
|
||||
<span>🌒</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DarkMode
|
|
@ -1,5 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"plugins": [
|
||||
{
|
||||
"name": "typescript-styled-plugin",
|
||||
"validate": false
|
||||
}
|
||||
],
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import "styled-components"
|
||||
import type { SiteTheme } from "../src/globalContext"
|
||||
import type { Theme } from "@developomp-site/theme"
|
||||
import { SiteTheme } from "../src/globalContext"
|
||||
|
||||
declare module "styled-components" {
|
||||
export interface DefaultTheme {
|
||||
currentTheme: SiteTheme
|
||||
setTheme(setThemeTo: SiteTheme): void
|
||||
theme: Theme
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Meta, ColorPalette, ColorItem } from "@storybook/addon-docs"
|
||||
import darkTheme from "@developomp-site/theme/dark"
|
||||
import darkTheme from "@developomp-site/theme/dist/dark.json"
|
||||
|
||||
<Meta title="Colors" />
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue