feat: Small confirmation dialogs (#3293)

* wip

* refinement
This commit is contained in:
Tom Moor
2022-03-30 17:11:19 -07:00
committed by GitHub
parent 7f3b602259
commit 6c25f8fc72
11 changed files with 148 additions and 62 deletions

View File

@@ -9,6 +9,8 @@ import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
import Scrollable from "~/components/Scrollable";
import Text from "~/components/Text";
import useMobile from "~/hooks/useMobile";
import usePrevious from "~/hooks/usePrevious";
import useUnmount from "~/hooks/useUnmount";
import { fadeAndScaleIn } from "~/styles/animations";
@@ -16,6 +18,7 @@ import { fadeAndScaleIn } from "~/styles/animations";
let openModals = 0;
type Props = {
isOpen: boolean;
isCentered?: boolean;
title?: React.ReactNode;
onRequestClose: () => void;
};
@@ -23,6 +26,7 @@ type Props = {
const Modal: React.FC<Props> = ({
children,
isOpen,
isCentered,
title = "Untitled",
onRequestClose,
}) => {
@@ -31,6 +35,7 @@ const Modal: React.FC<Props> = ({
});
const [depth, setDepth] = React.useState(0);
const wasOpen = usePrevious(isOpen);
const isMobile = useMobile();
const { t } = useTranslation();
React.useEffect(() => {
@@ -58,37 +63,59 @@ const Modal: React.FC<Props> = ({
return (
<DialogBackdrop {...dialog}>
{(props) => (
<Backdrop {...props}>
<Backdrop $isCentered={isCentered} {...props}>
<Dialog
{...dialog}
preventBodyScroll
hideOnEsc
hideOnClickOutside={false}
hideOnClickOutside={isCentered}
hide={onRequestClose}
>
{(props) => (
<Scene
$nested={!!depth}
style={{
marginLeft: `${depth * 12}px`,
}}
{...props}
>
<Content>
{(props) =>
isCentered && !isMobile ? (
<Small {...props}>
<Centered onClick={(ev) => ev.stopPropagation()} column>
{title && <h1>{title}</h1>}
{children}
<Header>
{title && (
<Text as="span" size="large">
{title}
</Text>
)}
<NudeButton onClick={onRequestClose}>
<CloseIcon color="currentColor" />
</NudeButton>
</Header>
<SmallContent shadow>{children}</SmallContent>
</Centered>
</Content>
<Back onClick={onRequestClose}>
<BackIcon size={32} color="currentColor" />
<Text>{t("Back")}</Text>
</Back>
<Close onClick={onRequestClose}>
<CloseIcon size={32} color="currentColor" />
</Close>
</Scene>
)}
</Small>
) : (
<Fullscreen
$nested={!!depth}
style={
isMobile
? undefined
: {
marginLeft: `${depth * 12}px`,
}
}
{...props}
>
<Content>
<Centered onClick={(ev) => ev.stopPropagation()} column>
{title && <h1>{title}</h1>}
{children}
</Centered>
</Content>
<Close onClick={onRequestClose}>
<CloseIcon size={32} color="currentColor" />
</Close>
<Back onClick={onRequestClose}>
<BackIcon size={32} color="currentColor" />
<Text as="span">{t("Back")} </Text>
</Back>
</Fullscreen>
)
}
</Dialog>
</Backdrop>
)}
@@ -96,14 +123,16 @@ const Modal: React.FC<Props> = ({
);
};
const Backdrop = styled.div`
const Backdrop = styled(Flex)<{ $isCentered?: boolean }>`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: ${(props) =>
transparentize(0.25, props.theme.background)} !important;
props.$isCentered
? props.theme.modalBackdrop
: transparentize(0.25, props.theme.background)} !important;
z-index: ${(props) => props.theme.depths.modalOverlay};
transition: opacity 50ms ease-in-out;
opacity: 0;
@@ -113,7 +142,7 @@ const Backdrop = styled.div`
}
`;
const Scene = styled.div<{ $nested: boolean }>`
const Fullscreen = styled.div<{ $nested: boolean }>`
animation: ${fadeAndScaleIn} 250ms ease;
position: absolute;
@@ -142,10 +171,10 @@ const Scene = styled.div<{ $nested: boolean }>`
const Content = styled(Scrollable)`
width: 100%;
padding: 8vh 2rem 2rem;
padding: 8vh 32px;
${breakpoint("tablet")`
padding-top: 13vh;
padding: 13vh 2rem 2rem;
`};
`;
@@ -156,13 +185,6 @@ const Centered = styled(Flex)`
margin: 0 auto;
`;
const Text = styled.span`
font-size: 16px;
font-weight: 500;
padding-right: 12px;
user-select: none;
`;
const Close = styled(NudeButton)`
position: absolute;
display: block;
@@ -191,6 +213,7 @@ const Back = styled(NudeButton)`
left: 2rem;
opacity: 0.75;
color: ${(props) => props.theme.text};
font-weight: 500;
width: auto;
height: auto;
@@ -203,4 +226,40 @@ const Back = styled(NudeButton)`
`};
`;
const Header = styled(Flex)`
color: ${(props) => props.theme.textSecondary};
align-items: center;
justify-content: space-between;
font-weight: 600;
padding: 24px 24px 4px;
`;
const Small = styled.div`
animation: ${fadeAndScaleIn} 250ms ease;
margin: auto auto;
min-width: 350px;
max-width: 30vw;
z-index: ${(props) => props.theme.depths.modal};
display: flex;
justify-content: center;
align-items: flex-start;
background: ${(props) => props.theme.modalBackground};
transition: ${(props) => props.theme.backgroundTransition};
box-shadow: ${(props) => props.theme.modalShadow};
border-radius: 8px;
outline: none;
${NudeButton} {
&:hover,
&[aria-expanded="true"] {
background: ${(props) => props.theme.sidebarControlHoverBackground};
}
}
`;
const SmallContent = styled(Scrollable)`
padding: 12px 24px 24px;
`;
export default observer(Modal);