Skip to content
This repository was archived by the owner on Jan 6, 2023. It is now read-only.

Commit cdfbb71

Browse files
committed
feat: UiPopover receives a close button in its flyout by default
1 parent debcd6a commit cdfbb71

File tree

6 files changed

+63
-24
lines changed

6 files changed

+63
-24
lines changed

addon/components/-internals/contextual-container/component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { EmberRunTimer } from '@ember/runloop/types';
22
import Component from '@ember/component';
3-
import EmberObject, { computed, set } from '@ember/object';
3+
import EmberObject, { action, computed, set } from '@ember/object';
44
import { reads, gt } from '@ember/object/computed';
55
import { addObserver } from '@ember/object/observers';
66
import { guidFor } from '@ember/object/internals';
@@ -780,6 +780,7 @@ export default class UiContextualContainer extends Component {
780780
/**
781781
* Yielded action to programmatically close from within the tooltip/flyout/whatever.
782782
*/
783+
@action
783784
protected close() {
784785
this.hide();
785786
}

addon/components/-internals/contextual-container/element/component.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ export default class UiContextualElement extends Component {
100100

101101
public testId?: string;
102102

103+
/**
104+
* A method provided by the parent container to dismiss the positioned element.
105+
*/
106+
public declare readonly close: () => void;
107+
108+
/**
109+
* If true, a floating close button will be rendered at the top of the positioned
110+
* element. This can be handy when using the "click" trigger.
111+
*/
112+
public showCloseButton = false;
113+
103114
/**
104115
* CSS class names for the element.
105116
*/

addon/components/-internals/contextual-container/element/template.hbs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
@htmlTagName={{if this.titleText "section" "div"}}
1414
>
1515
{{#if this.showArrow}}<div class="{{this.arrowClassName}}" data-popper-arrow></div>{{/if}}
16+
{{#if this.showCloseButton}}
17+
<button type="button"
18+
aria-label="Close"
19+
class="close"
20+
onclick={{this.close}}
21+
><span aria-hidden="true">×</span>
22+
</button>
23+
{{/if}}
1624
{{#if this.titleText}}<header class="{{this.titleTextClassName}}">{{this.titleText}}</header>{{/if}}
1725
<div class="{{this.innerClassName}}" style={{this.innerElementStyles}}>
1826
{{yield}}

addon/components/-internals/contextual-container/template.hbs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
popperTarget = this.triggerElement
2323
id = this.overlayElementId
2424
titleText = this.title
25+
close = this.close
2526
testId = this.testId
2627
showContent = this._showOverlay
2728
isHidden = this._isHidden
2829
}}
2930
{{#if (has-block)~}}
30-
{{yield (hash close=(action this.close))}}
31+
{{yield (hash close=this.close)}}
3132
{{~else~}}
3233
{{this.textContent}}
3334
{{~/if}}

addon/components/ui-popover/element/component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export default class UiPopoverContextElement extends UiContextualElement {
1010

1111
ariaRole = 'region';
1212

13+
showCloseButton = true;
14+
1315
@computed('fade', 'actualPlacement', 'showContent')
1416
public get popperClassNames() {
1517
const classNames = ['popover', this.actualPlacement];

tests/integration/components/ui-popover-test.ts

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,61 +8,58 @@ module('Integration | Component | ui-popover', function (hooks) {
88

99
test('it attaches event listeners to its parent element', async function (assert) {
1010
await render(hbs`
11-
<button>
12-
Foo <UiPopover @testId="tip">Hello World</UiPopover>
11+
<button id="toggle">
12+
Foo <UiPopover>Hello World</UiPopover>
1313
</button>
1414
`);
1515

16-
const overlay = find('.popover[data-test-id="tip"]') as Element;
16+
const overlay = find('.popover') as Element;
1717

1818
assert
19-
.dom('button')
19+
.dom('#toggle')
2020
.hasAttribute('aria-controls', overlay.id)
2121
.hasAttribute('aria-expanded', 'false');
22-
assert.dom('.popover[data-test-id="tip"]').isNotVisible();
22+
assert.dom('.popover').isNotVisible();
2323

24-
await click('button');
24+
await click('#toggle');
2525

26-
assert.dom('button').hasAttribute('aria-expanded', 'true');
27-
assert.dom('.popover[data-test-id="tip"]').isVisible().hasText('Hello World');
26+
assert.dom('#toggle').hasAttribute('aria-expanded', 'true');
27+
assert.dom('.popover .popover-content').isVisible().hasText('Hello World');
2828

29-
await click('button');
29+
await click('#toggle');
3030

31-
assert.dom('.popover[data-test-id="tip"]').isNotVisible();
31+
assert.dom('.popover').isNotVisible();
3232
});
3333

3434
test('it is not closed by outside interactions', async function (assert) {
3535
await render(hbs`
3636
<button id="toggle">
37-
Foo <UiPopover @testId="tip">Hello World</UiPopover>
37+
Foo <UiPopover>Hello World</UiPopover>
3838
</button>
3939
4040
<button id="other-button">Click Me</button>
4141
`);
4242

43-
assert.dom('.popover[data-test-id="tip"]').isNotVisible();
43+
assert.dom('.popover').isNotVisible();
4444

4545
await click('#toggle');
4646

47-
assert.dom('.popover[data-test-id="tip"]').isVisible();
47+
assert.dom('.popover').isVisible();
4848

4949
await click('#other-button');
5050

51-
assert.dom('.popover[data-test-id="tip"]').isVisible();
51+
assert.dom('.popover').isVisible();
5252
});
5353

5454
test('it accepts a heading', async function (assert) {
5555
await render(hbs`
5656
<button>
57-
Foo <UiPopover @testId="tip" @title="Popover Title">Hello World</UiPopover>
57+
Foo <UiPopover @title="Popover Title">Hello World</UiPopover>
5858
</button>
5959
`);
6060

61-
assert.dom('.popover[data-test-id="tip"]').hasTagName('section');
62-
assert
63-
.dom('.popover[data-test-id="tip"] .popover-title')
64-
.hasTagName('header')
65-
.hasText('Popover Title');
61+
assert.dom('.popover').hasTagName('section');
62+
assert.dom('.popover .popover-title').hasTagName('header').hasText('Popover Title');
6663
});
6764

6865
test('it manages focus as though it were inline with its trigger', async function (assert) {
@@ -102,7 +99,7 @@ module('Integration | Component | ui-popover', function (hooks) {
10299

103100
await triggerKeyEvent('#trigger', 'keydown', 'Tab');
104101

105-
assert.dom('.popover #username').isFocused();
102+
assert.dom('.popover button[aria-label="Close"]').isFocused();
106103

107104
await focus('.popover #submitLogin');
108105
await triggerKeyEvent('.popover', 'keydown', 'Tab');
@@ -113,9 +110,28 @@ module('Integration | Component | ui-popover', function (hooks) {
113110

114111
assert.dom('.popover #submitLogin').isFocused();
115112

116-
await focus('.popover #username');
113+
await focus('.popover button[aria-label="Close"]');
117114
await triggerKeyEvent('.popover', 'keydown', 'Tab', { shiftKey: true });
118115

119116
assert.dom('#trigger').isFocused();
120117
});
118+
119+
test('it can be closed with its own close button', async function (assert) {
120+
await render(hbs`
121+
<button id="toggle">
122+
Foo <UiPopover>Hello World</UiPopover>
123+
</button>
124+
`);
125+
126+
assert.dom('.popover').isNotVisible();
127+
128+
await click('#toggle');
129+
130+
assert.dom('.popover').isVisible();
131+
132+
await click('.popover button[aria-label="Close"]');
133+
134+
assert.dom('.popover').isNotVisible();
135+
assert.dom('#toggle').isFocused();
136+
});
121137
});

0 commit comments

Comments
 (0)