mirror of
https://github.com/zhigang1992/react.git
synced 2026-04-23 20:00:56 +08:00
Merge pull request #20 from unix/styles
Improve styles & add body scroll hooks
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import usePortal from '../utils/use-portal'
|
||||
import ModalTitle from './modal-title'
|
||||
@@ -10,6 +10,7 @@ import ModalActions from './modal-actions'
|
||||
import Backdrop from '../shared/backdrop'
|
||||
import { ModalConfig, ModalContext } from './modal-context'
|
||||
import { pickChild } from '../utils/collections'
|
||||
import useBodyScroll from '../utils/use-body-scroll'
|
||||
|
||||
interface Props {
|
||||
disableBackdropClick?: boolean
|
||||
@@ -31,27 +32,30 @@ const Modal: React.FC<React.PropsWithChildren<ModalProps>> = React.memo(({
|
||||
children, disableBackdropClick, onClose, onOpen, open
|
||||
}) => {
|
||||
const portal = usePortal('modal')
|
||||
const [, setBodyHidden] = useBodyScroll()
|
||||
const [visible, setVisible] = useState<boolean>(open)
|
||||
const [withoutActionsChildren, ActionsChildren] = pickChild(children, ModalAction)
|
||||
const hasActions = ActionsChildren && React.Children.count(ActionsChildren) > 0
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
const closeModal = () => {
|
||||
setVisible(false)
|
||||
onClose && onClose()
|
||||
}, [open])
|
||||
const openModal = useCallback(() => {
|
||||
}
|
||||
const openModal = () => {
|
||||
setVisible(true)
|
||||
|
||||
onOpen && onOpen()
|
||||
}, [])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setVisible(open)
|
||||
setBodyHidden(open)
|
||||
}, [open])
|
||||
|
||||
const closeFromBackdrop = useCallback(() => {
|
||||
const closeFromBackdrop = () => {
|
||||
if (disableBackdropClick && hasActions) return
|
||||
closeModal()
|
||||
}, [disableBackdropClick])
|
||||
}
|
||||
|
||||
const modalConfig: ModalConfig = useMemo(() => ({
|
||||
close: closeModal,
|
||||
|
||||
@@ -94,6 +94,7 @@ const ToastItem: React.FC<ToatItemProps> = React.memo(({
|
||||
<style jsx>{`
|
||||
.toast {
|
||||
width: 420px;
|
||||
max-width: 90vw;
|
||||
max-height: 75px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
76
components/utils/use-body-scroll.ts
Normal file
76
components/utils/use-body-scroll.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react'
|
||||
|
||||
export type ElementStackItem = {
|
||||
last: string
|
||||
}
|
||||
|
||||
export type BodyScrollOptions = {
|
||||
scrollLayer: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: BodyScrollOptions = {
|
||||
scrollLayer: false,
|
||||
}
|
||||
|
||||
const elementStack = new Map<HTMLElement, ElementStackItem>()
|
||||
|
||||
const isIos = () => {
|
||||
if (typeof window === 'undefined' || !window.navigator) return false
|
||||
return /iP(ad|hone|od)/.test(window.navigator.platform)
|
||||
}
|
||||
|
||||
const touchHandler = (event: TouchEvent): boolean => {
|
||||
if (event.touches.length > 1) return true
|
||||
event.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
const useBodyScroll = (
|
||||
elementRef?: MutableRefObject<HTMLElement> | null,
|
||||
options?: BodyScrollOptions,
|
||||
): [boolean, Dispatch<SetStateAction<boolean>>] => {
|
||||
if (typeof document === 'undefined') return [false, (t: boolean) => t]
|
||||
const elRef = elementRef || useRef<HTMLElement>(document.body)
|
||||
const [hidden, setHidden] = useState<boolean>(false)
|
||||
const safeOptions = {
|
||||
...defaultOptions,
|
||||
...(options || {}),
|
||||
}
|
||||
|
||||
|
||||
// don't prevent touch event when layer contain scroll
|
||||
const isIosWithCustom = () => {
|
||||
if (safeOptions.scrollLayer) return false
|
||||
return isIos()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const lastOverflow = elRef.current.style.overflow
|
||||
if (hidden) {
|
||||
if (elementStack.has(elRef.current)) return
|
||||
if (!isIosWithCustom()) {
|
||||
elRef.current.style.overflow = 'hidden'
|
||||
} else {
|
||||
document.addEventListener('touchmove', touchHandler, { passive: false })
|
||||
}
|
||||
elementStack.set(elRef.current, {
|
||||
last: lastOverflow,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// reset element overflow
|
||||
if (!elementStack.has(elRef.current)) return
|
||||
if (!isIosWithCustom()) {
|
||||
const store = elementStack.get(elRef.current) || { last: 'auto' }
|
||||
elRef.current.style.overflow = store.last
|
||||
} else {
|
||||
document.removeEventListener('touchmove', touchHandler)
|
||||
}
|
||||
elementStack.delete(elRef.current)
|
||||
}, [hidden, elRef.current])
|
||||
|
||||
return [hidden, setHidden]
|
||||
}
|
||||
|
||||
export default useBodyScroll
|
||||
@@ -5,6 +5,7 @@ import Sidebar from './sidebar'
|
||||
import Controls from 'lib/components/controls'
|
||||
import sides from 'lib/data/metadata.json'
|
||||
import TabbarMobile from './sidebar/tabbar-mobile'
|
||||
import useBodyScroll from 'components/utils/use-body-scroll'
|
||||
|
||||
export interface Meta {
|
||||
title: string
|
||||
@@ -18,9 +19,11 @@ export interface Props {
|
||||
|
||||
export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ children }) => {
|
||||
const theme = useTheme()
|
||||
const [, setBodyScroll] = useBodyScroll(null, { scrollLayer: true })
|
||||
const [expanded, setExpanded] = useState<boolean>(false)
|
||||
const mobileTabbarClickHandler = () => {
|
||||
setExpanded(!expanded)
|
||||
setBodyScroll(!expanded)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -42,6 +45,7 @@ export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ ch
|
||||
margin: 0 auto;
|
||||
padding: 0 ${theme.layout.gap};
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@@ -85,7 +89,7 @@ export const Layout: React.FC<React.PropsWithChildren<Props>> = React.memo(({ ch
|
||||
width: 100vw;
|
||||
height: ${expanded ? '100vh' : '0'};
|
||||
background-color: ${theme.palette.background};
|
||||
padding: 50px 12vw 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
transition: height 250ms ease;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ const editor = (code: string) => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
summary :global(svg) {
|
||||
|
||||
@@ -21,7 +21,6 @@ const ActiveCatalog: React.FC<Props> = React.memo(
|
||||
transition: all .2s ease;
|
||||
color: ${theme.palette.accents_3};
|
||||
text-transform: uppercase;
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
|
||||
.active {
|
||||
|
||||
@@ -66,7 +66,9 @@ export const Sidebar: React.FC<SideGroupProps> = React.memo(({ sides }) => {
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.box {
|
||||
padding-top: calc(1.5 * ${theme.layout.gap});
|
||||
padding: calc(3.5 * ${theme.layout.gap}) 15vw;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
|
||||
@@ -43,11 +43,6 @@ const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
})}
|
||||
<style jsx>{`
|
||||
.item {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -55,7 +50,7 @@ const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
width: 100%;
|
||||
color: ${theme.palette.accents_5};
|
||||
display: flex;
|
||||
height: 36px;
|
||||
height: 2.25rem;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
cursor: pointer;
|
||||
@@ -82,9 +77,9 @@ const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
padding-left: ${theme.layout.gapHalf};
|
||||
overflow: hidden;
|
||||
transition: all .2s ease-in-out;
|
||||
position: relative;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.active-title {
|
||||
@@ -92,7 +87,10 @@ const SideItem: React.FC<React.PropsWithChildren<SideItemProps>> = React.memo(({
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
|
||||
.link {
|
||||
border-bottom: 1px solid ${theme.palette.border};
|
||||
height: 3.5rem;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user