mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 19:47:54 +00:00
UI: Create enable input component (#24427)
* enable input component * add more stars * update css comments * Update ui/app/styles/helper-classes/flexbox-and-grid.scss * make attrOptions optional * add subtext to textfile * add docLink arg to form field textfile * update form field test * add test * add comment * update jsdoc * remove unused class * Update ui/tests/integration/components/enable-input-test.js Co-authored-by: Jordan Reimer <zofskeez@gmail.com> --------- Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
This commit is contained in:
@@ -6,6 +6,32 @@
|
|||||||
/* Helpers that define anything with the CSS flexbox or CSS grid. */
|
/* Helpers that define anything with the CSS flexbox or CSS grid. */
|
||||||
|
|
||||||
/* Flexbox helpers */
|
/* Flexbox helpers */
|
||||||
|
|
||||||
|
// FLEX CONTAINER (child helpers at end of file)
|
||||||
|
// new flex classes, these do not use !important
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
// direction
|
||||||
|
&.row-wrap {
|
||||||
|
flex-flow: row wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// alignment
|
||||||
|
&.space-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.row-gap-8 {
|
||||||
|
row-gap: $spacing-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.column-gap-16 {
|
||||||
|
column-gap: $spacing-16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid !important flex classes below
|
||||||
.is-flex {
|
.is-flex {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
@@ -104,28 +130,6 @@
|
|||||||
flex: 50%;
|
flex: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// moving away from !important, fresh flex styles below
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
// direction
|
|
||||||
&.row-wrap {
|
|
||||||
flex-flow: row wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// alignment
|
|
||||||
&.space-between {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.row-gap-8 {
|
|
||||||
row-gap: $spacing-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.column-gap-16 {
|
|
||||||
column-gap: $spacing-16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Flex Responsive */
|
/* Flex Responsive */
|
||||||
@media screen and (min-width: 769px), print {
|
@media screen and (min-width: 769px), print {
|
||||||
.is-flex-v-centered-tablet {
|
.is-flex-v-centered-tablet {
|
||||||
@@ -163,10 +167,6 @@
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-self-center {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-medium-height {
|
.is-medium-height {
|
||||||
height: 125px;
|
height: 125px;
|
||||||
}
|
}
|
||||||
@@ -178,3 +178,13 @@
|
|||||||
.grid-align-items-start {
|
.grid-align-items-start {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CHILD ELEMENT HELPERS
|
||||||
|
|
||||||
|
.align-self-center {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-self-end {
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
|||||||
27
ui/lib/core/addon/components/enable-input.hbs
Normal file
27
ui/lib/core/addon/components/enable-input.hbs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{{!
|
||||||
|
Copyright (c) HashiCorp, Inc.
|
||||||
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
|
~}}
|
||||||
|
|
||||||
|
{{#if this.enable}}
|
||||||
|
{{yield}}
|
||||||
|
{{else}}
|
||||||
|
<div class="flex" ...attributes>
|
||||||
|
<div class="is-flex-grow-1">
|
||||||
|
{{#if @attr}}
|
||||||
|
<ReadonlyFormField @attr={{@attr}} @value="**********" />
|
||||||
|
{{else}}
|
||||||
|
<Input disabled class="input" @type="text" @value="**********" />
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="align-self-end">
|
||||||
|
<Hds::Button
|
||||||
|
@text="Enable input"
|
||||||
|
@icon="edit"
|
||||||
|
@isIconOnly={{true}}
|
||||||
|
@color="tertiary"
|
||||||
|
{{on "click" (fn (mut this.enable))}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
43
ui/lib/core/addon/components/enable-input.ts
Normal file
43
ui/lib/core/addon/components/enable-input.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
|
interface Args {
|
||||||
|
attr?: AttrData;
|
||||||
|
}
|
||||||
|
interface AttrData {
|
||||||
|
name: string; // required if @attr is passed
|
||||||
|
options?: {
|
||||||
|
label?: string;
|
||||||
|
helpText?: string;
|
||||||
|
subText?: string;
|
||||||
|
possibleValues?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module EnableInput
|
||||||
|
* EnableInput components render a disabled input with a hardcoded masked value beside an "Edit" button to "enable" the input.
|
||||||
|
* Clicking "Edit" hides the disabled input and renders the yielded component. This way any data management is handled by the parent.
|
||||||
|
* These are useful for editing inputs of sensitive values not returned by the API. The extra click ensures the user is intentionally editing the field.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
<EnableInput class="field" @attr={{attr}}>
|
||||||
|
<FormField @attr={{attr}} @model={{@destination}} @modelValidations={{this.modelValidations}} />
|
||||||
|
</EnableInput>
|
||||||
|
|
||||||
|
// without passing @attr
|
||||||
|
<EnableInput>
|
||||||
|
<Input @type="text" />
|
||||||
|
</EnableInput>
|
||||||
|
|
||||||
|
* @param {object} [attr] - used to generate label for `ReadonlyFormField`, `name` key is required. Can be an attribute from a model exported with expandAttributeMeta.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class EnableInputComponent extends Component<Args> {
|
||||||
|
@tracked enable = false;
|
||||||
|
}
|
||||||
@@ -104,7 +104,9 @@
|
|||||||
<div class="has-bottom-margin-m">
|
<div class="has-bottom-margin-m">
|
||||||
<TextFile
|
<TextFile
|
||||||
@label={{this.labelString}}
|
@label={{this.labelString}}
|
||||||
|
@subText={{@attr.options.subText}}
|
||||||
@helpText={{@attr.options.helpText}}
|
@helpText={{@attr.options.helpText}}
|
||||||
|
@docLink={{@attr.options.docLink}}
|
||||||
@onChange={{this.setFile}}
|
@onChange={{this.setFile}}
|
||||||
@validationError={{this.validationError}}
|
@validationError={{this.validationError}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -42,7 +42,19 @@
|
|||||||
data-test-text-file-textarea
|
data-test-text-file-textarea
|
||||||
as |F|
|
as |F|
|
||||||
>
|
>
|
||||||
<F.HelperText>Enter the value as text </F.HelperText>
|
<F.HelperText>
|
||||||
|
Enter the value as text.
|
||||||
|
{{#if @subText}}
|
||||||
|
{{@subText}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if @docLink}}
|
||||||
|
See our
|
||||||
|
<Hds::Link::Inline @href={{doc-link @docLink}} @icon="docs-link" @iconPosition="trailing">
|
||||||
|
documentation
|
||||||
|
</Hds::Link::Inline>
|
||||||
|
for help.
|
||||||
|
{{/if}}
|
||||||
|
</F.HelperText>
|
||||||
</Hds::Form::MaskedInput::Field>
|
</Hds::Form::MaskedInput::Field>
|
||||||
{{else}}
|
{{else}}
|
||||||
<Hds::Form::FileInput::Field
|
<Hds::Form::FileInput::Field
|
||||||
@@ -51,7 +63,19 @@
|
|||||||
data-test-text-file-input
|
data-test-text-file-input
|
||||||
as |F|
|
as |F|
|
||||||
>
|
>
|
||||||
<F.HelperText>Select a file from your computer</F.HelperText>
|
<F.HelperText>
|
||||||
|
Select a file from your computer.
|
||||||
|
{{#if @subText}}
|
||||||
|
{{@subText}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if @docLink}}
|
||||||
|
See our
|
||||||
|
<Hds::Link::Inline @href={{doc-link @docLink}} @icon="docs-link" @iconPosition="trailing">
|
||||||
|
documentation
|
||||||
|
</Hds::Link::Inline>
|
||||||
|
for help.
|
||||||
|
{{/if}}
|
||||||
|
</F.HelperText>
|
||||||
</Hds::Form::FileInput::Field>
|
</Hds::Form::FileInput::Field>
|
||||||
{{#if (or @validationError this.uploadError)}}
|
{{#if (or @validationError this.uploadError)}}
|
||||||
<AlertInline
|
<AlertInline
|
||||||
|
|||||||
6
ui/lib/core/app/components/enable-input.js
Normal file
6
ui/lib/core/app/components/enable-input.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { default } from 'core/components/enable-input';
|
||||||
57
ui/tests/integration/components/enable-input-test.js
Normal file
57
ui/tests/integration/components/enable-input-test.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||||
|
import { click, render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
module('Integration | Component | EnableInput', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders and enables yielded input', async function (assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
await render(hbs`
|
||||||
|
<EnableInput>
|
||||||
|
<Input data-test-yielded-input @type='text' />
|
||||||
|
</EnableInput>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom('input').isDisabled('input is disabled');
|
||||||
|
assert.dom('input').hasValue('**********', 'disabled input renders asterisks');
|
||||||
|
await click('button');
|
||||||
|
assert.dom('[data-test-yielded-input]').isNotDisabled('toggles to enabled, yielded input');
|
||||||
|
assert.dom('button').doesNotExist('button disappears when input is enabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders passed attribute', async function (assert) {
|
||||||
|
assert.expect(6);
|
||||||
|
this.attr = {
|
||||||
|
name: 'specialClientCredentials',
|
||||||
|
type: 'string',
|
||||||
|
options: {
|
||||||
|
subText: 'This value is protected and not returned from the API. Enable input to update value.',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.model = { specialClientCredentials: '' };
|
||||||
|
await render(hbs`
|
||||||
|
<EnableInput @attr={{this.attr}} >
|
||||||
|
<FormField @attr={{this.attr}} @model={{this.model}} />
|
||||||
|
</EnableInput>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom(`[data-test-input="${this.attr.name}"]`).isDisabled('renders disabled ReadonlyFormField');
|
||||||
|
assert
|
||||||
|
.dom(`[data-test-input="${this.attr.name}"]`)
|
||||||
|
.hasValue('**********', 'disabled input renders asterisks');
|
||||||
|
assert.dom('[data-test-readonly-label]').hasText('Special client credentials');
|
||||||
|
assert.dom('p.sub-text').hasText(this.attr.options.subText);
|
||||||
|
await click('button');
|
||||||
|
assert
|
||||||
|
.dom(`[data-test-field="${this.attr.name}"] input`)
|
||||||
|
.isNotDisabled('toggles to enabled, yielded form field component');
|
||||||
|
assert.dom('button').doesNotExist('button disappears when input is enabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -106,9 +106,22 @@ module('Integration | Component | form field', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it renders: editType file', async function (assert) {
|
test('it renders: editType file', async function (assert) {
|
||||||
await setup.call(this, createAttr('foo', 'string', { editType: 'file' }));
|
const subText = 'My subtext.';
|
||||||
|
await setup.call(this, createAttr('foo', 'string', { editType: 'file', subText, docLink: '/docs' }));
|
||||||
assert.ok(component.hasTextFile, 'renders the text-file component');
|
assert.ok(component.hasTextFile, 'renders the text-file component');
|
||||||
|
assert
|
||||||
|
.dom('.hds-form-helper-text')
|
||||||
|
.hasText(
|
||||||
|
`Select a file from your computer. ${subText} See our documentation for help.`,
|
||||||
|
'renders subtext'
|
||||||
|
);
|
||||||
|
assert.dom('.hds-form-helper-text a').exists('renders doc link');
|
||||||
await click('[data-test-text-toggle]');
|
await click('[data-test-text-toggle]');
|
||||||
|
// assert again after toggling because subtext is rendered differently for each input
|
||||||
|
assert
|
||||||
|
.dom('.hds-form-helper-text')
|
||||||
|
.hasText(`Enter the value as text. ${subText} See our documentation for help.`, 'renders subtext');
|
||||||
|
assert.dom('.hds-form-helper-text a').exists('renders doc link');
|
||||||
await fillIn('[data-test-text-file-textarea]', 'hello world');
|
await fillIn('[data-test-text-file-textarea]', 'hello world');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user