1- import React , { useCallback , useEffect , useRef , useState } from 'react'
1+ import React , { useCallback , useRef } from 'react'
22import styled from 'styled-components'
33import Button , { ButtonPrimary , ButtonDanger , ButtonProps } from '../deprecated/Button'
44import Box from '../Box'
55import { get } from '../constants'
6- import { useOnEscapePress , useProvidedRefOrCreate } from '../hooks'
7- import { useFocusTrap } from '../hooks/useFocusTrap'
6+ import { useOnEscapePress } from '../hooks'
87import sx , { SxProp } from '../sx'
98import Octicon from '../Octicon'
109import { XIcon } from '@primer/octicons-react'
1110import { useFocusZone } from '../hooks/useFocusZone'
1211import { FocusKeys } from '@primer/behaviors'
13- import Portal from '../Portal'
1412import { useRefObjectAsForwardedRef } from '../hooks/useRefObjectAsForwardedRef'
1513import { useId } from '../hooks/useId'
14+ import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect'
1615
1716/* Dialog Version 2 */
1817
@@ -125,6 +124,11 @@ export interface DialogProps extends SxProp {
125124 * auto: variable based on contents
126125 */
127126 height ?: DialogHeight
127+
128+ /**
129+ * Whether or not the Dialog is rendered initially open
130+ */
131+ initiallyOpen ?: boolean
128132}
129133
130134/**
@@ -144,32 +148,11 @@ export interface DialogHeaderProps extends DialogProps {
144148 dialogDescriptionId : string
145149}
146150
147- const Backdrop = styled ( 'div' ) `
148- position: fixed;
149- top: 0;
150- left: 0;
151- bottom: 0;
152- right: 0;
153- display: flex;
154- align-items: center;
155- justify-content: center;
156- background-color: ${ get ( 'colors.primer.canvas.backdrop' ) } ;
157- animation: dialog-backdrop-appear ${ ANIMATION_DURATION } ${ get ( 'animation.easeOutCubic' ) } ;
158-
159- @keyframes dialog-backdrop-appear {
160- 0% {
161- opacity: 0;
162- }
163- 100% {
164- opacity: 1;
165- }
166- }
167- `
168-
169151const heightMap = {
170152 small : '480px' ,
171153 large : '640px' ,
172154 auto : 'auto' ,
155+ 'min-content' : 'min-content' ,
173156} as const
174157
175158const widthMap = {
@@ -187,8 +170,7 @@ type StyledDialogProps = {
187170 height ?: DialogHeight
188171} & SxProp
189172
190- const StyledDialog = styled . div < StyledDialogProps > `
191- display: flex;
173+ const StyledDialog = styled . dialog < StyledDialogProps > `
192174 flex-direction: column;
193175 background-color: ${ get ( 'colors.canvas.overlay' ) } ;
194176 box-shadow: ${ get ( 'shadows.overlay.shadow' ) } ;
@@ -197,10 +179,30 @@ const StyledDialog = styled.div<StyledDialogProps>`
197179 max-height: calc(100vh - 64px);
198180 width: ${ props => widthMap [ props . width ?? ( 'xlarge' as const ) ] } ;
199181 height: ${ props => heightMap [ props . height ?? ( 'auto' as const ) ] } ;
182+ border: 0;
200183 border-radius: 12px;
184+ padding: 0;
201185 opacity: 1;
202186 animation: overlay--dialog-appear ${ ANIMATION_DURATION } ${ get ( 'animation.easeOutCubic' ) } ;
203187
188+ &[open] {
189+ display: flex;
190+ }
191+
192+ &::backdrop {
193+ background-color: ${ get ( 'colors.primer.canvas.backdrop' ) } ;
194+ animation: dialog-backdrop-appear ${ ANIMATION_DURATION } ${ get ( 'animation.easeOutCubic' ) } ;
195+ }
196+
197+ @keyframes dialog-backdrop-appear {
198+ 0% {
199+ opacity: 0;
200+ }
201+ 100% {
202+ opacity: 1;
203+ }
204+ }
205+
204206 @keyframes overlay--dialog-appear {
205207 0% {
206208 opacity: 0;
@@ -252,7 +254,7 @@ const DefaultFooter: React.FC<React.PropsWithChildren<DialogProps>> = ({footerBu
252254 ) : null
253255}
254256
255- const _Dialog = React . forwardRef < HTMLDivElement , React . PropsWithChildren < DialogProps > > ( ( props , forwardedRef ) => {
257+ const _Dialog = React . forwardRef < HTMLDialogElement , React . PropsWithChildren < DialogProps > > ( ( props , forwardedRef ) => {
256258 const {
257259 title = 'Dialog' ,
258260 subtitle = '' ,
@@ -264,6 +266,7 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
264266 width = 'xlarge' ,
265267 height = 'auto' ,
266268 footerButtons = [ ] ,
269+ initiallyOpen = true ,
267270 sx,
268271 } = props
269272 const dialogLabelId = useId ( )
@@ -276,10 +279,8 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
276279 }
277280 const defaultedProps = { ...props , title, subtitle, role, dialogLabelId, dialogDescriptionId}
278281
279- const dialogRef = useRef < HTMLDivElement > ( null )
282+ const dialogRef = useRef < HTMLDialogElement > ( null )
280283 useRefObjectAsForwardedRef ( forwardedRef , dialogRef )
281- const backdropRef = useRef < HTMLDivElement > ( null )
282- useFocusTrap ( { containerRef : dialogRef , restoreFocusOnCleanUp : true , initialFocusRef : autoFocusedFooterButtonRef } )
283284
284285 useOnEscapePress (
285286 ( event : KeyboardEvent ) => {
@@ -289,47 +290,30 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
289290 [ onClose ] ,
290291 )
291292
292- React . useEffect ( ( ) => {
293- const bodyOverflowStyle = document . body . style . overflow || ''
294- // If the body is already set to overflow: hidden, it likely means
295- // that there is already a modal open. In that case, we should bail
296- // so we don't re-enable scroll after the second dialog is closed.
297- if ( bodyOverflowStyle === 'hidden' ) {
298- return
299- }
300-
301- document . body . style . overflow = 'hidden'
302-
303- return ( ) => {
304- document . body . style . overflow = bodyOverflowStyle
293+ useIsomorphicLayoutEffect ( ( ) => {
294+ if ( initiallyOpen && ! dialogRef . current ?. open && dialogRef . current ?. isConnected ) {
295+ dialogRef . current . showModal ( )
305296 }
306- } , [ ] )
297+ } , [ initiallyOpen , dialogRef ] )
307298
308299 const header = ( renderHeader ?? DefaultHeader ) ( defaultedProps )
309300 const body = ( renderBody ?? DefaultBody ) ( defaultedProps )
310301 const footer = ( renderFooter ?? DefaultFooter ) ( defaultedProps )
311302
312303 return (
313- < >
314- < Portal >
315- < Backdrop ref = { backdropRef } >
316- < StyledDialog
317- width = { width }
318- height = { height }
319- ref = { dialogRef }
320- role = { role }
321- aria-labelledby = { dialogLabelId }
322- aria-describedby = { dialogDescriptionId }
323- aria-modal
324- sx = { sx }
325- >
326- { header }
327- { body }
328- { footer }
329- </ StyledDialog >
330- </ Backdrop >
331- </ Portal >
332- </ >
304+ < StyledDialog
305+ width = { width }
306+ height = { height === 'auto' ? 'min-content' : height }
307+ ref = { dialogRef }
308+ role = { role === 'alertdialog' ? role : undefined }
309+ aria-labelledby = { dialogLabelId }
310+ aria-describedby = { dialogDescriptionId }
311+ sx = { sx }
312+ >
313+ { header }
314+ { body }
315+ { footer }
316+ </ StyledDialog >
333317 )
334318} )
335319_Dialog . displayName = 'Dialog'
@@ -390,30 +374,13 @@ const buttonTypes = {
390374 danger : ButtonDanger ,
391375}
392376const Buttons : React . FC < React . PropsWithChildren < { buttons : DialogButtonProps [ ] } > > = ( { buttons} ) => {
393- const autoFocusRef = useProvidedRefOrCreate < HTMLButtonElement > ( buttons . find ( button => button . autoFocus ) ?. ref )
394- let autoFocusCount = 0
395- const [ hasRendered , setHasRendered ] = useState ( 0 )
396- useEffect ( ( ) => {
397- // hack to work around dialogs originating from other focus traps.
398- if ( hasRendered === 1 ) {
399- autoFocusRef . current ?. focus ( )
400- } else {
401- setHasRendered ( hasRendered + 1 )
402- }
403- } , [ autoFocusRef , hasRendered ] )
404-
405377 return (
406378 < >
407379 { buttons . map ( ( dialogButtonProps , index ) => {
408380 const { content, buttonType = 'normal' , autoFocus = false , ...buttonProps } = dialogButtonProps
409381 const ButtonElement = buttonTypes [ buttonType ]
410382 return (
411- < ButtonElement
412- key = { index }
413- { ...buttonProps }
414- variant = { buttonType }
415- ref = { autoFocus && autoFocusCount === 0 ? ( autoFocusCount ++ , autoFocusRef ) : null }
416- >
383+ < ButtonElement key = { index } { ...buttonProps } variant = { buttonType } autoFocus = { autoFocus } >
417384 { content }
418385 </ ButtonElement >
419386 )
0 commit comments