From ee8d25d17a3747ffd140e819838807de346e8527 Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 19:05:19 +0000 Subject: [PATCH 1/9] feat(Dialog): Convert Dialog v2 to CSS Modules --- packages/react/src/Dialog/Dialog.tsx | 422 ++++++++++++++++----------- 1 file changed, 250 insertions(+), 172 deletions(-) diff --git a/packages/react/src/Dialog/Dialog.tsx b/packages/react/src/Dialog/Dialog.tsx index 13d1b4fd752..128b64c700d 100644 --- a/packages/react/src/Dialog/Dialog.tsx +++ b/packages/react/src/Dialog/Dialog.tsx @@ -16,6 +16,11 @@ import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef' import {useId} from '../hooks/useId' import {ScrollableRegion} from '../ScrollableRegion' import type {ResponsiveValue} from '../hooks/useResponsiveValue' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' + +import classes from './Dialog.module.css' +import {useFeatureFlag} from '../FeatureFlags' +import {clsx} from 'clsx' /* Dialog Version 2 */ @@ -166,58 +171,65 @@ export interface DialogHeaderProps extends DialogProps { dialogDescriptionId: string } -const Backdrop = styled('div')` - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; - display: flex; - align-items: center; - justify-content: center; - background-color: ${get('colors.primer.canvas.backdrop')}; - animation: dialog-backdrop-appear 200ms ${get('animation.easeOutCubic')}; - - &[data-position-regular='center'] { +const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' + +const Backdrop = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div` + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; align-items: center; justify-content: center; - } - - &[data-position-regular='left'] { - align-items: center; - justify-content: flex-start; - } + background-color: ${get('colors.primer.canvas.backdrop')}; + animation: dialog-backdrop-appear 200ms ${get('animation.easeOutCubic')}; - &[data-position-regular='right'] { - align-items: center; - justify-content: flex-end; - } + &[data-position-regular='center'] { + align-items: center; + justify-content: center; + } - .DialogOverflowWrapper { - flex-grow: 1; - } + &[data-position-regular='left'] { + align-items: center; + justify-content: flex-start; + } - @media (max-width: 767px) { - &[data-position-narrow='center'] { + &[data-position-regular='right'] { align-items: center; - justify-content: center; + justify-content: flex-end; } - &[data-position-narrow='bottom'] { - align-items: end; - justify-content: center; + .DialogOverflowWrapper { + flex-grow: 1; } - } - @keyframes dialog-backdrop-appear { - 0% { - opacity: 0; + @media (max-width: 767px) { + &[data-position-narrow='center'] { + align-items: center; + justify-content: center; + } + + &[data-position-narrow='bottom'] { + align-items: end; + justify-content: center; + } } - 100% { - opacity: 1; + + @keyframes dialog-backdrop-appear { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } } - } -` + `, +) +Backdrop.displayName = 'Backdrop' const heightMap = { small: '480px', @@ -240,121 +252,125 @@ type StyledDialogProps = { height?: DialogHeight } & SxProp -const StyledDialog = styled.div` - display: flex; - flex-direction: column; - background-color: ${get('colors.canvas.overlay')}; - box-shadow: ${get('shadows.overlay.shadow')}; - width: ${props => widthMap[props.width ?? ('xlarge' as const)]}; - height: ${props => heightMap[props.height ?? ('auto' as const)]}; - min-width: 296px; - max-width: calc(100dvw - 64px); - max-height: calc(100dvh - 64px); - border-radius: 12px; - opacity: 1; - - @media screen and (prefers-reduced-motion: no-preference) { - animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; - } - - &[data-position-regular='center'] { - border-radius: var(--borderRadius-large, 0.75rem); +const StyledDialog = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div` + display: flex; + flex-direction: column; + background-color: ${get('colors.canvas.overlay')}; + box-shadow: ${get('shadows.overlay.shadow')}; + width: ${props => widthMap[props.width ?? ('xlarge' as const)]}; + height: ${props => heightMap[props.height ?? ('auto' as const)]}; + min-width: 296px; + max-width: calc(100dvw - 64px); + max-height: calc(100dvh - 64px); + border-radius: 12px; + opacity: 1; @media screen and (prefers-reduced-motion: no-preference) { animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; } - } - - &[data-position-regular='left'] { - height: 100dvh; - max-height: unset; - border-radius: var(--borderRadius-large, 0.75rem); - border-top-left-radius: 0; - border-bottom-left-radius: 0; - @media screen and (prefers-reduced-motion: no-preference) { - animation: Overlay--motion-slideInRight 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; - } - } - - &[data-position-regular='right'] { - height: 100dvh; - max-height: unset; - border-radius: var(--borderRadius-large, 0.75rem); - border-top-right-radius: 0; - border-bottom-right-radius: 0; + &[data-position-regular='center'] { + border-radius: var(--borderRadius-large, 0.75rem); - @media screen and (prefers-reduced-motion: no-preference) { - animation: Overlay--motion-slideInLeft 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } } - } - @media (max-width: 767px) { - &[data-position-narrow='center'] { + &[data-position-regular='left'] { + height: 100dvh; + max-height: unset; border-radius: var(--borderRadius-large, 0.75rem); - width: ${props => widthMap[props.width ?? ('xlarge' as const)]}; - height: ${props => heightMap[props.height ?? ('auto' as const)]}; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-slideInRight 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } } - &[data-position-narrow='bottom'] { - width: 100dvw; - height: auto; - max-width: 100dvw; - max-height: calc(100dvh - 64px); + &[data-position-regular='right'] { + height: 100dvh; + max-height: unset; border-radius: var(--borderRadius-large, 0.75rem); + border-top-right-radius: 0; border-bottom-right-radius: 0; - border-bottom-left-radius: 0; @media screen and (prefers-reduced-motion: no-preference) { - animation: Overlay--motion-slideUp 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + animation: Overlay--motion-slideInLeft 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; } } - &[data-position-narrow='fullscreen'] { - width: 100%; - max-width: 100dvw; - height: 100%; - max-height: 100dvh; - border-radius: unset !important; - flex-grow: 1; + @media (max-width: 767px) { + &[data-position-narrow='center'] { + border-radius: var(--borderRadius-large, 0.75rem); + width: ${props => widthMap[props.width ?? ('xlarge' as const)]}; + height: ${props => heightMap[props.height ?? ('auto' as const)]}; + } - @media screen and (prefers-reduced-motion: no-preference) { - animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + &[data-position-narrow='bottom'] { + width: 100dvw; + height: auto; + max-width: 100dvw; + max-height: calc(100dvh - 64px); + border-radius: var(--borderRadius-large, 0.75rem); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-slideUp 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } } - } - } - @keyframes Overlay--motion-scaleFade { - 0% { - opacity: 0; - transform: scale(0.5); + &[data-position-narrow='fullscreen'] { + width: 100%; + max-width: 100dvw; + height: 100%; + max-height: 100dvh; + border-radius: unset !important; + flex-grow: 1; + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } + } } - 100% { - opacity: 1; - transform: scale(1); + + @keyframes Overlay--motion-scaleFade { + 0% { + opacity: 0; + transform: scale(0.5); + } + 100% { + opacity: 1; + transform: scale(1); + } } - } - @keyframes Overlay--motion-slideUp { - from { - transform: translateY(100%); + @keyframes Overlay--motion-slideUp { + from { + transform: translateY(100%); + } } - } - @keyframes Overlay--motion-slideInRight { - from { - transform: translateX(-100%); + @keyframes Overlay--motion-slideInRight { + from { + transform: translateX(-100%); + } } - } - @keyframes Overlay--motion-slideInLeft { - from { - transform: translateX(100%); + @keyframes Overlay--motion-slideInLeft { + from { + transform: translateX(100%); + } } - } - ${sx}; -` + ${sx}; + `, +) const DefaultHeader: React.FC> = ({ dialogLabelId, @@ -416,6 +432,8 @@ const _Dialog = React.forwardRef(null) @@ -487,9 +505,10 @@ const _Dialog = React.forwardRef { + onMouseDown={(e: React.MouseEvent) => { setLastMouseDownIsBackdrop(e.target === e.currentTarget) }} > @@ -503,7 +522,9 @@ const _Dialog = React.forwardRef {header} @@ -518,51 +539,108 @@ const _Dialog = React.forwardRef` - box-shadow: 0 1px 0 ${get('colors.border.default')}; - padding: ${get('space.2')}; - z-index: 1; - flex-shrink: 0; - ${sx}; -` - -const Title = styled.h1` - font-size: ${get('fontSizes.1')}; - font-weight: ${get('fontWeights.bold')}; - margin: 0; /* override default margin */ - ${sx}; -` - -const Subtitle = styled.h2` - font-size: ${get('fontSizes.0')}; - color: ${get('colors.fg.muted')}; - margin: 0; /* override default margin */ - margin-top: ${get('space.1')}; - font-weight: normal; - - ${sx}; -` - -const Body = styled.div` - flex-grow: 1; - overflow: auto; - padding: ${get('space.3')}; - - ${sx}; -` - -const Footer = styled.div` - box-shadow: 0 -1px 0 ${get('colors.border.default')}; - padding: ${get('space.3')}; - display: flex; - flex-flow: wrap; - justify-content: flex-end; - gap: ${get('space.2')}; - z-index: 1; - flex-shrink: 0; - - ${sx}; -` +const StyledHeader = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div` + box-shadow: 0 1px 0 ${get('colors.border.default')}; + padding: ${get('space.2')}; + z-index: 1; + flex-shrink: 0; + ${sx}; + `, +) +type StyledHeaderProps = React.ComponentProps<'div'> & SxProp + +const Header = React.forwardRef(function Header({className, ...rest}, forwardRef) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return +}) +Header.displayName = 'Dialog.Header' + +const StyledTitle = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'h1', + styled.h1` + font-size: ${get('fontSizes.1')}; + font-weight: ${get('fontWeights.bold')}; + margin: 0; /* override default margin */ + ${sx}; + `, +) +type StyledTitleProps = React.ComponentProps<'h1'> & SxProp + +const Title = React.forwardRef(function Title({className, ...rest}, forwardRef) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return +}) +Title.displayName = 'Dialog.Title' + +const StyledSubtitle = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'h2', + styled.h2` + font-size: ${get('fontSizes.0')}; + color: ${get('colors.fg.muted')}; + margin: 0; /* override default margin */ + margin-top: ${get('space.1')}; + font-weight: normal; + ${sx}; + `, +) +type StyledSubtitleProps = React.ComponentProps<'h2'> & SxProp + +const Subtitle = React.forwardRef(function Subtitle( + {className, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return +}) +Subtitle.displayName = 'Dialog.Subtitle' + +const StyledBody = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div` + flex-grow: 1; + overflow: auto; + padding: ${get('space.3')}; + + ${sx}; + `, +) +type StyledBodyProps = React.ComponentProps<'div'> & SxProp + +const Body = React.forwardRef(function Body({className, ...rest}, forwardRef) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return +}) +Body.displayName = 'Dialog.Body' + +const StyledFooter = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div` + box-shadow: 0 -1px 0 ${get('colors.border.default')}; + padding: ${get('space.3')}; + display: flex; + flex-flow: wrap; + justify-content: flex-end; + gap: ${get('space.2')}; + z-index: 1; + flex-shrink: 0; + + ${sx}; + `, +) +type StyledFooterProps = React.ComponentProps<'div'> & SxProp + +const Footer = React.forwardRef(function Footer({className, ...rest}, forwardRef) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return +}) +Footer.displayName = 'Dialog.Footer' const Buttons: React.FC> = ({buttons}) => { const autoFocusRef = useProvidedRefOrCreate(buttons.find(button => button.autoFocus)?.ref) From c19f55ecfb4c7e1c7b94a8cc3d8a1d3a1ca3c523 Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 19:06:01 +0000 Subject: [PATCH 2/9] changeset --- .changeset/forty-hornets-exercise.md | 5 + packages/react/src/Dialog/Dialog.module.css | 253 ++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 .changeset/forty-hornets-exercise.md create mode 100644 packages/react/src/Dialog/Dialog.module.css diff --git a/.changeset/forty-hornets-exercise.md b/.changeset/forty-hornets-exercise.md new file mode 100644 index 00000000000..2f5eb8c0058 --- /dev/null +++ b/.changeset/forty-hornets-exercise.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Convert Dialog v2 to CSS Modules behind feature flag diff --git a/packages/react/src/Dialog/Dialog.module.css b/packages/react/src/Dialog/Dialog.module.css new file mode 100644 index 00000000000..80328436849 --- /dev/null +++ b/packages/react/src/Dialog/Dialog.module.css @@ -0,0 +1,253 @@ +@keyframes dialog-backdrop-appear { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes Overlay--motion-scaleFade { + 0% { + opacity: 0; + transform: scale(0.5); + } + + 100% { + opacity: 1; + transform: scale(1); + } +} + +@keyframes Overlay--motion-slideUp { + from { + transform: translateY(100%); + } +} + +@keyframes Overlay--motion-slideInRight { + from { + transform: translateX(-100%); + } +} + +@keyframes Overlay--motion-slideInLeft { + from { + transform: translateX(100%); + } +} + +.Backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + background-color: var(--overlay-backdrop-bgColor); + animation: dialog-backdrop-appear 200ms cubic-bezier(0.33, 1, 0.68, 1); + align-items: center; + justify-content: center; + + &[data-position-regular='center'] { + align-items: center; + justify-content: center; + } + + &[data-position-regular='left'] { + align-items: center; + justify-content: flex-start; + } + + &[data-position-regular='right'] { + align-items: center; + justify-content: flex-end; + } + + .DialogOverflowWrapper { + flex-grow: 1; + } + + @media (max-width: 767px) { + &[data-position-narrow='center'] { + align-items: center; + justify-content: center; + } + + &[data-position-narrow='bottom'] { + align-items: end; + justify-content: center; + } + } +} + +.Dialog { + display: flex; + /* stylelint-disable-next-line primer/responsive-widths */ + width: 640px; + min-width: 296px; + max-width: calc(100dvw - 64px); + height: auto; + max-height: calc(100dvh - 64px); + flex-direction: column; + background-color: var(--overlay-bgColor); + border-radius: var(--borderRadius-large); + border-radius: var(--borderRadius-large, var(--borderRadius-large)); + box-shadow: var(--shadow-floating-small); + opacity: 1; + + &:where([data-width='small']) { + width: 296px; + } + + &:where([data-width='medium']) { + width: 320px; + } + + &:where([data-width='large']) { + /* stylelint-disable-next-line primer/responsive-widths */ + width: 480px; + } + + &:where([data-height='small']) { + height: 480px; + } + + &:where([data-height='large']) { + height: 640px; + } + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } + + &[data-position-regular='center'] { + border-radius: var(--borderRadius-large, var(--borderRadius-large)); + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } + } + + &[data-position-regular='left'] { + height: 100dvh; + max-height: unset; + border-radius: var(--borderRadius-large, var(--borderRadius-large)); + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-slideInRight 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } + } + + &[data-position-regular='right'] { + height: 100dvh; + max-height: unset; + border-radius: var(--borderRadius-large, var(--borderRadius-large)); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-slideInLeft 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } + } + + @media (max-width: 767px) { + &[data-position-narrow='center'] { + /* stylelint-disable-next-line primer/responsive-widths */ + width: 640px; + height: auto; + border-radius: var(--borderRadius-large, var(--borderRadius-large)); + + &:where([data-width='small']) { + width: 296px; + } + + &:where([data-width='medium']) { + width: 320px; + } + + &:where([data-width='large']) { + /* stylelint-disable-next-line primer/responsive-widths */ + width: 480px; + } + + &:where([data-height='small']) { + height: 480px; + } + + &:where([data-height='large']) { + height: 640px; + } + } + + &[data-position-narrow='bottom'] { + width: 100dvw; + max-width: 100dvw; + height: auto; + max-height: calc(100dvh - 64px); + border-radius: var(--borderRadius-large, var(--borderRadius-large)); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-slideUp 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } + } + + &[data-position-narrow='fullscreen'] { + width: 100%; + max-width: 100dvw; + height: 100%; + max-height: 100dvh; + border-radius: unset !important; + flex-grow: 1; + + @media screen and (prefers-reduced-motion: no-preference) { + animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running; + } + } + } +} + +.Header { + z-index: 1; + padding: var(--base-size-8); + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: 0 1px 0 var(--borderColor-default); + flex-shrink: 0; +} + +.Title { + margin: 0; /* override default margin */ + font-size: var(--text-body-size-medium); + font-weight: var(--text-title-weight-large); +} + +.Subtitle { + margin: 0; /* override default margin */ + margin-top: var(--base-size-4); + font-size: var(--text-body-size-small); + font-weight: var(--base-text-weight-normal); + color: var(--fgColor-muted); +} + +.Body { + padding: var(--base-size-16); + overflow: auto; + flex-grow: 1 +} + +.Footer { + z-index: 1; + display: flex; + padding: var(--base-size-20); + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: 0 -1px 0 var(--borderColor-default); + flex-flow: wrap; + justify-content: flex-end; + gap: var(--base-size-8); + flex-shrink: 0; +} \ No newline at end of file From 67dc6011b9d560d5eabaaaaf65a5b4a049acbd78 Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 19:40:24 +0000 Subject: [PATCH 3/9] lint fix --- packages/react/src/Dialog/Dialog.module.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react/src/Dialog/Dialog.module.css b/packages/react/src/Dialog/Dialog.module.css index 80328436849..421894d2d82 100644 --- a/packages/react/src/Dialog/Dialog.module.css +++ b/packages/react/src/Dialog/Dialog.module.css @@ -164,11 +164,11 @@ &:where([data-width='small']) { width: 296px; } - + &:where([data-width='medium']) { width: 320px; } - + &:where([data-width='large']) { /* stylelint-disable-next-line primer/responsive-widths */ width: 480px; @@ -177,7 +177,7 @@ &:where([data-height='small']) { height: 480px; } - + &:where([data-height='large']) { height: 640px; } @@ -237,7 +237,7 @@ .Body { padding: var(--base-size-16); overflow: auto; - flex-grow: 1 + flex-grow: 1; } .Footer { @@ -250,4 +250,4 @@ justify-content: flex-end; gap: var(--base-size-8); flex-shrink: 0; -} \ No newline at end of file +} From 7e0677611e5300112f6c78ab4a05a8fe8e9b6b9a Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 19:43:16 +0000 Subject: [PATCH 4/9] fix backdrop classname --- packages/react/src/Dialog/Dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/Dialog/Dialog.tsx b/packages/react/src/Dialog/Dialog.tsx index 128b64c700d..29412d18a06 100644 --- a/packages/react/src/Dialog/Dialog.tsx +++ b/packages/react/src/Dialog/Dialog.tsx @@ -505,7 +505,7 @@ const _Dialog = React.forwardRef ) => { From d499a62e2934ca34b591b11be3d61b0a2976a54a Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 20:05:26 +0000 Subject: [PATCH 5/9] add unit test --- packages/react/src/Dialog/Dialog.test.tsx | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/react/src/Dialog/Dialog.test.tsx b/packages/react/src/Dialog/Dialog.test.tsx index 85095fa0b20..7b1184ff94b 100644 --- a/packages/react/src/Dialog/Dialog.test.tsx +++ b/packages/react/src/Dialog/Dialog.test.tsx @@ -6,6 +6,8 @@ import MatchMediaMock from 'jest-matchmedia-mock' import {behavesAsComponent, checkExports} from '../utils/testing' import axe from 'axe-core' import {Button} from '../Button' +import {FeatureFlags} from '../FeatureFlags' +import exp from 'constants' let matchMedia: MatchMediaMock @@ -226,6 +228,53 @@ describe('Dialog', () => { expect(getByRole('button', {name: 'return focus to (button 2)'})).toHaveFocus() }) + + it('should support `className` on the Dialog element', async () => { + const Fixture = () => { + const [isOpen, setIsOpen] = React.useState(true) + const triggerRef = React.useRef(null) + + return ( + <> + + {isOpen && ( + setIsOpen(false)} returnFocusRef={triggerRef} className="custom-class"> + body + + )} + + ) + } + + const FeatureFlagElement = () => { + return ( + + + + ) + } + + const user = userEvent.setup() + + let component = render() + let triggerButton = component.getByRole('button', {name: 'Show dialog'}) + await user.click(triggerButton) + expect(component.getByRole('dialog')).toHaveClass('custom-class') + component.unmount() + + component = render() + triggerButton = component.getByRole('button', {name: 'Show dialog'}) + await user.click(triggerButton) + expect(component.getByRole('dialog')).toHaveClass('custom-class') + }) }) it('automatically focuses the element that is specified as initialFocusRef', () => { From 0b345a6272335a8af0c7e1383efb3c8b5f140bea Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 20:11:03 +0000 Subject: [PATCH 6/9] lint fix --- packages/react/src/Dialog/Dialog.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/Dialog/Dialog.test.tsx b/packages/react/src/Dialog/Dialog.test.tsx index 7b1184ff94b..1d707fd321f 100644 --- a/packages/react/src/Dialog/Dialog.test.tsx +++ b/packages/react/src/Dialog/Dialog.test.tsx @@ -7,7 +7,6 @@ import {behavesAsComponent, checkExports} from '../utils/testing' import axe from 'axe-core' import {Button} from '../Button' import {FeatureFlags} from '../FeatureFlags' -import exp from 'constants' let matchMedia: MatchMediaMock From 777003f5d94043997c4da7b03704135839772ea8 Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 21:17:53 +0000 Subject: [PATCH 7/9] fix padding --- packages/react/src/Dialog/Dialog.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/Dialog/Dialog.module.css b/packages/react/src/Dialog/Dialog.module.css index 421894d2d82..444df0b3510 100644 --- a/packages/react/src/Dialog/Dialog.module.css +++ b/packages/react/src/Dialog/Dialog.module.css @@ -243,7 +243,7 @@ .Footer { z-index: 1; display: flex; - padding: var(--base-size-20); + padding: var(--base-size-16); /* stylelint-disable-next-line primer/box-shadow */ box-shadow: 0 -1px 0 var(--borderColor-default); flex-flow: wrap; From 19f0e8e0a81bdfc500e3db37a6b5fa7c7161cc2d Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Thu, 14 Nov 2024 22:55:48 +0000 Subject: [PATCH 8/9] make body polymorphic --- packages/react/src/Dialog/Dialog.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react/src/Dialog/Dialog.tsx b/packages/react/src/Dialog/Dialog.tsx index 29412d18a06..82831caed68 100644 --- a/packages/react/src/Dialog/Dialog.tsx +++ b/packages/react/src/Dialog/Dialog.tsx @@ -17,6 +17,7 @@ import {useId} from '../hooks/useId' import {ScrollableRegion} from '../ScrollableRegion' import type {ResponsiveValue} from '../hooks/useResponsiveValue' import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' import classes from './Dialog.module.css' import {useFeatureFlag} from '../FeatureFlags' @@ -615,7 +616,8 @@ type StyledBodyProps = React.ComponentProps<'div'> & SxProp const Body = React.forwardRef(function Body({className, ...rest}, forwardRef) { const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) return -}) +}) as PolymorphicForwardRefComponent<'div', StyledBodyProps> + Body.displayName = 'Dialog.Body' const StyledFooter = toggleStyledComponent( From 72dc7aad039395927604346f3ff7b6489a10be33 Mon Sep 17 00:00:00 2001 From: Hussam Ghazzi Date: Fri, 15 Nov 2024 21:03:46 +0000 Subject: [PATCH 9/9] fix width height props --- packages/react/src/Dialog/Dialog.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/react/src/Dialog/Dialog.tsx b/packages/react/src/Dialog/Dialog.tsx index 82831caed68..de6299e7e1f 100644 --- a/packages/react/src/Dialog/Dialog.tsx +++ b/packages/react/src/Dialog/Dialog.tsx @@ -251,7 +251,8 @@ export type DialogHeight = keyof typeof heightMap type StyledDialogProps = { width?: DialogWidth height?: DialogHeight -} & SxProp +} & SxProp & + React.ComponentPropsWithRef<'div'> const StyledDialog = toggleStyledComponent( CSS_MODULES_FEATURE_FLAG, @@ -500,6 +501,7 @@ const _Dialog = React.forwardRef @@ -514,18 +516,15 @@ const _Dialog = React.forwardRef {header}