UI: Pull nested interactives out of labels (#10231) (#10367)

* pull nested interactives out of labels

* move label styles into new class

* update tooltip testd

* fix test

* update missed css classes + update css variable usage

* update font size

Co-authored-by: lane-wetmore <lane.wetmore@hashicorp.com>
This commit is contained in:
Vault Automation 2025-10-24 14:01:19 -04:00 committed by GitHub
parent 22f221129e
commit bfac07958a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 148 additions and 68 deletions

View File

@ -14,14 +14,16 @@
)
}}
{{#unless (eq @attr.type "object")}}
<label for={{@attr.name}} class="is-label">
{{capitalize (or @attr.options.label (humanize (dasherize @attr.name)))}}
<div class="label-row">
<label for={{@attr.name}}>
{{capitalize (or @attr.options.label (humanize (dasherize @attr.name)))}}
</label>
{{#if @attr.options.helpText}}
<Hds::TooltipButton @text={{@attr.options.helpText}} aria-label="More information">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
{{/unless}}
{{/unless}}
{{#if @attr.options.possibleValues}}
@ -69,15 +71,16 @@
onchange={{action (mut (get this.model @attr.name)) value="target.checked"}}
data-test-input={{@attr.name}}
/>
<label for={{@attr.name}} class="is-label">
{{capitalize (or @attr.options.label (humanize (dasherize @attr.name)))}}
<div class="label-row">
<label for={{@attr.name}}>
{{capitalize (or @attr.options.label (humanize (dasherize @attr.name)))}}
</label>
{{#if @attr.options.helpText}}
<Hds::TooltipButton @text={{@attr.options.helpText}} aria-label="More information">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
</div>
{{else if (eq @attr.type "object")}}
<JsonEditor

View File

@ -5,14 +5,16 @@
<div ...attributes>
{{#if @label}}
<label for={{@name}} class="is-label">
{{@label}}
<div class="label-row">
<label for={{@name}}>
{{@label}}
</label>
{{#if @helpText}}
<Hds::TooltipButton @text={{@helpText}} aria-label="More information">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
{{/if}}
{{#if this.authMethods.isRunning}}
<div>

View File

@ -6,25 +6,19 @@
{{#if @attr}}
<div class="field" data-test-regex-validator-pattern>
<div class="regex-label-wrapper">
<div class="regex-label">
<label for={{@attr.name}} class="is-label">
<div class="regex-label label-row">
<label for={{@attr.name}}>
{{@labelString}}
{{#if @attr.options.helpText}}
<Hds::TooltipButton @text={{@attr.options.helpText}} @placement="bottom" aria-label="More information">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
{{#if @attr.options.subText}}
<p class="sub-text">
{{@attr.options.subText}}
{{#if @attr.options.docLink}}
<DocLink @path={{@attr.options.docLink}}>
See our documentation
</DocLink>
for help.
{{/if}}
</p>
{{#if @attr.options.helpText}}
<Hds::TooltipButton
@text={{@attr.options.helpText}}
@placement="bottom"
aria-label="More information"
data-test-tooltip="regex-validator"
>
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</div>
<div>
@ -37,6 +31,17 @@
</Toggle>
</div>
</div>
{{#if @attr.options.subText}}
<p class="sub-text">
{{@attr.options.subText}}
{{#if @attr.options.docLink}}
<DocLink @path={{@attr.options.docLink}}>
See our documentation
</DocLink>
for help.
{{/if}}
</p>
{{/if}}
<input
id={{@attr.name}}
data-test-input={{@attr.name}}

View File

@ -30,3 +30,16 @@
border-color: var(--token-color-palette-neutral-300);
}
}
.label-row {
display: flex;
align-items: center;
gap: size_variables.$spacing-4;
margin-bottom: size_variables.$spacing-4;
label {
color: var(--token-color-palette-neutral-600);
font-size: var(--token-typography-body-200-font-size);
font-weight: var(--token-typography-font-weight-bold);
}
}

View File

@ -4,14 +4,16 @@
}}
{{#if @label}}
<label data-test-form-field-label class="is-label" ...attributes>
{{@label}}
<div class="label-row">
<label data-test-form-field-label ...attributes>
{{@label}}
</label>
{{#if @helpText}}
<Hds::TooltipButton @text={{@helpText}} aria-label="More information" data-test-tooltip="form field label">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
{{/if}}
{{#if @subText}}
<p class="sub-text" data-test-label-subtext>

View File

@ -315,7 +315,7 @@
@inputValue={{get @model this.valuePath}}
@wildcardLabel={{@attr.options.wildcardLabel}}
@label={{this.labelString}}
@labelClass={{if @attr.options.isSectionHeader "title is-4" "is-label"}}
@labelClass={{if @attr.options.isSectionHeader "title is-4" ""}}
@subText={{@attr.options.subText}}
@helpText={{this.helpTextString}}
@fallbackComponent={{@attr.options.fallbackComponent}}

View File

@ -2,15 +2,16 @@
Copyright IBM Corp. 2016, 2025
SPDX-License-Identifier: BUSL-1.1
}}
<label for={{@attr.name}} class="is-label" data-test-readonly-label>
{{this.labelString}}
<div class="label-row">
<label for={{@attr.name}} data-test-readonly-label>
{{this.labelString}}
</label>
{{#if @attr.options.helpText}}
<Hds::TooltipButton @text={{@attr.options.helpText}} aria-label="More information">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
{{#if @attr.options.subText}}
<p class="sub-text">{{@attr.options.subText}}</p>
{{/if}}

View File

@ -24,14 +24,16 @@
}}
{{else}}
{{#if @label}}
<label for={{@id}} class={{or @labelClass "is-label"}} data-test-field-label>
{{@label}}
<div class="label-row">
<label for={{@id}} class={{@labelClass}} data-test-field-label>
{{@label}}
</label>
{{#if @helpText}}
<Hds::TooltipButton @text={{@helpText}} aria-label="More information">
<Hds::TooltipButton @text={{@helpText}} class={{@labelClass}} aria-label="More information">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
{{/if}}
{{#if @subText}}
<p data-test-modal-subtext class="sub-text">{{@subText}}</p>

View File

@ -24,14 +24,21 @@
}}
{{else}}
{{#if @label}}
<label for={{@id}} class={{or @labelClass "is-label"}} data-test-field-label>
{{@label}}
<div class="label-row">
<label for={{@id}} class={{@labelClass}} data-test-field-label>
{{@label}}
</label>
{{#if @helpText}}
<Hds::TooltipButton @text={{@helpText}} aria-label="More information" data-test-tooltip="search-select">
<Hds::TooltipButton
@text={{@helpText}}
class={{@labelClass}}
aria-label="More information"
data-test-tooltip="search-select"
>
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
{{/if}}
{{#if @subText}}
<p class="sub-text">{{@subText}}</p>

View File

@ -12,14 +12,16 @@
...attributes
>
{{#if @label}}
<label class="is-label" data-test-string-list-label="true">
{{@label}}
<div class="label-row">
<label data-test-string-list-label="true">
{{@label}}
</label>
{{#if @helpText}}
<Hds::TooltipButton @text={{@helpText}} aria-label="More information" data-test-tooltip="string-list">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
</div>
{{#if @subText}}
<p class="sub-text" id={{concat (dasherize @label) "-helper-text"}}>
{{@subText}}

View File

@ -7,15 +7,15 @@
<label id="text-file-input-{{this.elementId}}" class="sr-only">{{or @label "File"}}</label>
{{else}}
<div class="level is-mobile">
<div class="level-left">
<label id="text-file-input-{{this.elementId}}" class="has-text-weight-semibold" data-test-text-file-label>
<div class="level-left label-row">
<label id="text-file-input-{{this.elementId}}" data-test-text-file-label>
{{or @label "File"}}
{{#if @helpText}}
<Hds::TooltipButton @text={{@helpText}} aria-label="More information">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</label>
{{#if @helpText}}
<Hds::TooltipButton @text={{@helpText}} aria-label="More information" data-test-tooltip="text-file">
<Hds::Icon @name="info" />
</Hds::TooltipButton>
{{/if}}
</div>
<div class="level-right">
<Input

View File

@ -18,7 +18,7 @@ import { buildWaiter } from '@ember/test-waiters';
*
* @param {function} onChange - Callback function to call when the value of the input changes, returns an object in the shape of { value: fileContents, filename: 'some-file.txt' }
* @param {bool} [uploadOnly=false] - When true, renders a static file upload input and removes the option to toggle and input plain text
* @param {string} [helpText] - Text underneath label.
* @param {string} [helpText] - Text in tooltip alongside label. Shown when 'uploadOnly' is false.
* @param {string} [label=File] - Text to use as the label for the file input. If none, default of 'File' is rendered
*/

View File

@ -31,6 +31,5 @@
@onChange={{this.onStringListChange}}
@attrName="extKeyUsageOids"
@subText="A list of extended key usage OIDs. Add one item per row."
@showHelpText={{false}}
/>
</div>

View File

@ -273,8 +273,9 @@ module('Integration | Component | form field', function (hooks) {
this,
createAttr('foo', 'string', { editType: 'stringArray', helpText: 'Here is some help text' })
);
assert.dom(GENERAL.tooltipText).hasNoText();
await click(GENERAL.tooltip('string-list'));
assert.dom(GENERAL.tooltipText).exists('renders the tooltip component');
assert.dom(GENERAL.tooltipText).hasText('Here is some help text', 'renders the tooltip component');
});
test('it should not expand and toggle ttl when default 0s value is present', async function (assert) {

View File

@ -188,8 +188,9 @@ module('Integration | Component | InfoTableRow', function (hooks) {
/>
</div>`);
assert.dom('[data-test-component="info-table-row"]').exists('Row renders');
assert.dom(GENERAL.tooltipText).hasNoText();
await click(GENERAL.tooltip('info table row'));
assert.dom(GENERAL.tooltipText).exists('Label tooltip exists');
assert.dom(GENERAL.tooltipText).hasText(this.label, 'Label tooltip exists');
});
test('Renders if block value and alwaysrender=false', async function (assert) {

View File

@ -32,7 +32,8 @@ module('Integration | Component | regex-validator', function (hooks) {
@labelString={{this.labelString}}
/>`
);
assert.dom('.regex-label label').hasText('Regex Example', 'Label is correct');
assert.dom('label').hasText('Regex Example', 'Label is correct');
assert.dom(GENERAL.toggleInput('example-validation-toggle')).exists('Validation toggle exists');
assert.dom('[data-test-regex-validator-test-string]').doesNotExist('Test string input does not show');
@ -73,6 +74,35 @@ module('Integration | Component | regex-validator', function (hooks) {
assert.ok(spy.calledOnce, 'Calls the passed onChange function when main input is changed');
});
test('it renders tooltip', async function (assert) {
const tooltipText = 'Shows in tooltip';
const attr = EmberObject.create({
name: 'example',
options: {
helpText: tooltipText,
subText: 'Shows underneath label',
},
});
const spy = sinon.spy();
this.set('onChange', spy);
this.set('attr', attr);
this.set('value', '(\\d{4})');
this.set('labelString', 'Regex Example');
await render(
hbs`<RegexValidator
@onChange={{this.onChange}}
@attr={{this.attr}}
@value={{this.value}}
@labelString={{this.labelString}}
/>`
);
assert.dom(GENERAL.tooltipText).hasNoText();
await click(GENERAL.tooltip('regex-validator'));
assert.dom(GENERAL.tooltipText).hasText(tooltipText, 'Tooltip text is rendered');
});
test('it renders test input only when attr is not provided', async function (assert) {
this.setProperties({
value: null,

View File

@ -8,6 +8,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn, triggerKeyEvent } from '@ember/test-helpers';
import sinon from 'sinon';
import hbs from 'htmlbars-inline-precompile';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
module('Integration | Component | string list', function (hooks) {
setupRenderingTest(hooks);
@ -43,6 +44,21 @@ module('Integration | Component | string list', function (hooks) {
assertBlank(assert);
});
test('it renders the tooltip', async function (assert) {
assert.expect(4);
await render(
hbs`<StringList @label="CRL distribution points" @helpText="this is my help text" @onChange={{this.spy}} />`
);
await click(GENERAL.tooltip('string-list'));
assert
.dom(GENERAL.tooltipText)
.hasText('this is my help text', 'renders the help text in a tooltip when provided');
await render(hbs`<StringList />`);
assert.dom(GENERAL.tooltip('string-list')).doesNotExist('does not render the tooltip');
assertBlank(assert);
});
test('it renders inputValue from empty string', async function (assert) {
assert.expect(2);
await render(hbs`<StringList @inputValue="" />`);

View File

@ -8,7 +8,6 @@ import { setupRenderingTest } from 'vault/tests/helpers';
import { click, fillIn, render, triggerEvent } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import sinon from 'sinon';
import { setRunOptions } from 'ember-a11y-testing/test-support';
import { CERTIFICATES } from 'vault/tests/helpers/pki/pki-helpers';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
@ -27,26 +26,23 @@ module('Integration | Component | text-file', function (hooks) {
});
test('it renders with label and toggle by default', async function (assert) {
await render(hbs`<TextFile @onChange={{this.onChange}} />`);
await render(hbs`<TextFile @onChange={{this.onChange}} @helpText="this is my help text"/>`);
assert.dom(SELECTORS.label).hasText('File', 'renders default label');
assert.dom(GENERAL.textToggle).exists({ count: 1 }, 'toggle exists');
assert.dom(SELECTORS.fileUpload).exists({ count: 1 }, 'File input shown');
assert.dom(GENERAL.tooltipText).hasNoText();
await click(GENERAL.tooltip('text-file'));
assert.dom(GENERAL.tooltipText).hasText('this is my help text', 'Tooltip text renders');
});
test('it renders without toggle and option for text input when uploadOnly=true', async function (assert) {
setRunOptions({
rules: {
// TODO: fix textFile / replace with HDS
label: { enabled: false },
'label-title-only': { enabled: false },
},
});
await render(hbs`<TextFile @onChange={{this.onChange}} @uploadOnly={{true}} />`);
assert.dom(SELECTORS.label).doesNotExist('Label no longer rendered');
assert.dom(GENERAL.textToggle).doesNotExist('toggle no longer rendered');
assert.dom(GENERAL.tooltip('text-file')).doesNotExist('tooltip icon no longer rendered');
assert.dom(SELECTORS.fileUpload).exists({ count: 1 }, 'File input shown');
});