mirror of
https://github.com/zhigang1992/react.git
synced 2026-02-11 09:11:07 +08:00
Merge pull request #12 from unix/mobile
docs: improve the layout of mobile screen
This commit is contained in:
@@ -25,6 +25,10 @@ const Attributes: React.FC<React.PropsWithChildren<AttributesProps>> = React.mem
|
||||
<Link color target="_blank" className="attributes-link" href={link} rel="nofollow">Edit this page on GitHub</Link>
|
||||
|
||||
<style global jsx>{`
|
||||
.attr table {
|
||||
margin-right: ${theme.layout.gap};
|
||||
}
|
||||
|
||||
.attr pre {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -95,6 +99,18 @@ const Attributes: React.FC<React.PropsWithChildren<AttributesProps>> = React.mem
|
||||
.attr td:nth-child(1) {
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.attr {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.attr::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import useTheme from 'components/styles/use-theme'
|
||||
import { Button, Col, Image } from 'components'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Button, useTheme, Spacer } from 'components'
|
||||
import useConfigs from 'lib/states/use-config'
|
||||
import MoonIcon from './icons/moon'
|
||||
import SunIcon from './icons/sun'
|
||||
@@ -10,10 +9,7 @@ const Controls: React.FC<{}> = React.memo(({
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const config = useConfigs()
|
||||
const timer = useRef<number>()
|
||||
const [hover, setHover] = useState<boolean>(false)
|
||||
const isDark = useMemo(() => theme.type === 'dark', [theme.type])
|
||||
const hideLogo = useMemo(() => isDark || hover, [isDark, hover])
|
||||
const switchThemes = useCallback(() => {
|
||||
const isDark = theme.type === 'dark'
|
||||
config.onChange && config.onChange(!isDark)
|
||||
@@ -25,29 +21,10 @@ const Controls: React.FC<{}> = React.memo(({
|
||||
}
|
||||
}
|
||||
|
||||
const hoverHandler = (next: boolean) => {
|
||||
if (typeof window === 'undefined') return
|
||||
if (next) {
|
||||
timer.current && clearTimeout(timer.current)
|
||||
return setHover(true)
|
||||
}
|
||||
|
||||
timer.current = window.setTimeout(() => {
|
||||
setHover(false)
|
||||
clearTimeout(timer.current)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="controls">
|
||||
<Col className="line" span={18}
|
||||
onMouseEnter={() => hoverHandler(true)}
|
||||
onMouseLeave={() => hoverHandler(false)}>
|
||||
<Image draggable={false} width={150} height={55} src="/images/zeit-react-logo.png" />
|
||||
</Col>
|
||||
<div className="tools"
|
||||
onMouseEnter={() => hoverHandler(true)}
|
||||
onMouseLeave={() => hoverHandler(false)}>
|
||||
<div className="tools">
|
||||
<Spacer x={.5} />
|
||||
<Button className="button" auto type="abort"
|
||||
onClick={switchThemes}>
|
||||
{isDark ? <SunIcon width={16} height={16} /> : <MoonIcon width={16} height={16} />}
|
||||
@@ -61,11 +38,11 @@ const Controls: React.FC<{}> = React.memo(({
|
||||
.controls {
|
||||
height: 110px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column-reverse;
|
||||
margin: 0;
|
||||
padding-bottom: ${theme.layout.gapHalf};
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.controls :global(.button) {
|
||||
@@ -80,20 +57,22 @@ const Controls: React.FC<{}> = React.memo(({
|
||||
|
||||
.tools {
|
||||
display: flex;
|
||||
padding: 7px 0;
|
||||
height: 54px;
|
||||
height: 2.5rem;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 0;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tools:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 1.25rem;
|
||||
width: .3125rem;
|
||||
background-color: ${theme.palette.accents_2};
|
||||
}
|
||||
|
||||
.controls :global(.line) {
|
||||
width: 150px;
|
||||
height: ${hideLogo ? 0 : '55px'};
|
||||
opacity: ${hideLogo ? 0 : 1};
|
||||
height: 55px;
|
||||
cursor: pointer;
|
||||
background-color: ${theme.palette.background};
|
||||
position: relative;
|
||||
@@ -101,6 +80,14 @@ const Controls: React.FC<{}> = React.memo(({
|
||||
transition: all 200ms ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.controls {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
|
||||
45
lib/components/icons/toggle.tsx
Normal file
45
lib/components/icons/toggle.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import withDefaults from 'components/utils/with-defaults'
|
||||
import useTheme from 'components/styles/use-theme'
|
||||
|
||||
interface Props {
|
||||
width?: number
|
||||
height?: number
|
||||
color?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
width: 15,
|
||||
height: 15,
|
||||
}
|
||||
|
||||
export type ToggleIconProps = Props & typeof defaultProps & React.SVGAttributes<any>
|
||||
|
||||
const ToggleIcon: React.FC<ToggleIconProps> = React.memo(({
|
||||
width, height, color, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<svg width={width} height={height} {...props} viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5"
|
||||
strokeLinecap="round" strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision"
|
||||
className="bar-toggle">
|
||||
<path d="M4 21v-7" />
|
||||
<path d="M4 10V3" />
|
||||
<path d="M12 21v-9" />
|
||||
<path d="M12 8V3" />
|
||||
<path d="M20 21v-5" />
|
||||
<path d="M20 12V3" />
|
||||
<path d="M1 14h6" />
|
||||
<path d="M9 8h6" />
|
||||
<path d="M17 16h6" />
|
||||
|
||||
<style jsx>{`
|
||||
svg {
|
||||
color: ${color || theme.palette.foreground};
|
||||
}
|
||||
`}</style>
|
||||
</svg>
|
||||
)
|
||||
})
|
||||
|
||||
export default withDefaults(ToggleIcon, defaultProps)
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { withRouter, Router } from 'next/router'
|
||||
import { useTheme } from 'components/index'
|
||||
import Sidebar from './sidebar'
|
||||
import Controls from 'lib/components/controls'
|
||||
import sides from 'lib/data/metadata.json'
|
||||
import TabbarMobile from './sidebar/tabbar-mobile'
|
||||
|
||||
export interface Meta {
|
||||
title: string
|
||||
@@ -17,9 +18,14 @@ export interface Props {
|
||||
|
||||
export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ children }) => {
|
||||
const theme = useTheme()
|
||||
const [expanded, setExpanded] = useState<boolean>(false)
|
||||
const mobileTabbarClickHandler = () => {
|
||||
setExpanded(!expanded)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="layout">
|
||||
<TabbarMobile onClick={mobileTabbarClickHandler} />
|
||||
<aside className="sidebar">
|
||||
<Controls />
|
||||
<Sidebar sides={sides}/>
|
||||
@@ -32,7 +38,7 @@ export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ ch
|
||||
<style jsx>{`
|
||||
.layout {
|
||||
min-height: 100vh;
|
||||
max-width: 1100px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 0 ${theme.layout.gap};
|
||||
display: flex;
|
||||
@@ -40,7 +46,7 @@ export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ ch
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
margin-right: 50px;
|
||||
margin-right: 20px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-flex-shrink: 0;
|
||||
height: 100%;
|
||||
@@ -48,20 +54,53 @@ export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ ch
|
||||
}
|
||||
|
||||
.side-shadow {
|
||||
width: 250px;
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
max-width: calc(100% - 250px);
|
||||
max-width: calc(100% - 220px);
|
||||
flex-direction: column;
|
||||
padding-left: 20px;
|
||||
padding-top: 25px;
|
||||
flex: 0 0 100%;
|
||||
padding-bottom: 150px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.layout {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
padding: 5rem 1rem;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
width: 100vw;
|
||||
height: ${expanded ? '100vh' : '0'};
|
||||
background-color: ${theme.palette.background};
|
||||
padding: 50px 12vw 0;
|
||||
overflow: hidden;
|
||||
transition: height 250ms ease;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 90vw;
|
||||
max-width: 90vw;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.side-shadow {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,25 +4,24 @@ import useTheme from 'components/styles/use-theme'
|
||||
|
||||
export interface Props {
|
||||
name: string
|
||||
onClick: React.MouseEventHandler<HTMLElement>
|
||||
}
|
||||
|
||||
const ActiveCatalog: React.FC<Props> = React.memo(
|
||||
({ name, onClick, ...props }) => {
|
||||
({ name, ...props }) => {
|
||||
const theme = useTheme()
|
||||
const { pathname } = useRouter()
|
||||
const isActive = pathname.includes(`/${name}/`)
|
||||
|
||||
return (
|
||||
<span {...props} onClick={onClick} className={isActive ? 'active' : ''}>
|
||||
<span {...props} className={isActive ? 'active' : ''}>
|
||||
{name}
|
||||
|
||||
<style jsx>{`
|
||||
span {
|
||||
font-size: .8rem;
|
||||
font-size: .9rem;
|
||||
transition: all .2s ease;
|
||||
color: ${theme.palette.accents_3};
|
||||
text-transform: uppercase;
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
|
||||
.active {
|
||||
|
||||
@@ -45,11 +45,14 @@ export const Sidebar: React.FC<SideGroupProps> = React.memo(({ sides }) => {
|
||||
width: 100%;
|
||||
padding-bottom: ${theme.layout.gap};
|
||||
}
|
||||
|
||||
|
||||
.box {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: calc(100vh - 140px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.box::-webkit-scrollbar {
|
||||
@@ -58,7 +61,13 @@ export const Sidebar: React.FC<SideGroupProps> = React.memo(({ sides }) => {
|
||||
}
|
||||
|
||||
.box>:global(.item) {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: ${theme.layout.gap};
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.box {
|
||||
padding-top: calc(1.5 * ${theme.layout.gap});
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, ReactElement, useCallback } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import ActiveLink from './active-link'
|
||||
import ArrowIcon from '../icons/arrow'
|
||||
import ActiveCatalog from './active-catalog'
|
||||
import { useTheme } from 'components'
|
||||
|
||||
@@ -14,43 +13,27 @@ export interface SideItemProps {
|
||||
sides: Array<Sides>
|
||||
}
|
||||
|
||||
const getChildrenHeight = (children?: Sides | Array<Sides>) => {
|
||||
if (!children || !Array.isArray(children)) return 0
|
||||
return children.length * 36
|
||||
}
|
||||
|
||||
const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
children, sides,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const [childrenActived, setChildrenActived] = useState<Array<boolean>>(() => sides.map(() => true))
|
||||
|
||||
const next = useCallback((index) => {
|
||||
setChildrenActived(childrenActived.map((bool, i) => index !== i ? bool : !bool))
|
||||
}, [childrenActived])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{sides.map((side, index) => {
|
||||
const childrenHeight = getChildrenHeight(side.children)
|
||||
const isActived = childrenActived[index]
|
||||
|
||||
return (
|
||||
<div key={`${side.name}-${index}`} className="item">
|
||||
<div className="link">
|
||||
{side.children && <div className="icon"><ArrowIcon rotate={isActived? 90 : 0}/></div>}
|
||||
{
|
||||
!side.url
|
||||
? <ActiveCatalog name={side.name} onClick={() => next(index)} />
|
||||
: (
|
||||
<ActiveLink href={side.url} index={index} total={sides.length}>
|
||||
<a>{side.name}</a>
|
||||
</ActiveLink>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{side.children && <div className="children" style={{ height: isActived ? childrenHeight + 'px' : 0 }}>
|
||||
<span className="line" />
|
||||
{!side.url && <ActiveCatalog name={side.name} />}
|
||||
{side.url && (
|
||||
<div className="link">
|
||||
<ActiveLink href={side.url} index={index} total={sides.length}>
|
||||
<a>{side.name}</a>
|
||||
</ActiveLink>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{side.children && <div className="children">
|
||||
{/*<span className="line" />*/}
|
||||
{React.cloneElement(children as ReactElement, {
|
||||
sides: side.children,
|
||||
})}
|
||||
@@ -64,35 +47,33 @@ const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.link {
|
||||
width: 100%;
|
||||
color: ${theme.palette.accents_5};
|
||||
display: flex;
|
||||
height: 36px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
cursor: pointer;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
|
||||
.link :global(a) {
|
||||
color: ${theme.palette.accents_6};
|
||||
padding-left: .7rem;
|
||||
font-size: 14px;
|
||||
color: ${theme.palette.accents_7};
|
||||
font-size: .9rem;
|
||||
padding: 0 ${theme.layout.gapQuarter};
|
||||
transition: color 200ms ease;
|
||||
}
|
||||
|
||||
.link :global(a.active) {
|
||||
color: ${theme.palette.success};
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 10px;
|
||||
margin-left: 5px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
color: ${theme.palette.background};
|
||||
font-weight: 700;
|
||||
background-color: ${theme.palette.accents_7};
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.children {
|
||||
@@ -100,8 +81,7 @@ const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
margin-left: ${theme.layout.gapHalf};
|
||||
padding-left: ${theme.layout.gap};
|
||||
padding-left: ${theme.layout.gapHalf};
|
||||
overflow: hidden;
|
||||
transition: all .2s ease-in-out;
|
||||
position: relative;
|
||||
@@ -111,13 +91,8 @@ const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 1px;
|
||||
bottom: 9px;
|
||||
width: 1px;
|
||||
background-color: ${theme.palette.accents_2};
|
||||
@media only screen and (max-width: 767px) {
|
||||
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
|
||||
66
lib/components/sidebar/tabbar-mobile.tsx
Normal file
66
lib/components/sidebar/tabbar-mobile.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import { Button, useTheme } from 'components'
|
||||
import ToggleIcon from '../icons/toggle'
|
||||
|
||||
interface Props {
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
}
|
||||
|
||||
const TabbarMobile:React.FC<Props> = ({ onClick }) => {
|
||||
const theme = useTheme()
|
||||
const handler = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
onClick && onClick(event)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tabbar">
|
||||
<Button className="toggle" auto type="abort" onClick={handler}>
|
||||
<ToggleIcon color={theme.palette.accents_7} />
|
||||
</Button>
|
||||
<span>ZEIT-UI React</span>
|
||||
<style jsx>{`
|
||||
.tabbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10000;
|
||||
height: 3.7rem;
|
||||
background-color: ${theme.palette.background};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 calc(${theme.layout.gap} * 2);
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid ${theme.palette.border};
|
||||
}
|
||||
|
||||
.tabbar :global(.toggle) {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
span {
|
||||
color: ${theme.palette.accents_7};
|
||||
font-size: .75rem;
|
||||
display: inline-flex;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 767px) {
|
||||
.tabbar {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
top: -1000px;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabbarMobile
|
||||
@@ -28,7 +28,7 @@ const Application: NextPage<AppProps> = ({ Component, pageProps }) => {
|
||||
<meta property="og:description" content="React implementation for ZEIT design." />
|
||||
<meta property="og:image" content="https://user-images.githubusercontent.com/11304944/76085431-fd036480-5fec-11ea-8412-9e581425344a.png" />
|
||||
<meta property="twitter:image" content="https://user-images.githubusercontent.com/11304944/76085431-fd036480-5fec-11ea-8412-9e581425344a.png" />
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=5, minimum-scale=1, viewport-fit=cover" />
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, viewport-fit=cover" />
|
||||
</Head>
|
||||
<ZEITUIProvider theme={{ type: themeType }}>
|
||||
<CSSBaseline />
|
||||
@@ -76,6 +76,10 @@ const Application: NextPage<AppProps> = ({ Component, pageProps }) => {
|
||||
color: ${theme.palette.accents_3};
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background-color: ${theme.palette.accents_1};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import Introduction from './getting-started/introduction.mdx'
|
||||
import redirect from 'lib/redirect'
|
||||
|
||||
export default Introduction
|
||||
export default redirect('/docs/getting-started/introduction')
|
||||
|
||||
Reference in New Issue
Block a user