From bcbe48ee7532b9d5a2982e6cb3cfe7aefe50edbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Bie=C5=84?= Date: Thu, 9 May 2024 19:52:39 +0200 Subject: [PATCH] feat(Sheet): add Sheet component --- .../stories/components/sheet.stories.tsx | 399 ++++++++++++++++++ packages/frosted-ui/package.json | 3 +- packages/frosted-ui/src/components/index.ts | 1 + packages/frosted-ui/src/components/sheet.css | 77 ++++ packages/frosted-ui/src/components/sheet.tsx | 183 ++++++++ packages/frosted-ui/src/styles/index.css | 1 + pnpm-lock.yaml | 17 + 7 files changed, 680 insertions(+), 1 deletion(-) create mode 100644 packages/frosted-ui/.storybook/stories/components/sheet.stories.tsx create mode 100644 packages/frosted-ui/src/components/sheet.css create mode 100644 packages/frosted-ui/src/components/sheet.tsx diff --git a/packages/frosted-ui/.storybook/stories/components/sheet.stories.tsx b/packages/frosted-ui/.storybook/stories/components/sheet.stories.tsx new file mode 100644 index 00000000..185b109f --- /dev/null +++ b/packages/frosted-ui/.storybook/stories/components/sheet.stories.tsx @@ -0,0 +1,399 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import { + Badge, + Button, + Checkbox, + Flex, + Inset, + ScrollArea, + Sheet, + Table, + Text, + TextField, +} from '../../../src/components'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +const meta = { + title: 'Components/Sheet', + component: Sheet.Content, + args: { + // size: dialogContentPropDefs.size.default, + }, + + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Default: Story = { + render: ({ children, ...args }) => ( + + + + + + + + Edit profile + Make changes to your profile. + + + + + + + + + + + + + + + + + + + ), +}; + +export const InsetContent: Story = { + name: 'With inset content', + render: ({ children, ...args }) => ( + + + + + + + Users + + The following users have access to this project. + + + + + + + + + + + + + + Full name + + + Email + Group + + + + + + + + + Danilo Sousa + + + danilo@example.com + + Developer + + + + + + + + Zahra Ambessa + + + zahra@example.com + + Admin + + + + + + + + Jasper Eriksson + + + jasper@example.com + + Developer + + + + + + + + + + + + + + + + + ), +}; + +export const ScrollableContent: Story = { + name: 'Scrollable content', + render: ({ children, ...args }) => ( + + + + + + + Users + + The following users have access to this project. + + + + + + Make changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes + + + Make changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes + + + Make changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes to your profile. Make + changes to your profile. Make changes + + + + + + ), +}; + +export const Controlled: Story = { + name: 'Controlled (non dismissable)', + render: ({ children, ...args }) => { + const [open, setOpen] = React.useState(false); + + return ( + + setOpen(true)}> + + + + + Delete account + + Are you sure you want to delete your account? + + + + + setOpen(false)}> + + + setOpen(false)}> + + + + + + + ); + }, +}; + +// TODO: add support for snapPoints +// export const SnapPoints: Story = { +// name: 'Snap points', +// render: ({ children, ...args }) => { +// const snapPoints = ['120px', '240px', 1]; +// const [snap, setSnap] = React.useState('420px'); + +// return ( +// console.log({ a })} +// > +// +// +// +// +// +// Delete account +// +// Are you sure you want to delete your account? +// +// +// +//
+//
+//
+// +// +// +// ); +// }, +// }; diff --git a/packages/frosted-ui/package.json b/packages/frosted-ui/package.json index 14f7f4aa..c4991aed 100644 --- a/packages/frosted-ui/package.json +++ b/packages/frosted-ui/package.json @@ -100,7 +100,8 @@ "input-otp": "^1.1.0", "react-aria-components": "1.1.1", "tailwindcss": "^3.3.5", - "tslib": "^2.6.2" + "tslib": "^2.6.2", + "vaul": "^0.9.1" }, "peerDependencies": { "@types/react": "*", diff --git a/packages/frosted-ui/src/components/index.ts b/packages/frosted-ui/src/components/index.ts index 7540c9aa..b0f82fb8 100644 --- a/packages/frosted-ui/src/components/index.ts +++ b/packages/frosted-ui/src/components/index.ts @@ -80,6 +80,7 @@ export * from './hover-card.props'; export * as Accordion from './lab/accordion'; export * as Popover from './popover'; export * from './popover.props'; +export * as Sheet from './sheet'; export * from './stacked-horizontal-bar-chart'; export * from './tooltip'; export * from './tooltip.props'; diff --git a/packages/frosted-ui/src/components/sheet.css b/packages/frosted-ui/src/components/sheet.css new file mode 100644 index 00000000..3e9b001b --- /dev/null +++ b/packages/frosted-ui/src/components/sheet.css @@ -0,0 +1,77 @@ +.fui-SheetOverlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.8); +} + +.fui-SheetContent { + --sheet-content-padding: 20px; + --inset-padding: var(--sheet-content-padding); + --sheet-border-color: var(--gray-a3); + + position: fixed; + left: 0; + right: 0; + bottom: 0; + max-height: calc(100% - 48px); + z-index: 50; + /* margin-top: 96px; */ + display: flex; + flex-direction: column; + height: auto; + border-top-left-radius: 24px; + border-top-right-radius: 24px; + border: 1px solid var(--sheet-border-color); + background-color: var(--color-panel-solid); +} +.fui-SheetContent[vaul-drawer][vaul-drawer-direction='bottom']::after { + border-left: 1px solid var(--sheet-border-color); + border-right: 1px solid var(--sheet-border-color); + left: -1px; + right: -1px; +} +.fui-SheetBody { + padding: var(--inset-padding); +} +.fui-SheetContentHandle { + margin-left: auto; + margin-right: auto; + margin-top: 16px; + margin-bottom: 12px; + height: 5px; + width: 40px; + border-radius: 999px; + background-color: var(--gray-a4); +} + +.fui-SheetHeader { + display: grid; + padding: 0 var(--inset-padding) var(--inset-padding) var(--inset-padding); + gap: 12px; +} + +.fui-SheetTitle { + font-size: 20px; + font-weight: 600; + color: var(--color-text-primary); + color: blue; +} +.fui-SheetDescription { + color: green; +} + +.fui-SheetContentHandle, +.fui-SheetHeader, +.fui-SheetBody { + min-height: 0; +} +.fui-SheetContentHandle, +.fui-SheetHeader { + flex-shrink: 0; +} +.fui-SheetBody { + flex: 1; + height: 100%; + display: flex; + flex-direction: column; +} diff --git a/packages/frosted-ui/src/components/sheet.tsx b/packages/frosted-ui/src/components/sheet.tsx new file mode 100644 index 00000000..56ec2029 --- /dev/null +++ b/packages/frosted-ui/src/components/sheet.tsx @@ -0,0 +1,183 @@ +'use client'; + +import * as React from 'react'; +import { Drawer as DrawerPrimitive } from 'vaul'; + +import classNames from 'classnames'; +import { ExtractPropsForTag } from '../helpers'; +import { Theme } from '../theme'; +import { Heading } from './heading'; +import { Text } from './text'; + +type SheetRootProps = Omit< + React.ComponentProps, + | 'shouldScaleBackground' + | 'direction' + // TODO: add support for snap points + | 'fadeFromIndex' + | 'snapPoints' + | 'activeSnapPoint' +>; + +const SheetRoot = ({ ...props }: SheetRootProps) => ( + +); +SheetRoot.displayName = 'SheetRoot'; + +type SheetTriggerElement = React.ElementRef; +interface SheetTriggerProps + extends Omit< + React.ComponentPropsWithoutRef, + 'asChild' + > {} +const SheetTrigger = React.forwardRef( + (props, forwardedRef) => ( + + ), +); +SheetTrigger.displayName = 'SheetTrigger'; + +type SheetCloseElement = React.ElementRef; +interface SheetCloseProps + extends Omit< + React.ComponentPropsWithoutRef, + 'asChild' + > {} +const SheetClose = React.forwardRef( + (props, forwardedRef) => ( + + ), +); +SheetClose.displayName = 'SheetClose'; + +const SheetPortal = DrawerPrimitive.Portal; +SheetPortal.displayName = 'SheetPortal'; + +type SheetOverlayElement = React.ElementRef; +interface SheetOverlayProps + extends React.ComponentPropsWithoutRef {} + +const SheetOverlay = React.forwardRef( + ({ className, ...props }, forwardedRef) => ( + + ), +); +SheetOverlay.displayName = 'SheetOverlay'; + +type SheetContentElement = React.ElementRef; +interface SheetContentProps + extends React.ComponentPropsWithoutRef {} + +const SheetContent = React.forwardRef( + ({ className, children, ...props }, forwardedRef) => ( + + + + + + +
+ {children} + + + + ), +); +SheetContent.displayName = 'SheetContent'; + +type SheetHeaderElement = React.ElementRef<'div'>; +type SheetHeaderProps = React.ComponentPropsWithoutRef<'div'>; +const SheetHeader = React.forwardRef( + ({ children, className, ...props }, forwardedRef) => ( +
+ {children} +
+ ), +); +SheetHeader.displayName = 'SheetHeader'; + +type SheetBodyElement = React.ElementRef<'div'>; +type SheetBodyProps = React.ComponentPropsWithoutRef<'div'>; +const SheetBody = React.forwardRef( + ({ children, className, ...props }, forwardedRef) => ( +
+ {children} +
+ ), +); +SheetBody.displayName = 'SheetBody'; + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +SheetFooter.displayName = 'SheetFooter'; + +type SheetTitleElement = React.ElementRef; +type SheetTitleProps = React.ComponentPropsWithoutRef; + +const SheetTitle = React.forwardRef( + ({ size = '5', weight = 'bold', ...props }, forwardedRef) => { + return ( + + + + ); + }, +); +SheetTitle.displayName = 'SheetTitle'; + +type SheetDescriptionElement = HTMLParagraphElement; +type SheetDescriptionProps = ExtractPropsForTag; + +const SheetDescription = React.forwardRef< + SheetDescriptionElement, + SheetDescriptionProps +>(({ size = '3', weight = 'regular', ...props }, forwardedRef) => { + return ( + + + + ); +}); +SheetDescription.displayName = 'SheetDescription'; + +export { + SheetBody as Body, + SheetClose as Close, + SheetContent as Content, + SheetDescription as Description, + SheetHeader as Header, + SheetRoot as Root, + SheetTitle as Title, + SheetTrigger as Trigger, +}; + +export { + SheetBodyProps as BodyProps, + SheetCloseProps as CloseProps, + SheetContentProps as ContentProps, + SheetDescriptionProps as DescriptionProps, + SheetHeaderProps as HeaderProps, + SheetRootProps as RootProps, + SheetTitleProps as TitleProps, + SheetTriggerProps as TriggerProps, +}; diff --git a/packages/frosted-ui/src/styles/index.css b/packages/frosted-ui/src/styles/index.css index 7f62a23c..2395abeb 100644 --- a/packages/frosted-ui/src/styles/index.css +++ b/packages/frosted-ui/src/styles/index.css @@ -30,6 +30,7 @@ @import '../components/data-list.css'; @import '../components/dialog.css'; @import '../components/drawer.css'; +@import '../components/sheet.css'; @import '../components/dropdown-menu.css'; @import '../components/em.css'; @import '../components/filter-chip.css'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cfe08d8..b9f44fee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -263,6 +263,9 @@ importers: tslib: specifier: ^2.6.2 version: 2.6.2 + vaul: + specifier: ^0.9.1 + version: 0.9.1(@types/react-dom@18.2.18)(@types/react@18.2.53)(react-dom@18.2.0)(react@18.2.0) devDependencies: '@frosted-ui/icons': specifier: workspace:* @@ -13652,6 +13655,20 @@ packages: engines: {node: '>= 0.8'} dev: true + /vaul@0.9.1(@types/react-dom@18.2.18)(@types/react@18.2.53)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.53)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + dev: false + /vite@4.4.11(@types/node@17.0.45): resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==} engines: {node: ^14.18.0 || >=16.0.0}