mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +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. */
|
||||
|
||||
/* 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 {
|
||||
display: flex !important;
|
||||
}
|
||||
@@ -104,28 +130,6 @@
|
||||
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 */
|
||||
@media screen and (min-width: 769px), print {
|
||||
.is-flex-v-centered-tablet {
|
||||
@@ -163,10 +167,6 @@
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.align-self-center {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.is-medium-height {
|
||||
height: 125px;
|
||||
}
|
||||
@@ -178,3 +178,13 @@
|
||||
.grid-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">
|
||||
<TextFile
|
||||
@label={{this.labelString}}
|
||||
@subText={{@attr.options.subText}}
|
||||
@helpText={{@attr.options.helpText}}
|
||||
@docLink={{@attr.options.docLink}}
|
||||
@onChange={{this.setFile}}
|
||||
@validationError={{this.validationError}}
|
||||
/>
|
||||
|
||||
@@ -42,7 +42,19 @@
|
||||
data-test-text-file-textarea
|
||||
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>
|
||||
{{else}}
|
||||
<Hds::Form::FileInput::Field
|
||||
@@ -51,7 +63,19 @@
|
||||
data-test-text-file-input
|
||||
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>
|
||||
{{#if (or @validationError this.uploadError)}}
|
||||
<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) {
|
||||
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
|
||||
.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]');
|
||||
// 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');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user