diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 00000000..f7d1c10b --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,5 @@ + diff --git a/src/components/Backdrop/Backdrop.tsx b/src/components/Backdrop/Backdrop.tsx index 03d7010f..4df1c104 100644 --- a/src/components/Backdrop/Backdrop.tsx +++ b/src/components/Backdrop/Backdrop.tsx @@ -1,6 +1,7 @@ -import { Content, Overlay, Root } from '@radix-ui/react-dialog' -import React, { FC } from 'react' +import { Content, Overlay, Portal, Root } from '@radix-ui/react-dialog' +import React, { ComponentProps, FC } from 'react' import { CSS, styled } from '../../stitches.config' +import { ConditionalWrapper } from '../../utils' import { overlayAnimationStyles, overlayStyles } from '../Overlay' const StyledOverlay = styled(Overlay, overlayStyles, overlayAnimationStyles, { @@ -33,6 +34,10 @@ type BackdropProps = React.ComponentProps & { overlayCss?: CSS /** Modify the default styling of the content wrapper */ contentCss?: CSS + /** By default, portals your overlay and content parts into the body, set false to add at dom location. */ + portalled?: boolean + /** Specify a container element to portal the content into. */ + container?: ComponentProps['container'] } /** @@ -45,13 +50,22 @@ type BackdropProps = React.ComponentProps & { export const Backdrop: FC = ({ overlayCss, contentCss, + container, + portalled = true, children, ...props }) => { return ( - - {children} + {child}} + > + <> + + {children} + + ) } diff --git a/src/components/ComponentsProvider/ComponentsProvider.tsx b/src/components/ComponentsProvider/ComponentsProvider.tsx index 9df606a1..980f5df8 100644 --- a/src/components/ComponentsProvider/ComponentsProvider.tsx +++ b/src/components/ComponentsProvider/ComponentsProvider.tsx @@ -1,4 +1,5 @@ import React, { FC, PropsWithChildren } from 'react' +import { CSSProps, styled } from '../../stitches.config' import { ConditionalWrapper } from '../../utils' import { ThemeProvider, ThemeProviderProps } from '../ThemeProvider' import { ToastProvider, ToastViewport } from '../Toast' @@ -40,7 +41,15 @@ export type ComponentsProviderProps = { * Toast viewport configuration options */ viewport?: false | ToastViewportPropsWithoutChildren -} + /** + * By default the childre are put into their own stacking context to better separate the content from the portalled dialog elements. Set false to turn this off and controll it yourself. + */ + isolated?: boolean +} & CSSProps + +const Isolate = styled('div', { + variants: { isolated: { false: {}, true: { isolation: 'isolate' } } }, +}) /** * The `ComponentsProvider` should wrap you application. @@ -53,7 +62,15 @@ export type ComponentsProviderProps = { */ export const ComponentsProvider: FC< PropsWithChildren -> = ({ theme = {}, tooltip = {}, toast = {}, viewport = {}, children }) => ( +> = ({ + theme = {}, + tooltip = {}, + toast = {}, + viewport = {}, + css, + isolated = true, + children, +}) => ( ( @@ -73,7 +90,9 @@ export const ComponentsProvider: FC< )} > <> - {children} + + {children} + {viewport && } diff --git a/src/components/ConfirmDialog/ConfirmDialog.tsx b/src/components/ConfirmDialog/ConfirmDialog.tsx index 8ce0d2c1..0f37841e 100644 --- a/src/components/ConfirmDialog/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog/ConfirmDialog.tsx @@ -4,6 +4,7 @@ import { Content, Description, Overlay, + Portal, Root, Title, Trigger, @@ -11,6 +12,7 @@ import { import React, { ComponentProps, ElementRef, FC, forwardRef } from 'react' import type { CSSProps } from '../../stitches.config' import { CSS, styled } from '../../stitches.config' +import { ConditionalWrapper } from '../../utils' import { Button } from '../Button' import { Heading } from '../Heading' import { overlayAnimationStyles, overlayStyles } from '../Overlay' @@ -56,11 +58,6 @@ export const StyledContent = styled( overlayAnimationStyles ) -type ConfirmDialogProps = ComponentProps & { - /** Modify the default styling of the overlay */ - overlayCss?: CSS -} - /** * The `ConfirmDialog` component can be used get confirmation of an action from the user. * This is done by isolating the user from the main window by overlaying @@ -80,18 +77,7 @@ type ConfirmDialogProps = ComponentProps & { * * Based on [Radix Alert Dialog](https://radix-ui.com/primitives/docs/components/alert-dialog). */ -export const ConfirmDialog: FC = ({ - children, - overlayCss, - ...props -}) => { - return ( - - - {children} - - ) -} +export const ConfirmDialog = Root type ConfirmDialogContentProps = ComponentProps & CSSProps & { @@ -99,20 +85,47 @@ type ConfirmDialogContentProps = ComponentProps & title?: string /** Add a description to the content. */ description?: string + /** Modify the default styling of the overlay */ + overlayCss?: CSS + /** By default, portals your overlay and content parts into the body, set false to add at dom location. */ + portalled?: boolean + /** Specify a container element to portal the content into. */ + container?: ComponentProps['container'] } export const ConfirmDialogContent = forwardRef< ElementRef, ConfirmDialogContentProps ->(({ title, description, children, ...props }, forwardedRef) => ( - - {title && {title}} - {description && ( - {description} - )} - {children} - -)) +>( + ( + { + title, + description, + overlayCss, + container, + portalled = true, + children, + ...props + }, + forwardedRef + ) => ( + {child}} + > + <> + + + {title && {title}} + {description && ( + {description} + )} + {children} + + + + ) +) ConfirmDialogContent.toString = () => `.${StyledContent.className}` export const ConfirmDialogTrigger = forwardRef< diff --git a/src/components/Dialog/Dialog.stories.tsx b/src/components/Dialog/Dialog.stories.tsx index e9f2d5ce..120ce9f4 100644 --- a/src/components/Dialog/Dialog.stories.tsx +++ b/src/components/Dialog/Dialog.stories.tsx @@ -1,7 +1,7 @@ import { useBoolean } from '@committed/hooks' import { action } from '@storybook/addon-actions' import { Meta, Story } from '@storybook/react' -import React from 'react' +import React, { useState } from 'react' import { Dialog, DialogClose, @@ -10,7 +10,7 @@ import { DialogTitle, DialogTrigger, } from '.' -import { Button, IconButton, Link, Row, Text } from '../' +import { Box, Button, Column, IconButton, Link, Row, Text } from '../' import { Close as CloseIcon } from '../Icons' export default { @@ -94,3 +94,254 @@ export const CloseButton: Story = () => { ) } + +/** + * The dialog is portalled by default. This means the dialog will be appended to the body and so over lay other elements. + * In addition, the `CompontsProvider`, by default, isolates the children in their own stacking context so this will not be afected by the z-index of other elements of the UI. + * + * This example shows how the z-index of other elements in the UI do not affect the dialog. + * + * This may not be the desired behaviour for your application so these defaults can be overridden, see below. + */ +export const Portalled: Story = () => { + return ( + + + + + + + This is a dialog + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + ) +} + +/** + * Turning off portalling will mean the zIndex of the dialog will be relative to the zIndex of the element it is appended to. + */ +export const zIndex: Story = () => { + return ( + + + + + + + This is a dialog + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + ) +} + +/** + * The element the dialog portalls to can be provided, and different effects can be achieved by supplying additional css. For example: + */ +export const Container: Story = () => { + const [element, setElement] = useState(null) + + return ( + + + + + + + + This is a dialog + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + + + + + + This is a dialog + + + + + + This area will now contain the dialog + + + ) +} diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 63f0b9c9..ad641e5d 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -3,6 +3,7 @@ import { Content, Description, Overlay, + Portal, Root, Title, Trigger, @@ -10,6 +11,7 @@ import { import React, { ComponentProps, ElementRef, FC, forwardRef } from 'react' import type { CSSProps } from '../../stitches.config' import { CSS, styled } from '../../stitches.config' +import { ConditionalWrapper } from '../../utils' import { Heading } from '../Heading' import { IconButton } from '../IconButton' import { Close as Icon } from '../Icons' @@ -63,11 +65,6 @@ const StyledIconButton = styled(IconButton, { top: '$1', }) -type DialogProps = React.ComponentProps & { - /** Modify the default styling of the overlay */ - overlayCss?: CSS -} - /** * The Dialog component can be used to isolate the user from the main window by overlaying * another window that requires the users attention. @@ -81,36 +78,55 @@ type DialogProps = React.ComponentProps & { * * Based on [Radix Dialog](https://radix-ui.com/primitives/docs/components/dialog). */ -export const Dialog: FC = ({ children, overlayCss, ...props }) => { - return ( - - - {children} - - ) -} +export const Dialog = Root type DialogContentProps = Omit, 'asChild'> & CSSProps & { /** Closable, add a standard close icon. */ defaultClose?: boolean + /** Modify the default styling of the overlay */ + overlayCss?: CSS + /** By default, portals your overlay and content parts into the body, set false to add at dom location. */ + portalled?: boolean + /** Specify a container element to portal the content into. */ + container?: ComponentProps['container'] } export const DialogContent = forwardRef< ElementRef, DialogContentProps ->(({ children, defaultClose = true, ...props }, forwardedRef) => ( - - {defaultClose && ( - - - - - - )} - {children} - -)) +>( + ( + { + children, + overlayCss, + container, + portalled = true, + defaultClose = true, + ...props + }, + forwardedRef + ) => ( + {child}} + > + <> + + + {defaultClose && ( + + + + + + )} + {children} + + + + ) +) DialogContent.toString = () => `.${StyledContent.className}` export const DialogTrigger = forwardRef< diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx index 97530186..ca9ac6bc 100644 --- a/src/components/Drawer/Drawer.tsx +++ b/src/components/Drawer/Drawer.tsx @@ -1,12 +1,8 @@ -import { Close, Content, Root } from '@radix-ui/react-dialog' +import { Close, Content, Portal, Root } from '@radix-ui/react-dialog' import React, { ComponentProps, ElementRef, forwardRef } from 'react' -import type { - ChildProps, - CSS, - CSSProps, - VariantProps, -} from '../../stitches.config' +import type { CSS, CSSProps, VariantProps } from '../../stitches.config' import { keyframes, styled } from '../../stitches.config' +import { ConditionalWrapper } from '../../utils' import { DialogClose, DialogTrigger, StyledOverlay } from '../Dialog/Dialog' import { IconButton } from '../IconButton' import { Close as Icon } from '../Icons' @@ -90,34 +86,54 @@ type DrawerContentProps = Omit, 'asChild'> & DrawerContentVariants & { /** Closable, add a standard close icon. */ defaultClose?: boolean + /** Modify the default styling of the overlay */ + overlayCss?: CSS + /** By default, portals your overlay and content parts into the body, set false to add at dom location. */ + portalled?: boolean + /** Specify a container element to portal the content into. */ + container?: ComponentProps['container'] } export const DrawerContent = forwardRef< ElementRef, DrawerContentProps ->(({ defaultClose, children, ...props }, forwardedRef) => ( - - {children} - {defaultClose && ( - - - - - - )} - -)) +>( + ( + { + defaultClose, + children, + overlayCss, + container, + portalled = true, + ...props + }, + forwardedRef + ) => ( + {child}} + > + <> + + + {children} + {defaultClose && ( + + + + + + )} + + + + ) +) DrawerContent.toString = () => `.${StyledContent.className}` export const DrawerTrigger = DialogTrigger export const DrawerClose = DialogClose -type DrawerProps = React.ComponentProps & - ChildProps & { - /** Modify the default styling of the overlay */ - overlayCss?: CSS - } - /** * The Drawer component can be used to overlay a panel from any side. * @@ -127,15 +143,4 @@ type DrawerProps = React.ComponentProps & * * Based on [Radix Dialog](https://radix-ui.com/primitives/docs/components/dialog). */ -export const Drawer: React.FC = ({ - children, - overlayCss, - ...props -}) => { - return ( - - - {children} - - ) -} +export const Drawer = Root