Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tall-impalas-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Dialog: Support custom width values.
4 changes: 2 additions & 2 deletions packages/react/src/Dialog/Dialog.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
},
{
"name": "width",
"type": "'small' | 'medium' | 'large' | 'xlarge'",
"description": "The width of the dialog.\nsmall: 296px\nmedium: 320px\nlarge: 480px\nxlarge: 640px"
"type": "'small' | 'medium' | 'large' | 'xlarge' | string",
"description": "The width of the dialog.\nsmall: 296px\nmedium: 320px\nlarge: 480px\nxlarge: 640px\n\nAlso accepts any valid CSS width value (e.g. '400px', '80rem')."
Comment thread
liuliu-dev marked this conversation as resolved.
Outdated
},
{
"name": "height",
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/Dialog/Dialog.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,22 @@ export const AlignBottom = () => {
)
}
AlignBottom.storyName = '[Align] Bottom'

export const CustomWidth = () => {
const [isOpen, setIsOpen] = useState(true)
const buttonRef = useRef<HTMLButtonElement>(null)
const onDialogClose = useCallback(() => setIsOpen(false), [])

return (
<>
<Button ref={buttonRef} onClick={() => setIsOpen(true)}>
Show dialog
</Button>
{isOpen && (
<Dialog title="Custom Width Dialog" onClose={onDialogClose} width="400px">
{bodyContent}
</Dialog>
)}
</>
)
}
4 changes: 2 additions & 2 deletions packages/react/src/Dialog/Dialog.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
.Dialog {
display: flex;
/* stylelint-disable-next-line primer/responsive-widths */
width: 640px;
width: var(--dialog-width, 640px);
min-width: 296px;
max-width: calc(100dvw - 64px);
height: auto;
Expand Down Expand Up @@ -213,7 +213,7 @@
@media (max-width: 767px) {
&[data-position-narrow='center'] {
/* stylelint-disable-next-line primer/responsive-widths */
width: 640px;
width: var(--dialog-width, 640px);
height: auto;
border-radius: var(--borderRadius-large, var(--borderRadius-large));

Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/Dialog/Dialog.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ Playground.args = {
Playground.argTypes = {
width: {
control: {
type: 'radio',
type: 'text',
},
options: ['small', 'medium', 'large', 'xlarge'],
description: 'Named size (small, medium, large, xlarge) or a custom CSS width value (e.g. 400px, 80rem)',
},
height: {
control: {
Expand Down
24 changes: 24 additions & 0 deletions packages/react/src/Dialog/Dialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,4 +417,28 @@ describe('Footer button loading states', () => {
expect(document.body.hasAttribute('data-dialog-scroll-disabled')).toBe(false)
})
})

describe('width prop', () => {
it('sets data-width for named sizes', () => {
const {getByRole} = render(
<Dialog onClose={() => {}} width="small">
Content
</Dialog>,
)
const dialog = getByRole('dialog')
expect(dialog).toHaveAttribute('data-width', 'small')
expect(dialog.style.getPropertyValue('--dialog-width')).toBe('')
})

it('sets --dialog-width custom property for custom width values', () => {
const {getByRole} = render(
<Dialog onClose={() => {}} width="400px">
Content
</Dialog>,
)
const dialog = getByRole('dialog')
expect(dialog).not.toHaveAttribute('data-width')
expect(dialog.style.getPropertyValue('--dialog-width')).toBe('400px')
})
Comment thread
liuliu-dev marked this conversation as resolved.
})
})
14 changes: 9 additions & 5 deletions packages/react/src/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useCallback, useEffect, useRef, useState, type SyntheticEvent} from 'react'
import React, {useCallback, useEffect, useRef, useState, type CSSProperties, type SyntheticEvent} from 'react'
import type {ButtonProps} from '../Button'
import {Button, IconButton} from '../Button'
import {useOnEscapePress, useProvidedRefOrCreate} from '../hooks'
Expand Down Expand Up @@ -124,6 +124,8 @@ export interface DialogProps {
* medium: 320px
* large: 480px
* xlarge: 640px
*
* Also accepts any valid CSS width value (e.g. '400px', '80rem').
*/
width?: DialogWidth

Expand Down Expand Up @@ -193,15 +195,14 @@ const heightMap = {
auto: 'auto',
} as const

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const widthMap = {
small: '296px',
medium: '320px',
large: '480px',
xlarge: '640px',
} as const

export type DialogWidth = keyof typeof widthMap
export type DialogWidth = keyof typeof widthMap | Exclude<CSSProperties['width'], undefined>
export type DialogHeight = keyof typeof heightMap

const DefaultHeader: React.FC<React.PropsWithChildren<DialogHeaderProps>> = ({
Expand Down Expand Up @@ -397,12 +398,15 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
aria-modal
{...positionDataAttributes}
{...(align && {'data-align': align})}
data-width={width}
data-width={width in widthMap ? width : undefined}
data-height={height}
data-has-footer={hasFooter ? '' : undefined}
data-footer-button-layout={hasFooter ? footerButtonLayout : undefined}
className={clsx(className, classes.Dialog)}
style={style}
style={{
...style,
...(!(width in widthMap) ? {'--dialog-width': width} : {}),
}}
Comment thread
liuliu-dev marked this conversation as resolved.
Outdated
>
{header}
<ScrollableRegion aria-labelledby={dialogLabelId} className={classes.DialogOverflowWrapper}>
Expand Down
Loading