From 0f8ad0bf6233c1f88a9fca6c9fafa02b58fc8e71 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 4 Dec 2023 14:41:48 +0000
Subject: [PATCH 01/85] Kickoff
---
src/Dialog/Dialog.docs.json | 5 +
src/Dialog/Dialog.features.stories.tsx | 28 +++
src/Dialog/Dialog.stories.tsx | 12 +-
src/Dialog/Dialog.tsx | 238 +++++++++++++++----------
src/deprecated/Button/ButtonBase.tsx | 4 +-
5 files changed, 190 insertions(+), 97 deletions(-)
diff --git a/src/Dialog/Dialog.docs.json b/src/Dialog/Dialog.docs.json
index 5b803588341..30521c2a9c2 100644
--- a/src/Dialog/Dialog.docs.json
+++ b/src/Dialog/Dialog.docs.json
@@ -45,6 +45,11 @@
"type": "'dialog' | 'alertdialog'",
"description": "The ARIA role to assign to this dialog."
},
+ {
+ "name": "type",
+ "type": "'default' | 'full-screen' | 'action-sheet'",
+ "description": "The type of dialog to render. 'default' renders a dialog in the center of the screen. 'full-screen' renders the dialog full screen and ignored the width and height. 'full-screen' is often used for mobile breakpoints."
+ },
{
"name": "width",
"type": "'small' | 'medium' | 'large' | 'xlarge'"
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index 33331260526..479a49b3510 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -209,6 +209,34 @@ export const StressTest = ({width, height, subtitle}: DialogStoryProps) => {
)
}
+export const Responsive = ({width, height}: DialogStoryProps) => {
+ const [isOpen, setIsOpen] = useState(false)
+ const buttonRef = useRef(null)
+ const onDialogClose = useCallback(() => setIsOpen(false), [])
+ return (
+ <>
+ setIsOpen(!isOpen)}>
+ Show dialog
+
+ {isOpen && (
+
+ {lipsum}
+
+ )}
+ >
+ )
+}
+
// repro for https://github.com/github/primer/issues/2480
export const ReproMultistepDialogWithConditionalFooter = ({width, height}: DialogStoryProps) => {
const [isOpen, setIsOpen] = useState(false)
diff --git a/src/Dialog/Dialog.stories.tsx b/src/Dialog/Dialog.stories.tsx
index 99a9d77f891..9527cb8cf29 100644
--- a/src/Dialog/Dialog.stories.tsx
+++ b/src/Dialog/Dialog.stories.tsx
@@ -3,7 +3,7 @@ import {Meta} from '@storybook/react'
import {BaseStyles, ThemeProvider} from '..'
import {Button} from '../Button'
-import {Dialog, DialogWidth, DialogHeight} from './Dialog'
+import {Dialog, DialogWidth, DialogHeight, DialogType} from './Dialog'
/* Dialog Version 2 */
@@ -106,7 +106,7 @@ export const Default = () => {
)
}
-export const Playground = ({width, height, subtitle}: DialogStoryProps) => {
+export const Playground = ({width, height, subtitle, type}: DialogStoryProps) => {
const [isOpen, setIsOpen] = useState(false)
const [secondOpen, setSecondOpen] = useState(false)
const buttonRef = useRef(null)
@@ -123,6 +123,7 @@ export const Playground = ({width, height, subtitle}: DialogStoryProps) => {
title="My Dialog"
subtitle={subtitle ? 'This is a subtitle!' : undefined}
onClose={onDialogClose}
+ type={type}
width={width}
height={height}
footerButtons={[
@@ -146,8 +147,15 @@ Playground.args = {
width: 'xlarge',
height: 'auto',
subtitle: true,
+ type: 'default',
}
Playground.argTypes = {
+ type: {
+ control: {
+ type: 'radio',
+ },
+ options: ['default', 'full-screen', 'action-sheet'],
+ },
width: {
control: {
type: 'radio',
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index f5511aea6bd..0bb9c1412dc 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -6,6 +6,7 @@ import {get} from '../constants'
import {useOnEscapePress, useProvidedRefOrCreate} from '../hooks'
import {useFocusTrap} from '../hooks/useFocusTrap'
import sx, {SxProp} from '../sx'
+import {ResponsiveValue, useResponsiveValue} from '../hooks/useResponsiveValue'
import Octicon from '../Octicon'
import {XIcon} from '@primer/octicons-react'
import {useFocusZone} from '../hooks/useFocusZone'
@@ -109,6 +110,13 @@ export interface DialogProps extends SxProp {
*/
role?: 'dialog' | 'alertdialog'
+ /**
+ * Normally a dialog is display in the center of a viewport but sometimes
+ * it is useful to display this full screen on mobile viewports to allow for
+ * more space for content. When full-screen the width and height is ignored.
+ */
+ type?: DialogType | ResponsiveValue
+
/**
* The width of the dialog.
* small: 296px
@@ -144,28 +152,6 @@ 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 ${ANIMATION_DURATION} ${get('animation.easeOutCubic')};
-
- @keyframes dialog-backdrop-appear {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
- }
-`
-
const heightMap = {
small: '480px',
large: '640px',
@@ -181,40 +167,13 @@ const widthMap = {
export type DialogWidth = keyof typeof widthMap
export type DialogHeight = keyof typeof heightMap
+export type DialogType = 'default' | 'full-screen' | 'action-sheet'
type StyledDialogProps = {
width?: DialogWidth
height?: DialogHeight
} & SxProp
-const StyledDialog = styled.div`
- display: flex;
- flex-direction: column;
- background-color: ${get('colors.canvas.overlay')};
- box-shadow: ${get('shadows.overlay.shadow')};
- min-width: 296px;
- max-width: calc(100vw - 64px);
- max-height: calc(100vh - 64px);
- width: ${props => widthMap[props.width ?? ('xlarge' as const)]};
- height: ${props => heightMap[props.height ?? ('auto' as const)]};
- border-radius: 12px;
- opacity: 1;
- animation: overlay--dialog-appear ${ANIMATION_DURATION} ${get('animation.easeOutCubic')};
-
- @keyframes overlay--dialog-appear {
- 0% {
- opacity: 0;
- transform: scale(0.5);
- }
- 100% {
- opacity: 1;
- transform: scale(1);
- }
- }
-
- ${sx};
-`
-
const DefaultHeader: React.FC> = ({
dialogLabelId,
title,
@@ -263,6 +222,7 @@ const _Dialog = React.forwardRef(null)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
@@ -312,10 +273,8 @@ const _Dialog = React.forwardRef
-
-
-
+
+ ) : (
+
+
+ {header}
+ {body}
+ {footer}
+
+
+ )}
>
)
})
_Dialog.displayName = 'Dialog'
+const buttonTypes = {
+ normal: Button,
+ primary: ButtonPrimary,
+ danger: ButtonDanger,
+}
+const Buttons: React.FC> = ({buttons}) => {
+ const autoFocusRef = useProvidedRefOrCreate(buttons.find(button => button.autoFocus)?.ref)
+ let autoFocusCount = 0
+ const [hasRendered, setHasRendered] = useState(0)
+ useEffect(() => {
+ // hack to work around dialogs originating from other focus traps.
+ if (hasRendered === 1) {
+ autoFocusRef.current?.focus()
+ } else {
+ setHasRendered(hasRendered + 1)
+ }
+ }, [autoFocusRef, hasRendered])
+
+ return (
+ <>
+ {buttons.map((dialogButtonProps, index) => {
+ const {content, buttonType = 'normal', autoFocus = false, ...buttonProps} = dialogButtonProps
+ const ButtonElement = buttonTypes[buttonType]
+ return (
+
+ {content}
+
+ )
+ })}
+ >
+ )
+}
+
+/**
+ * Styled wrappers
+ */
+
+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 ${ANIMATION_DURATION} ${get('animation.easeOutCubic')};
+
+ @keyframes dialog-backdrop-appear {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+`
+
+const NormalDialog = styled.div`
+ display: flex;
+ flex-direction: column;
+ background-color: ${get('colors.canvas.overlay')};
+ box-shadow: ${get('shadows.overlay.shadow')};
+ min-width: 296px;
+ max-width: calc(100vw - 64px);
+ max-height: calc(100vh - 64px);
+ width: ${props => widthMap[props.width ?? ('xlarge' as const)]};
+ height: ${props => heightMap[props.height ?? ('auto' as const)]};
+ border-radius: 12px;
+ opacity: 1;
+ animation: overlay--dialog-appear ${ANIMATION_DURATION} ${get('animation.easeOutCubic')};
+
+ @keyframes overlay--dialog-appear {
+ 0% {
+ opacity: 0;
+ transform: scale(0.5);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ ${sx};
+`
+
+const FullScreenDialog = styled.div`
+ display: flex;
+ flex-direction: column;
+ background-color: ${get('colors.canvas.overlay')};
+ width: 100vw;
+ height: 100vh;
+ opacity: 1;
+ animation: overlay--dialog-appear ${ANIMATION_DURATION} ${get('animation.easeOutCubic')};
+ @keyframes overlay--dialog-appear {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+ ${sx};
+`
+
const Header = styled.div`
box-shadow: 0 1px 0 ${get('colors.border.default')};
padding: ${get('space.2')};
@@ -350,6 +437,7 @@ const Title = styled.h1`
const Subtitle = styled.h2`
font-size: ${get('fontSizes.0')};
+ font-weight: ${get('fontWeights.normal')};
color: ${get('colors.fg.muted')};
margin: 0; /* override default margin */
margin-top: ${get('space.1')};
@@ -378,43 +466,6 @@ const Footer = styled.div`
${sx};
`
-const buttonTypes = {
- normal: Button,
- primary: ButtonPrimary,
- danger: ButtonDanger,
-}
-const Buttons: React.FC> = ({buttons}) => {
- const autoFocusRef = useProvidedRefOrCreate(buttons.find(button => button.autoFocus)?.ref)
- let autoFocusCount = 0
- const [hasRendered, setHasRendered] = useState(0)
- useEffect(() => {
- // hack to work around dialogs originating from other focus traps.
- if (hasRendered === 1) {
- autoFocusRef.current?.focus()
- } else {
- setHasRendered(hasRendered + 1)
- }
- }, [autoFocusRef, hasRendered])
-
- return (
- <>
- {buttons.map((dialogButtonProps, index) => {
- const {content, buttonType = 'normal', autoFocus = false, ...buttonProps} = dialogButtonProps
- const ButtonElement = buttonTypes[buttonType]
- return (
-
- {content}
-
- )
- })}
- >
- )
-}
const DialogCloseButton = styled(Button)`
border-radius: 4px;
background: transparent;
@@ -426,6 +477,7 @@ const DialogCloseButton = styled(Button)`
line-height: normal;
box-shadow: none;
`
+
const CloseButton: React.FC void}>> = ({onClose}) => {
return (
diff --git a/src/deprecated/Button/ButtonBase.tsx b/src/deprecated/Button/ButtonBase.tsx
index 32ddc3a4563..ef3e5b79ad4 100644
--- a/src/deprecated/Button/ButtonBase.tsx
+++ b/src/deprecated/Button/ButtonBase.tsx
@@ -7,10 +7,10 @@ const variants = variant({
variants: {
small: {
p: '4px 12px',
- fontSize: 0,
+ fontSize: 2,
},
medium: {
- fontSize: 1,
+ fontSize: 2,
},
large: {
fontSize: 2,
From 623f300f89203a38c42b367ed374b8c8a658d3a5 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 4 Dec 2023 22:14:59 +0000
Subject: [PATCH 02/85] Start adding action-sheet
---
src/Dialog/Dialog.tsx | 83 ++++++++------
src/Dialog/DialogActionSheet.tsx | 178 +++++++++++++++++++++++++++++++
2 files changed, 226 insertions(+), 35 deletions(-)
create mode 100644 src/Dialog/DialogActionSheet.tsx
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 0bb9c1412dc..85df2b62d40 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -2,6 +2,7 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'
import styled from 'styled-components'
import Button, {ButtonPrimary, ButtonDanger, ButtonProps} from '../deprecated/Button'
import Box from '../Box'
+import DialogActionSheet from './DialogActionSheet'
import {get} from '../constants'
import {useOnEscapePress, useProvidedRefOrCreate} from '../hooks'
import {useFocusTrap} from '../hooks/useFocusTrap'
@@ -101,7 +102,7 @@ export interface DialogProps extends SxProp {
* gesture argument indicates the gesture that was used to close the dialog
* (either 'close-button' or 'escape').
*/
- onClose: (gesture: 'close-button' | 'escape') => void
+ onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
/**
* Default: "dialog". The ARIA role to assign to this dialog.
@@ -270,42 +271,54 @@ const _Dialog = React.forwardRef
+ if (responsiveType === 'full-screen') {
+ return (
- {responsiveType === 'full-screen' ? (
-
- {header}
- {body}
- {footer}
-
- ) : (
-
-
- {header}
- {body}
- {footer}
-
-
- )}
+
+ {header}
+ {body}
+ {footer}
+
- >
+ )
+ }
+
+ if (responsiveType === 'action-sheet') {
+ return (
+
+ {header}
+ {body}
+ {footer}
+
+ )
+ }
+
+ return (
+
+
+
+ {header}
+ {body}
+ {footer}
+
+
+
)
})
_Dialog.displayName = 'Dialog'
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
new file mode 100644
index 00000000000..0a70ec3b722
--- /dev/null
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -0,0 +1,178 @@
+import {useState, useEffect, useRef} from 'react'
+import Box from '../Box'
+import {DialogProps} from './Dialog'
+
+const DialogActionSheet: React.FC<
+ React.PropsWithChildren<{
+ open: boolean
+ onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
+ }>
+> = ({open, onClose, children}) => {
+ // Refs
+ let sheetContentRef = useRef()
+ let dragIconRef = useRef()
+
+ // Variables
+ let startY = useRef(0)
+ let startHeight = useRef(0)
+ let isDragging = useRef(false)
+ let sheetHeight = useRef(0)
+
+ // Accessibility
+ const isReduced =
+ window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
+ window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true
+
+ const showBottomSheet = () => {
+ onClose(false)
+ updateSheetHeight(50)
+ document.body.style.overflowY = 'hidden'
+ }
+ const hideBottomSheet = (gesture: 'close-button' | 'escape' | 'overlay') => {
+ onClose(gesture)
+ document.body.style.overflowY = 'auto'
+ }
+
+ const updateSheetHeight = (height: number) => {
+ sheetContentRef.current.style.height = `${height}vh` // updates the height of the sheet content
+ }
+
+ const dragStop = e => {
+ isDragging.current = false
+ const sheetHeight = parseInt(sheetContentRef.current?.style.height ?? 0)
+ sheetContentRef.current.style.transition = isReduced ? 'none' : '0.3s ease'
+
+ if (sheetHeight < 25) {
+ return hideBottomSheet('drag')
+ }
+
+ if (sheetHeight > 75) {
+ return updateSheetHeight(90)
+ }
+
+ updateSheetHeight(50)
+ }
+ const dragStart = e => {
+ console.log('drag start')
+ startY.current = e.pageY || e.touches?.[0].pageY
+ startHeight.current = parseInt(sheetContentRef.current?.style.height ?? 0)
+ isDragging.current = true
+ sheetContentRef.current.style.transition = 'none'
+ }
+
+ const dragging = e => {
+ if (!isDragging.current) return
+ const delta = startY.current - (e.pageY || e.touches?.[0].pageY)
+ const newHeight = startHeight.current + (delta / window.innerHeight) * 100
+ updateSheetHeight(newHeight)
+ }
+
+ useEffect(() => {
+ document.addEventListener('mouseup', dragStop)
+ dragIconRef.current.addEventListener('mousedown', dragStart)
+ document.addEventListener('mousemove', dragging)
+
+ document.addEventListener('touchend', dragStop)
+ dragIconRef.current.addEventListener('touchstart', dragStart)
+ document.addEventListener('touchmove', dragging)
+
+ return () => {
+ document.removeEventListener('mouseup', dragStop)
+ dragIconRef.current.removeEventListener('mousedown', dragStart)
+ document.removeEventListener('mousemove', dragging)
+
+ document.removeEventListener('touchend', dragStop)
+ dragIconRef.current.removeEventListener('touchstart', dragStart)
+ document.removeEventListener('touchmove', dragging)
+ }
+ }, [])
+
+ const isFullScreen = sheetHeight?.current === 100
+
+ return (
+
+ hideBottomSheet('overlay')}
+ sx={{
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ zIndex: -1,
+ width: '100%',
+ height: '100%',
+ bg: 'primer.canvas.backdrop',
+ }}
+ >
+
+
+
+
+ {children}
+
+
+
+ )
+}
+
+export default DialogActionSheet
From d251d91e6bb7e09b9f41c624fe45e11e82b114e8 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 5 Dec 2023 12:03:34 +0000
Subject: [PATCH 03/85] WIP
---
src/Dialog/Dialog.tsx | 5 +++--
src/Dialog/DialogActionSheet.tsx | 34 +++++++++++++++++++++-----------
2 files changed, 25 insertions(+), 14 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 85df2b62d40..f2289754a87 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -113,8 +113,9 @@ export interface DialogProps extends SxProp {
/**
* Normally a dialog is display in the center of a viewport but sometimes
- * it is useful to display this full screen on mobile viewports to allow for
- * more space for content. When full-screen the width and height is ignored.
+ * it is useful to display this full screen or as an action sheet on mobile viewports
+ * to allow for more space for content. When full-screen or action sheet the width
+ * and height is ignored.
*/
type?: DialogType | ResponsiveValue
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 0a70ec3b722..c36e64a50f6 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -1,6 +1,5 @@
import {useState, useEffect, useRef} from 'react'
import Box from '../Box'
-import {DialogProps} from './Dialog'
const DialogActionSheet: React.FC<
React.PropsWithChildren<{
@@ -24,11 +23,10 @@ const DialogActionSheet: React.FC<
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true
const showBottomSheet = () => {
- onClose(false)
updateSheetHeight(50)
document.body.style.overflowY = 'hidden'
}
- const hideBottomSheet = (gesture: 'close-button' | 'escape' | 'overlay') => {
+ const hideBottomSheet = (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => {
onClose(gesture)
document.body.style.overflowY = 'auto'
}
@@ -69,24 +67,28 @@ const DialogActionSheet: React.FC<
useEffect(() => {
document.addEventListener('mouseup', dragStop)
- dragIconRef.current.addEventListener('mousedown', dragStart)
+ dragIconRef.current?.addEventListener('mousedown', dragStart)
document.addEventListener('mousemove', dragging)
document.addEventListener('touchend', dragStop)
- dragIconRef.current.addEventListener('touchstart', dragStart)
+ dragIconRef.current?.addEventListener('touchstart', dragStart)
document.addEventListener('touchmove', dragging)
return () => {
document.removeEventListener('mouseup', dragStop)
- dragIconRef.current.removeEventListener('mousedown', dragStart)
+ dragIconRef.current?.removeEventListener('mousedown', dragStart)
document.removeEventListener('mousemove', dragging)
document.removeEventListener('touchend', dragStop)
- dragIconRef.current.removeEventListener('touchstart', dragStart)
+ dragIconRef.current?.removeEventListener('touchstart', dragStart)
document.removeEventListener('touchmove', dragging)
}
}, [])
+ useEffect(() => {
+ showBottomSheet()
+ }, [showBottomSheet])
+
const isFullScreen = sheetHeight?.current === 100
return (
@@ -124,12 +126,15 @@ const DialogActionSheet: React.FC<
id="content"
ref={sheetContentRef}
sx={{
+ display: 'flex',
+ flexDirection: 'column',
bg: 'canvas.default',
height: '50vh',
maxHeight: '100vh',
width: '100%',
- borderRadius: isFullScreen ? 0 : '10px 10px 0 0',
+ borderRadius: isFullScreen ? 0 : '12px 12px 0 0',
overflowY: isFullScreen && 'hidden',
+ overflowX: 'hidden',
position: 'relative',
transform: open ? 'translateY(0%)' : 'translateY(100%)',
}}
@@ -139,6 +144,10 @@ const DialogActionSheet: React.FC<
sx={{
display: 'flex',
justifyContent: 'center',
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ left: 0,
}}
>
From 3179354331070fdc521d9c447659b11c4d400a33 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 6 Dec 2023 20:46:40 +0000
Subject: [PATCH 04/85] Fix dragging
---
src/Dialog/DialogActionSheet.tsx | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index c36e64a50f6..034d1576494 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -146,6 +146,7 @@ const DialogActionSheet: React.FC<
justifyContent: 'center',
position: 'absolute',
top: 0,
+ zIndex: 2,
right: 0,
left: 0,
}}
@@ -171,15 +172,7 @@ const DialogActionSheet: React.FC<
-
- {children}
-
+ {children}
)
From 522f3fd902ea883219eef7d8d7532d5509690dd6 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 6 Dec 2023 23:01:30 +0100
Subject: [PATCH 05/85] Improvements
---
src/Dialog/Dialog.tsx | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index f2289754a87..ede4f07b143 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -293,30 +293,32 @@ const _Dialog = React.forwardRef
- {header}
- {body}
- {footer}
-
- )
- }
-
- return (
-
-
-
+
{header}
{body}
{footer}
+
+
+ )
+ }
+
+ return (
+
+
+
+ {header}
+ {body}
+ {footer}
From 4249d9f08a411e8b65295cecdbb286d13ad548c0 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 6 Dec 2023 23:04:14 +0100
Subject: [PATCH 06/85] Add hover state
---
src/Dialog/DialogActionSheet.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 034d1576494..f79d603f200 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -156,6 +156,9 @@ const DialogActionSheet: React.FC<
sx={{
cursor: 'grab',
userSelect: 'none',
+ 'span:hover': {
+ bg: 'border.default',
+ },
}}
>
From 7bd0a10c6393b286ffb3a7e94257380f8ed02bc3 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 8 Dec 2023 10:58:15 +0100
Subject: [PATCH 07/85] Remove unused backdropRef
---
src/Dialog/Dialog.tsx | 21 ++-------------------
1 file changed, 2 insertions(+), 19 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index ede4f07b143..282186af647 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -241,7 +241,6 @@ const _Dialog = React.forwardRef(null)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
- const backdropRef = useRef(null)
useFocusTrap({containerRef: dialogRef, restoreFocusOnCleanUp: true, initialFocusRef: autoFocusedFooterButtonRef})
useOnEscapePress(
@@ -275,14 +274,7 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
@@ -294,16 +286,7 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
From 69708014ab24d0e63d67cefa3cae7a2acc354ca3 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 8 Dec 2023 13:08:09 +0100
Subject: [PATCH 08/85] Remove listeners
---
src/Dialog/Dialog.tsx | 12 +++--
src/Dialog/DialogActionSheet.tsx | 88 ++++++++++++++++++--------------
2 files changed, 56 insertions(+), 44 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 282186af647..d7ccefb885a 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -1,21 +1,23 @@
import React, {useCallback, useEffect, useRef, useState} from 'react'
import styled from 'styled-components'
+import {XIcon} from '@primer/octicons-react'
+import {FocusKeys} from '@primer/behaviors'
+
import Button, {ButtonPrimary, ButtonDanger, ButtonProps} from '../deprecated/Button'
import Box from '../Box'
-import DialogActionSheet from './DialogActionSheet'
import {get} from '../constants'
+import Portal from '../Portal'
import {useOnEscapePress, useProvidedRefOrCreate} from '../hooks'
import {useFocusTrap} from '../hooks/useFocusTrap'
import sx, {SxProp} from '../sx'
import {ResponsiveValue, useResponsiveValue} from '../hooks/useResponsiveValue'
import Octicon from '../Octicon'
-import {XIcon} from '@primer/octicons-react'
import {useFocusZone} from '../hooks/useFocusZone'
-import {FocusKeys} from '@primer/behaviors'
-import Portal from '../Portal'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {useId} from '../hooks/useId'
+import DialogActionSheet from './DialogActionSheet'
+
/* Dialog Version 2 */
const ANIMATION_DURATION = '200ms'
@@ -297,7 +299,7 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index f79d603f200..dbacd8cebb8 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -1,14 +1,32 @@
-import {useState, useEffect, useRef} from 'react'
+import React, {useEffect, useRef, PropsWithChildren, MouseEvent, TouchEvent} from 'react'
import Box from '../Box'
+import {SxProp} from '../sx'
+import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
+
+/**
+ * Props to customize the rendering of the Dialog.
+ */
+export interface DialogActionSheetProps extends SxProp {
+ /**
+ * Sets the visibility op the dialog
+ */
+ open: boolean
+
+ /**
+ * This method is invoked when a gesture to close the dialog is used
+ */
+ onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
+}
+
+type DialogActionSheetPropsChildren = PropsWithChildren
+
+const DialogActionSheet = React.forwardRef((props, forwardedRef) => {
+ const {open, onClose, children, sx} = props
+
+ const dialogRef = useRef(null)
+ useRefObjectAsForwardedRef(forwardedRef, dialogRef)
-const DialogActionSheet: React.FC<
- React.PropsWithChildren<{
- open: boolean
- onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
- }>
-> = ({open, onClose, children}) => {
// Refs
- let sheetContentRef = useRef()
let dragIconRef = useRef()
// Variables
@@ -32,13 +50,16 @@ const DialogActionSheet: React.FC<
}
const updateSheetHeight = (height: number) => {
- sheetContentRef.current.style.height = `${height}vh` // updates the height of the sheet content
+ if (!dialogRef.current) return
+ dialogRef.current.style.height = `${height}vh` // updates the height of the sheet content
}
- const dragStop = e => {
+ const dragStop = () => {
+ if (!dialogRef.current) return
+
isDragging.current = false
- const sheetHeight = parseInt(sheetContentRef.current?.style.height ?? 0)
- sheetContentRef.current.style.transition = isReduced ? 'none' : '0.3s ease'
+ const sheetHeight = parseInt(dialogRef.current?.style.height ?? 0)
+ dialogRef.current.style.transition = isReduced ? 'none' : '0.3s ease'
if (sheetHeight < 25) {
return hideBottomSheet('drag')
@@ -50,41 +71,24 @@ const DialogActionSheet: React.FC<
updateSheetHeight(50)
}
- const dragStart = e => {
+ const dragStart = (e: MouseEvent | TouchEvent) => {
+ if (!dialogRef.current) return
+
console.log('drag start')
startY.current = e.pageY || e.touches?.[0].pageY
- startHeight.current = parseInt(sheetContentRef.current?.style.height ?? 0)
+ startHeight.current = parseInt(dialogRef.current?.style.height ?? 0)
isDragging.current = true
- sheetContentRef.current.style.transition = 'none'
+ dialogRef.current.style.transition = 'none'
}
- const dragging = e => {
+ const dragging = (e: MouseEvent) => {
+ if (!dialogRef.current) return
if (!isDragging.current) return
const delta = startY.current - (e.pageY || e.touches?.[0].pageY)
const newHeight = startHeight.current + (delta / window.innerHeight) * 100
updateSheetHeight(newHeight)
}
- useEffect(() => {
- document.addEventListener('mouseup', dragStop)
- dragIconRef.current?.addEventListener('mousedown', dragStart)
- document.addEventListener('mousemove', dragging)
-
- document.addEventListener('touchend', dragStop)
- dragIconRef.current?.addEventListener('touchstart', dragStart)
- document.addEventListener('touchmove', dragging)
-
- return () => {
- document.removeEventListener('mouseup', dragStop)
- dragIconRef.current?.removeEventListener('mousedown', dragStart)
- document.removeEventListener('mousemove', dragging)
-
- document.removeEventListener('touchend', dragStop)
- dragIconRef.current?.removeEventListener('touchstart', dragStart)
- document.removeEventListener('touchmove', dragging)
- }
- }, [])
-
useEffect(() => {
showBottomSheet()
}, [showBottomSheet])
@@ -94,6 +98,10 @@ const DialogActionSheet: React.FC<
return (
)
-}
+})
export default DialogActionSheet
From de138d4b9e1f2a80be49cbda18d4ebc5e0b19eac Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 8 Dec 2023 13:15:20 +0100
Subject: [PATCH 09/85] Fix warnings
---
src/Dialog/DialogActionSheet.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index dbacd8cebb8..23b4f0ea5c2 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -141,9 +141,9 @@ const DialogActionSheet = React.forwardRef
Date: Fri, 8 Dec 2023 15:03:46 +0100
Subject: [PATCH 10/85] Reduce code
---
src/Dialog/DialogActionSheet.tsx | 175 ++++++++++++++-----------------
1 file changed, 80 insertions(+), 95 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 23b4f0ea5c2..6f8c46a584b 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -1,7 +1,9 @@
import React, {useEffect, useRef, PropsWithChildren, MouseEvent, TouchEvent} from 'react'
-import Box from '../Box'
-import {SxProp} from '../sx'
+import styled from 'styled-components'
+import sx, {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
+import {get} from '../constants'
+import Box from '../Box'
/**
* Props to customize the rendering of the Dialog.
@@ -35,11 +37,6 @@ const DialogActionSheet = React.forwardRef {
updateSheetHeight(50)
document.body.style.overflowY = 'hidden'
@@ -51,7 +48,7 @@ const DialogActionSheet = React.forwardRef {
if (!dialogRef.current) return
- dialogRef.current.style.height = `${height}vh` // updates the height of the sheet content
+ dialogRef.current.style.height = `${height}vh`
}
const dragStop = () => {
@@ -59,7 +56,7 @@ const DialogActionSheet = React.forwardRef
- hideBottomSheet('overlay')}
- sx={{
- position: 'fixed',
- top: 0,
- left: 0,
- zIndex: -1,
- width: '100%',
- height: '100%',
- bg: 'primer.canvas.backdrop',
- }}
- >
-
-
-
+ hideBottomSheet('overlay')}>
+
+
+
+
{children}
-
-
+
+
)
})
+const FullScreenContainer = styled.div<{open: boolean}>`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ opacity: ${props => (props.open ? 1 : 0)};
+ pointerevents: ${props => (props.open ? 'auto' : 'none')};
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: center;
+ transition: 0.1s linear;
+ @media (prefers-reduced-motion) {
+ transition: none;
+ }
+`
+const Overlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: -1;
+ width: 100%;
+ height: 100%;
+ background-color: ${get('colors.primer.canvas.backdrop')};
+`
+
+const DraggableRegionPill = styled.div`
+ height: 6px;
+ width: 70px;
+ margin-top: ${get('space.2')};
+ display: block;
+ background-color: ${get('colors.border.muted')};
+ border-radius: 3px;
+`
+
+const DraggableRegion = styled.div`
+ display: flex;
+ justify-content: center;
+ position: absolute;
+ top: 0;
+ z-index: 2;
+ right: 0;
+ left: 0;
+ cursor: grab;
+ user-select: none;
+ &:hover ${DraggableRegionPill} {
+ background-color: ${get('colors.border.default')};
+ }
+`
+
+const Content = styled.div<{open: boolean; isFullScreen: boolean}>`
+ display: flex;
+ flex-direction: column;
+ background-color: ${get('colors.canvas.default')};
+ height: 50vh;
+ maxheight: 100vh;
+ width: 100%;
+ border-radius: ${props => (props.isFullScreen ? 0 : '12px 12px 0 0')};
+ position: relative;
+ overflow-x: hidden;
+ overflow-y: ${props => (props.isFullScreen ? 'hidden' : 'auto')};
+ transform: ${props => (props.open ? 'translateY(0%)' : 'translateY(100%)')};
+`
+
export default DialogActionSheet
From b3f17cbdd4e04f16bb8e38cf16dc03c5ee423fc8 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 8 Dec 2023 15:37:08 +0100
Subject: [PATCH 11/85] Fix reduce motion
---
src/Dialog/DialogActionSheet.tsx | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 6f8c46a584b..0c2e3972ed7 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -28,15 +28,13 @@ const DialogActionSheet = React.forwardRef(null)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
- // Refs
- let dragIconRef = useRef()
-
- // Variables
+ // References
let startY = useRef(0)
let startHeight = useRef(0)
let isDragging = useRef(false)
let sheetHeight = useRef(0)
+ // Actions
const showBottomSheet = () => {
updateSheetHeight(50)
document.body.style.overflowY = 'hidden'
@@ -56,15 +54,12 @@ const DialogActionSheet = React.forwardRef 75) {
- return updateSheetHeight(90)
- }
+ if (sheetHeight < 25) return hideBottomSheet('drag')
+ if (sheetHeight > 75) return updateSheetHeight(90)
updateSheetHeight(50)
}
@@ -111,6 +106,11 @@ const DialogActionSheet = React.forwardRef {
+ const mediaQueryList = window.matchMedia('(prefers-reduced-motion: no-preference)')
+ return !mediaQueryList.matches
+}
+
const FullScreenContainer = styled.div<{open: boolean}>`
position: fixed;
top: 0;
From e8b3be900699916f857df55e2367639c289fda5c Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 8 Dec 2023 16:31:19 +0100
Subject: [PATCH 12/85] Add open animation
---
src/Dialog/Dialog.tsx | 2 +-
src/Dialog/DialogActionSheet.tsx | 43 ++++++++++++++++++++++++--------
2 files changed, 34 insertions(+), 11 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index d7ccefb885a..02b8352aa98 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -288,7 +288,7 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 0c2e3972ed7..52f9318986b 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect, useRef, PropsWithChildren, MouseEvent, TouchEvent} from 'react'
+import React, {useEffect, useRef, useState, PropsWithChildren, MouseEvent, TouchEvent} from 'react'
import styled from 'styled-components'
import sx, {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
@@ -23,25 +23,50 @@ export interface DialogActionSheetProps extends SxProp {
type DialogActionSheetPropsChildren = PropsWithChildren
const DialogActionSheet = React.forwardRef((props, forwardedRef) => {
- const {open, onClose, children, sx} = props
+ const {onClose, children, sx} = props
const dialogRef = useRef(null)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
+ // States
+ const [open, setIsOpen] = useState(false)
+ const [fireDelayedOnClose, setFireDelayedOnClose] = useState<{
+ gesture: 'close-button' | 'escape' | 'drag' | 'overlay' | undefined
+ }>()
+
+ // Accessibility
+ const isReduced = prefersReducedMotion()
+
// References
let startY = useRef(0)
let startHeight = useRef(0)
let isDragging = useRef(false)
let sheetHeight = useRef(0)
+ // Hooks
+ useEffect(() => {
+ if (!fireDelayedOnClose) return
+ const timer = setTimeout(() => onClose, 300)
+ return () => clearTimeout(timer)
+ }, [fireDelayedOnClose])
+
+ useEffect(() => {
+ showBottomSheet(true)
+ }, [open])
+
// Actions
const showBottomSheet = () => {
+ setIsOpen(true)
updateSheetHeight(50)
- document.body.style.overflowY = 'hidden'
}
- const hideBottomSheet = (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => {
- onClose(gesture)
- document.body.style.overflowY = 'auto'
+
+ const hideBottomSheet = async (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => {
+ setIsOpen(false)
+ if (isReduced) {
+ onClose(gesture)
+ } else {
+ setFireDelayedOnClose(gesture)
+ }
}
const updateSheetHeight = (height: number) => {
@@ -65,8 +90,6 @@ const DialogActionSheet = React.forwardRef {
if (!dialogRef.current) return
-
- console.log('drag start')
startY.current = e.pageY || e.touches?.[0].pageY
startHeight.current = parseInt(dialogRef.current?.style.height ?? 0)
isDragging.current = true
@@ -74,8 +97,7 @@ const DialogActionSheet = React.forwardRef {
- if (!dialogRef.current) return
- if (!isDragging.current) return
+ if (!dialogRef.current || !isDragging.current) return
const delta = startY.current - (e.pageY || e.touches?.[0].pageY)
const newHeight = startHeight.current + (delta / window.innerHeight) * 100
updateSheetHeight(newHeight)
@@ -174,6 +196,7 @@ const Content = styled.div<{open: boolean; isFullScreen: boolean}>`
overflow-x: hidden;
overflow-y: ${props => (props.isFullScreen ? 'hidden' : 'auto')};
transform: ${props => (props.open ? 'translateY(0%)' : 'translateY(100%)')};
+ transition: 0.3s ease;
`
export default DialogActionSheet
From 80c6a29f1331ad00a66126867852dd06e3e7aa1e Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Sat, 9 Dec 2023 11:58:19 +0100
Subject: [PATCH 13/85] Improvements
---
src/Dialog/Dialog.tsx | 4 +--
src/Dialog/DialogActionSheet.tsx | 62 ++++++++++++++++++--------------
2 files changed, 37 insertions(+), 29 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 02b8352aa98..a6fa7086e11 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -288,7 +288,7 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
@@ -300,7 +300,7 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 52f9318986b..adc5381cfe7 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -10,51 +10,59 @@ import Box from '../Box'
*/
export interface DialogActionSheetProps extends SxProp {
/**
- * Sets the visibility op the dialog
+ * This method is invoked when a gesture to close the dialog is used
*/
- open: boolean
+ onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
/**
- * This method is invoked when a gesture to close the dialog is used
+ * Default: "dialog". The ARIA role to assign to this dialog.
+ * @see https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal
+ * @see https://www.w3.org/TR/wai-aria-practices-1.1/#alertdialog
*/
- onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
+ role?: 'dialog' | 'alertdialog'
}
type DialogActionSheetPropsChildren = PropsWithChildren
const DialogActionSheet = React.forwardRef((props, forwardedRef) => {
- const {onClose, children, sx} = props
+ const {onClose, children, role, sx} = props
- const dialogRef = useRef(null)
- useRefObjectAsForwardedRef(forwardedRef, dialogRef)
-
- // States
+ // 🔄 States
const [open, setIsOpen] = useState(false)
- const [fireDelayedOnClose, setFireDelayedOnClose] = useState<{
- gesture: 'close-button' | 'escape' | 'drag' | 'overlay' | undefined
- }>()
+ const [fireDelayedOnClose, setFireDelayedOnClose] = useState<
+ 'close-button' | 'escape' | 'drag' | 'overlay' | undefined
+ >()
- // Accessibility
+ // 🧑🦽 Accessibility
const isReduced = prefersReducedMotion()
- // References
+ // 📎 References
+ let dialogRef = useRef(null)
let startY = useRef(0)
let startHeight = useRef(0)
let isDragging = useRef(false)
let sheetHeight = useRef(0)
- // Hooks
+ useRefObjectAsForwardedRef(forwardedRef, dialogRef)
+
+ // 🪝 Hooks
useEffect(() => {
if (!fireDelayedOnClose) return
- const timer = setTimeout(() => onClose, 300)
+ console.log('Planning to close the dialog...')
+ setIsOpen(false)
+ const timer = setTimeout(() => {
+ console.log('onClose...')
+
+ onClose(fireDelayedOnClose)
+ }, 300)
return () => clearTimeout(timer)
- }, [fireDelayedOnClose])
+ }, [fireDelayedOnClose, onClose])
useEffect(() => {
- showBottomSheet(true)
- }, [open])
+ showBottomSheet()
+ }, [])
- // Actions
+ // 🥊 Actions
const showBottomSheet = () => {
setIsOpen(true)
updateSheetHeight(50)
@@ -103,10 +111,6 @@ const DialogActionSheet = React.forwardRef {
- showBottomSheet()
- }, [showBottomSheet])
-
const isFullScreen = sheetHeight?.current === 100
return (
@@ -118,7 +122,7 @@ const DialogActionSheet = React.forwardRef
hideBottomSheet('overlay')}>
-
+
@@ -183,8 +187,12 @@ const DraggableRegion = styled.div`
background-color: ${get('colors.border.default')};
}
`
-
-const Content = styled.div<{open: boolean; isFullScreen: boolean}>`
+const Content = styled.div<
+ {
+ open: boolean
+ isFullScreen: boolean
+ } & SxProp
+>`
display: flex;
flex-direction: column;
background-color: ${get('colors.canvas.default')};
From 8dc8a176fe61b313b5d130227a7064c90fa800b4 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Sat, 9 Dec 2023 19:03:32 +0100
Subject: [PATCH 14/85] Improve the docs
---
src/Dialog/Dialog.docs.json | 4 ++--
src/Dialog/DialogActionSheet.tsx | 26 ++++++++++++++++----------
2 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/src/Dialog/Dialog.docs.json b/src/Dialog/Dialog.docs.json
index 30521c2a9c2..48346c97be5 100644
--- a/src/Dialog/Dialog.docs.json
+++ b/src/Dialog/Dialog.docs.json
@@ -37,8 +37,8 @@
},
{
"name": "onClose",
- "type": "(gesture: 'close-button' | 'escape') => void",
- "description": "This method is invoked when a gesture to close the dialog is used (either an Escape key press or clicking the 'X' in the top-right corner). The gesture argument indicates the gesture that was used to close the dialog (either 'close-button' or 'escape')."
+ "type": "(gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void",
+ "description": "This method is invoked when a gesture to close the dialog is used (either an Escape key press, clicking/tapping on the backdrop, clicking/tapping the 'X' in the top-right corner or dragging away an action sheet). The gesture argument indicates the gesture that was used to close the dialog."
},
{
"name": "role",
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index adc5381cfe7..98db64ad8bb 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -22,21 +22,22 @@ export interface DialogActionSheetProps extends SxProp {
role?: 'dialog' | 'alertdialog'
}
-type DialogActionSheetPropsChildren = PropsWithChildren
-
-const DialogActionSheet = React.forwardRef((props, forwardedRef) => {
+export default React.forwardRef>((props, forwardedRef) => {
const {onClose, children, role, sx} = props
- // 🔄 States
+ // 🔄 STATES
+
const [open, setIsOpen] = useState(false)
const [fireDelayedOnClose, setFireDelayedOnClose] = useState<
'close-button' | 'escape' | 'drag' | 'overlay' | undefined
>()
- // 🧑🦽 Accessibility
+ // 🧑🦽 ACCESSIBILITY
+
const isReduced = prefersReducedMotion()
- // 📎 References
+ // 📎 REFERENCES
+
let dialogRef = useRef(null)
let startY = useRef(0)
let startHeight = useRef(0)
@@ -45,7 +46,9 @@ const DialogActionSheet = React.forwardRef {
if (!fireDelayedOnClose) return
console.log('Planning to close the dialog...')
@@ -58,11 +61,13 @@ const DialogActionSheet = React.forwardRef clearTimeout(timer)
}, [fireDelayedOnClose, onClose])
+ // Animates the bottom sheet in on mount
useEffect(() => {
showBottomSheet()
}, [])
- // 🥊 Actions
+ // 🥊 ACTIONS
+
const showBottomSheet = () => {
setIsOpen(true)
updateSheetHeight(50)
@@ -82,6 +87,8 @@ const DialogActionSheet = React.forwardRef {
if (!dialogRef.current) return
@@ -96,6 +103,7 @@ const DialogActionSheet = React.forwardRef {
if (!dialogRef.current) return
startY.current = e.pageY || e.touches?.[0].pageY
@@ -206,5 +214,3 @@ const Content = styled.div<
transform: ${props => (props.open ? 'translateY(0%)' : 'translateY(100%)')};
transition: 0.3s ease;
`
-
-export default DialogActionSheet
From ca03cf5589f0e25bf68df4309336feec10ae5c53 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Sat, 9 Dec 2023 19:26:02 +0100
Subject: [PATCH 15/85] Make the storybook easier to manage
---
src/Dialog/Dialog.features.stories.tsx | 124 ++++++++++++-------------
src/Dialog/Dialog.tsx | 4 +-
2 files changed, 64 insertions(+), 64 deletions(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index 479a49b3510..e2e137cb034 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -3,6 +3,7 @@ import {Meta} from '@storybook/react'
import {BaseStyles, ThemeProvider, Box, TextInput} from '..'
import {Button} from '../Button'
+import Text from '../Text'
import {Dialog, DialogProps, DialogWidth, DialogHeight} from './Dialog'
/* Dialog Version 2 */
@@ -23,45 +24,10 @@ export default {
)
},
],
- args: {
- width: 'xlarge',
- height: 'auto',
- subtitle: true,
- },
- argTypes: {
- width: {
- control: {
- type: 'radio',
- },
- options: ['small', 'medium', 'large', 'xlarge'],
- },
- height: {
- control: {
- type: 'radio',
- },
- options: ['small', 'large', 'auto'],
- },
- subtitle: {
- name: 'show subtitle',
- control: {
- type: 'boolean',
- },
- },
- title: {table: {disable: true}},
-
- renderHeader: {table: {disable: true}},
- renderBody: {table: {disable: true}},
- renderFooter: {table: {disable: true}},
- onClose: {table: {disable: true}},
- role: {table: {disable: true}},
- ref: {table: {disable: true}},
- key: {table: {disable: true}},
- footerButtons: {table: {disable: true}},
- },
} as Meta
const lipsum = (
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque sollicitudin mauris maximus elit sagittis, nec
lobortis ligula elementum. Nam iaculis, urna nec lobortis posuere, eros urna venenatis eros, vel accumsan turpis
@@ -100,14 +66,8 @@ const lipsum = (
pharetra dolor at dictum tempor. Quisque ut est a ligula hendrerit sodales. Curabitur ornare a nulla in laoreet.
Maecenas semper mi egestas, dignissim nisi et, elementum neque.
-
+
)
-interface DialogStoryProps {
- width: DialogWidth
- height: DialogHeight
- subtitle: boolean
-}
-
function CustomHeader({
title,
subtitle,
@@ -129,7 +89,7 @@ function CustomHeader({
}
return null
}
-function CustomBody({children}: React.PropsWithChildren) {
+function CustomBody() {
return {children}
}
function CustomFooter({footerButtons}: React.PropsWithChildren) {
@@ -139,7 +99,7 @@ function CustomFooter({footerButtons}: React.PropsWithChildren) {
)
}
-export const WithCustomRenderers = ({width, height, subtitle}: DialogStoryProps) => {
+export const WithCustomRenderers = () => {
const [isOpen, setIsOpen] = useState(false)
const onDialogClose = useCallback(() => setIsOpen(false), [])
return (
@@ -148,9 +108,7 @@ export const WithCustomRenderers = ({width, height, subtitle}: DialogStoryProps)
{isOpen && (
{
+export const StressTest = () => {
const [isOpen, setIsOpen] = useState(false)
const [secondOpen, setSecondOpen] = useState(false)
const buttonRef = useRef(null)
@@ -183,14 +141,8 @@ export const StressTest = ({width, height, subtitle}: DialogStoryProps) => {
{isOpen && (
{
)
}
-export const Responsive = ({width, height}: DialogStoryProps) => {
+export const Responsive = () => {
const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef(null)
const onDialogClose = useCallback(() => setIsOpen(false), [])
@@ -223,8 +175,58 @@ export const Responsive = ({width, height}: DialogStoryProps) => {
title="Your title"
onClose={onDialogClose}
type={{narrow: 'full-screen', regular: 'default', wide: 'default'}}
- width={width}
- height={height}
+ footerButtons={[
+ {buttonType: 'normal', content: 'Cancel', onClick: onDialogClose},
+ {buttonType: 'primary', content: 'Submit', autoFocus: true},
+ ]}
+ >
+ {lipsum}
+
+ )}
+ >
+ )
+}
+
+export const FullScreen = () => {
+ const [isOpen, setIsOpen] = useState(false)
+ const buttonRef = useRef(null)
+ const onDialogClose = useCallback(() => setIsOpen(false), [])
+ return (
+ <>
+ setIsOpen(!isOpen)}>
+ Show dialog
+
+ {isOpen && (
+
+ {lipsum}
+
+ )}
+ >
+ )
+}
+
+export const ActionSheet = () => {
+ const [isOpen, setIsOpen] = useState(false)
+ const buttonRef = useRef(null)
+ const onDialogClose = useCallback(() => setIsOpen(false), [])
+ return (
+ <>
+ setIsOpen(!isOpen)}>
+ Show dialog
+
+ {isOpen && (
+ {
}
// repro for https://github.com/github/primer/issues/2480
-export const ReproMultistepDialogWithConditionalFooter = ({width, height}: DialogStoryProps) => {
+export const ReproMultistepDialogWithConditionalFooter = () => {
const [isOpen, setIsOpen] = useState(false)
const onDialogClose = useCallback(() => setIsOpen(false), [])
const [step, setStep] = React.useState(1)
@@ -259,8 +261,6 @@ export const ReproMultistepDialogWithConditionalFooter = ({width, height}: Dialo
{isOpen && (
-
+
{header}
{body}
{footer}
@@ -300,7 +300,7 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
From 1acf99b10d48b5c1d25ff5c5c4a791e7d46c7adb Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Sat, 9 Dec 2023 19:48:48 +0100
Subject: [PATCH 16/85] Fix
---
src/Dialog/Dialog.features.stories.tsx | 133 +++++++++++++------------
1 file changed, 69 insertions(+), 64 deletions(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index e2e137cb034..91ce216a13c 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -90,7 +90,7 @@ function CustomHeader({
return null
}
function CustomBody() {
- return {children}
+ return {lipsum}
}
function CustomFooter({footerButtons}: React.PropsWithChildren) {
return (
@@ -161,81 +161,86 @@ export const StressTest = () => {
)
}
+export const NonDeclaritive = () => {
+ return (
+ {}}>
+
+ It's a common scenario, to show a dialog that's just informational and therefore doesn't have footers in the
+ button
+
+
+ )
+}
+
+export const SizeSmallDialog = () => {
+ return (
+ {}}>
+ {lipsum}
+
+ )
+}
+
+export const SizeXLargeDialog = () => {
+ return (
+ {}}>
+ {lipsum}
+
+ )
+}
+
+export const SizeLargeDialog = () => {
+ return (
+ {}}>
+ {lipsum}
+
+ )
+}
+
export const Responsive = () => {
- const [isOpen, setIsOpen] = useState(false)
- const buttonRef = useRef(null)
- const onDialogClose = useCallback(() => setIsOpen(false), [])
return (
- <>
- setIsOpen(!isOpen)}>
- Show dialog
-
- {isOpen && (
-
- {lipsum}
-
- )}
- >
+ {}}
+ type={{narrow: 'full-screen', regular: 'default', wide: 'default'}}
+ footerButtons={[
+ {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
+ {buttonType: 'primary', content: 'Submit', autoFocus: true},
+ ]}
+ >
+ {lipsum}
+
)
}
export const FullScreen = () => {
- const [isOpen, setIsOpen] = useState(false)
- const buttonRef = useRef(null)
- const onDialogClose = useCallback(() => setIsOpen(false), [])
return (
- <>
- setIsOpen(!isOpen)}>
- Show dialog
-
- {isOpen && (
-
- {lipsum}
-
- )}
- >
+ {}}
+ type="full-screen"
+ footerButtons={[
+ {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
+ {buttonType: 'primary', content: 'Submit', autoFocus: true},
+ ]}
+ >
+ {lipsum}
+
)
}
export const ActionSheet = () => {
- const [isOpen, setIsOpen] = useState(false)
- const buttonRef = useRef(null)
- const onDialogClose = useCallback(() => setIsOpen(false), [])
return (
- <>
- setIsOpen(!isOpen)}>
- Show dialog
-
- {isOpen && (
-
- {lipsum}
-
- )}
- >
+ {}}
+ footerButtons={[
+ {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
+ {buttonType: 'primary', content: 'Submit', autoFocus: true},
+ ]}
+ >
+ {lipsum}
+
)
}
From 75eba8395416ea7b0b834e966c4285f143e3b3dc Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Sat, 9 Dec 2023 19:59:43 +0100
Subject: [PATCH 17/85] Make it easier to get the code for custom renderes
---
src/Dialog/Dialog.features.stories.tsx | 89 ++++++++++++--------------
1 file changed, 41 insertions(+), 48 deletions(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index 91ce216a13c..81ee1a7d00c 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -68,60 +68,53 @@ const lipsum = (
)
-function CustomHeader({
- title,
- subtitle,
- dialogLabelId,
- dialogDescriptionId,
- onClose,
-}: React.PropsWithChildren) {
- const onCloseClick = useCallback(() => {
- onClose('close-button')
- }, [onClose])
- if (typeof title === 'string' && typeof subtitle === 'string') {
- return (
-
- {title.toUpperCase()}
- {subtitle.toLowerCase()}
-
-
- )
+
+export const WithCustomRenderers = () => {
+ const CustomHeader = ({
+ title,
+ subtitle,
+ dialogLabelId,
+ dialogDescriptionId,
+ onClose,
+ }: React.PropsWithChildren) => {
+ const onCloseClick = useCallback(() => {
+ onClose('close-button')
+ }, [onClose])
+ if (typeof title === 'string' && typeof subtitle === 'string') {
+ return (
+
+ {title.toUpperCase()}
+ {subtitle.toLowerCase()}
+
+
+ )
+ }
+ return null
}
- return null
-}
-function CustomBody() {
- return {lipsum}
-}
-function CustomFooter({footerButtons}: React.PropsWithChildren) {
- return (
+
+ const CustomBody = () => {lipsum}
+
+ const CustomFooter = ({footerButtons}: React.PropsWithChildren) => (
{footerButtons ? : null}
)
-}
-export const WithCustomRenderers = () => {
- const [isOpen, setIsOpen] = useState(false)
- const onDialogClose = useCallback(() => setIsOpen(false), [])
+
return (
- <>
- setIsOpen(!isOpen)}>Show dialog
- {isOpen && (
-
- {lipsum}
-
- )}
- >
+ {}}
+ footerButtons={[
+ {buttonType: 'danger', content: 'Delete the universe'},
+ {buttonType: 'primary', content: 'Proceed'},
+ ]}
+ >
+ {lipsum}
+
)
}
From 4ff9440a1ce6f1d2715bd45697f1f945a33992f8 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 11:22:57 +0000
Subject: [PATCH 18/85] Revert
---
src/deprecated/Button/ButtonBase.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/deprecated/Button/ButtonBase.tsx b/src/deprecated/Button/ButtonBase.tsx
index ef3e5b79ad4..32ddc3a4563 100644
--- a/src/deprecated/Button/ButtonBase.tsx
+++ b/src/deprecated/Button/ButtonBase.tsx
@@ -7,10 +7,10 @@ const variants = variant({
variants: {
small: {
p: '4px 12px',
- fontSize: 2,
+ fontSize: 0,
},
medium: {
- fontSize: 2,
+ fontSize: 1,
},
large: {
fontSize: 2,
From bf4b3d0962759b4058304f93065497fe68ff3339 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 11:59:09 +0000
Subject: [PATCH 19/85] Remove unused imports
---
src/Dialog/Dialog.features.stories.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index 81ee1a7d00c..e0546181990 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -4,7 +4,7 @@ import {Meta} from '@storybook/react'
import {BaseStyles, ThemeProvider, Box, TextInput} from '..'
import {Button} from '../Button'
import Text from '../Text'
-import {Dialog, DialogProps, DialogWidth, DialogHeight} from './Dialog'
+import {Dialog, DialogProps} from './Dialog'
/* Dialog Version 2 */
From 5c501409c9d24ab88934605f9b3faf568079569d Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 12:13:38 +0000
Subject: [PATCH 20/85] Use ' instead
---
src/Dialog/Dialog.features.stories.tsx | 4 ++--
src/Dialog/Dialog.stories.tsx | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index e0546181990..1fc1e061381 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -158,8 +158,8 @@ export const NonDeclaritive = () => {
return (
{}}>
- It's a common scenario, to show a dialog that's just informational and therefore doesn't have footers in the
- button
+ It's a common scenario, to show a dialog that's just informational and therefore doesn't have
+ footers in the button
)
diff --git a/src/Dialog/Dialog.stories.tsx b/src/Dialog/Dialog.stories.tsx
index 9527cb8cf29..d6418e12f52 100644
--- a/src/Dialog/Dialog.stories.tsx
+++ b/src/Dialog/Dialog.stories.tsx
@@ -3,7 +3,7 @@ import {Meta} from '@storybook/react'
import {BaseStyles, ThemeProvider} from '..'
import {Button} from '../Button'
-import {Dialog, DialogWidth, DialogHeight, DialogType} from './Dialog'
+import {Dialog, DialogWidth, DialogHeight} from './Dialog'
/* Dialog Version 2 */
From 88afe610296a1017111497658d75f4468aef63b8 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 12:14:34 +0000
Subject: [PATCH 21/85] Remove unused props
---
src/Dialog/DialogActionSheet.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 98db64ad8bb..2040713920d 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -1,9 +1,8 @@
import React, {useEffect, useRef, useState, PropsWithChildren, MouseEvent, TouchEvent} from 'react'
import styled from 'styled-components'
-import sx, {SxProp} from '../sx'
+import {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {get} from '../constants'
-import Box from '../Box'
/**
* Props to customize the rendering of the Dialog.
From 7c3c892f1382116117f49bdcc60850d405551feb Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 17:30:21 +0000
Subject: [PATCH 22/85] Improve accessibility further
---
src/Dialog/DialogActionSheet.tsx | 136 +++++++++++++++++++++++++++----
1 file changed, 122 insertions(+), 14 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 2040713920d..0f4fd801ef9 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -1,6 +1,16 @@
-import React, {useEffect, useRef, useState, PropsWithChildren, MouseEvent, TouchEvent} from 'react'
+import React, {
+ useEffect,
+ useRef,
+ useState,
+ PropsWithChildren,
+ MouseEvent,
+ TouchEvent,
+ KeyboardEvent,
+ FormEventHandler,
+} from 'react'
import styled from 'styled-components'
import {SxProp} from '../sx'
+import VisuallyHidden from '../_VisuallyHidden'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {get} from '../constants'
@@ -24,6 +34,11 @@ export interface DialogActionSheetProps extends SxProp {
export default React.forwardRef>((props, forwardedRef) => {
const {onClose, children, role, sx} = props
+ // 📏 SIZES
+
+ const fullHeight = 90
+ const halfHeight = 50
+
// 🔄 STATES
const [open, setIsOpen] = useState(false)
@@ -31,10 +46,6 @@ export default React.forwardRef()
- // 🧑🦽 ACCESSIBILITY
-
- const isReduced = prefersReducedMotion()
-
// 📎 REFERENCES
let dialogRef = useRef(null)
@@ -45,6 +56,10 @@ export default React.forwardRef 75) return updateSheetHeight(90)
+ if (sheetHeight > 75) return updateSheetHeight(fullHeight)
- updateSheetHeight(50)
+ updateSheetHeight(halfHeight)
}
const dragStart = (e: MouseEvent | TouchEvent) => {
@@ -118,7 +133,31 @@ export default React.forwardRef) => {
+ const height = dialogRef.current?.style.height ?? '0'
+ const sheetHeight = parseInt(height)
+
+ if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
+ if (sheetHeight === halfHeight) {
+ e.preventDefault()
+ return updateSheetHeight(fullHeight)
+ }
+ }
+ if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
+ if (sheetHeight === fullHeight) {
+ e.preventDefault()
+ return updateSheetHeight(halfHeight)
+ }
+
+ if (sheetHeight === halfHeight) {
+ e.preventDefault()
+ return hideBottomSheet('drag')
+ }
+ }
+ }
+
const isFullScreen = sheetHeight?.current === 100
+ const currentHeight = sheetHeight?.current ?? 0
return (
hideBottomSheet('overlay')}>
-
+
{children}
+ {
+ if (size === 'small') {
+ updateSheetHeight(halfHeight)
+ } else {
+ updateSheetHeight(fullHeight)
+ }
+ }}
+ />
)
})
-const prefersReducedMotion = () => {
- const mediaQueryList = window.matchMedia('(prefers-reduced-motion: no-preference)')
- return !mediaQueryList.matches
-}
+// 🖌️ Styles
const FullScreenContainer = styled.div<{open: boolean}>`
position: fixed;
@@ -174,25 +229,38 @@ const Overlay = styled.div`
const DraggableRegionPill = styled.div`
height: 6px;
width: 70px;
- margin-top: ${get('space.2')};
display: block;
background-color: ${get('colors.border.muted')};
border-radius: 3px;
`
-const DraggableRegion = styled.div`
+const DraggableRegion = styled.button`
display: flex;
justify-content: center;
+ border: none;
position: absolute;
+ background: transparent;
top: 0;
z-index: 2;
right: 0;
left: 0;
+ padding-top: ${get('space.2')};
+ padding-bottom: ${get('space.2')};
cursor: grab;
user-select: none;
&:hover ${DraggableRegionPill} {
background-color: ${get('colors.border.default')};
}
+ &:focus {
+ outline: none;
+ }
+ &:focus-visible:not([disabled]) {
+ outline: 2px solid;
+ outline-offset: -${get('space.1')};
+ border-top-left-radius: 11px;
+ border-top-right-radius: 11px;
+ outline-color: ${get('colors.accent.emphasis')};
+ },
`
const Content = styled.div<
{
@@ -213,3 +281,43 @@ const Content = styled.div<
transform: ${props => (props.open ? 'translateY(0%)' : 'translateY(100%)')};
transition: 0.3s ease;
`
+
+// 🧑🦽 Accessibility
+
+const prefersReducedMotion = () => {
+ const mediaQueryList = window.matchMedia('(prefers-reduced-motion: no-preference)')
+ return !mediaQueryList.matches
+}
+
+interface HiddenAccessibilityFormCallbackProps extends HTMLFormElement {
+ dialogSize: HTMLInputElement
+}
+
+interface HiddenAccessibilityFormProps {
+ onSubmit: (size: 'small' | 'large') => void
+}
+
+const HiddenAccessibilityForm = ({onSubmit}: HiddenAccessibilityFormProps) => {
+ const name = 'dialog-size'
+ const handleSubmit: FormEventHandler = e => {
+ e.preventDefault()
+ const sizeValue = e.currentTarget[name].value
+ onSubmit(sizeValue)
+ }
+
+ return (
+
+
+
+ )
+}
From 4c159279e8ee4e48f98aa4150cce001e47d7c418 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 19:19:06 +0000
Subject: [PATCH 23/85] Fix typescript warnings
---
src/Dialog/DialogActionSheet.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 0f4fd801ef9..be44e52486c 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -120,7 +120,13 @@ export default React.forwardRef {
if (!dialogRef.current) return
- startY.current = e.pageY || e.touches?.[0].pageY
+
+ if (e.type === 'touchstart' && 'touches' in e) {
+ startY.current = e.touches?.[0].pageY ?? 0
+ } else if ('clientX' in e) {
+ startY.current = e.pageY
+ }
+
startHeight.current = parseInt(dialogRef.current?.style.height ?? 0)
isDragging.current = true
dialogRef.current.style.transition = 'none'
From a5933871ee25fa84c424ff7d1d5a4ded19d84d6c Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 19:29:30 +0000
Subject: [PATCH 24/85] Remove more warnings
---
src/Dialog/Dialog.stories.tsx | 3 ++-
src/Dialog/DialogActionSheet.tsx | 12 ++++++++++--
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/Dialog/Dialog.stories.tsx b/src/Dialog/Dialog.stories.tsx
index d6418e12f52..69ccf921404 100644
--- a/src/Dialog/Dialog.stories.tsx
+++ b/src/Dialog/Dialog.stories.tsx
@@ -3,7 +3,7 @@ import {Meta} from '@storybook/react'
import {BaseStyles, ThemeProvider} from '..'
import {Button} from '../Button'
-import {Dialog, DialogWidth, DialogHeight} from './Dialog'
+import {Dialog, DialogWidth, DialogHeight, DialogType} from './Dialog'
/* Dialog Version 2 */
@@ -71,6 +71,7 @@ interface DialogStoryProps {
width: DialogWidth
height: DialogHeight
subtitle: boolean
+ type: DialogType
}
export const Default = () => {
const [isOpen, setIsOpen] = useState(false)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index be44e52486c..e07b832ab46 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -132,9 +132,17 @@ export default React.forwardRef {
+ const dragging = (e: MouseEvent | TouchEvent) => {
if (!dialogRef.current || !isDragging.current) return
- const delta = startY.current - (e.pageY || e.touches?.[0].pageY)
+
+ var pageY
+ if (e.type === 'touchstart' && 'touches' in e) {
+ pageY = e.touches?.[0].pageY
+ } else if ('clientX' in e) {
+ pageY = e.pageY
+ }
+
+ const delta = startY.current - (pageY || 0)
const newHeight = startHeight.current + (delta / window.innerHeight) * 100
updateSheetHeight(newHeight)
}
From 2cf0fb2170604b6305fcb4b167e0b41d416cb38c Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 19:31:17 +0000
Subject: [PATCH 25/85] Remove more warnings
---
src/Dialog/DialogActionSheet.tsx | 42 ++++++++++++++++----------------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index e07b832ab46..cb0726055b9 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -60,26 +60,6 @@ export default React.forwardRef {
- if (!fireDelayedOnClose) return
- console.log('Planning to close the dialog...')
- setIsOpen(false)
- const timer = setTimeout(() => {
- console.log('onClose...')
-
- onClose(fireDelayedOnClose)
- }, 300)
- return () => clearTimeout(timer)
- }, [fireDelayedOnClose, onClose])
-
- // Animates the bottom sheet in on mount
- useEffect(() => {
- showBottomSheet()
- }, [])
-
// 🥊 ACTIONS
const showBottomSheet = () => {
@@ -107,7 +87,7 @@ export default React.forwardRef {
+ if (!fireDelayedOnClose) return
+ console.log('Planning to close the dialog...')
+ setIsOpen(false)
+ const timer = setTimeout(() => {
+ console.log('onClose...')
+
+ onClose(fireDelayedOnClose)
+ }, 300)
+ return () => clearTimeout(timer)
+ }, [fireDelayedOnClose, onClose])
+
+ // Animates the bottom sheet in on mount
+ useEffect(() => {
+ showBottomSheet()
+ }, [showBottomSheet])
+
const isFullScreen = sheetHeight?.current === 100
const currentHeight = sheetHeight?.current ?? 0
From 5bb1b5f22da46f0ed90da677d508dc9cb95bdcf5 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Mon, 11 Dec 2023 20:06:48 +0000
Subject: [PATCH 26/85] Fix
---
src/Dialog/DialogActionSheet.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index cb0726055b9..aa2e3d964b7 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -102,7 +102,7 @@ export default React.forwardRef
Date: Mon, 11 Dec 2023 20:49:54 +0000
Subject: [PATCH 27/85] Update docs
---
src/ConfirmationDialog/ConfirmationDialog.tsx | 2 +-
src/Dialog/Dialog.tsx | 10 ++++++----
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/ConfirmationDialog/ConfirmationDialog.tsx b/src/ConfirmationDialog/ConfirmationDialog.tsx
index d659ab23e1c..f29b4116342 100644
--- a/src/ConfirmationDialog/ConfirmationDialog.tsx
+++ b/src/ConfirmationDialog/ConfirmationDialog.tsx
@@ -16,7 +16,7 @@ export interface ConfirmationDialogProps {
* Required. This callback is invoked when a gesture to close the dialog
* is performed. The first argument indicates the gesture.
*/
- onClose: (gesture: 'confirm' | 'cancel' | 'close-button' | 'cancel' | 'escape') => void
+ onClose: (gesture: 'confirm' | 'cancel' | 'close-button' | 'escape' | 'drag' | 'overlay') => void
/**
* Required. The title of the ConfirmationDialog. This is usually a brief
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index f2f2df0f16e..d0bf7b80463 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -99,11 +99,13 @@ export interface DialogProps extends SxProp {
footerButtons?: DialogButtonProps[]
/**
- * This method is invoked when a gesture to close the dialog is used (either
- * an Escape key press or clicking the "X" in the top-right corner). The
- * gesture argument indicates the gesture that was used to close the dialog
- * (either 'close-button' or 'escape').
+ * This method is invoked when a gesture to close the dialog is used
+ * (either an Escape key press, clicking/tapping on the backdrop,
+ * clicking/tapping the 'X' in the top-right corner or dragging away an
+ * action sheet). The gesture argument indicates the gesture that was
+ * used to close the dialog.
*/
+
onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
/**
From d2b64dd7c4b4346a3d9144542db88d53afb9de77 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 12:25:46 +0000
Subject: [PATCH 28/85] Fix
---
src/Dialog/Dialog.features.stories.tsx | 10 +---------
src/Dialog/DialogActionSheet.tsx | 12 +++++-------
2 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index 1fc1e061381..035e5c4ff96 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -223,15 +223,7 @@ export const FullScreen = () => {
export const ActionSheet = () => {
return (
- {}}
- footerButtons={[
- {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
- {buttonType: 'primary', content: 'Submit', autoFocus: true},
- ]}
- >
+ {}}>
{lipsum}
)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index aa2e3d964b7..7182c5094b2 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -14,6 +14,7 @@ import VisuallyHidden from '../_VisuallyHidden'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {get} from '../constants'
+const ANIMATION_DURATION = 300
/**
* Props to customize the rendering of the Dialog.
*/
@@ -155,20 +156,17 @@ export default React.forwardRef {
if (!fireDelayedOnClose) return
- console.log('Planning to close the dialog...')
setIsOpen(false)
const timer = setTimeout(() => {
- console.log('onClose...')
-
onClose(fireDelayedOnClose)
- }, 300)
+ }, ANIMATION_DURATION)
return () => clearTimeout(timer)
}, [fireDelayedOnClose, onClose])
// Animates the bottom sheet in on mount
useEffect(() => {
showBottomSheet()
- }, [showBottomSheet])
+ }, [])
const isFullScreen = sheetHeight?.current === 100
const currentHeight = sheetHeight?.current ?? 0
@@ -225,7 +223,7 @@ const FullScreenContainer = styled.div<{open: boolean}>`
flex-direction: column;
justify-content: flex-end;
align-items: center;
- transition: 0.1s linear;
+ transition: ${ANIMATION_DURATION / 3}ms linear;
@media (prefers-reduced-motion) {
transition: none;
}
@@ -293,7 +291,7 @@ const Content = styled.div<
overflow-x: hidden;
overflow-y: ${props => (props.isFullScreen ? 'hidden' : 'auto')};
transform: ${props => (props.open ? 'translateY(0%)' : 'translateY(100%)')};
- transition: 0.3s ease;
+ transition: ${ANIMATION_DURATION}ms ease;
`
// 🧑🦽 Accessibility
From f7cd19ae6452732d5ffa2c57ce0d0740893669e9 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 14:29:48 +0000
Subject: [PATCH 29/85] Use a slider instead of resizing
---
src/Dialog/DialogActionSheet.tsx | 128 +++++++++----------------------
1 file changed, 37 insertions(+), 91 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 7182c5094b2..89d0a9defb4 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -1,16 +1,6 @@
-import React, {
- useEffect,
- useRef,
- useState,
- PropsWithChildren,
- MouseEvent,
- TouchEvent,
- KeyboardEvent,
- FormEventHandler,
-} from 'react'
+import React, {useEffect, useRef, useState, PropsWithChildren, MouseEvent, TouchEvent, ChangeEvent} from 'react'
import styled from 'styled-components'
import {SxProp} from '../sx'
-import VisuallyHidden from '../_VisuallyHidden'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {get} from '../constants'
@@ -53,6 +43,7 @@ export default React.forwardRef) => {
- const height = dialogRef.current?.style.height ?? '0'
- const sheetHeight = parseInt(height)
-
- if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
- if (sheetHeight === halfHeight) {
- e.preventDefault()
- return updateSheetHeight(fullHeight)
- }
- }
- if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
- if (sheetHeight === fullHeight) {
- e.preventDefault()
- return updateSheetHeight(halfHeight)
- }
-
- if (sheetHeight === halfHeight) {
- e.preventDefault()
- return hideBottomSheet('drag')
- }
+ const onSliderChange = (e: ChangeEvent) => {
+ const value = e.target.valueAsNumber
+ if (value === 1) {
+ updateSheetHeight(halfHeight)
+ } else if (value === 2) {
+ updateSheetHeight(fullHeight)
}
}
@@ -170,7 +147,10 @@ export default React.forwardRef
-
-
- {children}
- {
- if (size === 'small') {
- updateSheetHeight(halfHeight)
- } else {
- updateSheetHeight(fullHeight)
- }
- }}
/>
+
+ {children}
)
@@ -244,9 +217,14 @@ const DraggableRegionPill = styled.div`
display: block;
background-color: ${get('colors.border.muted')};
border-radius: 3px;
+ top: ${get('space.2')};
+ position: absolute;
+ left: 50%;
+ margin-left: -35px;
`
-const DraggableRegion = styled.button`
+const DraggableRegion = styled.input`
+ appearance: none;
display: flex;
justify-content: center;
border: none;
@@ -257,22 +235,23 @@ const DraggableRegion = styled.button`
right: 0;
left: 0;
padding-top: ${get('space.2')};
- padding-bottom: ${get('space.2')};
cursor: grab;
user-select: none;
- &:hover ${DraggableRegionPill} {
+ &:hover ~ ${DraggableRegionPill} {
background-color: ${get('colors.border.default')};
}
&:focus {
outline: none;
}
- &:focus-visible:not([disabled]) {
- outline: 2px solid;
- outline-offset: -${get('space.1')};
- border-top-left-radius: 11px;
- border-top-right-radius: 11px;
- outline-color: ${get('colors.accent.emphasis')};
- },
+ &:focus-visible:not([disabled]) ~ ${DraggableRegionPill} {
+ background-color: ${get('colors.accent.emphasis')};
+ }
+ ::-moz-range-track {
+ appearance: none;
+ }
+ ::-webkit-slider-thumb {
+ appearance: none;
+ }
`
const Content = styled.div<
{
@@ -286,7 +265,7 @@ const Content = styled.div<
height: 50vh;
maxheight: 100vh;
width: 100%;
- border-radius: ${props => (props.isFullScreen ? 0 : '12px 12px 0 0')};
+ border-radius: 12px 12px 0 0;
position: relative;
overflow-x: hidden;
overflow-y: ${props => (props.isFullScreen ? 'hidden' : 'auto')};
@@ -300,36 +279,3 @@ const prefersReducedMotion = () => {
const mediaQueryList = window.matchMedia('(prefers-reduced-motion: no-preference)')
return !mediaQueryList.matches
}
-
-interface HiddenAccessibilityFormCallbackProps extends HTMLFormElement {
- dialogSize: HTMLInputElement
-}
-
-interface HiddenAccessibilityFormProps {
- onSubmit: (size: 'small' | 'large') => void
-}
-
-const HiddenAccessibilityForm = ({onSubmit}: HiddenAccessibilityFormProps) => {
- const name = 'dialog-size'
- const handleSubmit: FormEventHandler = e => {
- e.preventDefault()
- const sizeValue = e.currentTarget[name].value
- onSubmit(sizeValue)
- }
-
- return (
-
-
-
- )
-}
From 3538a943d4e81789dd2a21454979f8bb94d76d22 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 15:46:24 +0000
Subject: [PATCH 30/85] Improve code
---
src/Dialog/DialogActionSheet.tsx | 46 ++++++++++++++++----------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 89d0a9defb4..773731d5965 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -4,7 +4,10 @@ import {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {get} from '../constants'
-const ANIMATION_DURATION = 300
+const ANIMATION_DURATION: number = 300
+const FULL_HEIGHT: number = 90
+const HALF_HEIGHT: number = 50
+
/**
* Props to customize the rendering of the Dialog.
*/
@@ -25,11 +28,6 @@ export interface DialogActionSheetProps extends SxProp {
export default React.forwardRef>((props, forwardedRef) => {
const {onClose, children, role, sx} = props
- // 📏 SIZES
-
- const fullHeight = 90
- const halfHeight = 50
-
// 🔄 STATES
const [open, setIsOpen] = useState(false)
@@ -43,8 +41,7 @@ export default React.forwardRef {
setIsOpen(true)
- updateSheetHeight(50)
+ updateSheetHeight(HALF_HEIGHT)
}
const hideBottomSheet = async (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => {
@@ -71,6 +68,7 @@ export default React.forwardRef {
if (!dialogRef.current) return
dialogRef.current.style.height = `${height}vh`
+ sheetHeight.current = height
}
// 🎪 EVENTS
@@ -85,9 +83,9 @@ export default React.forwardRef 75) return updateSheetHeight(fullHeight)
+ if (sheetHeight > 75) return updateSheetHeight(FULL_HEIGHT)
- updateSheetHeight(halfHeight)
+ updateSheetHeight(HALF_HEIGHT)
}
const dragStart = (e: MouseEvent | TouchEvent) => {
@@ -120,11 +118,12 @@ export default React.forwardRef) => {
- const value = e.target.valueAsNumber
+ const value = parseInt(e.target.value)
+ console.log('value', value)
if (value === 1) {
- updateSheetHeight(halfHeight)
+ updateSheetHeight(HALF_HEIGHT)
} else if (value === 2) {
- updateSheetHeight(fullHeight)
+ updateSheetHeight(FULL_HEIGHT)
}
}
@@ -145,11 +144,10 @@ export default React.forwardRef
hideBottomSheet('overlay')}>
-
+
@@ -235,6 +235,7 @@ const DraggableRegion = styled.input`
right: 0;
left: 0;
padding-top: ${get('space.2')};
+ padding-bottom: ${get('space.1')};
cursor: grab;
user-select: none;
&:hover ~ ${DraggableRegionPill} {
@@ -256,7 +257,6 @@ const DraggableRegion = styled.input`
const Content = styled.div<
{
open: boolean
- isFullScreen: boolean
} & SxProp
>`
display: flex;
@@ -268,7 +268,7 @@ const Content = styled.div<
border-radius: 12px 12px 0 0;
position: relative;
overflow-x: hidden;
- overflow-y: ${props => (props.isFullScreen ? 'hidden' : 'auto')};
+ overflow-y: auto;
transform: ${props => (props.open ? 'translateY(0%)' : 'translateY(100%)')};
transition: ${ANIMATION_DURATION}ms ease;
`
From 318fe3fd462941406e7778d102dbe998a8a73017 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 15:56:21 +0000
Subject: [PATCH 31/85] Fix
---
src/Dialog/DialogActionSheet.tsx | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 773731d5965..c1b86d77a3a 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -31,6 +31,8 @@ export default React.forwardRef(false)
+ const [snappedHeight, setSnappedHeight] = useState(HALF_HEIGHT)
+
const [fireDelayedOnClose, setFireDelayedOnClose] = useState<
'close-button' | 'escape' | 'drag' | 'overlay' | undefined
>()
@@ -41,7 +43,6 @@ export default React.forwardRef {
if (!dialogRef.current) return
dialogRef.current.style.height = `${height}vh`
- sheetHeight.current = height
+ setSnappedHeight(height)
}
// 🎪 EVENTS
@@ -144,11 +145,8 @@ export default React.forwardRef
{children}
From ba765f5cc40714ca77f559887b611a0873a634fd Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 16:29:49 +0000
Subject: [PATCH 32/85] Fix dragging
---
src/Dialog/DialogActionSheet.tsx | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index c1b86d77a3a..5b430a6bcbe 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -119,12 +119,14 @@ export default React.forwardRef) => {
- const value = parseInt(e.target.value)
- console.log('value', value)
- if (value === 1) {
- updateSheetHeight(HALF_HEIGHT)
- } else if (value === 2) {
- updateSheetHeight(FULL_HEIGHT)
+ if (!isDragging.current) {
+ console.log('onSliderChange')
+ const value = parseInt(e.target.value)
+ if (value === 1) {
+ updateSheetHeight(HALF_HEIGHT)
+ } else if (value === 2) {
+ updateSheetHeight(FULL_HEIGHT)
+ }
}
}
From 24ceca7ba09eca2ce1b463d016e733b03e9c489b Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 17:22:01 +0000
Subject: [PATCH 33/85] Create rare-geese-melt.md
---
.changeset/rare-geese-melt.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/rare-geese-melt.md
diff --git a/.changeset/rare-geese-melt.md b/.changeset/rare-geese-melt.md
new file mode 100644
index 00000000000..b6bfed391d9
--- /dev/null
+++ b/.changeset/rare-geese-melt.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": major
+---
+
+Dialog: `full-screen` and `action-sheet` variants
From 6df46aec8fab9a038b9b0965607de78ddd7b4600 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 17:23:12 +0000
Subject: [PATCH 34/85] Update rare-geese-melt.md
---
.changeset/rare-geese-melt.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/rare-geese-melt.md b/.changeset/rare-geese-melt.md
index b6bfed391d9..bd324284689 100644
--- a/.changeset/rare-geese-melt.md
+++ b/.changeset/rare-geese-melt.md
@@ -1,5 +1,5 @@
---
-"@primer/react": major
+"@primer/react": minor
---
Dialog: `full-screen` and `action-sheet` variants
From 6a60f2ad0e09a6f3e1991268429d000c2cdb797d Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 17:56:27 +0000
Subject: [PATCH 35/85] Fixes for FireFox
---
src/Dialog/DialogActionSheet.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 5b430a6bcbe..b89589ee527 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -232,23 +232,27 @@ const DraggableRegion = styled.input`
background: transparent;
top: 0;
z-index: 2;
+ width: 100%;
right: 0;
left: 0;
padding-top: ${get('space.2')};
padding-bottom: ${get('space.1')};
cursor: grab;
user-select: none;
+ height: 12px;
&:hover ~ ${DraggableRegionPill} {
background-color: ${get('colors.border.default')};
}
&:focus {
outline: none;
+ opacity: 0;
}
&:focus-visible:not([disabled]) ~ ${DraggableRegionPill} {
background-color: ${get('colors.accent.emphasis')};
}
- ::-moz-range-track {
+ ::-moz-range-thumb {
appearance: none;
+ visibility: hidden;
}
::-webkit-slider-thumb {
appearance: none;
From 245f453f4774ee6ae93ca86674bc5c3a09aba5d1 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 18:10:07 +0000
Subject: [PATCH 36/85] Fix: Type number trivially inferred from a number
literal, remove type annotation
---
src/Dialog/DialogActionSheet.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index b89589ee527..e29b38e2113 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -4,9 +4,9 @@ import {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {get} from '../constants'
-const ANIMATION_DURATION: number = 300
-const FULL_HEIGHT: number = 90
-const HALF_HEIGHT: number = 50
+const ANIMATION_DURATION = 300
+const FULL_HEIGHT = 90
+const HALF_HEIGHT = 50
/**
* Props to customize the rendering of the Dialog.
From c09c492de652d6c23afcc10a9af64fc1ba3c7ff8 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 18:11:19 +0000
Subject: [PATCH 37/85] Switch refs to const
---
src/Dialog/DialogActionSheet.tsx | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index e29b38e2113..32fb4f2e34a 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -39,10 +39,10 @@ export default React.forwardRef(null)
- let startY = useRef(0)
- let startHeight = useRef(0)
- let isDragging = useRef(false)
+ const dialogRef = useRef(null)
+ const startY = useRef(0)
+ const startHeight = useRef(0)
+ const isDragging = useRef(false)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
@@ -93,12 +93,12 @@ export default React.forwardRef
Date: Tue, 12 Dec 2023 18:30:16 +0000
Subject: [PATCH 38/85] Fix linter warnings
---
src/Dialog/DialogActionSheet.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 32fb4f2e34a..c518c922aeb 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -98,7 +98,7 @@ export default React.forwardRef {
if (!dialogRef.current || !isDragging.current) return
- var pageY
+ let pageY
if (e.type === 'touchstart' && 'touches' in e) {
pageY = e.touches[0].pageY
} else if ('clientX' in e) {
@@ -120,7 +120,6 @@ export default React.forwardRef) => {
if (!isDragging.current) {
- console.log('onSliderChange')
const value = parseInt(e.target.value)
if (value === 1) {
updateSheetHeight(HALF_HEIGHT)
@@ -145,6 +144,7 @@ export default React.forwardRef {
showBottomSheet()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const currentSliderValue = snappedHeight === HALF_HEIGHT ? 1 : 2
From 28801c32e5a03138cdb6bf66f52f47da4da9024d Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Tue, 12 Dec 2023 18:47:13 +0000
Subject: [PATCH 39/85] Add missing props back
---
src/Dialog/Dialog.tsx | 30 +++++++++++++++++++++++++++---
src/Dialog/DialogActionSheet.tsx | 27 +++++++++++++++++++++++++--
2 files changed, 52 insertions(+), 5 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index d0bf7b80463..3731a2e304a 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -278,7 +278,14 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
@@ -290,7 +297,15 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
@@ -302,7 +317,16 @@ const _Dialog = React.forwardRef
-
+
{header}
{body}
{footer}
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index c518c922aeb..43ac33d3c99 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -23,10 +23,25 @@ export interface DialogActionSheetProps extends SxProp {
* @see https://www.w3.org/TR/wai-aria-practices-1.1/#alertdialog
*/
role?: 'dialog' | 'alertdialog'
+
+ /**
+ * Identifies the element (or elements) that labels the element it is applied to.
+ */
+ ariaLabelledby: string
+
+ /**
+ * Identifies the element (or elements) that describes the element on which the attribute is set.
+ */
+ ariaDescribedby: string
+
+ /**
+ * Indicates whether an element is modal when displayed.
+ */
+ ariaModal: boolean
}
export default React.forwardRef>((props, forwardedRef) => {
- const {onClose, children, role, sx} = props
+ const {onClose, children, role, ariaLabelledby, ariaDescribedby, ariaModal, sx} = props
// 🔄 STATES
@@ -158,7 +173,15 @@ export default React.forwardRef
hideBottomSheet('overlay')}>
-
+
Date: Wed, 13 Dec 2023 12:19:57 +0000
Subject: [PATCH 40/85] Increase the hit target on mobile
---
src/Dialog/Dialog.tsx | 7 +++-
src/Dialog/DialogActionSheet.tsx | 67 +++++++++++++++++++-------------
2 files changed, 44 insertions(+), 30 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 3731a2e304a..002cf49322d 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -304,9 +304,9 @@ const _Dialog = React.forwardRef
- {header}
{body}
{footer}
@@ -452,6 +452,7 @@ const Header = styled.div`
box-shadow: 0 1px 0 ${get('colors.border.default')};
padding: ${get('space.2')};
z-index: 1;
+ pointer-events: none;
flex-shrink: 0;
`
@@ -459,6 +460,7 @@ const Title = styled.h1`
font-size: ${get('fontSizes.1')};
font-weight: ${get('fontWeights.bold')};
margin: 0; /* override default margin */
+ width: fit-content;
${sx};
`
@@ -468,7 +470,7 @@ const Subtitle = styled.h2`
color: ${get('colors.fg.muted')};
margin: 0; /* override default margin */
margin-top: ${get('space.1')};
-
+ width: fit-content;
${sx};
`
@@ -501,6 +503,7 @@ const DialogCloseButton = styled(Button)`
color: ${get('colors.fg.muted')};
padding: ${get('space.2')};
align-self: flex-start;
+ pointer-events: auto; // Allows clicking when combined with a draggable action-sheet header
line-height: normal;
box-shadow: none;
`
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 43ac33d3c99..18191e4dac1 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -2,10 +2,11 @@ import React, {useEffect, useRef, useState, PropsWithChildren, MouseEvent, Touch
import styled from 'styled-components'
import {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
+import Box from '../Box'
import {get} from '../constants'
const ANIMATION_DURATION = 300
-const FULL_HEIGHT = 90
+const FULL_HEIGHT = 95
const HALF_HEIGHT = 50
/**
@@ -17,6 +18,11 @@ export interface DialogActionSheetProps extends SxProp {
*/
onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
+ /**
+ * Passed through to use as the draggable area of the dialog.
+ */
+ header: React.ReactNode
+
/**
* Default: "dialog". The ARIA role to assign to this dialog.
* @see https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal
@@ -41,13 +47,12 @@ export interface DialogActionSheetProps extends SxProp {
}
export default React.forwardRef>((props, forwardedRef) => {
- const {onClose, children, role, ariaLabelledby, ariaDescribedby, ariaModal, sx} = props
+ const {onClose, children, role, ariaLabelledby, ariaDescribedby, ariaModal, header, sx} = props
// 🔄 STATES
const [open, setIsOpen] = useState(false)
const [snappedHeight, setSnappedHeight] = useState(HALF_HEIGHT)
-
const [fireDelayedOnClose, setFireDelayedOnClose] = useState<
'close-button' | 'escape' | 'drag' | 'overlay' | undefined
>()
@@ -83,7 +88,7 @@ export default React.forwardRef {
if (!dialogRef.current) return
- dialogRef.current.style.height = `${height}vh`
+ dialogRef.current.style.height = `${height}dvh`
setSnappedHeight(height)
}
@@ -98,7 +103,7 @@ export default React.forwardRef 75) return updateSheetHeight(FULL_HEIGHT)
updateSheetHeight(HALF_HEIGHT)
@@ -122,7 +127,7 @@ export default React.forwardRef
hideBottomSheet('overlay')}>
-
-
+
+
+
+ {header}
+
{children}
@@ -242,6 +250,7 @@ const DraggableRegionPill = styled.div`
border-radius: 3px;
top: ${get('space.2')};
position: absolute;
+ pointer-events: none;
left: 50%;
margin-left: -35px;
`
@@ -254,15 +263,15 @@ const DraggableRegion = styled.input`
position: absolute;
background: transparent;
top: 0;
- z-index: 2;
width: 100%;
right: 0;
left: 0;
+ bottom: 0;
padding-top: ${get('space.2')};
padding-bottom: ${get('space.1')};
cursor: grab;
user-select: none;
- height: 12px;
+ min-height: 50px;
&:hover ~ ${DraggableRegionPill} {
background-color: ${get('colors.border.default')};
}
@@ -289,11 +298,13 @@ const Content = styled.div<
display: flex;
flex-direction: column;
background-color: ${get('colors.canvas.default')};
- height: 50vh;
- maxheight: 100vh;
width: 100%;
border-radius: 12px 12px 0 0;
position: relative;
+ height: 50vh;
+ height: 50dvh;
+ max-height: 100vh;
+ max-height: 100dvh;
overflow-x: hidden;
overflow-y: auto;
transform: ${props => (props.open ? 'translateY(0%)' : 'translateY(100%)')};
From 8655d4767324657d93826c73ebc49c1093166950 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 13 Dec 2023 12:46:23 +0000
Subject: [PATCH 41/85] Add preventDefault() for Firefox
---
src/Dialog/DialogActionSheet.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogActionSheet.tsx
index 18191e4dac1..d80620d0df2 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogActionSheet.tsx
@@ -94,9 +94,11 @@ export default React.forwardRef {
+ const dragStop = (e: MouseEvent | TouchEvent) => {
if (!dialogRef.current) return
+ e.preventDefault()
+
isDragging.current = false
const sheetHeight = parseInt(dialogRef.current.style.height)
@@ -112,6 +114,8 @@ export default React.forwardRef {
if (!dialogRef.current) return
+ e.preventDefault()
+
if (e.type === 'touchstart' && 'touches' in e) {
startY.current = e.touches[0].pageY
} else if ('clientX' in e) {
@@ -126,6 +130,8 @@ export default React.forwardRef {
if (!dialogRef.current || !isDragging.current) return
+ e.preventDefault()
+
let pageY
if (e.type === 'touchmove' && 'touches' in e) {
pageY = e.touches[0].pageY
From d648ab2bdd7795a92e145c25c8aae5839d239b04 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 13 Dec 2023 13:01:51 +0000
Subject: [PATCH 42/85] Add support for setting overflow area height
---
src/Dialog/Dialog.tsx | 7 +++----
src/internal/components/ScrollableRegion.tsx | 8 +++++---
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 64ec0be9fa7..6b425119edf 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -18,6 +18,7 @@ import {useId} from '../hooks/useId'
import {ScrollableRegion} from '../internal/components/ScrollableRegion'
import DialogActionSheet from './DialogActionSheet'
+import {minHeight} from 'styled-system'
/* Dialog Version 2 */
@@ -288,7 +289,7 @@ const _Dialog = React.forwardRef
{header}
-
+
{body}
{footer}
@@ -310,7 +311,7 @@ const _Dialog = React.forwardRef
-
+
{body}
{footer}
@@ -485,7 +486,6 @@ const Body = styled.div`
flex-grow: 1;
overflow: auto;
padding: ${get('space.3')};
-
${sx};
`
@@ -498,7 +498,6 @@ const Footer = styled.div`
gap: ${get('space.2')};
z-index: 1;
flex-shrink: 0;
-
${sx};
`
diff --git a/src/internal/components/ScrollableRegion.tsx b/src/internal/components/ScrollableRegion.tsx
index 0600f5cba92..46537324db0 100644
--- a/src/internal/components/ScrollableRegion.tsx
+++ b/src/internal/components/ScrollableRegion.tsx
@@ -1,13 +1,15 @@
import React from 'react'
import Box from '../../Box'
import {useOverflow} from '../hooks/useOverflow'
+import {SxProp} from '../../sx'
type ScrollableRegionProps = React.PropsWithChildren<{
'aria-labelledby'?: string
className?: string
-}>
+}> &
+ SxProp
-export function ScrollableRegion({'aria-labelledby': labelledby, children, ...rest}: ScrollableRegionProps) {
+export function ScrollableRegion({'aria-labelledby': labelledby, children, sx, ...rest}: ScrollableRegionProps) {
const ref = React.useRef(null)
const hasOverflow = useOverflow(ref)
const regionProps = hasOverflow
@@ -19,7 +21,7 @@ export function ScrollableRegion({'aria-labelledby': labelledby, children, ...re
: {}
return (
-
+
{children}
)
From 1b5f6c83ea30905034349574513eb47da7e8acba Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Thu, 14 Dec 2023 23:31:03 +0000
Subject: [PATCH 43/85] Rename ActionSheet to BottomSheet and add max-width
---
src/Dialog/Dialog.docs.json | 4 ++--
src/Dialog/Dialog.features.stories.tsx | 4 ++--
src/Dialog/Dialog.stories.tsx | 3 +--
src/Dialog/Dialog.tsx | 21 +++++++++----------
...gActionSheet.tsx => DialogBottomSheet.tsx} | 8 ++++---
5 files changed, 20 insertions(+), 20 deletions(-)
rename src/Dialog/{DialogActionSheet.tsx => DialogBottomSheet.tsx} (98%)
diff --git a/src/Dialog/Dialog.docs.json b/src/Dialog/Dialog.docs.json
index 48346c97be5..ca21a3b1669 100644
--- a/src/Dialog/Dialog.docs.json
+++ b/src/Dialog/Dialog.docs.json
@@ -38,7 +38,7 @@
{
"name": "onClose",
"type": "(gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void",
- "description": "This method is invoked when a gesture to close the dialog is used (either an Escape key press, clicking/tapping on the backdrop, clicking/tapping the 'X' in the top-right corner or dragging away an action sheet). The gesture argument indicates the gesture that was used to close the dialog."
+ "description": "This method is invoked when a gesture to close the dialog is used (either an Escape key press, clicking/tapping on the backdrop, clicking/tapping the 'X' in the top-right corner or dragging away a bottom sheet). The gesture argument indicates the gesture that was used to close the dialog."
},
{
"name": "role",
@@ -47,7 +47,7 @@
},
{
"name": "type",
- "type": "'default' | 'full-screen' | 'action-sheet'",
+ "type": "'default' | 'full-screen' | 'bottom-sheet'",
"description": "The type of dialog to render. 'default' renders a dialog in the center of the screen. 'full-screen' renders the dialog full screen and ignored the width and height. 'full-screen' is often used for mobile breakpoints."
},
{
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index 035e5c4ff96..bd34f332cf3 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -221,9 +221,9 @@ export const FullScreen = () => {
)
}
-export const ActionSheet = () => {
+export const BottomSheet = () => {
return (
- {}}>
+ {}}>
{lipsum}
)
diff --git a/src/Dialog/Dialog.stories.tsx b/src/Dialog/Dialog.stories.tsx
index 69ccf921404..22f8b8a354d 100644
--- a/src/Dialog/Dialog.stories.tsx
+++ b/src/Dialog/Dialog.stories.tsx
@@ -1,6 +1,5 @@
import React, {useState, useRef, useCallback} from 'react'
import {Meta} from '@storybook/react'
-
import {BaseStyles, ThemeProvider} from '..'
import {Button} from '../Button'
import {Dialog, DialogWidth, DialogHeight, DialogType} from './Dialog'
@@ -155,7 +154,7 @@ Playground.argTypes = {
control: {
type: 'radio',
},
- options: ['default', 'full-screen', 'action-sheet'],
+ options: ['default', 'full-screen', 'bottom-sheet'],
},
width: {
control: {
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 6b425119edf..a62f15907a4 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -16,8 +16,7 @@ import {useFocusZone} from '../hooks/useFocusZone'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {useId} from '../hooks/useId'
import {ScrollableRegion} from '../internal/components/ScrollableRegion'
-
-import DialogActionSheet from './DialogActionSheet'
+import DialogBottomSheet from './DialogBottomSheet'
import {minHeight} from 'styled-system'
/* Dialog Version 2 */
@@ -103,8 +102,8 @@ export interface DialogProps extends SxProp {
/**
* This method is invoked when a gesture to close the dialog is used
* (either an Escape key press, clicking/tapping on the backdrop,
- * clicking/tapping the 'X' in the top-right corner or dragging away an
- * action sheet). The gesture argument indicates the gesture that was
+ * clicking/tapping the 'X' in the top-right corner or dragging away a
+ * bottom sheet). The gesture argument indicates the gesture that was
* used to close the dialog.
*/
@@ -119,8 +118,8 @@ export interface DialogProps extends SxProp {
/**
* Normally a dialog is display in the center of a viewport but sometimes
- * it is useful to display this full screen or as an action sheet on mobile viewports
- * to allow for more space for content. When full-screen or action sheet the width
+ * it is useful to display this full screen or as an bottom sheet on mobile viewports
+ * to allow for more space for content. When full-screen or bottom sheet the width
* and height is ignored.
*/
type?: DialogType | ResponsiveValue
@@ -175,7 +174,7 @@ const widthMap = {
export type DialogWidth = keyof typeof widthMap
export type DialogHeight = keyof typeof heightMap
-export type DialogType = 'default' | 'full-screen' | 'action-sheet'
+export type DialogType = 'default' | 'full-screen' | 'bottom-sheet'
type StyledDialogProps = {
width?: DialogWidth
@@ -298,10 +297,10 @@ const _Dialog = React.forwardRef
-
{footer}
-
+
)
}
@@ -509,7 +508,7 @@ const DialogCloseButton = styled(Button)`
color: ${get('colors.fg.muted')};
padding: ${get('space.2')};
align-self: flex-start;
- pointer-events: auto; // Allows clicking when combined with a draggable action-sheet header
+ pointer-events: auto; // Allows clicking when combined with a draggable bottom-sheet header
line-height: normal;
box-shadow: none;
`
diff --git a/src/Dialog/DialogActionSheet.tsx b/src/Dialog/DialogBottomSheet.tsx
similarity index 98%
rename from src/Dialog/DialogActionSheet.tsx
rename to src/Dialog/DialogBottomSheet.tsx
index d80620d0df2..4be67384f85 100644
--- a/src/Dialog/DialogActionSheet.tsx
+++ b/src/Dialog/DialogBottomSheet.tsx
@@ -12,7 +12,7 @@ const HALF_HEIGHT = 50
/**
* Props to customize the rendering of the Dialog.
*/
-export interface DialogActionSheetProps extends SxProp {
+export interface DialogBottomSheetProps extends SxProp {
/**
* This method is invoked when a gesture to close the dialog is used
*/
@@ -46,7 +46,7 @@ export interface DialogActionSheetProps extends SxProp {
ariaModal: boolean
}
-export default React.forwardRef>((props, forwardedRef) => {
+export default React.forwardRef>((props, forwardedRef) => {
const {onClose, children, role, ariaLabelledby, ariaDescribedby, ariaModal, header, sx} = props
// 🔄 STATES
@@ -304,7 +304,9 @@ const Content = styled.div<
display: flex;
flex-direction: column;
background-color: ${get('colors.canvas.default')};
- width: 100%;
+ width: 100vw;
+ max-width: 100dvh;
+ max-width: 480px;
border-radius: 12px 12px 0 0;
position: relative;
height: 50vh;
From f58a1e932605fe2703ed4bc09bb9ca6915610feb Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Thu, 14 Dec 2023 23:38:10 +0000
Subject: [PATCH 44/85] Remove minHeight
---
src/Dialog/Dialog.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index a62f15907a4..637e3448adb 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -17,7 +17,6 @@ import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {useId} from '../hooks/useId'
import {ScrollableRegion} from '../internal/components/ScrollableRegion'
import DialogBottomSheet from './DialogBottomSheet'
-import {minHeight} from 'styled-system'
/* Dialog Version 2 */
From d980b620558bd8a879f02133ca0646c96acc360d Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 20 Dec 2023 13:28:14 +0000
Subject: [PATCH 45/85] Fix merge conflicts
---
package-lock.json | 4 ++--
src/Dialog/Dialog.tsx | 36 ++++++++++++++++++------------------
2 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 7147d597b10..ff600016ec0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@primer/react",
- "version": "36.5.0",
+ "version": "36.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@primer/react",
- "version": "36.5.0",
+ "version": "36.4.0",
"license": "MIT",
"dependencies": {
"@github/combobox-nav": "^2.1.5",
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index d63f0921da7..88b27d2c27a 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -1,18 +1,23 @@
import React, {useCallback, useEffect, useRef, useState} from 'react'
import styled from 'styled-components'
-import {Button, ButtonProps} from '../Button'
-import Box from '../Box'
-import {get} from '../constants'
-import Portal from '../Portal'
+import {FocusKeys} from '@primer/behaviors'
+import {XIcon} from '@primer/octicons-react'
+
import {useOnEscapePress, useProvidedRefOrCreate} from '../hooks'
-import {useFocusTrap} from '../hooks/useFocusTrap'
-import sx, {SxProp} from '../sx'
+import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {ResponsiveValue, useResponsiveValue} from '../hooks/useResponsiveValue'
-import Octicon from '../Octicon'
import {useFocusZone} from '../hooks/useFocusZone'
-import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
+import {useFocusTrap} from '../hooks/useFocusTrap'
import {useId} from '../hooks/useId'
+
import {ScrollableRegion} from '../internal/components/ScrollableRegion'
+import sx, {SxProp} from '../sx'
+import {get} from '../constants'
+import {Button, ButtonProps} from '../Button'
+import Box from '../Box'
+import Portal from '../Portal'
+import Octicon from '../Octicon'
+
import DialogBottomSheet from './DialogBottomSheet'
/* Dialog Version 2 */
@@ -340,11 +345,6 @@ const _Dialog = React.forwardRef> = ({buttons}) => {
const autoFocusRef = useProvidedRefOrCreate(buttons.find(button => button.autoFocus)?.ref)
let autoFocusCount = 0
@@ -361,17 +361,17 @@ const Buttons: React.FC>
return (
<>
{buttons.map((dialogButtonProps, index) => {
- const {content, buttonType = 'normal', autoFocus = false, ...buttonProps} = dialogButtonProps
- const ButtonElement = buttonTypes[buttonType]
+ const {content, buttonType = 'default', autoFocus = false, ...buttonProps} = dialogButtonProps
return (
-
{content}
-
+
)
})}
>
From de74cbc957a2ba01fd45ff23c948bf3da1a977ad Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 10 Jan 2024 15:26:04 +0000
Subject: [PATCH 46/85] Fix close button interactions
---
src/Dialog/Dialog.tsx | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 88b27d2c27a..281c911e725 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -455,15 +455,16 @@ const Header = styled.div`
box-shadow: 0 1px 0 ${get('colors.border.default')};
padding: ${get('space.2')};
z-index: 1;
- pointer-events: none;
+ pointer-events: none ${/* Allows clicks to pass through the header in bottom sheets */ ''};
flex-shrink: 0;
+ position: relative;
`
const Title = styled.h1`
font-size: ${get('fontSizes.1')};
font-weight: ${get('fontWeights.bold')};
margin: 0; /* override default margin */
- width: fit-content;
+ width: calc(100% - ${get('space.4')});
${sx};
`
@@ -503,8 +504,12 @@ const DialogCloseButton = styled(Button)`
vertical-align: middle;
color: ${get('colors.fg.muted')};
padding: ${get('space.2')};
+ z-index: 2;
+ right: 0;
+ margin-right: ${get('space.2')};
+ position: absolute;
align-self: flex-start;
- pointer-events: auto; // Allows clicking when combined with a draggable bottom-sheet header
+ pointer-events: auto ${/* Allows clicks to pass through the header in bottom sheets */ ''};
line-height: normal;
box-shadow: none;
`
From 5c4e8f50ac62d20477c6fc95b4e2df1bded808ae Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 10 Jan 2024 15:59:24 +0000
Subject: [PATCH 47/85] Remove gestures we won't use
---
src/ConfirmationDialog/ConfirmationDialog.tsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/ConfirmationDialog/ConfirmationDialog.tsx b/src/ConfirmationDialog/ConfirmationDialog.tsx
index d0cb67420ad..c13aee068a2 100644
--- a/src/ConfirmationDialog/ConfirmationDialog.tsx
+++ b/src/ConfirmationDialog/ConfirmationDialog.tsx
@@ -17,7 +17,7 @@ export interface ConfirmationDialogProps {
* Required. This callback is invoked when a gesture to close the dialog
* is performed. The first argument indicates the gesture.
*/
- onClose: (gesture: 'confirm' | 'cancel' | 'close-button' | 'escape' | 'drag' | 'overlay') => void
+ onClose: (gesture: 'confirm' | 'cancel' | 'close-button' | 'escape') => void
/**
* Required. The title of the ConfirmationDialog. This is usually a brief
* question.
@@ -131,7 +131,11 @@ export const ConfirmationDialog: React.FC {
+ if (gesture !== 'overlay' && gesture !== 'drag') {
+ onClose(gesture)
+ }
+ }}
title={title}
footerButtons={footerButtons}
role="alertdialog"
From f6f41990dc5d35bb7d23f259bb95b65b154bb640 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 10 Jan 2024 16:05:58 +0000
Subject: [PATCH 48/85] Fix copy
---
src/Dialog/DialogBottomSheet.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Dialog/DialogBottomSheet.tsx b/src/Dialog/DialogBottomSheet.tsx
index 4be67384f85..d2635f1d2c6 100644
--- a/src/Dialog/DialogBottomSheet.tsx
+++ b/src/Dialog/DialogBottomSheet.tsx
@@ -19,7 +19,7 @@ export interface DialogBottomSheetProps extends SxProp {
onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
/**
- * Passed through to use as the draggable area of the dialog.
+ * Content that appears in the draggable header area.
*/
header: React.ReactNode
From 1e41f5e3e9178c27db0f91143f2f5014c3b4a715 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Wed, 10 Jan 2024 16:12:49 +0000
Subject: [PATCH 49/85] Remove overkill isReduced
---
src/Dialog/DialogBottomSheet.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Dialog/DialogBottomSheet.tsx b/src/Dialog/DialogBottomSheet.tsx
index d2635f1d2c6..f16a4c16007 100644
--- a/src/Dialog/DialogBottomSheet.tsx
+++ b/src/Dialog/DialogBottomSheet.tsx
@@ -102,7 +102,6 @@ export default React.forwardRef
Date: Wed, 10 Jan 2024 21:04:26 +0000
Subject: [PATCH 50/85] Auto focus on close button if no buttons
---
src/Dialog/Dialog.tsx | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 281c911e725..a0034a230ff 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -158,6 +158,11 @@ export interface DialogHeaderProps extends DialogProps {
* dialog. This ID should be set to the element that renders the dialog's subtitle.
*/
dialogDescriptionId: string
+
+ /**
+ * A reference to the close button DOM node
+ */
+ closeButtonRef?: React.RefObject
}
const heightMap = {
@@ -188,6 +193,7 @@ const DefaultHeader: React.FC> = ({
subtitle,
dialogDescriptionId,
onClose,
+ closeButtonRef,
}) => {
const onCloseClick = useCallback(() => {
onClose('close-button')
@@ -199,7 +205,7 @@ const DefaultHeader: React.FC> = ({
{title ?? 'Dialog'}
{subtitle && {subtitle} }
-
+
)
@@ -237,17 +243,22 @@ const _Dialog = React.forwardRef(null)
+ const closeButtonRef = useRef(null)
for (const footerButton of footerButtons) {
if (footerButton.autoFocus) {
footerButton.ref = autoFocusedFooterButtonRef
}
}
- const defaultedProps = {...props, title, subtitle, role, dialogLabelId, dialogDescriptionId}
+ const defaultedProps = {...props, title, subtitle, role, dialogLabelId, dialogDescriptionId, closeButtonRef}
const responsiveType = useResponsiveValue(type, 'default')
const dialogRef = useRef(null)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
- useFocusTrap({containerRef: dialogRef, restoreFocusOnCleanUp: true, initialFocusRef: autoFocusedFooterButtonRef})
+ useFocusTrap({
+ containerRef: dialogRef,
+ restoreFocusOnCleanUp: true,
+ initialFocusRef: footerButtons.length > 0 ? autoFocusedFooterButtonRef : closeButtonRef,
+ })
useOnEscapePress(
(event: KeyboardEvent) => {
@@ -514,13 +525,14 @@ const DialogCloseButton = styled(Button)`
box-shadow: none;
`
-const CloseButton: React.FC void}>> = ({onClose}) => {
+const CloseButton = React.forwardRef void}>((props, ref) => {
+ const {onClose} = props
return (
-
+
)
-}
+})
/**
* A dialog is a type of overlay that can be used for confirming actions, asking
From 932e26c26befd0690aaeeb726129f21388224ba1 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Thu, 11 Jan 2024 10:47:47 +0000
Subject: [PATCH 51/85] Remove gestures
---
src/Dialog/Dialog.tsx | 4 ++--
src/Dialog/DialogBottomSheet.tsx | 18 ++++++++----------
2 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index a0034a230ff..e141e6852cc 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -108,7 +108,7 @@ export interface DialogProps extends SxProp {
* used to close the dialog.
*/
- onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
+ onClose: (gesture: 'close-button' | 'escape') => void
/**
* Default: "dialog". The ARIA role to assign to this dialog.
@@ -315,7 +315,7 @@ const _Dialog = React.forwardRef onClose('close-button')}
ariaLabelledby={dialogLabelId}
ariaDescribedby={dialogDescriptionId}
ariaModal
diff --git a/src/Dialog/DialogBottomSheet.tsx b/src/Dialog/DialogBottomSheet.tsx
index f16a4c16007..daf5c418642 100644
--- a/src/Dialog/DialogBottomSheet.tsx
+++ b/src/Dialog/DialogBottomSheet.tsx
@@ -16,7 +16,7 @@ export interface DialogBottomSheetProps extends SxProp {
/**
* This method is invoked when a gesture to close the dialog is used
*/
- onClose: (gesture: 'close-button' | 'escape' | 'drag' | 'overlay') => void
+ onClose: () => void
/**
* Content that appears in the draggable header area.
@@ -53,9 +53,7 @@ export default React.forwardRef(false)
const [snappedHeight, setSnappedHeight] = useState(HALF_HEIGHT)
- const [fireDelayedOnClose, setFireDelayedOnClose] = useState<
- 'close-button' | 'escape' | 'drag' | 'overlay' | undefined
- >()
+ const [fireDelayedOnClose, setFireDelayedOnClose] = useState(false)
// 📎 REFERENCES
@@ -77,12 +75,12 @@ export default React.forwardRef {
+ const hideBottomSheet = async () => {
setIsOpen(false)
if (isReduced) {
- onClose(gesture)
+ onClose()
} else {
- setFireDelayedOnClose(gesture)
+ setFireDelayedOnClose(true)
}
}
@@ -104,7 +102,7 @@ export default React.forwardRef 75) return updateSheetHeight(FULL_HEIGHT)
updateSheetHeight(HALF_HEIGHT)
@@ -161,7 +159,7 @@ export default React.forwardRef {
- onClose(fireDelayedOnClose)
+ onClose()
}, ANIMATION_DURATION)
return () => clearTimeout(timer)
}, [fireDelayedOnClose, onClose])
@@ -182,7 +180,7 @@ export default React.forwardRef
- hideBottomSheet('overlay')}>
+ hideBottomSheet()}>
Date: Thu, 11 Jan 2024 11:48:02 +0000
Subject: [PATCH 52/85] Revert
---
src/ConfirmationDialog/ConfirmationDialog.tsx | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/ConfirmationDialog/ConfirmationDialog.tsx b/src/ConfirmationDialog/ConfirmationDialog.tsx
index c13aee068a2..1477ccb79fa 100644
--- a/src/ConfirmationDialog/ConfirmationDialog.tsx
+++ b/src/ConfirmationDialog/ConfirmationDialog.tsx
@@ -131,11 +131,7 @@ export const ConfirmationDialog: React.FC {
- if (gesture !== 'overlay' && gesture !== 'drag') {
- onClose(gesture)
- }
- }}
+ onClose={onClose}
title={title}
footerButtons={footerButtons}
role="alertdialog"
From 5f9aa8d1ae8e5e1897166a97b1959fe5d80845f5 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Thu, 11 Jan 2024 18:40:38 +0000
Subject: [PATCH 53/85] Fix JEST files
---
.../ConfirmationDialog.test.tsx | 16 ++++++++++++++++
src/DataTable/__tests__/ErrorDialog.test.tsx | 16 ++++++++++++++++
src/Dialog/DialogBottomSheet.tsx | 10 ++--------
src/TreeView/TreeView.test.tsx | 16 ++++++++++++++++
src/__tests__/__snapshots__/exports.test.ts.snap | 1 +
5 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/src/ConfirmationDialog/ConfirmationDialog.test.tsx b/src/ConfirmationDialog/ConfirmationDialog.test.tsx
index 37792d8153c..b334b4044e0 100644
--- a/src/ConfirmationDialog/ConfirmationDialog.test.tsx
+++ b/src/ConfirmationDialog/ConfirmationDialog.test.tsx
@@ -12,6 +12,22 @@ import {ThemeProvider} from '../ThemeProvider'
import {SSRProvider} from '../utils/ssr'
import {behavesAsComponent, checkExports} from '../utils/testing'
+// The Dialog uses `matchMedia` to determine whether or not to reduce motion.
+
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+})
+
const Basic = ({confirmButtonType}: Pick, 'confirmButtonType'>) => {
const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef(null)
diff --git a/src/DataTable/__tests__/ErrorDialog.test.tsx b/src/DataTable/__tests__/ErrorDialog.test.tsx
index f15164e4748..cb0fe0ab1da 100644
--- a/src/DataTable/__tests__/ErrorDialog.test.tsx
+++ b/src/DataTable/__tests__/ErrorDialog.test.tsx
@@ -3,6 +3,22 @@ import {render, screen} from '@testing-library/react'
import React from 'react'
import {ErrorDialog} from '../ErrorDialog'
+// The ErrorDialog uses `matchMedia` because the ConfirmationDialog uses it to determine whether or not to reduce motion.
+
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+})
+
describe('Table.ErrorDialog', () => {
it('should use a default title of "Error" if `title` is not provided', () => {
render( )
diff --git a/src/Dialog/DialogBottomSheet.tsx b/src/Dialog/DialogBottomSheet.tsx
index daf5c418642..e968a5c3f4d 100644
--- a/src/Dialog/DialogBottomSheet.tsx
+++ b/src/Dialog/DialogBottomSheet.tsx
@@ -4,6 +4,7 @@ import {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import Box from '../Box'
import {get} from '../constants'
+import {useMedia} from '../hooks/useMedia'
const ANIMATION_DURATION = 300
const FULL_HEIGHT = 95
@@ -66,7 +67,7 @@ export default React.forwardRef (props.open ? 'translateY(0%)' : 'translateY(100%)')};
transition: ${ANIMATION_DURATION}ms ease;
`
-
-// 🧑🦽 Accessibility
-
-const prefersReducedMotion = () => {
- const mediaQueryList = window.matchMedia('(prefers-reduced-motion: no-preference)')
- return !mediaQueryList.matches
-}
diff --git a/src/TreeView/TreeView.test.tsx b/src/TreeView/TreeView.test.tsx
index 881d35bfcca..8a07a8923f8 100644
--- a/src/TreeView/TreeView.test.tsx
+++ b/src/TreeView/TreeView.test.tsx
@@ -6,6 +6,22 @@ import {SubTreeState, TreeView} from './TreeView'
jest.useFakeTimers()
+// The TreeView.ErrorDialog uses `matchMedia` because the ConfirmationDialog uses it to determine whether or not to reduce motion.
+
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+})
+
// TODO: Move this function into a shared location
function renderWithTheme(
ui: Parameters[0],
diff --git a/src/__tests__/__snapshots__/exports.test.ts.snap b/src/__tests__/__snapshots__/exports.test.ts.snap
index e3584fc6957..e988f7d783a 100644
--- a/src/__tests__/__snapshots__/exports.test.ts.snap
+++ b/src/__tests__/__snapshots__/exports.test.ts.snap
@@ -254,6 +254,7 @@ exports[`@primer/react/drafts should not update exports without a semver change
"type DialogHeaderProps",
"type DialogHeight",
"type DialogProps",
+ "type DialogType",
"type DialogWidth",
"type Emoji",
"type FileType",
From 8fb0b5ef8b201e8c7441d4dbfb4f49c710fe6b4c Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Thu, 11 Jan 2024 18:40:51 +0000
Subject: [PATCH 54/85] Update exports.test.ts.snap
---
src/__tests__/__snapshots__/exports.test.ts.snap | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/__tests__/__snapshots__/exports.test.ts.snap b/src/__tests__/__snapshots__/exports.test.ts.snap
index e988f7d783a..b0a161dde8e 100644
--- a/src/__tests__/__snapshots__/exports.test.ts.snap
+++ b/src/__tests__/__snapshots__/exports.test.ts.snap
@@ -335,6 +335,7 @@ exports[`@primer/react/experimental should not update exports without a semver c
"type DialogHeaderProps",
"type DialogHeight",
"type DialogProps",
+ "type DialogType",
"type DialogWidth",
"type Emoji",
"type FileType",
From 50a322de7bce81eb33046444b56ec19f32f01a78 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Thu, 11 Jan 2024 18:48:25 +0000
Subject: [PATCH 55/85] Fix isReduced function
---
src/Dialog/DialogBottomSheet.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Dialog/DialogBottomSheet.tsx b/src/Dialog/DialogBottomSheet.tsx
index e968a5c3f4d..9af00ef9772 100644
--- a/src/Dialog/DialogBottomSheet.tsx
+++ b/src/Dialog/DialogBottomSheet.tsx
@@ -67,7 +67,7 @@ export default React.forwardRef
Date: Thu, 11 Jan 2024 19:12:42 +0000
Subject: [PATCH 56/85] Keep dialog open
---
src/Dialog/Dialog.features.stories.tsx | 139 +++++++++++++++++--------
1 file changed, 94 insertions(+), 45 deletions(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index bd34f332cf3..ec1e3035b31 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -1,7 +1,7 @@
import React, {useState, useRef, useCallback} from 'react'
import {Meta} from '@storybook/react'
-import {BaseStyles, ThemeProvider, Box, TextInput} from '..'
+import {BaseStyles, ThemeProvider, Box, TextInput, Truncate} from '..'
import {Button} from '../Button'
import Text from '../Text'
import {Dialog, DialogProps} from './Dialog'
@@ -119,7 +119,7 @@ export const WithCustomRenderers = () => {
}
export const StressTest = () => {
- const [isOpen, setIsOpen] = useState(false)
+ const [isOpen, setIsOpen] = useState(true)
const [secondOpen, setSecondOpen] = useState(false)
const buttonRef = useRef(null)
const onDialogClose = useCallback(() => setIsOpen(false), [])
@@ -128,7 +128,7 @@ export const StressTest = () => {
const manyButtons = new Array(10).fill(undefined).map((_, i) => ({content: `Button ${i}`}))
return (
<>
- setIsOpen(!isOpen)}>
+ setIsOpen(false)}>
Show dialog
{isOpen && (
@@ -155,84 +155,133 @@ export const StressTest = () => {
}
export const NonDeclaritive = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
return (
- {}}>
-
- It's a common scenario, to show a dialog that's just informational and therefore doesn't have
- footers in the button
-
-
+ <>
+ setIsOpen(true)}>Trigger dialog
+ {isOpen && (
+ setIsOpen(false)}>
+
+ It's a common scenario, to show a dialog that's just informational and therefore doesn't have
+ footers in the button
+
+
+ )}
+ >
)
}
export const SizeSmallDialog = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
return (
- {}}>
- {lipsum}
-
+ <>
+ setIsOpen(true)}>Show dialog
+ {isOpen && (
+ setIsOpen(false)}>
+ {lipsum}
+
+ )}
+ >
)
}
export const SizeXLargeDialog = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
return (
- {}}>
- {lipsum}
-
+ <>
+ setIsOpen(true)}>Show dialog
+ {isOpen && (
+ setIsOpen(false)}>
+ {lipsum}
+
+ )}
+ >
)
}
export const SizeLargeDialog = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
return (
- {}}>
- {lipsum}
-
+ <>
+ setIsOpen(true)}>Show dialog
+ {isOpen && (
+ setIsOpen(false)}>
+ {lipsum}
+
+ )}
+ >
)
}
export const Responsive = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
return (
- {}}
- type={{narrow: 'full-screen', regular: 'default', wide: 'default'}}
- footerButtons={[
- {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
- {buttonType: 'primary', content: 'Submit', autoFocus: true},
- ]}
- >
- {lipsum}
-
+ <>
+ setIsOpen(true)}>Show dialog
+ {isOpen && (
+ {}}
+ type={{narrow: 'full-screen', regular: 'default', wide: 'default'}}
+ footerButtons={[
+ {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
+ {buttonType: 'primary', content: 'Submit', autoFocus: true},
+ ]}
+ >
+ {lipsum}
+
+ )}
+ >
)
}
export const FullScreen = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
return (
- {}}
- type="full-screen"
- footerButtons={[
- {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
- {buttonType: 'primary', content: 'Submit', autoFocus: true},
- ]}
- >
- {lipsum}
-
+ <>
+ setIsOpen(true)}>Show dialog
+ {isOpen && (
+ setIsOpen(false)}
+ type="full-screen"
+ footerButtons={[
+ {buttonType: 'normal', content: 'Cancel', onClick: () => {}},
+ {buttonType: 'primary', content: 'Submit', autoFocus: true},
+ ]}
+ >
+ {lipsum}
+
+ )}
+ >
)
}
export const BottomSheet = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
return (
- {}}>
- {lipsum}
-
+ <>
+ setIsOpen(true)}>Show dialog
+ {isOpen && (
+ setIsOpen(false)}>
+ {lipsum}
+
+ )}
+ >
)
}
// repro for https://github.com/github/primer/issues/2480
export const ReproMultistepDialogWithConditionalFooter = () => {
- const [isOpen, setIsOpen] = useState(false)
const onDialogClose = useCallback(() => setIsOpen(false), [])
+ const [isOpen, setIsOpen] = useState(true)
const [step, setStep] = React.useState(1)
const renderFooterConditionally = () => {
@@ -247,7 +296,7 @@ export const ReproMultistepDialogWithConditionalFooter = () => {
return (
<>
- setIsOpen(!isOpen)}>Show dialog
+ setIsOpen(true)}>Show dialog
{isOpen && (
Date: Thu, 11 Jan 2024 19:38:02 +0000
Subject: [PATCH 57/85] Remove Truncate
---
src/Dialog/Dialog.features.stories.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index ec1e3035b31..b2e7bacd292 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -1,7 +1,7 @@
import React, {useState, useRef, useCallback} from 'react'
import {Meta} from '@storybook/react'
-import {BaseStyles, ThemeProvider, Box, TextInput, Truncate} from '..'
+import {BaseStyles, ThemeProvider, Box, TextInput} from '..'
import {Button} from '../Button'
import Text from '../Text'
import {Dialog, DialogProps} from './Dialog'
From cf4e9831c8b539efe6895f511f290e7e55a531bd Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 12 Jan 2024 21:08:58 +0000
Subject: [PATCH 58/85] Fix test
---
e2e/components/Dialog.test.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/e2e/components/Dialog.test.ts b/e2e/components/Dialog.test.ts
index 82255b03545..97f0566a123 100644
--- a/e2e/components/Dialog.test.ts
+++ b/e2e/components/Dialog.test.ts
@@ -43,7 +43,6 @@ test.describe('Dialog', () => {
},
})
- await page.getByRole('button', {name: 'Show dialog'}).click()
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(`Dialog.Stress Test.${theme}.png`)
})
@@ -71,7 +70,6 @@ test.describe('Dialog', () => {
},
})
- await page.getByRole('button', {name: 'Show dialog'}).click()
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
`Dialog.With Custom Renderers.${theme}.png`,
)
From 0e31960c2a863239ddb4de01ae129a791aaa41da Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 12 Jan 2024 21:32:11 +0000
Subject: [PATCH 59/85] Fix close button in custom UI
---
src/Dialog/Dialog.tsx | 27 ++++++++++++++++++---------
1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index e141e6852cc..5db813129bc 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -205,7 +205,17 @@ const DefaultHeader: React.FC> = ({
{title ?? 'Dialog'}
{subtitle && {subtitle} }
-
+
)
@@ -515,20 +525,19 @@ const DialogCloseButton = styled(Button)`
vertical-align: middle;
color: ${get('colors.fg.muted')};
padding: ${get('space.2')};
- z-index: 2;
- right: 0;
- margin-right: ${get('space.2')};
- position: absolute;
align-self: flex-start;
- pointer-events: auto ${/* Allows clicks to pass through the header in bottom sheets */ ''};
line-height: normal;
box-shadow: none;
`
-const CloseButton = React.forwardRef void}>((props, ref) => {
- const {onClose} = props
+export interface CloseButtonProps extends SxProp {
+ onClose: () => void
+}
+
+const CloseButton = React.forwardRef((props, ref) => {
+ const {onClose, ...rest} = props
return (
-
+
)
From 910452d9b40b4bc60b9a457a3f394ee321729872 Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 12 Jan 2024 21:40:42 +0000
Subject: [PATCH 60/85] Update Dialog.tsx
---
src/Dialog/Dialog.tsx | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index 5db813129bc..bb161d09023 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -205,9 +205,7 @@ const DefaultHeader: React.FC> = ({
{title ?? 'Dialog'}
{subtitle && {subtitle} }
- > = ({
position: 'absolute',
pointerEvents: 'auto', // Allows clicks to pass through the header in bottom sheets
}}
- />
+ >
+
+
)
@@ -535,9 +535,8 @@ export interface CloseButtonProps extends SxProp {
}
const CloseButton = React.forwardRef((props, ref) => {
- const {onClose, ...rest} = props
return (
-
+
)
From 7676e66654655823672aa7af662d293d8b98570a Mon Sep 17 00:00:00 2001
From: Maxime De Greve
Date: Fri, 12 Jan 2024 22:00:06 +0000
Subject: [PATCH 61/85] Fix test
---
e2e/components/Dialog.test.ts | 1 -
src/Dialog/Dialog.features.stories.tsx | 37 +++++++++++++++-----------
src/Dialog/Dialog.tsx | 2 +-
3 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/e2e/components/Dialog.test.ts b/e2e/components/Dialog.test.ts
index 97f0566a123..3dc6c2d327f 100644
--- a/e2e/components/Dialog.test.ts
+++ b/e2e/components/Dialog.test.ts
@@ -42,7 +42,6 @@ test.describe('Dialog', () => {
colorScheme: theme,
},
})
-
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(`Dialog.Stress Test.${theme}.png`)
})
diff --git a/src/Dialog/Dialog.features.stories.tsx b/src/Dialog/Dialog.features.stories.tsx
index b2e7bacd292..49cfa3c9a37 100644
--- a/src/Dialog/Dialog.features.stories.tsx
+++ b/src/Dialog/Dialog.features.stories.tsx
@@ -70,6 +70,8 @@ const lipsum = (
)
export const WithCustomRenderers = () => {
+ const [isOpen, setIsOpen] = useState(true)
+
const CustomHeader = ({
title,
subtitle,
@@ -101,20 +103,25 @@ export const WithCustomRenderers = () => {
)
return (
- {}}
- footerButtons={[
- {buttonType: 'danger', content: 'Delete the universe'},
- {buttonType: 'primary', content: 'Proceed'},
- ]}
- >
- {lipsum}
-
+ <>
+ setIsOpen(true)}>Show dialog
+ {isOpen && (
+ setIsOpen(false)}
+ footerButtons={[
+ {buttonType: 'danger', content: 'Delete the universe'},
+ {buttonType: 'primary', content: 'Proceed'},
+ ]}
+ >
+ {lipsum}
+
+ )}
+ >
)
}
@@ -128,7 +135,7 @@ export const StressTest = () => {
const manyButtons = new Array(10).fill(undefined).map((_, i) => ({content: `Button ${i}`}))
return (
<>
- setIsOpen(false)}>
+ setIsOpen(true)}>
Show dialog
{isOpen && (
diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx
index bb161d09023..3fae4c2671d 100644
--- a/src/Dialog/Dialog.tsx
+++ b/src/Dialog/Dialog.tsx
@@ -209,7 +209,7 @@ const DefaultHeader: React.FC> = ({
sx={{
zIndex: 2,
right: 0,
- mr: get('space.2'),
+ pr: get('space.2'),
position: 'absolute',
pointerEvents: 'auto', // Allows clicks to pass through the header in bottom sheets
}}
From 1414c49a9c3ad5e6ca6fcd0b55b985c22b687df1 Mon Sep 17 00:00:00 2001
From: maximedegreve
Date: Fri, 12 Jan 2024 22:16:45 +0000
Subject: [PATCH 62/85] test(vrt): update snapshots
---
...alog-Stress-Test-dark-colorblind-linux.png | Bin 166883 -> 166968 bytes
.../Dialog-Stress-Test-dark-dimmed-linux.png | Bin 171386 -> 171225 bytes
...g-Stress-Test-dark-high-contrast-linux.png | Bin 165626 -> 165866 bytes
.../Dialog-Stress-Test-dark-linux.png | Bin 163451 -> 163471 bytes
...alog-Stress-Test-dark-tritanopia-linux.png | Bin 166854 -> 166934 bytes
...log-Stress-Test-light-colorblind-linux.png | Bin 164217 -> 164085 bytes
...-Stress-Test-light-high-contrast-linux.png | Bin 164997 -> 164889 bytes
.../Dialog-Stress-Test-light-linux.png | Bin 164174 -> 164018 bytes
...log-Stress-Test-light-tritanopia-linux.png | Bin 164175 -> 164042 bytes
...Custom-Renderers-dark-colorblind-linux.png | Bin 141732 -> 142033 bytes
...ith-Custom-Renderers-dark-dimmed-linux.png | Bin 145679 -> 145949 bytes
...tom-Renderers-dark-high-contrast-linux.png | Bin 141990 -> 142265 bytes
...ialog-With-Custom-Renderers-dark-linux.png | Bin 140362 -> 140593 bytes
...Custom-Renderers-dark-tritanopia-linux.png | Bin 141486 -> 141774 bytes
...ustom-Renderers-light-colorblind-linux.png | Bin 140373 -> 140601 bytes
...om-Renderers-light-high-contrast-linux.png | Bin 140204 -> 140401 bytes
...alog-With-Custom-Renderers-light-linux.png | Bin 140329 -> 140552 bytes
...ustom-Renderers-light-tritanopia-linux.png | Bin 140724 -> 141043 bytes
18 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-colorblind-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-colorblind-linux.png
index 304040711a8062668641beaa3cbd8e2b28546ccd..e9054a40acbc6ce25b9c40968377bd67ed961542 100644
GIT binary patch
delta 96858
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