Merge pull request #84 from unix/themes

docs: keep the theme perferences for visitors
This commit is contained in:
witt
2020-04-11 16:50:23 +08:00
committed by GitHub
14 changed files with 127 additions and 1621 deletions

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useRef, useState } from 'react' import React, { useEffect, useMemo, useRef, useState } from 'react'
import { NormalSizes } from '../utils/prop-types' import { NormalSizes } from '../utils/prop-types'
import useClickAway from '../utils/use-click-away' import useClickAway from '../utils/use-click-away'
import { pickChildByProps, pickChildrenFirst } from '../utils/collections' import { pickChildByProps, pickChildrenFirst } from '../utils/collections'
@@ -12,6 +12,7 @@ import { getSizes } from './styles'
interface Props { interface Props {
disabled?: boolean disabled?: boolean
size?: NormalSizes size?: NormalSizes
value?: string
initialValue?: string initialValue?: string
placeholder?: React.ReactNode | string placeholder?: React.ReactNode | string
icon?: React.ReactNode icon?: React.ReactNode
@@ -32,8 +33,8 @@ type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type SelectProps = Props & typeof defaultProps & NativeAttrs export type SelectProps = Props & typeof defaultProps & NativeAttrs
const Select: React.FC<React.PropsWithChildren<SelectProps>> = ({ const Select: React.FC<React.PropsWithChildren<SelectProps>> = ({
children, size, disabled, initialValue: init, placeholder, children, size, disabled, initialValue: init, value: customValue,
icon: Icon, onChange, className, pure, ...props icon: Icon, onChange, className, pure, placeholder, ...props
}) => { }) => {
const theme = useTheme() const theme = useTheme()
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
@@ -63,6 +64,11 @@ const Select: React.FC<React.PropsWithChildren<SelectProps>> = ({
useClickAway(ref, () => setVisible(false)) useClickAway(ref, () => setVisible(false))
useEffect(() => {
if (customValue === undefined) return
setValue(customValue)
}, [customValue])
const selectedChild = useMemo(() => { const selectedChild = useMemo(() => {
const [, optionChildren] = pickChildByProps(children, 'value', value) const [, optionChildren] = pickChildByProps(children, 'value', value)
const child = pickChildrenFirst(optionChildren) const child = pickChildrenFirst(optionChildren)

View File

@@ -1,4 +1,4 @@
import React, { MutableRefObject, useState } from 'react' import React, { MutableRefObject, useEffect, useState } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import usePortal from '../utils/use-portal' import usePortal from '../utils/use-portal'
import useResize from '../utils/use-resize' import useResize from '../utils/use-resize'
@@ -54,6 +54,14 @@ const Dropdown: React.FC<React.PropsWithChildren<Props>> = React.memo(({
if (!shouldUpdatePosition) return if (!shouldUpdatePosition) return
updateRect() updateRect()
}) })
useEffect(() => {
if (!parent || !parent.current) return
parent.current.addEventListener('mouseenter', updateRect)
return () => {
if (!parent || !parent.current) return
parent.current.removeEventListener('mouseenter', updateRect)
}
}, [parent])
const clickHandler = (event: React.MouseEvent<HTMLDivElement>) => { const clickHandler = (event: React.MouseEvent<HTMLDivElement>) => {
event.stopPropagation() event.stopPropagation()

View File

@@ -21,6 +21,8 @@ const Controls: React.FC<{}> = React.memo(({
const switchThemes = (type: 'dark' | 'light') => { const switchThemes = (type: 'dark' | 'light') => {
updateCustomTheme({ type }) updateCustomTheme({ type })
if (typeof window === 'undefined' || !window.localStorage) return
window.localStorage.setItem('theme', type)
} }
const switchLanguages = () => { const switchLanguages = () => {
updateChineseState(!isChinese) updateChineseState(!isChinese)
@@ -41,7 +43,7 @@ const Controls: React.FC<{}> = React.memo(({
onClick={redirectGithub} onClick={redirectGithub}
title={isChinese? '代码仓库' : 'Github Repository'}>{isChinese ? '代码仓库' : 'Github'}</Button> title={isChinese? '代码仓库' : 'Github Repository'}>{isChinese ? '代码仓库' : 'Github'}</Button>
<Spacer x={.75} /> <Spacer x={.75} />
<Select size="small" pure onChange={switchThemes} initialValue={isDark ? 'dark' : 'light'} <Select size="small" pure onChange={switchThemes} value={isDark ? 'dark' : 'light'}
title={isChinese ? '切换主题' : 'Switch Themes'}> title={isChinese ? '切换主题' : 'Switch Themes'}>
<Select.Option value="light"> <Select.Option value="light">
<div className="select-content"> <div className="select-content">

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import { useTheme } from 'components' import { useTheme } from 'components'
import Sidebar from './sidebar' import Sidebar from './sidebar'
import TabbarMobile from './sidebar/tabbar-mobile' import TabbarMobile from './sidebar/tabbar-mobile'
@@ -16,6 +16,7 @@ export interface Props {
export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ children }) => { export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ children }) => {
const theme = useTheme() const theme = useTheme()
const [showAfterRender, setShowAfterRender] = useState<boolean>(false)
const { tabbarFixed } = useConfigs() const { tabbarFixed } = useConfigs()
const [, setBodyScroll] = useBodyScroll(null, { scrollLayer: true }) const [, setBodyScroll] = useBodyScroll(null, { scrollLayer: true })
const [expanded, setExpanded] = useState<boolean>(false) const [expanded, setExpanded] = useState<boolean>(false)
@@ -24,6 +25,9 @@ export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ ch
setBodyScroll(!expanded) setBodyScroll(!expanded)
} }
useEffect(() => setShowAfterRender(true), [])
if (!showAfterRender) return null
return ( return (
<div className="layout"> <div className="layout">
<TabbarMobile onClick={mobileTabbarClickHandler} /> <TabbarMobile onClick={mobileTabbarClickHandler} />

View File

@@ -1,8 +1,12 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import MenuLinks from './menu-links' import MenuLinks from './menu-links'
import MenuSticker from './menu-sticker' import MenuSticker from './menu-sticker'
const Menu = () => { const Menu: React.FC<{}> = () => {
const [showAfterRender, setShowAfterRender] = useState<boolean>(false)
useEffect(() => setShowAfterRender(true), [])
if (!showAfterRender) return null
return ( return (
<div> <div>
<MenuLinks /> <MenuLinks />

View File

@@ -57,6 +57,7 @@ const MenuSticker = () => {
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
background-color: ${theme.palette.background};
} }
.nav-fill.active { .nav-fill.active {

10
lib/use-dom-clean.ts Normal file
View File

@@ -0,0 +1,10 @@
import { useEffect } from 'react'
const useDomClean = (): void => {
useEffect(() => {
document.documentElement.removeAttribute('style')
document.body.removeAttribute('style')
}, [])
}
export default useDomClean

View File

@@ -60,7 +60,7 @@
"eslint-plugin-react": "^7.19.0", "eslint-plugin-react": "^7.19.0",
"extract-mdx-metadata": "^1.0.0", "extract-mdx-metadata": "^1.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"next": "^9.3.2", "next": "^9.3.4",
"react": "^16.13.0", "react": "^16.13.0",
"react-color": "^2.18.0", "react-color": "^2.18.0",
"react-dom": "^16.13.0", "react-dom": "^16.13.0",
@@ -71,4 +71,4 @@
"webpack-cli": "^3.3.11" "webpack-cli": "^3.3.11"
}, },
"dependencies": {} "dependencies": {}
} }

View File

@@ -1,18 +1,26 @@
import Head from 'next/head' import Head from 'next/head'
import { NextPage } from 'next' import { NextPage } from 'next'
import { AppProps } from 'next/app' import { AppProps } from 'next/app'
import { useState } from 'react' import React, { useEffect, useState } from 'react'
import { CSSBaseline, ZEITUIProvider, useTheme, ZeitUIThemes } from 'components' import { CSSBaseline, ZEITUIProvider, useTheme, ZeitUIThemes } from 'components'
import Menu from 'lib/components/menu' import Menu from 'lib/components/menu'
import ConfigContext from 'lib/config-provider' import ConfigContext from 'lib/config-provider'
import useDomClean from 'lib/use-dom-clean'
import { DeepPartial } from 'components/utils/types' import { DeepPartial } from 'components/utils/types'
const Application: NextPage<AppProps> = ({ Component, pageProps }) => { const Application: NextPage<AppProps<{}>> = ({ Component, pageProps }) => {
const theme = useTheme() const theme = useTheme()
const [customTheme, setCustomTheme] = useState<DeepPartial<ZeitUIThemes>>({}) const [customTheme, setCustomTheme] = useState<DeepPartial<ZeitUIThemes>>({})
const themeChangeHandle = (theme: DeepPartial<ZeitUIThemes>) => { const themeChangeHandle = (theme: DeepPartial<ZeitUIThemes>) => {
setCustomTheme(theme) setCustomTheme(theme)
} }
useEffect(() => {
const theme = window.localStorage.getItem('theme')
if (theme !== 'dark') return
themeChangeHandle({ type: 'dark' })
}, [])
useDomClean()
return ( return (
<> <>

View File

@@ -22,20 +22,29 @@ class MyDocument extends Document {
<Html> <Html>
<Head /> <Head />
<body> <body>
<Main /> <script dangerouslySetInnerHTML={{ __html: `
<NextScript /> (function(){
<script async src={`https://www.googletagmanager.com/gtag/js?id=UA-110371817-12`} /> if (!window.localStorage) return;
<script if (window.localStorage.getItem('theme') === 'dark') {
async document.documentElement.style.background = '#000';
dangerouslySetInnerHTML={{ document.body.style.background = '#000';
__html: ` };
window.dataLayer = window.dataLayer || []; })()
function gtag(){dataLayer.push(arguments);} `}} />
gtag('js', new Date()); <Main />
gtag('config', 'UA-110371817-12'); <NextScript />
` <script async src="https://www.googletagmanager.com/gtag/js?id=UA-110371817-12" />
}} <script
/> async
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-110371817-12');
`
}}
/>
</body> </body>
</Html> </Html>
) )

View File

@@ -167,7 +167,7 @@ Retrieve text input from a user.
| Attribute | Description | Type | Accepted values | Default | Attribute | Description | Type | Accepted values | Default
| ---------- | ---------- | ---- | -------------- | ------ | | ---------- | ---------- | ---- | -------------- | ------ |
| **value** | input value | `string` | - | - | | **value** | input value | `string` | - | - |
| **initialValue** | inital value | `string` | - | - | | **initialValue** | initial value | `string` | - | - |
| **placeholder** | placeholder | `string` | - | - | | **placeholder** | placeholder | `string` | - | - |
| **size** | input size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` | | **size** | input size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` |
| **status** | current status | `NormalTypes` | `'default', 'secondary', 'success', 'warning', 'error'` | `default` | | **status** | current status | `NormalTypes` | `'default', 'secondary', 'success', 'warning', 'error'` | `default` |

View File

@@ -75,7 +75,8 @@ Display a dropdown list of items.
| Attribute | Description | Type | Accepted values | Default | Attribute | Description | Type | Accepted values | Default
| ---------- | ---------- | ---- | -------------- | ------ | | ---------- | ---------- | ---- | -------------- | ------ |
| **initialValue** | selected value | `string` | - | - | | **value** | selected value | `string` | - | - |
| **initialValue** | initial value | `string` | - | - |
| **placeholder** | placeholder string | `string` | - | - | | **placeholder** | placeholder string | `string` | - | - |
| **size** | select component size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` | | **size** | select component size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` |
| **icon** | icon component | `ReactNode` | - | `SVG Component` | | **icon** | icon component | `ReactNode` | - | `SVG Component` |

View File

@@ -74,6 +74,7 @@ export const meta = {
| 属性 | 描述 | 类型 | 推荐值 | 默认 | 属性 | 描述 | 类型 | 推荐值 | 默认
| ---------- | ---------- | ---- | -------------- | ------ | | ---------- | ---------- | ---- | -------------- | ------ |
| **value** | 手动设置选择器的值 | `string` | - | - |
| **initialValue** | 选择器初始值 | `string` | - | - | | **initialValue** | 选择器初始值 | `string` | - | - |
| **placeholder** | 占位文本内容 | `string` | - | - | | **placeholder** | 占位文本内容 | `string` | - | - |
| **size** | 选择器组件大小 | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` | | **size** | 选择器组件大小 | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` |

1638
yarn.lock

File diff suppressed because it is too large Load Diff