Skip to content

Commit 3935811

Browse files
authored
feat(Dialog): Convert Dialog v2 to CSS Modules (#5298)
* feat(Dialog): Convert Dialog v2 to CSS Modules * changeset * lint fix * fix backdrop classname * add unit test * lint fix * fix padding * make body polymorphic * fix width height props
1 parent d73f465 commit 3935811

File tree

4 files changed

+561
-176
lines changed

4 files changed

+561
-176
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": minor
3+
---
4+
5+
Convert Dialog v2 to CSS Modules behind feature flag
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
@keyframes dialog-backdrop-appear {
2+
0% {
3+
opacity: 0;
4+
}
5+
6+
100% {
7+
opacity: 1;
8+
}
9+
}
10+
11+
@keyframes Overlay--motion-scaleFade {
12+
0% {
13+
opacity: 0;
14+
transform: scale(0.5);
15+
}
16+
17+
100% {
18+
opacity: 1;
19+
transform: scale(1);
20+
}
21+
}
22+
23+
@keyframes Overlay--motion-slideUp {
24+
from {
25+
transform: translateY(100%);
26+
}
27+
}
28+
29+
@keyframes Overlay--motion-slideInRight {
30+
from {
31+
transform: translateX(-100%);
32+
}
33+
}
34+
35+
@keyframes Overlay--motion-slideInLeft {
36+
from {
37+
transform: translateX(100%);
38+
}
39+
}
40+
41+
.Backdrop {
42+
position: fixed;
43+
top: 0;
44+
right: 0;
45+
bottom: 0;
46+
left: 0;
47+
display: flex;
48+
background-color: var(--overlay-backdrop-bgColor);
49+
animation: dialog-backdrop-appear 200ms cubic-bezier(0.33, 1, 0.68, 1);
50+
align-items: center;
51+
justify-content: center;
52+
53+
&[data-position-regular='center'] {
54+
align-items: center;
55+
justify-content: center;
56+
}
57+
58+
&[data-position-regular='left'] {
59+
align-items: center;
60+
justify-content: flex-start;
61+
}
62+
63+
&[data-position-regular='right'] {
64+
align-items: center;
65+
justify-content: flex-end;
66+
}
67+
68+
.DialogOverflowWrapper {
69+
flex-grow: 1;
70+
}
71+
72+
@media (max-width: 767px) {
73+
&[data-position-narrow='center'] {
74+
align-items: center;
75+
justify-content: center;
76+
}
77+
78+
&[data-position-narrow='bottom'] {
79+
align-items: end;
80+
justify-content: center;
81+
}
82+
}
83+
}
84+
85+
.Dialog {
86+
display: flex;
87+
/* stylelint-disable-next-line primer/responsive-widths */
88+
width: 640px;
89+
min-width: 296px;
90+
max-width: calc(100dvw - 64px);
91+
height: auto;
92+
max-height: calc(100dvh - 64px);
93+
flex-direction: column;
94+
background-color: var(--overlay-bgColor);
95+
border-radius: var(--borderRadius-large);
96+
border-radius: var(--borderRadius-large, var(--borderRadius-large));
97+
box-shadow: var(--shadow-floating-small);
98+
opacity: 1;
99+
100+
&:where([data-width='small']) {
101+
width: 296px;
102+
}
103+
104+
&:where([data-width='medium']) {
105+
width: 320px;
106+
}
107+
108+
&:where([data-width='large']) {
109+
/* stylelint-disable-next-line primer/responsive-widths */
110+
width: 480px;
111+
}
112+
113+
&:where([data-height='small']) {
114+
height: 480px;
115+
}
116+
117+
&:where([data-height='large']) {
118+
height: 640px;
119+
}
120+
121+
@media screen and (prefers-reduced-motion: no-preference) {
122+
animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running;
123+
}
124+
125+
&[data-position-regular='center'] {
126+
border-radius: var(--borderRadius-large, var(--borderRadius-large));
127+
128+
@media screen and (prefers-reduced-motion: no-preference) {
129+
animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running;
130+
}
131+
}
132+
133+
&[data-position-regular='left'] {
134+
height: 100dvh;
135+
max-height: unset;
136+
border-radius: var(--borderRadius-large, var(--borderRadius-large));
137+
border-top-left-radius: 0;
138+
border-bottom-left-radius: 0;
139+
140+
@media screen and (prefers-reduced-motion: no-preference) {
141+
animation: Overlay--motion-slideInRight 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running;
142+
}
143+
}
144+
145+
&[data-position-regular='right'] {
146+
height: 100dvh;
147+
max-height: unset;
148+
border-radius: var(--borderRadius-large, var(--borderRadius-large));
149+
border-top-right-radius: 0;
150+
border-bottom-right-radius: 0;
151+
152+
@media screen and (prefers-reduced-motion: no-preference) {
153+
animation: Overlay--motion-slideInLeft 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running;
154+
}
155+
}
156+
157+
@media (max-width: 767px) {
158+
&[data-position-narrow='center'] {
159+
/* stylelint-disable-next-line primer/responsive-widths */
160+
width: 640px;
161+
height: auto;
162+
border-radius: var(--borderRadius-large, var(--borderRadius-large));
163+
164+
&:where([data-width='small']) {
165+
width: 296px;
166+
}
167+
168+
&:where([data-width='medium']) {
169+
width: 320px;
170+
}
171+
172+
&:where([data-width='large']) {
173+
/* stylelint-disable-next-line primer/responsive-widths */
174+
width: 480px;
175+
}
176+
177+
&:where([data-height='small']) {
178+
height: 480px;
179+
}
180+
181+
&:where([data-height='large']) {
182+
height: 640px;
183+
}
184+
}
185+
186+
&[data-position-narrow='bottom'] {
187+
width: 100dvw;
188+
max-width: 100dvw;
189+
height: auto;
190+
max-height: calc(100dvh - 64px);
191+
border-radius: var(--borderRadius-large, var(--borderRadius-large));
192+
border-bottom-right-radius: 0;
193+
border-bottom-left-radius: 0;
194+
195+
@media screen and (prefers-reduced-motion: no-preference) {
196+
animation: Overlay--motion-slideUp 0.25s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running;
197+
}
198+
}
199+
200+
&[data-position-narrow='fullscreen'] {
201+
width: 100%;
202+
max-width: 100dvw;
203+
height: 100%;
204+
max-height: 100dvh;
205+
border-radius: unset !important;
206+
flex-grow: 1;
207+
208+
@media screen and (prefers-reduced-motion: no-preference) {
209+
animation: Overlay--motion-scaleFade 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running;
210+
}
211+
}
212+
}
213+
}
214+
215+
.Header {
216+
z-index: 1;
217+
padding: var(--base-size-8);
218+
/* stylelint-disable-next-line primer/box-shadow */
219+
box-shadow: 0 1px 0 var(--borderColor-default);
220+
flex-shrink: 0;
221+
}
222+
223+
.Title {
224+
margin: 0; /* override default margin */
225+
font-size: var(--text-body-size-medium);
226+
font-weight: var(--text-title-weight-large);
227+
}
228+
229+
.Subtitle {
230+
margin: 0; /* override default margin */
231+
margin-top: var(--base-size-4);
232+
font-size: var(--text-body-size-small);
233+
font-weight: var(--base-text-weight-normal);
234+
color: var(--fgColor-muted);
235+
}
236+
237+
.Body {
238+
padding: var(--base-size-16);
239+
overflow: auto;
240+
flex-grow: 1;
241+
}
242+
243+
.Footer {
244+
z-index: 1;
245+
display: flex;
246+
padding: var(--base-size-16);
247+
/* stylelint-disable-next-line primer/box-shadow */
248+
box-shadow: 0 -1px 0 var(--borderColor-default);
249+
flex-flow: wrap;
250+
justify-content: flex-end;
251+
gap: var(--base-size-8);
252+
flex-shrink: 0;
253+
}

packages/react/src/Dialog/Dialog.test.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import MatchMediaMock from 'jest-matchmedia-mock'
66
import {behavesAsComponent, checkExports} from '../utils/testing'
77
import axe from 'axe-core'
88
import {Button} from '../Button'
9+
import {FeatureFlags} from '../FeatureFlags'
910

1011
let matchMedia: MatchMediaMock
1112

@@ -226,6 +227,53 @@ describe('Dialog', () => {
226227

227228
expect(getByRole('button', {name: 'return focus to (button 2)'})).toHaveFocus()
228229
})
230+
231+
it('should support `className` on the Dialog element', async () => {
232+
const Fixture = () => {
233+
const [isOpen, setIsOpen] = React.useState(true)
234+
const triggerRef = React.useRef<HTMLButtonElement>(null)
235+
236+
return (
237+
<>
238+
<Button variant="primary" onClick={() => setIsOpen(true)}>
239+
Show dialog
240+
</Button>
241+
{isOpen && (
242+
<Dialog title="title" onClose={() => setIsOpen(false)} returnFocusRef={triggerRef} className="custom-class">
243+
body
244+
</Dialog>
245+
)}
246+
</>
247+
)
248+
}
249+
250+
const FeatureFlagElement = () => {
251+
return (
252+
<FeatureFlags
253+
flags={{
254+
primer_react_css_modules_team: true,
255+
primer_react_css_modules_staff: true,
256+
primer_react_css_modules_ga: true,
257+
}}
258+
>
259+
<Fixture />
260+
</FeatureFlags>
261+
)
262+
}
263+
264+
const user = userEvent.setup()
265+
266+
let component = render(<Fixture />)
267+
let triggerButton = component.getByRole('button', {name: 'Show dialog'})
268+
await user.click(triggerButton)
269+
expect(component.getByRole('dialog')).toHaveClass('custom-class')
270+
component.unmount()
271+
272+
component = render(<FeatureFlagElement />)
273+
triggerButton = component.getByRole('button', {name: 'Show dialog'})
274+
await user.click(triggerButton)
275+
expect(component.getByRole('dialog')).toHaveClass('custom-class')
276+
})
229277
})
230278

231279
it('automatically focuses the element that is specified as initialFocusRef', () => {

0 commit comments

Comments
 (0)