## Context Fixes #4808 TL;DR Introducing pure stateless modal component ("UI modal") for our auth modal not to have default hotkeyScope overriding our create-profile hotkeyScope + we dont want the shortcut to be available for all the modal content, only for the input that should not be using a hotkeyscope, so we are using onKeyDown for the specific issue on create profile. Explanation create-profile hotkey scope is set by PageChangeEffect; CreateProfile component adds enter key shortcut; but this scope is overwritten by the default scope by the Modal component that expects a hotkeyScope to reset to (and defaults to the default hotkeyScope if none indicated). In the auth flow we were using that Modal component to give a modal look to the flow but it is not a modal per say, it's a set of pages contained within a modal look. By creating this UI component we are escaping that hotkeyScope overriding that does not make sense in our context. ## How was it tested Locally Storybook
169 lines
3.9 KiB
TypeScript
169 lines
3.9 KiB
TypeScript
import React from 'react';
|
|
import styled from '@emotion/styled';
|
|
import { motion } from 'framer-motion';
|
|
|
|
const StyledModalDiv = styled(motion.div)<{
|
|
size?: ModalSize;
|
|
padding?: ModalPadding;
|
|
}>`
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: ${({ theme }) => theme.background.primary};
|
|
color: ${({ theme }) => theme.font.color.primary};
|
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
|
overflow: hidden;
|
|
max-height: 90vh;
|
|
z-index: 10000; // should be higher than Backdrop's z-index
|
|
|
|
width: ${({ size, theme }) => {
|
|
switch (size) {
|
|
case 'small':
|
|
return theme.modal.size.sm;
|
|
case 'medium':
|
|
return theme.modal.size.md;
|
|
case 'large':
|
|
return theme.modal.size.lg;
|
|
default:
|
|
return 'auto';
|
|
}
|
|
}};
|
|
|
|
padding: ${({ padding, theme }) => {
|
|
switch (padding) {
|
|
case 'none':
|
|
return theme.spacing(0);
|
|
case 'small':
|
|
return theme.spacing(2);
|
|
case 'medium':
|
|
return theme.spacing(4);
|
|
case 'large':
|
|
return theme.spacing(6);
|
|
default:
|
|
return 'auto';
|
|
}
|
|
}};
|
|
`;
|
|
|
|
const StyledHeader = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
flex-direction: row;
|
|
height: 60px;
|
|
overflow: hidden;
|
|
padding: ${({ theme }) => theme.spacing(5)};
|
|
`;
|
|
|
|
const StyledContent = styled.div`
|
|
display: flex;
|
|
flex: 1;
|
|
flex: 1 1 0%;
|
|
flex-direction: column;
|
|
overflow-y: auto;
|
|
padding: ${({ theme }) => theme.spacing(10)};
|
|
`;
|
|
|
|
const StyledFooter = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
flex-direction: row;
|
|
height: 60px;
|
|
overflow: hidden;
|
|
padding: ${({ theme }) => theme.spacing(5)};
|
|
`;
|
|
|
|
const StyledBackDrop = styled(motion.div)`
|
|
align-items: center;
|
|
background: ${({ theme }) => theme.background.overlay};
|
|
display: flex;
|
|
height: 100%;
|
|
justify-content: center;
|
|
left: 0;
|
|
position: fixed;
|
|
top: 0;
|
|
width: 100%;
|
|
z-index: 9999;
|
|
`;
|
|
|
|
/**
|
|
* Modal components
|
|
*/
|
|
type ModalLayoutHeaderProps = React.PropsWithChildren & {
|
|
className?: string;
|
|
};
|
|
|
|
const ModalLayoutHeader = ({ children, className }: ModalLayoutHeaderProps) => (
|
|
<StyledHeader className={className}>{children}</StyledHeader>
|
|
);
|
|
|
|
type ModalLayoutContentProps = React.PropsWithChildren & {
|
|
className?: string;
|
|
};
|
|
|
|
const ModalLayoutContent = ({
|
|
children,
|
|
className,
|
|
}: ModalLayoutContentProps) => (
|
|
<StyledContent className={className}>{children}</StyledContent>
|
|
);
|
|
|
|
type ModalLayoutFooterProps = React.PropsWithChildren & {
|
|
className?: string;
|
|
};
|
|
|
|
const ModalLayoutFooter = ({ children, className }: ModalLayoutFooterProps) => (
|
|
<StyledFooter className={className}>{children}</StyledFooter>
|
|
);
|
|
|
|
/**
|
|
* Modal
|
|
*/
|
|
export type ModalSize = 'small' | 'medium' | 'large';
|
|
export type ModalPadding = 'none' | 'small' | 'medium' | 'large';
|
|
|
|
export type ModalLayoutProps = React.PropsWithChildren & {
|
|
size?: ModalSize;
|
|
padding?: ModalPadding;
|
|
className?: string;
|
|
modalRef?: React.RefObject<HTMLElement>;
|
|
};
|
|
|
|
const modalVariants = {
|
|
hidden: { opacity: 0 },
|
|
visible: { opacity: 1 },
|
|
exit: { opacity: 0 },
|
|
};
|
|
|
|
// This component should be used over Modal when seeking a modal feel without modal state (hotkeyScope etc)
|
|
export const ModalLayout = ({
|
|
children,
|
|
size = 'medium',
|
|
padding = 'medium',
|
|
modalRef,
|
|
className,
|
|
}: ModalLayoutProps) => {
|
|
return (
|
|
<StyledBackDrop>
|
|
<StyledModalDiv
|
|
// framer-motion seems to have typing problems with refs
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
ref={modalRef}
|
|
size={size}
|
|
padding={padding}
|
|
initial="hidden"
|
|
animate="visible"
|
|
exit="exit"
|
|
layout
|
|
variants={modalVariants}
|
|
className={className}
|
|
>
|
|
{children}
|
|
</StyledModalDiv>
|
|
</StyledBackDrop>
|
|
);
|
|
};
|
|
|
|
ModalLayout.Header = ModalLayoutHeader;
|
|
ModalLayout.Content = ModalLayoutContent;
|
|
ModalLayout.Footer = ModalLayoutFooter;
|