Skip to content

Commit

Permalink
feat: add a BottomSheet component
Browse files Browse the repository at this point in the history
  • Loading branch information
haideralsh committed Nov 15, 2024
1 parent 9959206 commit 9f6eddd
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"dependencies": {
"@babel/runtime": "^7.9.6",
"@nulogy/tokens": "^5.4.0",
"@reach/dialog": "0.17.0",
"@styled-system/prop-types": "^5.1.4",
"@styled-system/theme-get": "^5.1.2",
"@types/styled-system": "5.1.22",
Expand Down
55 changes: 55 additions & 0 deletions src/BottomSheet/BottomSheet.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from "react";
import { Box } from "../Box";
import { Button } from "../Button";
import { FormSection, Form } from "../Form";
import { Input } from "../Input";
import { BottomSheet } from "./BottomSheet";

export default {
title: "Components/BottomSheet",
};

export const App = () => {
const [isOpen, setIsOpen] = React.useState(false);

return (
<Box>
<Button onClick={() => setIsOpen(true)}>Open Sheet</Button>

<BottomSheet isOpen={isOpen} onDismiss={() => setIsOpen(false)}>
<Form>
<FormSection title="Personal Information">
<Input id="name" labelText="Name" />
<Input
id="birthdate"
placeholder="DD-MM-YYYY"
labelText="Date of birth"
requirementText="(Optional)"
helpText="Enter a date below"
/>
<Input id="birthplace" labelText="Place of birth" requirementText="(Optional)" />
</FormSection>
<FormSection title="General Information">
<Input id="gender" labelText="Gender" />
<Input id="occupation" labelText="Occupation" />
</FormSection>
<FormSection title="Personal Information">
<Input id="name" labelText="Name" />
<Input
id="birthdate"
placeholder="DD-MM-YYYY"
labelText="Date of birth"
requirementText="(Optional)"
helpText="Enter a date below"
/>
<Input id="birthplace" labelText="Place of birth" requirementText="(Optional)" />
</FormSection>
<FormSection title="General Information">
<Input id="gender" labelText="Gender" />
<Input id="occupation" labelText="Occupation" />
</FormSection>
</Form>
</BottomSheet>
</Box>
);
};
177 changes: 177 additions & 0 deletions src/BottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import React from "react";
import { DialogContent as ReachDialogContent, DialogOverlay as ReachDialogOverlay } from "@reach/dialog";
import styled from "styled-components";
import { transparentize } from "polished";
import { motion, AnimatePresence } from "framer-motion";
import { Heading2, Text } from "../Type";
import { Box } from "../Box";
import { Link } from "../Link";
import { Button, PrimaryButton, QuietButton } from "../Button";
import { Flex } from "../Flex";

const Overlay = styled(motion(ReachDialogOverlay))(({ theme }) => ({
position: "fixed",
inset: 0,
display: "flex",
alignItems: "flex-end",
justifyContent: "center",
backgroundColor: transparentize(0.5, theme.colors.blackBlue),
}));

const Sheet = styled(motion(ReachDialogContent))(({ theme }) => ({
":focus": {
outline: "none",
},
inset: 0,
backgroundColor: theme.colors.white,
borderRadius: theme.radii.medium,
height: "auto",
margin: `0px ${theme.space.x2}`,
padding: 0,
"*": {
boxSizing: "border-box",
WebkitTapHighlightColor: "transparent",
},
color: theme.colors.black,
fontSize: theme.fontSizes.medium,
lineHeight: theme.lineHeights.base,
WebkitFontSmoothing: "antialiased",
WebkitTapHighlightColor: "transparent",
MozOsxFontSmoothing: "grayscale",

position: "relative",
overflow: "hidden",
display: "flex",
flexDirection: "column",
background: "white",
width: "100%",
borderTopLeftRadius: theme.radii.large,
borderTopRightRadius: theme.radii.large,
maxHeight: `calc(100dvh - ${theme.space.x7})`,
boxShadow: theme.shadows.large,

"@media (min-width: 768px)": {
maxWidth: `calc(100% - ${theme.space.x8})`,
maxHeight: "85.4dvh", // Golden Ratio
},
}));

const WidthBoundedContent = styled.div`
width: 100%;
margin: 0 auto;
@media (min-width: 768px) {
max-width: 704px;
}
`;

const ContentContainer = styled.div`
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
`;

const overlayVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1 },
exit: { opacity: 0 },
};

const sheetVariants = {
hidden: { y: "100%" },
visible: { y: 0 },
exit: { y: "100%" },
};

const overlayTransition = { duration: 0.25 };

const sheetTransition = {
duration: 0.5,
ease: [0.32, 0.72, 0, 1],
};

export const BottomSheet = ({ isOpen, onDismiss, children }) => {
function handleSheetClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
e.stopPropagation();
}

return (
<AnimatePresence>
{isOpen && (
<Overlay
variants={overlayVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={overlayTransition}
onClick={onDismiss}
isOpen={isOpen}
>
<Sheet
variants={sheetVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={sheetTransition}
onClick={handleSheetClick}
>
<ContentContainer>
<WidthBoundedContent>
<BottomSheetHeader>
<BottomSheetHeading>Carry over</BottomSheetHeading>
<BottomSheetHelpText>
Carry over remaining quantity to a future PO line item.{" "}
<Link href="https://www.nulogy.com">Learn more</Link>
</BottomSheetHelpText>
</BottomSheetHeader>
<Box px="x3" py="x4">
{children}
</Box>
</WidthBoundedContent>
<BottomSheetFooter>
<Flex justifyContent="space-between">
<Button onClick={onDismiss}>Close</Button>
<Flex gap="x2">
<QuietButton onClick={onDismiss}>Reset</QuietButton>
<PrimaryButton onClick={onDismiss}>Carry over</PrimaryButton>
</Flex>
</Flex>
</BottomSheetFooter>
</ContentContainer>
</Sheet>
</Overlay>
)}
</AnimatePresence>
);
};

const BottomSheetFooter = styled.div(({ theme }) => ({
position: "sticky",
bottom: 0,
marginLeft: theme.space.x1,
marginRight: theme.space.x1,
padding: theme.space.x2,
background: transparentize(0.4, theme.colors.white),
backdropFilter: "blur(8px)",
borderTop: "1px solid",
borderTopColor: theme.colors.lightGrey,
marginTop: "auto",
}));

const BottomSheetHeader = styled.div`
text-align: center;
padding: 24px 24px 0 24px;
`;

const BottomSheetHeading = styled(Heading2)(({ theme }) => ({
marginBottom: theme.space.x1,
}));

const BottomSheetHelpText = styled(Text)(({ theme }) => ({
color: theme.colors.midGrey,
}));
12 changes: 11 additions & 1 deletion src/NDSProvider/ModalStyleOverride.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@ import { GlobalStylesProps } from "./GlobalStyles";
const ModalStyleOverride = createGlobalStyle(({ theme, locale }: GlobalStylesProps) => {
const fontFamily = locale === "zh_CN" ? theme.fonts.sc : theme.fonts.base;
return {
".ReactModal__Content": {
".ReactModal__Content, [data-reach-dialog-content]": {
"-webkit-font-smoothing": "antialiased",
"-moz-osx-font-smoothing": "grayscale",
"*": { boxSizing: "border-box" },
color: theme.colors.black,
fontFamily,
fontSize: theme.fontSizes.medium,
lineHeight: theme.lineHeights.base,
img: {
maxWidth: "100%",
height: "auto",
},
button: {
fontFamily,
},
Expand Down
Loading

0 comments on commit 9f6eddd

Please sign in to comment.