Skip to content

Commit

Permalink
migrate DialogV1 to css modules (#5246)
Browse files Browse the repository at this point in the history
* migrate DialogV1 to css modules

* changeset

* fix stylelints

* remove type test

* use data attributes

---------

Co-authored-by: Katie Langerman <18661030+langermank@users.noreply.github.com>
  • Loading branch information
keithamus and langermank authored Nov 13, 2024
1 parent d6ea909 commit c4ecb73
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-donkeys-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Migrate DialogV1 to CSS Modules
67 changes: 67 additions & 0 deletions packages/react/src/DialogV1/Dialog.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
20 changes: 20 additions & 0 deletions packages/react/src/DialogV1/Dialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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*/

Expand Down Expand Up @@ -113,6 +114,25 @@ describe('Dialog', () => {
behavesAsComponent({Component: Dialog.Header})
})

it('should support `className` on the Dialog element', () => {
const Element = () => <Dialog isOpen className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.children[1]).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).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)
Expand Down
145 changes: 94 additions & 51 deletions packages/react/src/DialogV1/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,41 +25,50 @@ type StyledDialogBaseProps = {
wide?: boolean
} & SxProp

const DialogBase = styled.div<StyledDialogBaseProps>`
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<StyledDialogBaseProps>`
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)<SxProp>`
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)<SxProp>`
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<typeof DialogHeaderBase>

function DialogHeader({theme, children, backgroundColor = 'canvas.subtle', ...rest}: DialogHeaderProps) {
Expand All @@ -65,28 +80,40 @@ function DialogHeader({theme, children, backgroundColor = 'canvas.subtle', ...re
)
}

const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)

return (
<DialogHeaderBase theme={theme} p={3} backgroundColor={backgroundColor} {...rest}>
<DialogHeaderBase
theme={theme}
p={3}
backgroundColor={backgroundColor}
{...rest}
className={enabled ? classes.Header : undefined}
>
{children}
</DialogHeaderBase>
)
}

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
Expand All @@ -96,7 +123,7 @@ type InternalDialogProps = {
} & ComponentProps<typeof DialogBase>

const Dialog = forwardRef<HTMLDivElement, InternalDialogProps>(
({children, onDismiss = noop, isOpen, initialFocusRef, returnFocusRef, ...props}, forwardedRef) => {
({children, onDismiss = noop, isOpen, initialFocusRef, returnFocusRef, className, ...props}, forwardedRef) => {
const overlayRef = useRef(null)
const modalRef = useRef<HTMLDivElement>(null)
useRefObjectAsForwardedRef(forwardedRef, modalRef)
Expand All @@ -118,17 +145,33 @@ const Dialog = forwardRef<HTMLDivElement, InternalDialogProps>(
returnFocusRef,
overlayRef,
})

const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)

const iconStyles = enabled
? {className: classes.CloseIcon}
: {sx: {position: 'absolute', top: '8px', right: '16px'}}

return isOpen ? (
<>
<Overlay ref={overlayRef} />
<DialogBase tabIndex={-1} ref={modalRef} role="dialog" aria-modal="true" {...props} {...getDialogProps()}>
<Overlay className={enabled ? classes.Overlay : undefined} ref={overlayRef} />
<DialogBase
tabIndex={-1}
ref={modalRef}
role="dialog"
aria-modal="true"
{...props}
{...getDialogProps()}
className={clsx({[classes.Dialog]: enabled}, className)}
data-width={props.wide ? 'wide' : props.narrow ? 'narrow' : 'default'}
>
<IconButton
icon={XIcon}
ref={closeButtonRef}
onClick={onCloseClick}
sx={{position: 'absolute', top: '8px', right: '16px'}}
aria-label="Close"
variant="invisible"
{...iconStyles}
/>
{children}
</DialogBase>
Expand Down
5 changes: 0 additions & 5 deletions packages/react/src/DialogV1/Dialog.types.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,3 @@ import {Dialog} from '../DialogV1'
export function shouldAcceptCallWithNoProps() {
return <Dialog />
}

export function shouldNotAcceptSystemProps() {
// @ts-expect-error system props should not be accepted
return <Dialog backgroundColor="thistle" />
}

0 comments on commit c4ecb73

Please sign in to comment.