UNPKG

@navinc/base-react-components

Version:
215 lines (211 loc) 9.45 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { createContext, useContext, useState, useRef, useEffect, useCallback, } from 'react'; import styled, { useTheme, keyframes } from 'styled-components'; import { useDebouncedCallback } from 'use-debounce'; import { Header } from './header.js'; import { IconButton } from './icon-button'; import { noop } from '@navinc/utils'; const moveOffScreen = keyframes ` from { right: 0%; } to { right: -100%; } `; const moveOnScreen = keyframes ` from { right: -100%; } to { right: 0%; } `; const suddenIn = keyframes ` from { width: 100%; opacity: 0; } to { width: 100%; opacity: 1; } `; const suddenOut = keyframes ` 0% { width: 100%; opacity: 1; } 99.9% { width: 100%; } 100% { width: 0%; opacity: 0; } `; const Backdrop = styled.div.withConfig({ displayName: "brc-sc-Backdrop", componentId: "brc-sc-1kxu7f1" }) ` align-items: flex-start; animation: 0.2s ${({ theme }) => theme.materialTransitionTiming} both ${({ isOpen }) => isOpen ? suddenIn : suddenOut} ; background-color: hsla(0, 0%, 0%, .2)}; box-sizing: border-box; display: flex; height: 100%; justify-content: flex-end; overflow: hidden; pointer-events: ${({ isOpen }) => (isOpen ? 'initial' : 'none')}; position: fixed; right: 0; & * { box-sizing: border-box; } `; const drawerHeightVariations = (theme, type, mobileFullPage) => { switch (type) { case 'dynamic': return ` border-bottom-left-radius: 4px; border-top-left-radius: 4px; height: ${mobileFullPage ? '100%' : 'auto'} ; margin-top: ${mobileFullPage ? '0' : '100px'}; max-height: ${!mobileFullPage && 'calc(100vh - 100px)'}; min-height: 80px; @media (${theme.forLargerThanPhone}) { height: auto; max-height: calc(100vh - 100px); } `; case 'full': return ` align-content: flex-start; height 100vh; height: 100dvh; `; default: return null; } }; const contentHeightVariations = { dynamic: 'max-height: calc(100vh - 160px);', full: 'max-height: calc(100vh - 65px);', }; const Content = styled.div.withConfig({ displayName: "brc-sc-Content", componentId: "brc-sc-1aoi4h" }) ` overflow: auto; grid-column: 1 / -1; font-family: ${({ theme }) => theme.fontPrimary} ${({ heightVariation }) => { var _a; return (_a = contentHeightVariations[heightVariation]) !== null && _a !== void 0 ? _a : contentHeightVariations.dynamic; }}; `; const Drawer = styled.div.withConfig({ displayName: "brc-sc-Drawer", componentId: "brc-sc-1d32p6d" }) ` animation: 0.2s ${({ theme }) => theme.materialTransitionTiming} both ${({ isOpen }) => (isOpen ? moveOnScreen : moveOffScreen)}; background-color: ${({ theme }) => theme.navSecondary100}; display: grid; grid-template-columns: min-content 1fr; grid-template-rows: ${({ mobileFullPage }) => mobileFullPage && 'auto 1fr'}; gap: 16px; border-right: solid 1px ${({ theme }) => theme.border}; overflow: hidden; padding: 16px; // only use transition when not touching, otherwise dragging feels sluggish transition: ${({ isTouching, theme }) => !isTouching && `transform 0.2s ${theme.materialTransitionTiming};`} ${({ theme, heightVariation, mobileFullPage }) => { var _a; return (_a = drawerHeightVariations(theme, heightVariation, mobileFullPage)) !== null && _a !== void 0 ? _a : drawerHeightVariations(theme, 'dynamic'); }} width: 100%; @media (${({ theme }) => theme.forLargerThanPhone}) { position: ${({ orientBottom }) => orientBottom && `absolute`}; bottom: ${({ orientBottom }) => orientBottom && `0`}; width: ${({ orientBottom, width }) => width !== null && width !== void 0 ? width : (orientBottom ? `calc(100% - 260px)` : `485px`)}; padding: 24px 28px 32px; max-width: calc(100vw - 55px); } `; const InfoDrawerContext = createContext({ mobileFullPage: false, orientBottom: false, heightVariation: undefined, width: undefined, isOpen: false, setIsOpen: noop, content: null, title: null, setInfoDrawer: noop, closeHandlersRef: { current: [] }, close: noop, }); export const InfoDrawerProvider = ({ children }) => { const [groupedConfig, setGroupedConfig] = useState({ mobileFullPage: false, orientBottom: false, content: null, contentProps: undefined, title: null, heightVariation: undefined, width: undefined, }); const [isOpen, setIsOpen] = useState(false); const closeHandlersRef = useRef([]); const { mobileFullPage, orientBottom, content, contentProps, title, heightVariation, width } = groupedConfig; const close = useCallback((noClose = false) => { if (!noClose) { setIsOpen(false); } closeHandlersRef.current.forEach((closeHandler) => closeHandler()); }, []); const setInfoDrawer = useCallback((config = {}) => { setGroupedConfig((currentGroupedConfig) => (Object.assign(Object.assign({}, (config.isOpen ? {} : currentGroupedConfig)), config))); setIsOpen((currentIsOpen) => { var _a; const shouldBeOpen = (_a = config.isOpen) !== null && _a !== void 0 ? _a : currentIsOpen; if (currentIsOpen && !shouldBeOpen) { close(true); } return shouldBeOpen; }); }, [close]); return (_jsx(InfoDrawerContext.Provider, { value: { mobileFullPage, orientBottom, content, contentProps, isOpen, setIsOpen, title, closeHandlersRef, close, setInfoDrawer, heightVariation, width, }, children: children })); }; export const useInfoDrawer = ({ onClose } = {}) => { const { mobileFullPage, orientBottom, content, contentProps, title, setInfoDrawer, closeHandlersRef, isOpen, heightVariation, width, } = useContext(InfoDrawerContext); if (onClose && !closeHandlersRef.current.includes(onClose)) { closeHandlersRef.current = closeHandlersRef.current.concat(onClose); } // When the component unmounts, we want to clean up our onClose handler. useEffect(() => () => { closeHandlersRef.current = closeHandlersRef.current.filter((closeHandler) => closeHandler !== onClose); }, [closeHandlersRef, onClose]); return { mobileFullPage, orientBottom, content, contentProps, title, isOpen, setInfoDrawer, heightVariation, width, }; }; const _InfoDrawer = ({ variation, heightVariation = 'dynamic', className, }) => { const theme = useTheme(); const infoDrawerState = useInfoDrawer(); const { mobileFullPage, orientBottom, content: ContentComponent, contentProps, title, isOpen, setInfoDrawer, heightVariation: heightVariationOverride, width, } = infoDrawerState; const [moveStartX, setMoveStartX] = useState(0); const [translateX, setTranslateX] = useState(0); const onTouchMove = useDebouncedCallback((e) => { onTouchMove.cancel(); e.persist(); setTranslateX(e.touches[0].clientX); }, 2, { maxWait: 5 }); const configuredHeightVariation = heightVariationOverride !== null && heightVariationOverride !== void 0 ? heightVariationOverride : heightVariation; return (_jsx(Backdrop, { isOpen: isOpen, className: className, "data-testid": "info-drawer:backdrop", onTouchStart: (e) => setMoveStartX(e.touches[0].clientX), onTouchMove: onTouchMove, onTouchEnd: () => { onTouchMove.cancel(); translateX - moveStartX > 180 && setInfoDrawer({ isOpen: false }); setMoveStartX(0); setTranslateX(0); }, onClick: (e) => { e.persist(); if ('dataset' in e.target && e.target.dataset.testid === 'info-drawer:backdrop') { setInfoDrawer({ isOpen: false }); } }, children: _jsxs(Drawer, { mobileFullPage: mobileFullPage, orientBottom: orientBottom, style: { transform: `translateX(${isOpen ? Math.max(translateX - moveStartX, 0) : 485}px)`, }, isOpen: isOpen, isTouching: moveStartX !== 0, variation: variation, heightVariation: configuredHeightVariation, width: width, onAnimationEnd: () => { if (!isOpen) { // Reset content and props to ensure we are not holding onto old state. If the same info drawer was // displayed, the content and any state would have persisted, which may lead to unexpected behavior setInfoDrawer({ content: undefined, contentProps: undefined }); } }, "data-testid": "info-drawer:drawer", children: [_jsx(IconButton, { name: "actions/close", onClick: () => setInfoDrawer({ isOpen: false }), variation: "neutral", "data-testid": "info-drawer:close-button", size: "18px", color: theme.navNeutralDark }), _jsx(Header, { size: "sm", children: title }), _jsx(Content, { heightVariation: configuredHeightVariation, children: typeof ContentComponent === 'function' ? (_jsx(ContentComponent, Object.assign({}, infoDrawerState, contentProps))) : (ContentComponent) })] }) })); }; export const InfoDrawer = styled(_InfoDrawer).withConfig({ displayName: "brc-sc-InfoDrawer", componentId: "brc-sc-1ty4027" }) ``; //# sourceMappingURL=info-drawer.js.map