@navinc/base-react-components
Version:
Nav's Pattern Library
215 lines (211 loc) • 9.45 kB
JavaScript
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