diff --git a/.changeset/new-donkeys-attend.md b/.changeset/new-donkeys-attend.md
new file mode 100644
index 00000000000..ec5401959fa
--- /dev/null
+++ b/.changeset/new-donkeys-attend.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": minor
+---
+
+Migrate DialogV1 to CSS Modules
diff --git a/packages/react/src/DialogV1/Dialog.module.css b/packages/react/src/DialogV1/Dialog.module.css
new file mode 100644
index 00000000000..dba07129c37
--- /dev/null
+++ b/packages/react/src/DialogV1/Dialog.module.css
@@ -0,0 +1,67 @@
+.Overlay {
+ &::before {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 99;
+ display: block;
+ cursor: default;
+ content: ' ';
+ background: var(--overlay-backdrop-bgColor);
+ }
+}
+
+.CloseIcon {
+ position: absolute;
+ top: var(--base-size-8);
+ right: var(--base-size-16);
+}
+
+.Dialog {
+ position: fixed;
+ top: 0;
+ left: 50%;
+ z-index: 999;
+ max-height: 80vh;
+ margin: 10vh auto;
+ background-color: var(--bgColor-default);
+ border-radius: var(--borderRadius-medium);
+ outline: none;
+ box-shadow: var(--shadow-floating-large);
+ transform: translateX(-50%);
+
+ &:where([data-width='default']) {
+ /* stylelint-disable-next-line primer/responsive-widths */
+ width: 440px;
+ }
+
+ &:where([data-width='narrow']) {
+ width: 320px;
+ }
+
+ &:where([data-width='wide']) {
+ /* stylelint-disable-next-line primer/responsive-widths */
+ width: 640px;
+ }
+
+ @media screen and (max-width: 750px) {
+ width: 100dvw;
+ height: 100dvh;
+ margin: 0;
+ border-radius: 0;
+ }
+}
+
+.Header {
+ display: flex;
+ padding: var(--base-size-16);
+ background: var(--bgColor-muted);
+ border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);
+ border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0;
+
+ @media screen and (max-width: 750px) {
+ border-radius: 0;
+ }
+}
diff --git a/packages/react/src/DialogV1/Dialog.test.tsx b/packages/react/src/DialogV1/Dialog.test.tsx
index 7df87bd2c75..d4023e6365c 100644
--- a/packages/react/src/DialogV1/Dialog.test.tsx
+++ b/packages/react/src/DialogV1/Dialog.test.tsx
@@ -4,6 +4,7 @@ import {Dialog} from '../DialogV1'
import {render as HTMLRender, fireEvent} from '@testing-library/react'
import axe from 'axe-core'
import {behavesAsComponent} from '../utils/testing'
+import {FeatureFlags} from '../FeatureFlags'
/* Dialog Version 1*/
@@ -113,6 +114,25 @@ describe('Dialog', () => {
behavesAsComponent({Component: Dialog.Header})
})
+ it('should support `className` on the Dialog element', () => {
+ const Element = () =>
+ const FeatureFlagElement = () => {
+ return (
+
+
+
+ )
+ }
+ expect(HTMLRender().container.children[1]).toHaveClass('test-class-name')
+ expect(HTMLRender().container.children[1]).toHaveClass('test-class-name')
+ })
+
it('should have no axe violations', async () => {
const spy = jest.spyOn(console, 'warn').mockImplementation()
const {container} = HTMLRender(comp)
diff --git a/packages/react/src/DialogV1/Dialog.tsx b/packages/react/src/DialogV1/Dialog.tsx
index 3e565ba8d3d..eababe28cb7 100644
--- a/packages/react/src/DialogV1/Dialog.tsx
+++ b/packages/react/src/DialogV1/Dialog.tsx
@@ -10,6 +10,12 @@ import Text from '../Text'
import type {ComponentProps} from '../utils/types'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {XIcon} from '@primer/octicons-react'
+import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
+import {useFeatureFlag} from '../FeatureFlags'
+import {clsx} from 'clsx'
+import classes from './Dialog.module.css'
+
+const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team'
// Dialog v1
const noop = () => null
@@ -19,41 +25,50 @@ type StyledDialogBaseProps = {
wide?: boolean
} & SxProp
-const DialogBase = styled.div`
- box-shadow: ${get('shadows.shadow.large')};
- border-radius: ${get('radii.2')};
- position: fixed;
- top: 0;
- left: 50%;
- transform: translateX(-50%);
- max-height: 80vh;
- z-index: 999;
- margin: 10vh auto;
- background-color: ${get('colors.canvas.default')};
- width: ${props => (props.narrow ? '320px' : props.wide ? '640px' : '440px')};
- outline: none;
-
- @media screen and (max-width: 750px) {
- width: 100dvw;
- margin: 0;
- border-radius: 0;
- height: 100dvh;
- }
+const DialogBase = toggleStyledComponent(
+ CSS_MODULES_FEATURE_FLAG,
+ 'div',
+ styled.div`
+ box-shadow: ${get('shadows.shadow.large')};
+ border-radius: ${get('radii.2')};
+ position: fixed;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ max-height: 80vh;
+ z-index: 999;
+ margin: 10vh auto;
+ background-color: ${get('colors.canvas.default')};
+ width: ${props => (props.narrow ? '320px' : props.wide ? '640px' : '440px')};
+ outline: none;
+
+ @media screen and (max-width: 750px) {
+ width: 100dvw;
+ margin: 0;
+ border-radius: 0;
+ height: 100dvh;
+ }
- ${sx};
-`
+ ${sx};
+ `,
+)
-const DialogHeaderBase = styled(Box)`
- border-radius: ${get('radii.2')} ${get('radii.2')} 0px 0px;
- border-bottom: 1px solid ${get('colors.border.default')};
- display: flex;
+const DialogHeaderBase = toggleStyledComponent(
+ CSS_MODULES_FEATURE_FLAG,
+ 'div',
+ styled(Box)`
+ border-radius: ${get('radii.2')} ${get('radii.2')} 0px 0px;
+ border-bottom: 1px solid ${get('colors.border.default')};
+ display: flex;
- @media screen and (max-width: 750px) {
- border-radius: 0px;
- }
+ @media screen and (max-width: 750px) {
+ border-radius: 0px;
+ }
+
+ ${sx};
+ `,
+)
- ${sx};
-`
export type DialogHeaderProps = ComponentProps
function DialogHeader({theme, children, backgroundColor = 'canvas.subtle', ...rest}: DialogHeaderProps) {
@@ -65,28 +80,40 @@ function DialogHeader({theme, children, backgroundColor = 'canvas.subtle', ...re
)
}
+ const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
+
return (
-
+
{children}
)
}
-const Overlay = styled.span`
- &:before {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- display: block;
- cursor: default;
- content: ' ';
- background: transparent;
- z-index: 99;
- background: ${get('colors.primer.canvas.backdrop')};
- }
-`
+const Overlay = toggleStyledComponent(
+ CSS_MODULES_FEATURE_FLAG,
+ 'span',
+ styled.span`
+ &:before {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ display: block;
+ cursor: default;
+ content: ' ';
+ background: transparent;
+ z-index: 99;
+ background: ${get('colors.primer.canvas.backdrop')};
+ }
+ `,
+)
type InternalDialogProps = {
isOpen?: boolean
@@ -96,7 +123,7 @@ type InternalDialogProps = {
} & ComponentProps
const Dialog = forwardRef(
- ({children, onDismiss = noop, isOpen, initialFocusRef, returnFocusRef, ...props}, forwardedRef) => {
+ ({children, onDismiss = noop, isOpen, initialFocusRef, returnFocusRef, className, ...props}, forwardedRef) => {
const overlayRef = useRef(null)
const modalRef = useRef(null)
useRefObjectAsForwardedRef(forwardedRef, modalRef)
@@ -118,17 +145,33 @@ const Dialog = forwardRef(
returnFocusRef,
overlayRef,
})
+
+ const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
+
+ const iconStyles = enabled
+ ? {className: classes.CloseIcon}
+ : {sx: {position: 'absolute', top: '8px', right: '16px'}}
+
return isOpen ? (
<>
-
-
+
+
{children}
diff --git a/packages/react/src/DialogV1/Dialog.types.test.tsx b/packages/react/src/DialogV1/Dialog.types.test.tsx
index 010f84f6fed..8c31a1a8768 100644
--- a/packages/react/src/DialogV1/Dialog.types.test.tsx
+++ b/packages/react/src/DialogV1/Dialog.types.test.tsx
@@ -4,8 +4,3 @@ import {Dialog} from '../DialogV1'
export function shouldAcceptCallWithNoProps() {
return
}
-
-export function shouldNotAcceptSystemProps() {
- // @ts-expect-error system props should not be accepted
- return
-}