import { HTMLAttributes, FunctionComponent, createContext, useContext, useState, useRef, useLayoutEffect, useCallback, ReactNode } from 'react'; import cls from 'classnames'; import {renderForeground, useOnKey, useSize} from '@core0/utils'; import {RoundButton} from '@core0/round-button'; import {Tooltip} from '@core0/tooltip'; import {Transition} from '@core0/transition'; import {RectButton} from '@core0/rect-button'; import {Icon, IconType} from '@core0/icon'; import {Backdrop} from '@core0/backdrop'; import cn from './sideTray.module.styl'; type MyContext = [SideTrayContextProps, React.Dispatch>]; const SideTrayContext = createContext(null); interface SideTrayContextProps { transition: boolean; hasScrollRemaining: boolean; hasScrolled: boolean; } export interface SideTrayBackdropProps { show: boolean; setShow: (v: boolean) => void; className?: string; transition?: boolean; peek?: boolean; position?: 'cover' | 'global'; children?: ReactNode; } export interface SideTrayProps { show?: boolean; setShow?: (v: boolean) => void; className?: string; onBack?: () => void; transition?: boolean; peek?: boolean; position?: 'cover' | 'global'; children?: ReactNode; } export interface SideTrayContentProps { children: React.ReactNode; className?: string; } export interface SideTrayBodyProps { children: React.ReactNode; className?: string; transition?: boolean; } export interface SideTrayTitleProps extends Pick, 'onClick' | 'className'> { icon?: IconType; title: string; subtitle?: string; className?: string; } export interface SideTrayFooterProps { children: React.ReactNode; className?: string; transition?: boolean; } export const SideTrayBackdrop: FunctionComponent = (props) => { const {show, setShow, peek, className, position, children, transition = true} = props; const onClose = useCallback(() => (setShow ? setShow(false) : undefined), [setShow]); const closeIfOpen = useCallback(() => show && onClose(), [show, onClose]); useOnKey('Escape', onClose); function blockClick() { closeIfOpen() } return renderForeground(
{show && } {children}
); }; export const SideTray: FunctionComponent = (props) => { const { show = true, position = 'global', setShow, onBack, className, children, transition = true, peek = false } = props; const close = useCallback(() => (setShow ? setShow(false) : undefined), [setShow]); const open = useCallback(() => (setShow ? setShow(true) : undefined), [setShow]); const myContext = useState(() => ({transition, hasScrollRemaining: false, hasScrolled: false})); const ctx = myContext[0]; const classNames = cls( { [cn.sideTray]: true, [cn.cover]: position === 'cover', [cn.global]: position === 'global', [cn.tray]: true, [cn.peek]: peek, [cn.show]: show, [cn.hasScrollRemaining]: ctx.hasScrollRemaining, [cn.hasScrolled]: ctx.hasScrolled }, className ); return (
{onBack && ( )} {setShow && ( Close Side Tray )}
{children}
); }; export const SideTrayTitle: FunctionComponent = (props) => { const {icon, title, subtitle, className} = props; return (

{icon && } {title}

{subtitle &&

{subtitle}

}
); }; export function SideTrayContent(props: SideTrayContentProps) { const {children, className} = props; const ctx = useContext(SideTrayContext); if (ctx === null) { throw new Error('SideContent must be within SideTray'); } const [context, setContext] = ctx; const target = useRef(null); const size = useSize(target); function onScroll() { processScroll(target.current); } function processScroll(element: HTMLDivElement | null) { if (element === null) return; const hasScrollRemaining = element.scrollHeight > element.clientHeight + element.scrollTop + 10; const hasScrolled = element.scrollTop > 10; setContext({...context, hasScrollRemaining, hasScrolled}); } useLayoutEffect(() => { processScroll(target.current); }, [size, children]); return
{children}
; } export function SideTrayBodyContent(props: SideTrayContentProps) { const {children, className} = props; return
{children}
; } export function SideTrayBody(props: SideTrayBodyProps) { const {children, className, transition} = props; const ctx = useContext(SideTrayContext); if (ctx === null) { throw new Error('SideTrayBody must be within SideTray'); } const [context, setContext] = ctx; const enableTransition = transition ?? context.transition ?? true; return (
{children}
); } export function SideTrayFooter(props: SideTrayFooterProps) { const {children, className, transition} = props; const ctx = useContext(SideTrayContext); if (ctx === null) { throw new Error('SideTrayBody must be within SideTray'); } const [context] = ctx; const enableTransition = transition ?? context.transition ?? true; return ( ); }