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

Commit

Permalink
feat: UiPopover receives a close button in its flyout by default
Browse files Browse the repository at this point in the history
  • Loading branch information
mdeanjones committed Dec 29, 2022
1 parent debcd6a commit cdfbb71
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { EmberRunTimer } from '@ember/runloop/types';
import Component from '@ember/component';
import EmberObject, { computed, set } from '@ember/object';
import EmberObject, { action, computed, set } from '@ember/object';
import { reads, gt } from '@ember/object/computed';
import { addObserver } from '@ember/object/observers';
import { guidFor } from '@ember/object/internals';
Expand Down Expand Up @@ -780,6 +780,7 @@ export default class UiContextualContainer extends Component {
/**
* Yielded action to programmatically close from within the tooltip/flyout/whatever.
*/
@action
protected close() {
this.hide();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ export default class UiContextualElement extends Component {

public testId?: string;

/**
* A method provided by the parent container to dismiss the positioned element.
*/
public declare readonly close: () => void;

/**
* If true, a floating close button will be rendered at the top of the positioned
* element. This can be handy when using the "click" trigger.
*/
public showCloseButton = false;

/**
* CSS class names for the element.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
@htmlTagName={{if this.titleText "section" "div"}}
>
{{#if this.showArrow}}<div class="{{this.arrowClassName}}" data-popper-arrow></div>{{/if}}
{{#if this.showCloseButton}}
<button type="button"
aria-label="Close"
class="close"
onclick={{this.close}}
><span aria-hidden="true">×</span>
</button>
{{/if}}
{{#if this.titleText}}<header class="{{this.titleTextClassName}}">{{this.titleText}}</header>{{/if}}
<div class="{{this.innerClassName}}" style={{this.innerElementStyles}}>
{{yield}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
popperTarget = this.triggerElement
id = this.overlayElementId
titleText = this.title
close = this.close
testId = this.testId
showContent = this._showOverlay
isHidden = this._isHidden
}}
{{#if (has-block)~}}
{{yield (hash close=(action this.close))}}
{{yield (hash close=this.close)}}
{{~else~}}
{{this.textContent}}
{{~/if}}
Expand Down
2 changes: 2 additions & 0 deletions addon/components/ui-popover/element/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export default class UiPopoverContextElement extends UiContextualElement {

ariaRole = 'region';

showCloseButton = true;

@computed('fade', 'actualPlacement', 'showContent')
public get popperClassNames() {
const classNames = ['popover', this.actualPlacement];
Expand Down
60 changes: 38 additions & 22 deletions tests/integration/components/ui-popover-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,61 +8,58 @@ module('Integration | Component | ui-popover', function (hooks) {

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

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

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

await click('button');
await click('#toggle');

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

await click('button');
await click('#toggle');

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

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

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

await click('#toggle');

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

await click('#other-button');

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

test('it accepts a heading', async function (assert) {
await render(hbs`
<button>
Foo <UiPopover @testId="tip" @title="Popover Title">Hello World</UiPopover>
Foo <UiPopover @title="Popover Title">Hello World</UiPopover>
</button>
`);

assert.dom('.popover[data-test-id="tip"]').hasTagName('section');
assert
.dom('.popover[data-test-id="tip"] .popover-title')
.hasTagName('header')
.hasText('Popover Title');
assert.dom('.popover').hasTagName('section');
assert.dom('.popover .popover-title').hasTagName('header').hasText('Popover Title');
});

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

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

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

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

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

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

assert.dom('#trigger').isFocused();
});

test('it can be closed with its own close button', async function (assert) {
await render(hbs`
<button id="toggle">
Foo <UiPopover>Hello World</UiPopover>
</button>
`);

assert.dom('.popover').isNotVisible();

await click('#toggle');

assert.dom('.popover').isVisible();

await click('.popover button[aria-label="Close"]');

assert.dom('.popover').isNotVisible();
assert.dom('#toggle').isFocused();
});
});

0 comments on commit cdfbb71

Please sign in to comment.