Merge pull request #12 from unix/mobile

docs: improve the layout of mobile screen
This commit is contained in:
witt
2020-03-24 13:07:15 +08:00
committed by GitHub
10 changed files with 247 additions and 107 deletions

View File

@@ -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>
</>
)

View File

@@ -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>
)

View 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)

View File

@@ -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>
)

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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>
</>

View 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

View File

@@ -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};

View File

@@ -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')