diff --git a/changelog/25459.txt b/changelog/25459.txt new file mode 100644 index 0000000000..67787b67a5 --- /dev/null +++ b/changelog/25459.txt @@ -0,0 +1,3 @@ +```release-note:change +ui: flash messages render on right side of page +``` diff --git a/ui/app/components/flash-toast.hbs b/ui/app/components/flash-toast.hbs new file mode 100644 index 0000000000..a25c338e9d --- /dev/null +++ b/ui/app/components/flash-toast.hbs @@ -0,0 +1,11 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +~}} + + + {{this.title}} + +

{{@flash.message}}

+
+
\ No newline at end of file diff --git a/ui/app/components/flash-toast.js b/ui/app/components/flash-toast.js new file mode 100644 index 0000000000..fd971694ce --- /dev/null +++ b/ui/app/components/flash-toast.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { capitalize } from '@ember/string'; +import Component from '@glimmer/component'; + +/** + * FlashToast components are used to translate flash messages into toast notifications. + * Flash object passed should have a `type` and `message` property at minimum. + */ +export default class FlashToastComponent extends Component { + get color() { + switch (this.args.flash.type) { + case 'info': + return 'highlight'; + case 'danger': + return 'critical'; + case 'warning': + case 'success': + return this.args.flash.type; + default: + return 'neutral'; + } + } + + get title() { + if (this.args.title) return this.args.title; + switch (this.args.flash.type) { + case 'danger': + return 'Error'; + default: + return capitalize(this.args.flash.type); + } + } +} diff --git a/ui/app/services/flash-messages.ts b/ui/app/services/flash-messages.js similarity index 100% rename from ui/app/services/flash-messages.ts rename to ui/app/services/flash-messages.js diff --git a/ui/app/styles/components/global-flash.scss b/ui/app/styles/components/global-flash.scss index c1dc06117a..933ebe61a1 100644 --- a/ui/app/styles/components/global-flash.scss +++ b/ui/app/styles/components/global-flash.scss @@ -4,10 +4,10 @@ */ .global-flash { - bottom: 0; - left: $spacing-12; + bottom: $spacing-32; + right: $spacing-24; margin: 10px; - max-width: $drawer-width; + max-width: 360px; position: fixed; width: 95%; z-index: 300; diff --git a/ui/app/templates/vault/cluster.hbs b/ui/app/templates/vault/cluster.hbs index f0ee1b39c5..a2391b0ad9 100644 --- a/ui/app/templates/vault/cluster.hbs +++ b/ui/app/templates/vault/cluster.hbs @@ -75,24 +75,7 @@
{{#each this.flashMessages.queue as |flash|}} - {{#if flash.componentName}} - {{component flash.componentName content=flash.content}} - {{else}} - {{#let (hash info="highlight" success="success" danger="critical" warning="warning") as |color|}} - - {{#let (hash info="Info" success="Success" danger="Error" warning="Warning") as |title|}} - {{get title flash.type}} - {{/let}} - - {{#if flash.preformatted}} -

{{flash.message}}

- {{else}} - {{flash.message}} - {{/if}} -
-
- {{/let}} - {{/if}} +
{{/each}}
diff --git a/ui/lib/open-api-explorer/addon/routes/index.js b/ui/lib/open-api-explorer/addon/routes/index.js index 08c5ea970e..57408807b9 100644 --- a/ui/lib/open-api-explorer/addon/routes/index.js +++ b/ui/lib/open-api-explorer/addon/routes/index.js @@ -17,7 +17,6 @@ IF YOUR TOKEN HAS THE PROPER CAPABILITIES, THIS WILL CREATE AND DELETE ITEMS ON Your token will also be shown on the screen in the example curl command output.`; this.flashMessages.warning(warning, { sticky: true, - preformatted: true, }); } } diff --git a/ui/tests/integration/components/flash-toast-test.js b/ui/tests/integration/components/flash-toast-test.js new file mode 100644 index 0000000000..ee648f7d3f --- /dev/null +++ b/ui/tests/integration/components/flash-toast-test.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'vault/tests/helpers'; +import { click, find, render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import sinon from 'sinon'; + +module('Integration | Component | flash-toast', function (hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function () { + this.flash = { + type: 'info', + message: 'The bare minimum flash message', + }; + this.closeSpy = sinon.spy(); + }); + + test('it renders', async function (assert) { + await render(hbs``); + + assert.dom('[data-test-flash-message-body]').hasText('The bare minimum flash message'); + assert.dom('[data-test-flash-toast]').hasClass('hds-alert--color-highlight'); + await click('button'); + assert.ok(this.closeSpy.calledOnce, 'close action was called'); + }); + + [ + { type: 'info', title: 'Info', color: 'hds-alert--color-highlight' }, + { type: 'success', title: 'Success', color: 'hds-alert--color-success' }, + { type: 'warning', title: 'Warning', color: 'hds-alert--color-warning' }, + { type: 'danger', title: 'Error', color: 'hds-alert--color-critical' }, + { type: 'foobar', title: 'Foobar', color: 'hds-alert--color-neutral' }, + ].forEach(({ type, title, color }) => { + test(`it has correct title and color for type: ${type}`, async function (assert) { + this.flash.type = type; + await render(hbs``); + + assert.dom('[data-test-flash-toast-title]').hasText(title, 'title is correct'); + assert.dom('[data-test-flash-toast]').hasClass(color, 'color is correct'); + }); + }); + + test('it renders messages with whitespaces correctly', async function (assert) { + this.flash.message = `multi- + +line msg`; + + await render(hbs``); + const dom = find('[data-test-flash-message-body]'); + const lineHeight = 20; + assert.true(dom.clientHeight > lineHeight, 'renders message on multiple lines'); + }); +});