Skip to content

Commit 6fa6c45

Browse files
authored
Improve Vue dialog (#317)
1 parent d950146 commit 6fa6c45

File tree

7 files changed

+128
-175
lines changed

7 files changed

+128
-175
lines changed

packages/@headlessui-vue/src/components/dialog/README.md

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ This component can be used to render content inside a Dialog/Modal. This contain
1717
6. Marks other elements as `inert` (hides other elements from screen readers)
1818
7. Closes on `escape`
1919
8. Closes on click outside
20-
9. Once the Dialog becomes hidden (e.g.: `md:hidden`) it will also trigger the `onClose`
20+
9. Once the Dialog becomes hidden (e.g.: `md:hidden`) it will also emit the `close` event
2121

2222
### Installation
2323

@@ -33,7 +33,7 @@ yarn add @headlessui/vue
3333

3434
```vue
3535
<template>
36-
<Dialog :open="isOpen" :onClose="setIsOpen">
36+
<Dialog :open="isOpen" @close="setIsOpen">
3737
<DialogOverlay />
3838
3939
<DialogTitle>Deactivate account</DialogTitle>
@@ -75,7 +75,7 @@ export default {
7575

7676
```vue
7777
<template>
78-
<Dialog :open="isOpen" :onClose="setIsOpen">
78+
<Dialog :open="isOpen" @close="setIsOpen">
7979
<DialogOverlay />
8080
8181
<DialogTitle>Deactivate account</DialogTitle>
@@ -113,17 +113,22 @@ export default {
113113

114114
##### Props
115115

116-
| Prop | Type | Default | Description |
117-
| :------------- | :------------------ | :------ | :-------------------------------------------------------------------------------------------------------------------------------- |
118-
| `open` | Boolean | / | Wether the `Dialog` is open or not. |
119-
| `onClose` | Function | / | Called when the `Dialog` should close. For convenience we pass in a `onClose(false)` so that you can use: `:onClose="setIsOpen"`. |
120-
| `initialFocus` | HTMLElement | / | A ref to an element that should receive focus first. |
121-
| `as` | String \| Component | `div` | The element or component the `Dialog` should render as. |
122-
| `static` | Boolean | `false` | Whether the element should ignore the internally managed open/closed state. |
123-
| `unmount` | Boolean | `true` | Whether the element should be unmounted or hidden based on the open/closed state. |
116+
| Prop | Type | Default | Description |
117+
| :------------- | :------------------ | :------ | :-------------------------------------------------------------------------------- |
118+
| `open` | Boolean | / | Wether the `Dialog` is open or not. |
119+
| `initialFocus` | HTMLElement | / | A ref to an element that should receive focus first. |
120+
| `as` | String \| Component | `div` | The element or component the `Dialog` should render as. |
121+
| `static` | Boolean | `false` | Whether the element should ignore the internally managed open/closed state. |
122+
| `unmount` | Boolean | `true` | Whether the element should be unmounted or hidden based on the open/closed state. |
124123

125124
> **note**: `static` and `unmount` can not be used at the same time. You will get a TypeScript error if you try to do it.
126125
126+
##### Events
127+
128+
| Event Name | Description |
129+
| :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |
130+
| `@close` | Called when the `Dialog` should close. For convenience we emit `false` as the event value so that you can use: `@close="setIsOpen"`. |
131+
127132
##### Render prop object
128133

129134
| Prop | Type | Description |
@@ -142,8 +147,8 @@ This can be used to create an overlay for your Dialog component. Clicking on the
142147

143148
##### Render prop object
144149

145-
| Prop | Type | Description |
146-
| :----- | :------ | :------------------------------------- |
150+
| Prop | Type | Description |
151+
| :----- | :------ | :--------------------------------- |
147152
| `open` | Boolean | Whether or not the dialog is open. |
148153

149154
#### DialogTitle
@@ -158,8 +163,8 @@ This is the title for your Dialog. When this is used, it will set the `aria-labe
158163

159164
##### Render prop object
160165

161-
| Prop | Type | Description |
162-
| :----- | :------ | :------------------------------------- |
166+
| Prop | Type | Description |
167+
| :----- | :------ | :--------------------------------- |
163168
| `open` | Boolean | Whether or not the dialog is open. |
164169

165170
#### DialogDescription
@@ -174,6 +179,6 @@ This is the description for your Dialog. When this is used, it will set the `ari
174179

175180
##### Render prop object
176181

177-
| Prop | Type | Description |
178-
| :----- | :------ | :------------------------------------- |
182+
| Prop | Type | Description |
183+
| :----- | :------ | :--------------------------------- |
179184
| `open` | Boolean | Whether or not the dialog is open. |

packages/@headlessui-vue/src/components/dialog/dialog.test.ts

Lines changed: 18 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('Safe guards', () => {
7676
suppressConsoleLogs(async () => {
7777
renderTemplate(
7878
`
79-
<Dialog :open="false" :onClose="() => {}">
79+
<Dialog :open="false" @close="() => {}">
8080
<button>Trigger</button>
8181
<DialogOverlay />
8282
<DialogTitle />
@@ -97,48 +97,16 @@ describe('Safe guards', () => {
9797
describe('Rendering', () => {
9898
describe('Dialog', () => {
9999
it(
100-
'should complain when the `open` and `onClose` prop are missing',
100+
'should complain when an `open` prop is missing',
101101
suppressConsoleLogs(async () => {
102102
expect(() =>
103103
renderTemplate(
104104
`
105-
<Dialog as="div" />
105+
<Dialog as="div" @close="() => {}" />
106106
`
107107
)
108108
).toThrowErrorMatchingInlineSnapshot(
109-
`"You have to provide an \`open\` and an \`onClose\` prop to the \`Dialog\` component."`
110-
)
111-
expect.hasAssertions()
112-
})
113-
)
114-
115-
it(
116-
'should complain when an `open` prop is provided without an `onClose` prop',
117-
suppressConsoleLogs(async () => {
118-
expect(() =>
119-
renderTemplate(
120-
`
121-
<Dialog as="div" :open="false" />
122-
`
123-
)
124-
).toThrowErrorMatchingInlineSnapshot(
125-
`"You provided an \`open\` prop to the \`Dialog\`, but forgot an \`onClose\` prop."`
126-
)
127-
expect.hasAssertions()
128-
})
129-
)
130-
131-
it(
132-
'should complain when an `onClose` prop is provided without an `open` prop',
133-
suppressConsoleLogs(async () => {
134-
expect(() =>
135-
renderTemplate(
136-
`
137-
<Dialog as="div" :onClose="() => {}" />
138-
`
139-
)
140-
).toThrowErrorMatchingInlineSnapshot(
141-
`"You provided an \`onClose\` prop to the \`Dialog\`, but forgot an \`open\` prop."`
109+
`"You forgot to provide an \`open\` prop to the \`Dialog\`."`
142110
)
143111
expect.hasAssertions()
144112
})
@@ -150,7 +118,7 @@ describe('Rendering', () => {
150118
expect(() =>
151119
renderTemplate(
152120
`
153-
<Dialog as="div" :open="null" :onClose="() => {}" />
121+
<Dialog as="div" :open="null" @close="() => {}" />
154122
`
155123
)
156124
).toThrowErrorMatchingInlineSnapshot(
@@ -160,22 +128,6 @@ describe('Rendering', () => {
160128
})
161129
)
162130

163-
it(
164-
'should complain when an `onClose` prop is not a function',
165-
suppressConsoleLogs(async () => {
166-
expect(() =>
167-
renderTemplate(
168-
`
169-
<Dialog as="div" :open="false" :onClose="null" />
170-
`
171-
)
172-
).toThrowErrorMatchingInlineSnapshot(
173-
`"You provided an \`onClose\` prop to the \`Dialog\`, but the value is not a function. Received: null"`
174-
)
175-
expect.hasAssertions()
176-
})
177-
)
178-
179131
it(
180132
'should be possible to render a Dialog using a render prop',
181133
suppressConsoleLogs(async () => {
@@ -185,7 +137,7 @@ describe('Rendering', () => {
185137
<button id="trigger" @click="setIsOpen(true)">
186138
Trigger
187139
</button>
188-
<Dialog :open="isOpen" :onClose="setIsOpen" v-slot="data">
140+
<Dialog :open="isOpen" @close="setIsOpen" v-slot="data">
189141
<pre>{{JSON.stringify(data)}}</pre>
190142
<TabSentinel />
191143
</Dialog>
@@ -217,7 +169,7 @@ describe('Rendering', () => {
217169
<button id="trigger" @click="setIsOpen(true)">
218170
Trigger
219171
</button>
220-
<Dialog :open="isOpen" :onClose="setIsOpen" class="relative bg-blue-500">
172+
<Dialog :open="isOpen" @close="setIsOpen" class="relative bg-blue-500">
221173
<TabSentinel />
222174
</Dialog>
223175
</div>
@@ -246,7 +198,7 @@ describe('Rendering', () => {
246198
template: `
247199
<div>
248200
<button>Trigger</button>
249-
<Dialog :open="true" :onClose="() => {}" static>
201+
<Dialog :open="true" @close="() => {}" static>
250202
<p>Contents</p>
251203
<TabSentinel @focus="focusCounter" />
252204
</Dialog>
@@ -270,7 +222,7 @@ describe('Rendering', () => {
270222
template: `
271223
<div>
272224
<button>Trigger</button>
273-
<Dialog :open="false" :onClose="() => {}" static>
225+
<Dialog :open="false" @close="() => {}" static>
274226
<p>Contents</p>
275227
<TabSentinel @focus="focusCounter" />
276228
</Dialog>
@@ -294,7 +246,7 @@ describe('Rendering', () => {
294246
template: `
295247
<div>
296248
<button id="trigger" @click="isOpen = !isOpen">Trigger</button>
297-
<Dialog :open="isOpen" :onClose="setIsOpen" :unmount="false">
249+
<Dialog :open="isOpen" @close="setIsOpen" :unmount="false">
298250
<TabSentinel @focus="focusCounter" />
299251
</Dialog>
300252
</div>
@@ -340,7 +292,7 @@ describe('Rendering', () => {
340292
Trigger
341293
</button>
342294
343-
<Dialog :open="isOpen" :onClose="setIsOpen">
295+
<Dialog :open="isOpen" @close="setIsOpen">
344296
<input id="a" type="text" />
345297
<input id="b" type="text" />
346298
<input id="c" type="text" />
@@ -385,7 +337,7 @@ describe('Rendering', () => {
385337
<button id="trigger" @click="toggleOpen">
386338
Trigger
387339
</button>
388-
<Dialog :open="isOpen" :onClose="setIsOpen">
340+
<Dialog :open="isOpen" @close="setIsOpen">
389341
<DialogOverlay v-slot="data">{{JSON.stringify(data)}}</DialogOverlay>
390342
<TabSentinel />
391343
</Dialog>
@@ -427,7 +379,7 @@ describe('Rendering', () => {
427379
suppressConsoleLogs(async () => {
428380
renderTemplate(
429381
`
430-
<Dialog :open="true" :onClose="() => {}">
382+
<Dialog :open="true" @close="() => {}">
431383
<DialogTitle v-slot="data">{{JSON.stringify(data)}}</DialogTitle>
432384
<TabSentinel />
433385
</Dialog>
@@ -454,7 +406,7 @@ describe('Rendering', () => {
454406
suppressConsoleLogs(async () => {
455407
renderTemplate(
456408
`
457-
<Dialog :open="true" :onClose="() => {}">
409+
<Dialog :open="true" @close="() => {}">
458410
<DialogDescription v-slot="data">{{JSON.stringify(data)}}</DialogDescription>
459411
<TabSentinel />
460412
</Dialog>
@@ -487,7 +439,7 @@ describe('Keyboard interactions', () => {
487439
<button id="trigger" @click="toggleOpen">
488440
Trigger
489441
</button>
490-
<Dialog :open="isOpen" :onClose="setIsOpen">
442+
<Dialog :open="isOpen" @close="setIsOpen">
491443
Contents
492444
<TabSentinel />
493445
</Dialog>
@@ -538,7 +490,7 @@ describe('Mouse interactions', () => {
538490
<button id="trigger" @click="toggleOpen">
539491
Trigger
540492
</button>
541-
<Dialog :open="isOpen" :onClose="setIsOpen">
493+
<Dialog :open="isOpen" @close="setIsOpen">
542494
<DialogOverlay />
543495
Contents
544496
<TabSentinel />
@@ -580,7 +532,7 @@ describe('Mouse interactions', () => {
580532
template: `
581533
<div>
582534
<button @click="isOpen = !isOpen">Trigger</button>
583-
<Dialog :open="isOpen" :onClose="setIsOpen">
535+
<Dialog :open="isOpen" @close="setIsOpen">
584536
Contents
585537
<TabSentinel />
586538
</Dialog>
@@ -622,7 +574,7 @@ describe('Mouse interactions', () => {
622574
<div>
623575
<button>Hello</button>
624576
<button @click="isOpen = !isOpen">Trigger</button>
625-
<Dialog v-if="true" :open="isOpen" :onClose="setIsOpen">
577+
<Dialog v-if="true" :open="isOpen" @close="setIsOpen">
626578
Contents
627579
<TabSentinel />
628580
</Dialog>

0 commit comments

Comments
 (0)