chore: 🤖 refactor key value list component, text input support (#1957)

refactor: 💡 refactor list wrapper component and fix test
This commit is contained in:
Dharini Jeeva authored and GitHub committed 2023-10-30 12:30:05 -07:00
1 parent 3f8e39d0e1
commit 722acdc1fc
25 files changed
+689 -641

No files matched your search

@@ -83,7 +83,7 @@
}}</F.HelperText>
{{#let (unique-id) as |valueHeaderID|}}
<F.Control>
<Hds::Table class='key-value-list' name='certificates'>
<Hds::Table class='list-wrapper-field' name='certificates'>
<:head as |H|>
<H.Tr>
<H.Th id={{valueHeaderID}}>{{t 'titles.value'}}</H.Th>
@@ -424,7 +424,7 @@
}}</F.HelperText>
{{#let (unique-id) (unique-id) as |fromHeaderID toHeaderID|}}
<F.Control>
<Hds::Table class='key-value-list' name='account_attribute_maps'>
<Hds::Table class='list-wrapper-field' name='account_attribute_maps'>
<:head as |H|>
<H.Tr>
<H.Th id={{fromHeaderID}}>{{t
@@ -236,55 +236,72 @@
{{/if}}
</Hds::Form::TextInput::Field>
<Form::Field::KeyValueList
<Form::Field::ListWrapper
@layout='horizontal'
@isOptional={{true}}
@name='critical_options'
@options={{@model.critical_options}}
@addOption={{fn this.addOption 'critical_options'}}
@removeOptionByIndex={{fn this.removeOptionByIndex 'critical_options'}}
@disabled={{form.disabled}}
as |M|
>
<M.Legend>
{{t 'resources.credential-library.form.critical_options.label'}}
</M.Legend>
<M.HelperText>
{{t 'resources.credential-library.form.critical_options.help'}}
</M.HelperText>
{{#if @model.errors.critical_options}}
<M.Error as |E|>
{{#each @model.errors.critical_options as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</M.Error>
{{/if}}
</Form::Field::KeyValueList>
<:fieldset as |F|>
<F.Legend>
{{t 'resources.credential-library.form.critical_options.label'}}
</F.Legend>
<F.HelperText>
{{t 'resources.credential-library.form.critical_options.help'}}
</F.HelperText>
<Form::Field::KeyValueList
{{#if @model.errors.critical_options}}
<F.Error as |E|>
{{#each @model.errors.critical_options as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</:fieldset>
<:field as |F|>
<F.KeyValue
@name='critical_options'
@options={{@model.critical_options}}
@addOption={{fn this.addOption 'critical_options'}}
@removeOptionByIndex={{fn this.removeOptionByIndex 'critical_options'}}
@disabled={{form.disabled}}
/>
</:field>
</Form::Field::ListWrapper>
<Form::Field::ListWrapper
@layout='horizontal'
@isOptional={{true}}
@name='extensions'
@options={{@model.extensions}}
@addOption={{fn this.addOption 'extensions'}}
@removeOptionByIndex={{fn this.removeOptionByIndex 'extensions'}}
@disabled={{form.disabled}}
as |M|
>
<M.Legend>
{{t 'resources.credential-library.form.extensions.label'}}
</M.Legend>
<M.HelperText>
{{t 'resources.credential-library.form.extensions.help'}}
</M.HelperText>
{{#if @model.errors.extensions}}
<M.Error as |E|>
{{#each @model.errors.extensions as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</M.Error>
{{/if}}
</Form::Field::KeyValueList>
<:fieldset as |F|>
<F.Legend>
{{t 'resources.credential-library.form.extensions.label'}}
</F.Legend>
<F.HelperText>
{{t 'resources.credential-library.form.extensions.help'}}
</F.HelperText>
{{#if @model.errors.extensions}}
<F.Error as |E|>
{{#each @model.errors.extensions as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</:fieldset>
<:field as |F|>
<F.KeyValue
@name='extensions'
@options={{@model.extensions}}
@addOption={{fn this.addOption 'extensions'}}
@removeOptionByIndex={{fn this.removeOptionByIndex 'extensions'}}
@disabled={{form.disabled}}
/>
</:field>
</Form::Field::ListWrapper>
{{#if (can 'save model' @model)}}
<form.actions
@@ -1,107 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<Hds::Form::Fieldset
@layout={{@layout}}
@isRequired={{@isRequired}}
@isOptional={{@isOptional}}
as |F|
>
{{yield F}}
{{#let (unique-id) (unique-id) as |keyHeaderID valueHeaderID|}}
<F.Control>
<Hds::Table class='key-value-list' name={{@name}}>
<:head as |H|>
<H.Tr>
<H.Th id={{keyHeaderID}}>{{t 'form.key.label'}}</H.Th>
{{#unless @hasNoValue}}
<H.Th id={{valueHeaderID}}>{{t 'form.value.label'}}</H.Th>
{{/unless}}
<H.Th>
{{#if @removeOptionByIndex}}
{{t 'titles.actions'}}
{{/if}}
</H.Th>
</H.Tr>
</:head>
<:body as |B|>
{{#each @options as |option index|}}
<B.Tr>
<B.Td>
<Hds::Form::TextInput::Field
@value={{option.key}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{keyHeaderID}}
{{on 'input' (set-from-event option 'key')}}
/>
</B.Td>
{{#unless @hasNoValue}}
<B.Td>
<Hds::Form::TextInput::Field
@value={{option.value}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{valueHeaderID}}
{{on 'input' (set-from-event option 'value')}}
/>
</B.Td>
{{/unless}}
<B.Td>
{{#if @removeOptionByIndex}}
<Hds::Button
@text={{t 'actions.remove'}}
@color='critical'
@icon='trash'
@isIconOnly={{true}}
disabled={{@disabled}}
{{on 'click' (fn @removeOptionByIndex index)}}
/>
{{/if}}
</B.Td>
</B.Tr>
{{/each}}
<B.Tr>
<B.Td>
<Hds::Form::TextInput::Field
@value={{this.newOptionKey}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{keyHeaderID}}
{{on 'input' (set-from-event this 'newOptionKey')}}
/>
</B.Td>
{{#unless @hasNoValue}}
<B.Td>
<Hds::Form::TextInput::Field
@value={{this.newOptionValue}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{valueHeaderID}}
{{on 'input' (set-from-event this 'newOptionValue')}}
/>
</B.Td>
{{/unless}}
<B.Td>
<Hds::Button
@text={{t 'actions.add'}}
@color='secondary'
type='button'
disabled={{or @disabled (not this.newOptionKey)}}
{{on 'click' this.addOption}}
/>
</B.Td>
</B.Tr>
</:body>
</Hds::Table>
</F.Control>
{{/let}}
</Hds::Form::Fieldset>
@@ -0,0 +1,29 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<Hds::Form::Fieldset
@layout={{@layout}}
@isRequired={{@isRequired}}
@isOptional={{@isOptional}}
as |F|
>
{{yield F to='fieldset'}}
<F.Control>
{{yield
(hash
KeyValue=(component
'form/field/list-wrapper/key-value' disabled=@disabled
)
TextInput=(component
'form/field/list-wrapper/text-input' disabled=@disabled
)
)
to='field'
}}
</F.Control>
</Hds::Form::Fieldset>
@@ -0,0 +1,94 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
{{#let (unique-id) (unique-id) as |keyHeaderID valueHeaderID|}}
<Hds::Table class='list-wrapper-field' name={{@name}}>
<:head as |H|>
<H.Tr>
<H.Th id={{keyHeaderID}}>{{t 'form.key.label'}}</H.Th>
<H.Th id={{valueHeaderID}}>{{t 'form.value.label'}}</H.Th>
<H.Th>
{{#if @removeOptionByIndex}}
{{t 'titles.actions'}}
{{/if}}
</H.Th>
</H.Tr>
</:head>
<:body as |B|>
{{#each @options as |option index|}}
<B.Tr>
<B.Td>
<Hds::Form::TextInput::Field
@value={{option.key}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{keyHeaderID}}
{{on 'input' (set-from-event option 'key')}}
/>
</B.Td>
<B.Td>
<Hds::Form::TextInput::Field
@value={{option.value}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{valueHeaderID}}
{{on 'input' (set-from-event option 'value')}}
/>
</B.Td>
<B.Td>
{{#if @removeOptionByIndex}}
<Hds::Button
data-test-remove-button
@text={{t 'actions.remove'}}
@color='critical'
@icon='trash'
@isIconOnly={{true}}
disabled={{@disabled}}
{{on 'click' (fn @removeOptionByIndex index)}}
/>
{{/if}}
</B.Td>
</B.Tr>
{{/each}}
<B.Tr>
<B.Td>
<Hds::Form::TextInput::Field
@value={{this.newOptionKey}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{keyHeaderID}}
{{on 'input' (set-from-event this 'newOptionKey')}}
/>
</B.Td>
<B.Td>
<Hds::Form::TextInput::Field
@value={{this.newOptionValue}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{valueHeaderID}}
{{on 'input' (set-from-event this 'newOptionValue')}}
/>
</B.Td>
<B.Td>
<Hds::Button
@text={{t 'actions.add'}}
@color='secondary'
type='button'
disabled={{or @disabled (not this.newOptionKey)}}
{{on 'click' this.addOption}}
/>
</B.Td>
</B.Tr>
</:body>
</Hds::Table>
{{/let}}
@@ -30,7 +30,7 @@ export default class MappingListComponent extends Component {
@action
addOption() {
assert(
'[boundary-admin-key-value-list-field] `@addOption` is required.',
'[boundary-admin-list-wrapper] `@addOption` is required.',
this.args.addOption
);
@@ -0,0 +1,66 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
{{#let (unique-id) as |valueHeaderID|}}
<Hds::Table class='list-wrapper-field' name={{@name}}>
<:body as |B|>
{{#each @options as |option index|}}
<B.Tr>
<B.Td>
<Hds::Form::TextInput::Field
@value={{option.value}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{valueHeaderID}}
{{on 'input' (set-from-event option 'value')}}
/>
</B.Td>
<B.Td>
{{#if @removeOptionByIndex}}
<Hds::Button
data-test-remove-button
@text={{t 'actions.remove'}}
@color='critical'
@icon='trash'
@isIconOnly={{true}}
disabled={{@disabled}}
{{on 'click' (fn @removeOptionByIndex index)}}
/>
{{/if}}
</B.Td>
</B.Tr>
{{/each}}
<B.Tr>
<B.Td>
<Hds::Form::TextInput::Field
@value={{this.newOptionValue}}
@type='text'
disabled={{@disabled}}
aria-labelledby={{valueHeaderID}}
{{on 'input' (set-from-event this 'newOptionValue')}}
/>
</B.Td>
<B.Td>
<Hds::Button
@text={{t 'actions.add'}}
@color='secondary'
type='button'
disabled={{or @disabled (not this.newOptionValue)}}
{{on 'click' this.addOption}}
/>
</B.Td>
</B.Tr>
</:body>
</Hds::Table>
{{/let}}
@@ -0,0 +1,41 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { assert } from '@ember/debug';
export default class MappingListTextInputComponent extends Component {
// =attributes
/**
* @type {string}
*/
@tracked newOptionValue = '';
// =actions
/**
* If a new input is entered and an addOption method was specified,
* calls addOption with the new input. Resets previous value.
*/
@action
addOption() {
assert(
'[boundary-admin-list-wrapper] `@addOption` is required.',
this.args.addOption
);
if (this.newOptionValue) {
this.args.addOption({
value: this.newOptionValue,
});
}
this.newOptionValue = '';
}
}
@@ -69,178 +69,65 @@
</F.HelperText>
</InfoField>
<Rose::Form::Fieldset
@helperText={{t 'resources.host-set.form.preferred_endpoints.help'}}
@linkText={{t 'actions.learn-more'}}
@link={{doc-url 'host-set.preferred-endpoints'}}
<Form::Field::ListWrapper
@layout='horizontal'
@isOptional={{true}}
@disabled={{form.disabled}}
>
<:title>{{t 'resources.host-set.form.preferred_endpoints.label'}}</:title>
<:body>
<:fieldset as |F|>
<F.Legend>
{{t 'resources.host-set.form.preferred_endpoints.label'}}
</F.Legend>
<F.HelperText>
{{t 'resources.host-set.form.preferred_endpoints.help'}}
</F.HelperText>
{{#if @model.errors.preferred_endpoints}}
<Rose::Form::Errors as |errors|>
<F.Error as |E|>
{{#each @model.errors.preferred_endpoints as |error|}}
<errors.message>{{error.message}}</errors.message>
<E.Message>{{error.message}}</E.Message>
{{/each}}
</Rose::Form::Errors>
</F.Error>
{{/if}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.preferred_endpoints as |endpoint i|}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@name='preferred_endpoints'
@value={{endpoint.value}}
@error={{@model.errors.preferred_endpoints}}
@contextual={{true}}
placeholder='cidr:2001:4860:4860::8888/32'
as |field|
>
<field.field />
</form.input>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='warning'
@iconOnly='flight-icons/svg/trash-16'
title={{t 'actions.remove'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @removeItemByIndex 'preferred_endpoints' i)}}
>
{{t 'actions.remove'}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
</:fieldset>
<:field as |F|>
<F.TextInput
@name='preferred_endpoints'
@options={{@model.preferred_endpoints}}
@addOption={{fn @addOption 'preferred_endpoints'}}
@removeOptionByIndex={{fn @removeOptionByIndex 'preferred_endpoints'}}
@disabled={{form.disabled}}
/>
</:field>
</Form::Field::ListWrapper>
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@name='preferred_endpoints'
@value={{@model.preferred_endpoint}}
placeholder='cidr:2001:4860:4860::8888/32'
/>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='secondary'
title={{t 'actions.add'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on
'click'
(fn
@addStringItem
'preferred_endpoints'
@model.preferred_endpoint
)
}}
>
{{t 'actions.add'}}
</Rose::Button>
</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
</Rose::Form::Fieldset>
<Form::Field::ListWrapper @layout='horizontal' @disabled={{form.disabled}}>
<:fieldset as |F|>
<F.Legend>
{{t 'resources.host-set.form.filter.label'}}
</F.Legend>
<F.HelperText>
{{t 'resources.host-set.form.filter.aws.help'}}
</F.HelperText>
<Rose::Form::Fieldset>
<:title>{{t 'resources.host-set.form.filter.label'}}</:title>
<:description>{{t 'resources.host-set.form.filter.aws.help'}}</:description>
<:body>
{{#if @model.errors.filters}}
<Rose::Form::Errors as |errors|>
<F.Error as |E|>
{{#each @model.errors.filters as |error|}}
<errors.message>{{error.message}}</errors.message>
<E.Message>{{error.message}}</E.Message>
{{/each}}
</Rose::Form::Errors>
</F.Error>
{{/if}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.filters as |filter i|}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@name='filters'
@value={{filter.value}}
@error={{@model.errors.filters}}
@contextual={{true}}
as |field|
>
<field.field />
</form.input>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='warning'
@iconOnly='flight-icons/svg/trash-16'
title={{t 'actions.remove'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @removeItemByIndex 'filters' i)}}
>
{{t 'actions.remove'}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@name='filters'
@value={{@model.filter_string}}
/>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='secondary'
title={{t 'actions.add'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on
'click'
(fn @addStringItem 'filters' @model.filter_string)
}}
>
{{t 'actions.add'}}
</Rose::Button>
</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
</Rose::Form::Fieldset>
</:fieldset>
<:field as |F|>
<F.TextInput
@name='filters'
@options={{@model.filters}}
@addOption={{fn @addOption 'filters'}}
@removeOptionByIndex={{fn @removeOptionByIndex 'filters'}}
@disabled={{form.disabled}}
/>
</:field>
</Form::Field::ListWrapper>
<form.input
@name='sync_interval_seconds'
@@ -69,96 +69,38 @@
</F.HelperText>
</InfoField>
<Rose::Form::Fieldset
@helperText={{t 'resources.host-set.form.preferred_endpoints.help'}}
@linkText={{t 'actions.learn-more'}}
@link={{doc-url 'host-set.preferred-endpoints'}}
<Form::Field::ListWrapper
@layout='horizontal'
@isOptional={{true}}
@disabled={{form.disabled}}
>
<:title>{{t 'resources.host-set.form.preferred_endpoints.label'}}</:title>
<:body>
{{#if @model.errors.preferred_endpoints}}
<Rose::Form::Errors as |errors|>
{{#each @model.errors.preferred_endpoints as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</Rose::Form::Errors>
{{/if}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.preferred_endpoints as |endpoint i|}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@name='preferred_endpoints'
@value={{endpoint.value}}
@error={{@model.errors.preferred_endpoints}}
@contextual={{true}}
placeholder='cidr:2001:4860:4860::8888/32'
as |field|
>
<field.field />
</form.input>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='warning'
@iconOnly='flight-icons/svg/trash-16'
title={{t 'actions.remove'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @removeItemByIndex 'preferred_endpoints' i)}}
>
{{t 'actions.remove'}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
<:fieldset as |F|>
<F.Legend>
{{t 'resources.host-set.form.preferred_endpoints.label'}}
</F.Legend>
<F.HelperText>
{{t 'resources.host-set.form.preferred_endpoints.help'}}
</F.HelperText>
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@name='preferred_endpoints'
@value={{@model.preferred_endpoint}}
placeholder='cidr:2001:4860:4860::8888/32'
/>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='secondary'
title={{t 'actions.add'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on
'click'
(fn
@addStringItem
'preferred_endpoints'
@model.preferred_endpoint
)
}}
>
{{t 'actions.add'}}
</Rose::Button>
</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
</Rose::Form::Fieldset>
{{#if @model.errors.preferred_endpoints}}
<F.Error as |E|>
{{#each @model.errors.preferred_endpoints as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</:fieldset>
<:field as |F|>
<F.TextInput
@name='preferred_endpoints'
@options={{@model.preferred_endpoints}}
@addOption={{fn @addOption 'preferred_endpoints'}}
@removeOptionByIndex={{fn @removeOptionByIndex 'preferred_endpoints'}}
@disabled={{form.disabled}}
/>
</:field>
</Form::Field::ListWrapper>
<Hds::Form::Textarea::Field
@value={{@model.filter_string}}
@@ -9,6 +9,6 @@
edit=@edit
submit=@submit
cancel=@cancel
removeItemByIndex=@removeItemByIndex
addStringItem=@addStringItem
addOption=this.addOption
removeOptionByIndex=this.removeOptionByIndex
}}
@@ -0,0 +1,39 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { set } from '@ember/object';
export default class FormHostSetComponent extends Component {
// =attributes
// =actions
/**
* Adds a string to the array. We recreate a new array after adding
* so that ember is aware that the array has been modified.
* @param field {string}
* @param value {string}
*/
@action
addOption(field, { value }) {
const existingArray = this.args.model[field] ?? [];
const newArray = [...existingArray, { value }];
set(this.args.model, field, newArray);
}
/**
* Removes an option by index. We recreate a new array after
* splicing out the item so that ember is aware that the array has been modified.
* @param field {string}
* @param index {number}
*/
@action
removeOptionByIndex(field, index) {
const newArray = this.args.model[field].filter((_, i) => i !== index);
set(this.args.model, field, newArray);
}
}
@@ -61,7 +61,7 @@
}}</F.HelperText>
{{#let (unique-id) as |valueHeaderID|}}
<F.Control>
<Hds::Table class='key-value-list' name='group_names'>
<Hds::Table class='list-wrapper-field' name='group_names'>
<:head as |H|>
<H.Tr>
<H.Th id={{valueHeaderID}}>{{t 'titles.value'}}</H.Th>
@@ -366,30 +366,37 @@
{{/if}}
</Hds::Form::TextInput::Field>
<Form::Field::KeyValueList
<Form::Field::ListWrapper
@layout='horizontal'
@isOptional={{true}}
@name='role_tags'
@options={{@model.role_tags}}
@addOption={{fn this.addOption 'role_tags'}}
@removeOptionByIndex={{fn this.removeOptionByIndex 'role_tags'}}
@disabled={{form.disabled}}
as |M|
>
<M.Legend>
{{t 'resources.storage-bucket.form.role_tags.label'}}
</M.Legend>
<M.HelperText>
{{t 'resources.storage-bucket.form.role_tags.help'}}
</M.HelperText>
{{#if @model.errors.role_tags}}
<M.Error as |E|>
{{#each @model.errors.role_tags as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</M.Error>
{{/if}}
</Form::Field::KeyValueList>
<:fieldset as |F|>
<F.Legend>
{{t 'resources.storage-bucket.form.role_tags.label'}}
</F.Legend>
<F.HelperText>
{{t 'resources.storage-bucket.form.role_tags.label'}}
</F.HelperText>
{{#if @model.errors.role_tags}}
<F.Error as |E|>
{{#each @model.errors.role_tags as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</:fieldset>
<:field as |F|>
<F.TextInput
@name='role_tags'
@options={{@model.role_tags}}
@addOption={{fn this.addOption 'role_tags'}}
@removeOptionByIndex={{fn this.removeOptionByIndex 'role_tags'}}
@disabled={{form.disabled}}
/>
</:field>
</Form::Field::ListWrapper>
{{/if}}
<Hds::Form::TextInput::Field