UI/Glimmerize modal & confirmation modal component (#16032)
* remove commented out import from info-table-row * glimmerize * update docs * glimmerize confirmation modal * update modal usage * remove keyboard action * Revert "remove keyboard action" This reverts commit 42b7f5950b244b5a728f94a1fbb8cd836f646ae8. * remove keyboard actions * address comments * update tests
This commit is contained in:
parent
549005e4b7
commit
5cd4fe23b0
|
@ -346,7 +346,7 @@
|
|||
|
||||
<Modal
|
||||
@title="Rotate your root credentials?"
|
||||
@onClose={{action "continueWithoutRotate"}}
|
||||
@onClose={{this.continueWithoutRotate}}
|
||||
@isActive={{this.showSaveModal}}
|
||||
@type="info"
|
||||
@showCloseButton={{false}}
|
||||
|
@ -381,7 +381,6 @@
|
|||
@confirmText={{@model.name}}
|
||||
@toConfirmMsg="deleting the connection"
|
||||
@onConfirm={{action "delete"}}
|
||||
@testSelector="delete"
|
||||
>
|
||||
<p>
|
||||
Deleting the connection means that any associated roles won't be able to generate credentials until the connection is
|
||||
|
|
|
@ -256,7 +256,6 @@
|
|||
@confirmText={{@model.name}}
|
||||
@toConfirmMsg="deleting the key"
|
||||
@onConfirm={{fn this.deleteKey @model.id}}
|
||||
@testSelector="delete"
|
||||
>
|
||||
<p>
|
||||
Destroying the
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
@isActive={{this.isEditModalActive}}
|
||||
@confirmText={{@model.type}}
|
||||
@onConfirm={{perform this.save}}
|
||||
@testSelector="mfa"
|
||||
>
|
||||
<p>
|
||||
Editing this configuration will have an impact on all authentication types, methods, groups and entities which make use
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<Modal
|
||||
@title={{this.title}}
|
||||
@onClose={{this.onClose}}
|
||||
@isActive={{this.isActive}}
|
||||
@type={{this.type}}
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<Modal @title={{@title}} @onClose={{@onClose}} @isActive={{@isActive}} @type={{this.type}} @showCloseButton={{true}}>
|
||||
<section class="modal-card-body">
|
||||
|
||||
{{yield}}
|
||||
|
||||
<div class="modal-confirm-section">
|
||||
<p class="has-text-weight-semibold is-size-6">
|
||||
Confirm
|
||||
|
@ -21,7 +13,7 @@
|
|||
class="input has-margin-top"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-test-confirmation-modal-input={{this.testSelector}}
|
||||
data-test-confirmation-modal-input={{or @title true}}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -30,12 +22,12 @@
|
|||
type="button"
|
||||
class="button {{this.buttonClass}}"
|
||||
disabled={{not-eq this.confirmationInput this.confirmText}}
|
||||
onclick={{this.onConfirm}}
|
||||
data-test-confirm-button={{this.testSelector}}
|
||||
{{on "click" @onConfirm}}
|
||||
data-test-confirm-button={{or @title true}}
|
||||
>
|
||||
{{this.buttonText}}
|
||||
</button>
|
||||
<button type="button" class="button is-secondary" onclick={{action (mut this.isActive) false}} data-test-cancel-button>
|
||||
<button type="button" class="button is-secondary" {{on "click" @onClose}} data-test-cancel-button>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
|
@ -1,3 +1,4 @@
|
|||
import Component from '@glimmer/component';
|
||||
/**
|
||||
* @module ConfirmationModal
|
||||
* ConfirmationModal components are used to provide an alternative to ConfirmationButton that automatically prompts the user to fill in confirmation text before they can continue with a potentially destructive action. It is built off the Modal component
|
||||
|
@ -13,26 +14,34 @@
|
|||
* />
|
||||
* ```
|
||||
* @param {function} onConfirm - onConfirm is the action that happens when user clicks onConfirm after filling in the confirmation block
|
||||
* @param {function} onClose - specify what to do when user attempts to close modal
|
||||
* @param {boolean} isActive - Controls whether the modal is "active" eg. visible or not.
|
||||
* @param {string} title - Title of the modal
|
||||
* @param {function} onClose - specify what to do when user attempts to close modal
|
||||
* @param {string} [buttonText=Confirm] - Button text on the confirm button
|
||||
* @param {string} [confirmText=Yes] - The confirmation text that the user must type before continuing
|
||||
* @param {string} [toConfirmMsg=''] - Finishes the sentence "Type <confirmText> to confirm <toConfirmMsg>", default is an empty string (ex. 'secret deletion')
|
||||
* @param {string} [buttonText=Confirm] - Button text on the confirm button
|
||||
* @param {string} [buttonClass=is-danger] - extra class to add to confirm button (eg. "is-danger")
|
||||
* @param {string} [type=warning] - Applies message-type styling to header. Override to default with empty string
|
||||
* @param {string} [toConfirmMsg] - Finishes the sentence "Type YES to confirm ..."
|
||||
* @param {string} [testSelector] - The unique test selector used on the input to fill in text during tests.
|
||||
* @param {string} [type=warning] - The header styling based on type, passed into the message-types helper (in the Modal component).
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
import layout from '../templates/components/confirmation-modal';
|
||||
export default class ConfirmationModal extends Component {
|
||||
get buttonClass() {
|
||||
return this.args.buttonClass || 'is-danger';
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
buttonClass: 'is-danger',
|
||||
buttonText: 'Confirm',
|
||||
confirmText: 'Yes',
|
||||
type: 'warning',
|
||||
toConfirmMsg: '',
|
||||
testSelector: '',
|
||||
});
|
||||
get buttonText() {
|
||||
return this.args.buttonText || 'Confirm';
|
||||
}
|
||||
|
||||
get confirmText() {
|
||||
return this.args.confirmText || 'Yes';
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.args.type || 'warning';
|
||||
}
|
||||
|
||||
get toConfirmMsg() {
|
||||
return this.args.toConfirmMsg || '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { typeOf } from '@ember/utils';
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
// import layout from '../templates/components/info-table-row';
|
||||
|
||||
/**
|
||||
* @module InfoTableRow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<EmberWormhole @to="modal-wormhole">
|
||||
<div class="{{this.modalClass}} {{if this.isActive 'is-active'}}" aria-modal="true">
|
||||
<div class="modal-background" role="button" onclick={{this.onClose}} data-test-modal-background></div>
|
||||
<div class="{{this.modalClass}} {{if this.isActive 'is-active'}}" aria-modal="true" data-test-modal-div>
|
||||
<div class="modal-background" role="button" {{on "click" @onClose}} data-test-modal-background></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<h2 class="modal-card-title title is-5" data-test-modal-title>
|
||||
|
@ -12,14 +12,14 @@
|
|||
data-test-modal-glyph={{this.glyph.glyph}}
|
||||
/>
|
||||
{{/if}}
|
||||
<span>{{this.title}}</span>
|
||||
<span>{{@title}}</span>
|
||||
</h2>
|
||||
{{#if this.showCloseButton}}
|
||||
<button
|
||||
type="button"
|
||||
class="delete"
|
||||
aria-label="close"
|
||||
onclick={{this.onClose}}
|
||||
{{on "click" @onClose}}
|
||||
data-test-modal-close-button
|
||||
></button>
|
||||
{{/if}}
|
|
@ -1,3 +1,5 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { messageTypes } from 'core/helpers/message-types';
|
||||
/**
|
||||
* @module Modal
|
||||
* Modal components are used to overlay content on top of the page. Has a darkened background,
|
||||
|
@ -5,37 +7,44 @@
|
|||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <Modal @title={'myTitle'} @showCloseButton={true} @onClose={() => {}}/>
|
||||
* <Modal
|
||||
* @title="Export attribution data"
|
||||
* @type="info"
|
||||
* @isActive={{this.showModal}}
|
||||
* @showCloseButton={{true}}
|
||||
* @onClose={{action (mut this.showModal) false}}
|
||||
* >
|
||||
* Whatever content pops up when the modal isActive!
|
||||
* </Modal>
|
||||
* ```
|
||||
* @param {function} onClose - onClose is the action taken when someone clicks the modal background or close button (if shown).
|
||||
* @callback onClose
|
||||
* @param {onClose} onClose - onClose is the action taken when someone clicks the modal background or close button (if shown).
|
||||
* @param {boolean} isActive=false - whether or not modal displays
|
||||
* @param {string} [title] - This text shows up in the header section of the modal.
|
||||
* @param {boolean} [showCloseButton=false] - controls whether the close button in the top right corner shows.
|
||||
* @param {string} type=null - The header type. This comes from the message-types helper.
|
||||
* @param {string} [type=null] - The header styling based on type passed into the message-types helper.
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { messageTypes } from 'core/helpers/message-types';
|
||||
import layout from '../templates/components/modal';
|
||||
export default class ModalComponent extends Component {
|
||||
get isActive() {
|
||||
return this.args.isActive || false;
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
title: null,
|
||||
showCloseButton: false,
|
||||
type: null,
|
||||
glyph: computed('type', function () {
|
||||
const modalType = this.type;
|
||||
if (!modalType) {
|
||||
return;
|
||||
get showCloseButton() {
|
||||
return this.args.showCloseButton || false;
|
||||
}
|
||||
|
||||
get glyph() {
|
||||
if (!this.args.type) {
|
||||
return null;
|
||||
}
|
||||
return messageTypes([this.type]);
|
||||
}),
|
||||
modalClass: computed('type', function () {
|
||||
const modalType = this.type;
|
||||
if (!modalType) {
|
||||
return messageTypes([this.args.type]);
|
||||
}
|
||||
|
||||
get modalClass() {
|
||||
if (!this.args.type) {
|
||||
return 'modal';
|
||||
}
|
||||
return 'modal ' + messageTypes([this.type]).class;
|
||||
}),
|
||||
onClose: () => {},
|
||||
});
|
||||
return 'modal ' + messageTypes([this.args.type]).class;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
@confirmText={{this.model.replicationModeForDisplay}}
|
||||
@toConfirmMsg="demoting this cluster"
|
||||
@onConfirm={{action "onSubmit" "demote" this.model.replicationAttrs.modeForUrl}}
|
||||
@testSelector="demote"
|
||||
>
|
||||
<p class="has-bottom-margin-m">
|
||||
{{#if (and (eq this.replicationMode "dr") (not this.model.performance.replicationDisabled))}}
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
"disable"
|
||||
(if (eq this.model.replicationAttrs.modeForUrl "bootstrapping") this.mode this.model.replicationAttrs.modeForUrl)
|
||||
}}
|
||||
@testSelector="disable"
|
||||
>
|
||||
<p class="has-bottom-margin-m">
|
||||
{{#if (and this.model.replicationAttrs.isPrimary (eq this.model.replicationMode "dr"))}}
|
||||
|
|
|
@ -386,7 +386,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
|||
assert
|
||||
.dom('.modal.is-active .title')
|
||||
.hasText('Delete connection?', 'Modal appears asking to confirm delete action');
|
||||
await fillIn('[data-test-confirmation-modal-input="delete"]', connectionDetails.id);
|
||||
await fillIn('[data-test-confirmation-modal-input="Delete connection?"]', connectionDetails.id);
|
||||
await click('[data-test-confirm-button]');
|
||||
|
||||
assert.equal(currentURL(), `/vault/secrets/${backend}/list`, 'Redirects to connection list page');
|
||||
|
|
|
@ -1,31 +1,41 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import sinon from 'sinon';
|
||||
import { fillIn, render } from '@ember/test-helpers';
|
||||
import { click, fillIn, render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | confirmation-modal', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders with disabled confirmation button until input matches', async function (assert) {
|
||||
let spy = sinon.spy();
|
||||
this.set('onConfirm', spy);
|
||||
|
||||
let confirmAction = sinon.spy();
|
||||
let closeAction = sinon.spy();
|
||||
this.set('onConfirm', confirmAction);
|
||||
this.set('onClose', closeAction);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<ConfirmationModal
|
||||
@isActive={true}
|
||||
@onConfirm={this.onConfirm}
|
||||
@title="Confirmation Modal"
|
||||
@isActive={{true}}
|
||||
@onConfirm={{this.onConfirm}}
|
||||
@onClose={{this.onClose}}
|
||||
@buttonText="Plz Continue"
|
||||
@confirmText="Destructive Thing"
|
||||
@testSelector="demote"
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-confirm-button]').isDisabled();
|
||||
assert.dom('[data-test-modal-div]').hasAttribute('class', 'modal is-highlight is-active');
|
||||
assert.dom('[data-test-confirm-button]').hasText('Plz Continue', 'Confirm button has specified value');
|
||||
assert
|
||||
.dom('[data-test-modal-title]')
|
||||
.hasStyle({ color: 'rgb(160, 125, 2)' }, 'title exists with warning header');
|
||||
await fillIn('[data-test-confirmation-modal-input="Confirmation Modal"]', 'Destructive Thing');
|
||||
assert.dom('[data-test-confirm-button="Confirmation Modal"]').isNotDisabled();
|
||||
|
||||
await fillIn('[data-test-confirmation-modal-input="demote"]', 'Destructive Thing');
|
||||
assert.dom('[data-test-confirm-button="demote"]').isNotDisabled();
|
||||
await click('[data-test-cancel-button]');
|
||||
assert.true(closeAction.called, 'executes passed in onClose function');
|
||||
await click('[data-test-confirm-button]');
|
||||
assert.true(confirmAction.called, 'executes passed in onConfirm function');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,8 +53,8 @@ module('Integration | Component | mfa-method-form', function (hooks) {
|
|||
|
||||
await fillIn('[data-test-input="issuer"]', 'Vault');
|
||||
await click('[data-test-mfa-save]');
|
||||
await fillIn('[data-test-confirmation-modal-input="mfa"]', 'totp');
|
||||
await click('[data-test-confirm-button="mfa"]');
|
||||
await fillIn('[data-test-confirmation-modal-input="Edit totp configuration?"]', 'totp');
|
||||
await click('[data-test-confirm-button="Edit totp configuration?"]');
|
||||
assert.true(this.didSave, 'onSave callback triggered');
|
||||
assert.equal(this.model.issuer, 'Vault', 'Issuer property set on model');
|
||||
});
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | modal', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
const closeAction = sinon.spy();
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set('onClose', closeAction);
|
||||
});
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<Modal></Modal><div id="modal-wormhole"></div>`);
|
||||
await render(
|
||||
hbs`<Modal @isActive={{true}} @onClose={{this.onClose}}></Modal><div id="modal-wormhole"></div>`
|
||||
);
|
||||
|
||||
assert.dom(this.element).hasText('', 'renders without interior content');
|
||||
assert.dom('[data-test-modal-div]').hasAttribute('class', 'modal is-active');
|
||||
assert.dom('[data-test-modal-close-button]').doesNotExist('does not render close modal button');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<Modal @showCloseButton={{true}}>
|
||||
<Modal @isActive={{true}} @showCloseButton={{true}} @onClose={{this.onClose}} >
|
||||
template block text
|
||||
</Modal>
|
||||
<div id="modal-wormhole"></div>
|
||||
|
@ -26,11 +32,13 @@ module('Integration | Component | modal', function (hooks) {
|
|||
assert.dom(this.element).hasText('template block text', 'renders with interior content');
|
||||
assert.dom('[data-test-modal-close-button]').exists({ count: 1 }, 'renders close modal button');
|
||||
assert.dom('[data-test-modal-glyph]').doesNotExist('Glyph is not rendered by default');
|
||||
await click('[data-test-modal-close-button]');
|
||||
assert.true(closeAction.called, 'executes passed in onConfirm function');
|
||||
});
|
||||
|
||||
test('it adds the correct type class', async function (assert) {
|
||||
await render(hbs`
|
||||
<Modal @type="warning">
|
||||
<Modal @type="warning" @onClose={{this.onClose}}>
|
||||
template block text
|
||||
</Modal>
|
||||
<div id="modal-wormhole"></div>
|
||||
|
|
Loading…
Reference in New Issue