From 0f2f53d3dbb1bee500308f7911f98a4c859cad43 Mon Sep 17 00:00:00 2001 From: gmeigniez-pass <139768952+gmeigniez-pass@users.noreply.github.com> Date: Thu, 6 Feb 2025 11:35:02 +0100 Subject: [PATCH] (PC-34260)[PRO] feat: Add title and footer props to Dialog and prevent closing when clicking on an eleemnt on top of the content. --- .../DialogBuilder/DialogBuilder.module.scss | 19 ++++- .../DialogBuilder/DialogBuilder.spec.tsx | 76 +++++++++++++++++ .../DialogBuilder/DialogBuilder.stories.tsx | 82 ++++++++++++++++++- .../ui-kit/DialogBuilder/DialogBuilder.tsx | 51 +++++++++++- 4 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 pro/src/ui-kit/DialogBuilder/DialogBuilder.spec.tsx diff --git a/pro/src/ui-kit/DialogBuilder/DialogBuilder.module.scss b/pro/src/ui-kit/DialogBuilder/DialogBuilder.module.scss index 912af8c366f..47f5d2e4c8b 100644 --- a/pro/src/ui-kit/DialogBuilder/DialogBuilder.module.scss +++ b/pro/src/ui-kit/DialogBuilder/DialogBuilder.module.scss @@ -1,6 +1,7 @@ @use "styles/mixins/_rem.scss" as rem; @use "styles/variables/_z-index.scss" as zIndex; @use "styles/mixins/_size.scss" as size; +@use "styles/mixins/_fonts.scss" as fonts; $drawer-width: rem.torem(684px); $animation-duration: 0.15s; @@ -26,6 +27,22 @@ $animation-timing-function: ease-in-out; } } +.dialog-builder-title { + @include fonts.title3; +} + +.dialog-builder-section { + min-height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.dialog-builder-footer { + border-top: rem.torem(1px) solid var(--color-grey-medium); + padding-top: rem.torem(24px); +} + .dialog-builder-overlay { background-color: rgb(0 0 0 / 33%); display: grid; @@ -61,10 +78,10 @@ $animation-timing-function: ease-in-out; .dialog-builder-overlay-drawer > .dialog-builder-content { border-radius: 0; - height: 100vh; width: $drawer-width; max-width: 100vw; margin: 0; + height: 100%; @media not (prefers-reduced-motion) { &[data-state="open"] { diff --git a/pro/src/ui-kit/DialogBuilder/DialogBuilder.spec.tsx b/pro/src/ui-kit/DialogBuilder/DialogBuilder.spec.tsx new file mode 100644 index 00000000000..62602127256 --- /dev/null +++ b/pro/src/ui-kit/DialogBuilder/DialogBuilder.spec.tsx @@ -0,0 +1,76 @@ +import * as Dialog from '@radix-ui/react-dialog' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' + +import { Button } from 'ui-kit/Button/Button' +import { ButtonVariant } from 'ui-kit/Button/types' + +import { DialogBuilder, DialogBuilderProps } from './DialogBuilder' + +const defaultProps: DialogBuilderProps = { + children:

Dialog content

, + title: 'Dialog title', + trigger: , + footer: ( +
+ + + + + + +
+ ), +} + +function renderDialogBuilder(props = defaultProps) { + return render({props.children}) +} + +describe('DialogBuilder', () => { + it('should open a dialog with a title and a footer', async () => { + renderDialogBuilder() + + await userEvent.click( + screen.getByRole('button', { name: 'Open the dialog' }) + ) + expect( + screen.getByRole('heading', { name: 'Dialog title' }) + ).toBeInTheDocument() + expect( + screen.getByRole('button', { name: 'Continuer' }) + ).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Annuler' })).toBeInTheDocument() + expect( + screen.getByRole('button', { name: 'Fermer la modale' }) + ).toBeInTheDocument() + }) + + it('should close the dialog when clicking the close button', async () => { + renderDialogBuilder() + + await userEvent.click( + screen.getByRole('button', { name: 'Open the dialog' }) + ) + + await userEvent.click( + screen.getByRole('button', { name: 'Fermer la modale' }) + ) + expect( + screen.queryByRole('heading', { name: 'Dialog title' }) + ).not.toBeInTheDocument() + }) + + it('should close the dialog when clicking outside the dialog', async () => { + renderDialogBuilder() + + await userEvent.click( + screen.getByRole('button', { name: 'Open the dialog' }) + ) + + await userEvent.click(screen.getByTestId('dialog-overlay')) + expect( + screen.queryByRole('heading', { name: 'Dialog title' }) + ).not.toBeInTheDocument() + }) +}) diff --git a/pro/src/ui-kit/DialogBuilder/DialogBuilder.stories.tsx b/pro/src/ui-kit/DialogBuilder/DialogBuilder.stories.tsx index d32b44bbb8f..e5b6f85e2a8 100644 --- a/pro/src/ui-kit/DialogBuilder/DialogBuilder.stories.tsx +++ b/pro/src/ui-kit/DialogBuilder/DialogBuilder.stories.tsx @@ -2,6 +2,7 @@ import * as Dialog from '@radix-ui/react-dialog' import type { StoryObj } from '@storybook/react' import { Button } from 'ui-kit/Button/Button' +import { ButtonVariant } from 'ui-kit/Button/types' import { DialogBuilder } from './DialogBuilder' @@ -28,13 +29,88 @@ export const Drawer: StoryObj = { args: { variant: 'drawer', trigger: , + title: 'Dialog title', + footer: ( +
+ + + + + + +
+ ), children: ( <> - -

Dialog title

-

lorem ipsum dolor sit amet

), }, } + +export const DrawerWithLongContent: StoryObj = { + args: { + variant: 'drawer', + trigger: , + title: 'Dialog title', + footer: ( +
+ + + + + + +
+ ), + children: ( +
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam + blandit blandit purus, at suscipit arcu euismod lacinia. Suspendisse + sed porttitor arcu, nec consequat sem. Integer eget tincidunt lacus. + Nam luctus varius est a aliquam. Maecenas congue turpis a libero + pellentesque commodo a in nulla. Aliquam erat volutpat. Morbi non arcu + elit. Maecenas dictum at nunc nec dapibus. Donec sollicitudin nisi sed + cursus lobortis. Ut arcu odio, varius at elit a, pellentesque + scelerisque elit. Suspendisse potenti. Nullam malesuada dolor non + iaculis euismod. Praesent vel enim ac eros consectetur tempus. Donec + eget nibh nisl. +

+

+ Praesent tempor elementum enim vitae dapibus. Nunc consectetur finibus + orci, vel venenatis nisi commodo lacinia. Donec suscipit nibh non + pharetra porttitor. In nec arcu ultrices, consequat ex vel, + scelerisque eros. Integer porttitor ipsum et urna suscipit, vel + hendrerit orci lacinia. In ut facilisis tortor. Suspendisse luctus + neque a odio vulputate malesuada ut sit amet nulla. Nam nec ipsum non + mauris mollis rutrum. Phasellus vel sem volutpat, pellentesque augue + ut, rhoncus eros. Quisque eu tellus ac lorem ornare iaculis. +

+

+ Morbi accumsan maximus justo, eu varius nunc posuere a. Fusce sed + lacus a neque rutrum laoreet. Fusce varius bibendum interdum. Nunc + consequat ex ac nisi porta ultricies. Curabitur sodales cursus purus, + id efficitur orci condimentum eget. Etiam luctus massa id velit + volutpat tincidunt. Ut at risus arcu. Nunc semper lacus in sem rhoncus + elementum. Cras imperdiet vitae magna scelerisque ornare. Nam nec + cursus urna. +

+

+ Aliquam dictum luctus lectus ac dignissim. Cras elementum interdum + scelerisque. Cras consectetur purus ex, vel rutrum ante tincidunt non. + Sed sapien purus, dignissim non odio sed, efficitur convallis libero. + Proin tristique tellus fermentum nisl posuere, sed accumsan dui + vulputate. Curabitur consequat rhoncus lacus sit amet consequat. + Aliquam maximus, massa quis auctor lacinia, ipsum magna finibus quam, + id hendrerit tortor tortor sit amet tortor. Mauris sit amet + scelerisque dolor. Nulla facilisi. Cras a sapien vitae dolor euismod + posuere in ut sem. Suspendisse vel nibh tortor. Quisque tempus a ex + vitae auctor. Donec in viverra leo. Nam eu gravida quam, et feugiat + metus. Proin egestas ipsum eget augue porttitor lobortis. Donec vitae + quam dui. +

+
+ ), + }, +} diff --git a/pro/src/ui-kit/DialogBuilder/DialogBuilder.tsx b/pro/src/ui-kit/DialogBuilder/DialogBuilder.tsx index 03a374660bc..7ee33565b56 100644 --- a/pro/src/ui-kit/DialogBuilder/DialogBuilder.tsx +++ b/pro/src/ui-kit/DialogBuilder/DialogBuilder.tsx @@ -1,23 +1,32 @@ import * as Dialog from '@radix-ui/react-dialog' import cn from 'classnames' +import { useRef } from 'react' import styles from './DialogBuilder.module.scss' import { DialogBuilderCloseButton } from './DialogBuilderCloseButton' -export type DialogVariant = 'default' | 'drawer' +type DialogVariant = 'default' | 'drawer' /** * Props for the DialogBuilder component. */ -type DialogBuilderProps = { +export type DialogBuilderProps = { /** * The trigger element that opens the dialog. */ trigger?: React.ReactNode + /** + * The heading title of the dialog. + */ + title?: string /** * The content to be displayed inside the dialog. */ children: React.ReactNode + /** + * The content to be displayed at the bottom of the dialog, after the separator. + */ + footer?: React.ReactNode /** * Determines if the dialog is open by default. * @default false @@ -64,7 +73,9 @@ type DialogBuilderProps = { */ export function DialogBuilder({ trigger, + title, children, + footer, defaultOpen = false, onOpenChange, open, @@ -72,6 +83,7 @@ export function DialogBuilder({ className, variant = 'default', }: DialogBuilderProps) { + const contentRef = useRef(null) return ( { + if (!contentRef.current) { + return + } + const contentRect = contentRef.current.getBoundingClientRect() + // Detect if click actually happened within the bounds of content. + // This can happen if click was on an absolutely positioned element overlapping content, + // such as a click on the scroll bar (https://github.com/radix-ui/primitives/issues/1280) + const actuallyClickedInside = + e.detail.originalEvent.clientX > contentRect.left && + e.detail.originalEvent.clientX < + contentRect.left + contentRect.width && + e.detail.originalEvent.clientY > contentRect.top && + e.detail.originalEvent.clientY < + contentRect.top + contentRect.height + if (actuallyClickedInside) { + e.preventDefault() + } + }} > -
{children}
+
+
+ {title && ( + +

{title}

+
+ )} + {children} +
+ {footer && ( +
{footer}
+ )} +