This commit is contained in:
Cristiano Rastelli 2026-04-03 14:35:06 +00:00 committed by GitHub
commit e9ffeb143e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1310 additions and 2 deletions

View file

@ -13,6 +13,7 @@
<Icon @name="pencil-tool" /><Icon @name="git-branch" />
</div>
</div>
<Hds::Link::Standalone @text="Showcase" @icon="package" @color="secondary" @route="vault.cluster.showcase" />
{{/if}}
</AF.ExtraBefore>
<AF.Link @href={{changelog-url-for this.version.version}} data-test-footer-version>

View file

@ -0,0 +1,499 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Controller from '@ember/controller';
import { action } from '@ember/object';
// import { tracked } from '@glimmer/tracking';
export default class ShowcaseController extends Controller {
// ###########################################
// FORMFIELD
// ###########################################
jsonExample = JSON.stringify(
{ type: 'JSON', text: 'Lorem ipsum', value: 1234, object: { a: 'b', c: [9, 8, 7] } },
null,
2
);
@action
dynamicFormFieldModel(editType, variant) {
const model = {
// emulate model's setter
set: () => {},
};
if (editType === 'boolean') {
if (variant === 'checked' || variant === 'disabled') {
model.boolean = true;
}
} else if (editType === 'checkboxList') {
model.checkboxList = 'Ipsum';
} else if (editType === 'dateTimeLocal') {
if (variant === 'with value' || variant === 'with validation errors') {
model.dateTimeLocal = new Date();
}
} else if (editType === 'input') {
if (variant === 'with value' || variant === 'with validation errors') {
model.input = 'Lorem ipsum dolor sit amet';
} else if (variant === 'with character limit') {
model.input = '123456789';
} else if (variant === 'readonly' || variant === 'disabled') {
model.input = 'Lorem ipsum';
}
} else if (editType === 'json') {
model.json = '{}';
if (variant === 'with value' || variant === 'with value + restore') {
model.json = this.jsonExample;
}
} else if (editType === 'kv') {
if (variant === 'with value' || variant === 'with validation errors') {
model.kv = {
'my-key': 'This is the value for `my-key`',
};
} else if (variant === 'with whitespace in key') {
model.kv = {
'my key with space': 'You need to set `@allowWhiteSpace` to avoid this warning',
};
}
} else if (editType === 'mountAccessor') {
if (variant === 'with value') {
// TODO! not sure what to use here, it's too connected with the `authMethods` task (see the `mount-accessor-select` controller)
model.mountAccessor = '???';
}
} else if (editType === 'object') {
model.object = '{}';
if (variant === 'with value' || variant === 'with value + restore') {
model.object = { type: 'object', text: 'Hello world', number: 4567, array: ['a', 'b', 'c'] };
}
} else if (editType === 'optionalText') {
if (variant === 'with value' || variant === 'with value + subText + docLink') {
model.optionalText = 'Lorem ipsum';
}
} else if (editType === 'password') {
if (variant === 'with value') {
model.password = '123abc';
}
} else if (editType === 'radio') {
model.radio = 2;
} else if (editType === 'regex') {
if (variant === 'with value') {
model.regex = '/^lorem .*$/i';
}
} else if (editType === 'stringArray') {
if (variant === 'with value') {
model.stringArray = ['Lorem', 'Ipsum'];
}
} else if (editType === 'searchSelect') {
if (variant === 'with value') {
model.searchSelect = ['Lorem', 'Ipsum'];
}
} else if (editType === 'select') {
if (variant === 'with value') {
model.select = 'Ipsum';
}
} else if (editType === 'sensitive') {
if (variant === 'with value' || variant === 'with copy') {
model.sensitive = 'Lorem ipsum dolor';
}
} else if (editType === 'textarea') {
if (variant === 'with value' || variant === 'with validation errors') {
model.textarea = 'Lorem\nipsum\ndolor';
}
} else if (editType === 'toggleButton') {
model.toggleButton = false;
if (variant === 'checked' || variant === 'checked with opposite value' || variant === 'disabled') {
model.toggleButton = true;
}
} else if (editType === 'ttl') {
if (variant === 'with value' || variant === 'with validation errors') {
model.ttl = 123;
} else if (variant === 'with value 0s') {
model.ttl = '0s';
} else if (variant === 'with value 1h') {
model.ttl = '1h';
}
}
return model;
}
@action
dynamicFormFieldModelValidations(editType, variant) {
const modelValidations = {};
if (editType === 'boolean') {
if (variant === 'with validation errors and warnings') {
modelValidations.boolean = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
} else if (variant === 'with validation errors') {
modelValidations.boolean = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
} else if (variant === 'with validation warnings') {
modelValidations.boolean = {
isValid: true,
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
}
} else if (editType === 'checkboxList') {
if (variant === 'with validation errors and warnings') {
modelValidations.checkboxList = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
} else if (variant === 'with validation errors') {
modelValidations.checkboxList = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
} else if (variant === 'with validation warnings') {
modelValidations.checkboxList = {
isValid: true,
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
}
} else if (editType === 'dateTimeLocal') {
if (variant === 'with validation errors and warnings') {
modelValidations.dateTimeLocal = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
} else if (variant === 'with validation errors') {
modelValidations.dateTimeLocal = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
} else if (variant === 'with validation warnings') {
modelValidations.dateTimeLocal = {
isValid: true,
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
}
} else if (editType === 'input') {
if (variant === 'with validation errors') {
modelValidations.input = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
}
} else if (editType === 'file') {
if (variant === 'with validation errors') {
// NOTICE! this generates a double error message, it's a bug in the code (error is already output by the `FormField`, see line 374, but is also output by `TextFile` via the argument `@validationError`)
modelValidations.file = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
}
} else if (editType === 'kv') {
if (variant === 'with validation errors') {
modelValidations.kv = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
}
} else if (editType === 'radio') {
if (variant === 'with validation errors and warnings') {
modelValidations.radio = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
} else if (variant === 'with validation errors') {
modelValidations.radio = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
} else if (variant === 'with validation warnings') {
modelValidations.radio = {
isValid: true,
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
}
} else if (editType === 'stringArray') {
if (variant === 'with validation errors') {
modelValidations.stringArray = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
}
} else if (editType === 'select') {
if (variant === 'with validation errors and warnings') {
modelValidations.select = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
} else if (variant === 'with validation errors') {
modelValidations.select = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
} else if (variant === 'with validation warnings') {
modelValidations.select = {
isValid: true,
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
}
} else if (editType === 'textarea') {
if (variant === 'with validation errors') {
modelValidations.textarea = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
}
} else if (editType === 'password') {
if (variant === 'with validation errors and warnings') {
modelValidations.password = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
} else if (variant === 'with validation errors') {
modelValidations.password = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
} else if (variant === 'with validation warnings') {
modelValidations.password = {
isValid: true,
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
}
} else if (editType === 'toggleButton') {
if (variant === 'with validation errors and warnings') {
modelValidations.toggleButton = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
} else if (variant === 'with validation errors') {
modelValidations.toggleButton = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
} else if (variant === 'with validation warnings') {
modelValidations.toggleButton = {
isValid: true,
warnings: [
'This is the validation warning message #1',
'This is the validation warning message #2',
],
};
}
} else if (editType === 'ttl') {
if (variant === 'with validation errors') {
// NOTICE: there is a bug in the CSS for the class "ttl-picker-form-field-error" because the border color is applied to the `input` child, but such element is hidden so the red border is not visible!
modelValidations.ttl = {
isValid: false,
errors: ['This is the validation error message #1', 'This is the validation error message #2'],
};
}
}
return modelValidations;
}
@action
dynamicFormFieldOptionsModels(editType) {
let optionsModels = [];
if (editType === 'searchSelect') {
// TODO! find which is the right format for this
optionsModels = [];
}
return optionsModels;
}
@action
dynamicFormFieldOptionsPossibleValues(editType, variant) {
let possibleValues = [];
if (editType === 'checkboxList') {
possibleValues = ['Lorem', 'Ipsum', 'Dolor'];
} else if (editType === 'radio') {
possibleValues = [
{
label: 'One',
value: 1,
},
{
label: 'Two',
value: 2,
},
{
label: 'Three',
value: 3,
},
];
if (variant === 'with item subText') {
possibleValues.forEach((i) => (i.subText = `Subtext for ${i.label.toLowerCase()}`));
}
if (variant === 'with item helpText') {
possibleValues.forEach((i) => (i.helpText = `Helptext for ${i.label.toLowerCase()}`));
}
} else if (editType === 'select') {
possibleValues = ['Lorem', 'Ipsum', 'Dolor'];
}
return possibleValues;
}
@action
dynamicFormFieldOptionsFieldValue(editType) {
let fieldValue;
if (editType === 'checkboxList') {
fieldValue = 'checkboxList';
} else if (editType === 'input') {
fieldValue = 'input';
} else if (editType === 'optionalText') {
fieldValue = 'optionalText';
} else if (editType === 'radio') {
fieldValue = 'radio';
} else if (editType === 'regex') {
fieldValue = 'regex';
} else if (editType === 'toggleButton') {
fieldValue = 'toggleButton';
}
return fieldValue;
}
@action
dynamicFormFieldHelperTextEnabledValue(editType, variant) {
let helperTextEnabled;
if (editType === 'ttl' && variant === 'with custom helper text') {
helperTextEnabled = 'Enabled state custom helper text';
} else if (
editType === 'toggleButton' &&
(variant === 'with custom helper text' ||
variant === 'checked' ||
variant === 'checked with opposite value' ||
variant === 'disabled')
) {
helperTextEnabled = 'Enabled state custom helper text';
}
return helperTextEnabled;
}
@action
dynamicFormFieldHelperTextDisabledValue(editType, variant) {
let helperTextDisabled;
if (editType === 'ttl' && variant === 'with custom helper text') {
helperTextDisabled = 'Disabled state custom helper text';
} else if (
editType === 'toggleButton' &&
(variant === 'with custom helper text' ||
variant === 'checked' ||
variant === 'checked with opposite value' ||
variant === 'disabled')
) {
helperTextDisabled = 'Disabled state custom helper text';
}
return helperTextDisabled;
}
// ###########################################
// READONLY-FORMFIELD
// ###########################################
@action
dynamicReadonlyFormFieldValue(attrType, variant) {
let value;
// if (attrType === 'select') {
// } else {
// }
if (variant === 'with value' || variant === 'with value + helpText + subText') {
value = 'Lorem ipsum';
}
return value;
}
// ###########################################
// OBJECT-LIST-INPUT
// ###########################################
@action
dynamicObjectListInputObjectKeys(variant) {
// any variant needs the definition of the object keys
if (variant) {
return [
{ label: 'Label for input A', key: 'A', placeholder: 'Placeholder for A' },
{ label: 'Label for input B', key: 'B', placeholder: 'Placeholder for B' },
{ label: 'Label for input C', key: 'C' },
];
}
}
@action
dynamicObjectListInputInputValue(variant) {
let inputValue = [];
if (variant === 'with single set of values') {
inputValue = [{ A: 'First value for A', B: 'First value for B', C: '' }];
} else if (variant === 'with multiple sets of values' || variant === 'with validation errors') {
inputValue = [
{ A: 'First value for A', B: 'First value for B', C: '' },
{ A: 'Second value for A', B: 'Second value for B', C: '' },
];
}
return inputValue;
}
@action
dynamicObjectListInputValidationErrors(variant) {
let validationErrors = [];
if (variant === 'with validation errors') {
validationErrors = [
{ A: { errors: ['Error message for first A'], isValid: false } },
{ B: { errors: ['Error message for second B'], isValid: false } },
];
}
return validationErrors;
}
// ###########################################
// OTHER
// ###########################################
@action
noop() {}
}

View file

@ -14,6 +14,13 @@ export default class Router extends EmberRouter {
Router.map(function () {
this.route('vault', { path: '/' }, function () {
this.route('cluster', { path: '/:cluster_name' }, function () {
// ----------------------
// ----------------------
// Form Elements - Showcase
// ----------------------
this.route('showcase');
// ----------------------
// ----------------------
this.route('dashboard');
this.mount('config-ui');
this.mount('sync');

View file

@ -8,6 +8,7 @@
@use 'ember-power-select';
@use 'core';
@use 'docs';
@use './showcase';
@mixin font-face($name) {
@font-face {

View file

@ -0,0 +1,95 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
/* SHOWCASE OF COMPONENTS */
@use './utils/font_variables';
.shw-page-wrapper {
padding: 24px 48px;
/* ------------------ */
/* local styling for showcase text elements [intentionally scrappy but simple to use] */
h1:not([class]) {
font-family: font_variables.$family-sans;
font-size: 48px;
line-height: 1.2em;
font-weight: 900;
color: #000;
margin: 24px 0 8px;
}
h2:not([class]) {
font-family: font_variables.$family-sans;
font-size: 32px;
line-height: 1.2em;
font-weight: 700;
color: #000;
margin: 24px 0 8px;
}
h3:not([class]) {
font-family: font_variables.$family-sans;
font-size: 24px;
line-height: 1.2em;
font-weight: bold;
color: #000;
margin: 24px 0 8px;
}
h4:not([class]) {
font-family: font_variables.$family-sans;
font-size: 16px;
line-height: 1.2em;
font-weight: bold;
color: #000;
margin: 24px 0 8px;
}
pre:not([class]) {
font-family: font_variables.$family-monospace;
font-size: 11px;
line-height: 1.2em;
font-weight: bold;
color: #999;
margin: 24px 0 4px;
}
blockquote:not([class]) {
display: block;
font-family: font_variables.$family-monospace;
font-size: 12px;
line-height: 1.4em;
color: #666;
font-style: italic;
margin: 24px 0 32px;
padding-left: 12px;
border-left: 2px solid #ccc;
}
hr:not([class]) {
border: none;
background: none;
border-top: 1px solid #bac1cc;
margin: 48px 0;
}
hr:not([class])[level='2'] {
border: none;
background: none;
border-top: 1px dotted #bac1cc;
margin: 36px 0;
}
/* stylelint-disable-next-line selector-class-pattern */
.isHds {
border-left: 3px solid #06be03;
padding-left: 12px;
}
/* ------------------ */
}

View file

@ -0,0 +1,704 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<div class="shw-page-wrapper">
<h1>Vault Components Showcase</h1>
<hr />
<h2>Form Components</h2>
<h3>FormFieldLabel</h3>
<blockquote>FormFieldLabel components add labels and descriptions to inputs</blockquote>
<pre>Only Label</pre>
<FormFieldLabel @label="This is the label" />
<pre>Label with HelpText</pre>
<FormFieldLabel @label="This is the label" @helpText="This is the help text" />
<pre>Only SubText</pre>
<FormFieldLabel @subText="This is the subtext" />
<pre>SubText with DocLink</pre>
<FormFieldLabel @subText="This is the subtext" @docLink="#" />
<pre>Label + SubText</pre>
<FormFieldLabel @label="This is the label" @subText="This is the subtext" />
<pre>Label with HelpText + SubText with DocLink</pre>
<FormFieldLabel @label="This is the label" @helpText="This is the help text" @subText="This is the subtext" @docLink="#" />
<hr />
<h3>FormField</h3>
<blockquote>FormField components are field elements associated with a particular model.</blockquote>
{{#let
(array
(hash
editType="boolean"
variants=(array
"base"
"with label"
"with helpText + subText + docLink"
"checked"
"disabled"
"with validation errors"
"with validation warnings"
"with validation errors and warnings"
)
)
(hash
isHds=true
editType="checkboxList"
variants=(array
"base"
"with label"
"with helpText + subText + docLink"
"with validation errors"
"with validation warnings"
"with validation errors and warnings"
)
)
(hash
editType="dateTimeLocal"
variants=(array
"base"
"with label"
"with helpText + subText + docLink"
"with value"
"with validation errors"
"with validation warnings"
"with validation errors and warnings"
)
)
(hash
editType="file" variants=(array "base" "with label" "with helpText + subText + docLink" "with validation errors")
)
(hash
editType="input"
attrType="string"
variants=(array
"base"
"with label"
"with helpText + subText + docLink"
"with placeholder"
"with value"
"with character limit"
"with validation errors"
"readonly"
"disabled"
)
)
(hash
editType="json"
attrType="string"
variants=(array "base" "with label" "with helpText" "with value" "with value + restore")
)
(hash
editType="kv"
variants=(array
"base"
"with label"
"with label as section header"
"with helpText + subText"
"with key/value placeholders"
"single row"
"with value"
"with validation errors"
"with whitespace in key"
)
)
(hash editType="mountAccessor" variants=(array "base" "with label" "with helpText" "with value"))
(hash editType="object" attrType="object" variants=(array "base" "with label" "with helpText" "with value"))
(hash
editType="optionalText"
variants=(array
"base" "with label" "with subText + docLink" "with default subText" "with value" "with value + subText + docLink"
)
)
(hash
isHds=true
editType="password"
attrType="string"
variants=(array
"base"
"with label"
"with label as section header"
"with helpText + subText + docLink"
"with value"
"with validation errors"
"with validation warnings"
"with validation errors and warnings"
)
)
(hash
isHds=true
editType="radio"
variants=(array
"base"
"with label"
"with helpText + subText + docLink"
"with item subText"
"with item helpText"
"disabled"
"with validation errors"
"with validation warnings"
"with validation errors and warnings"
)
)
(hash editType="regex" variants=(array "base" "with label" "with helpText + subText + docLink" "with value"))
(hash
editType="searchSelect"
variants=(array "base" "with label" "with label as section header" "with helpText + subText" "with value")
)
(hash
isHds=true
editType="select"
variants=(array
"base"
"with label"
"with label as section header"
"with helpText + subText"
"with helpText + subText + docLink"
"with no default"
"with value"
"with validation errors"
"with validation warnings"
"with validation errors and warnings"
)
)
(hash editType="stringArray" variants=(array "base" "with label" "with helpText + subText" "with value"))
(hash editType="sensitive" variants=(array "base" "with label" "with value" "with copy"))
(hash
isHds=true
editType="textarea"
attrType="string"
variants=(array "base" "with label" "with helpText + subText + docLink" "with value" "with validation errors")
)
(hash
editType="toggleButton"
attrType="boolean"
variants=(array
"base"
"with label"
"with custom helper text"
"checked"
"checked with opposite value"
"disabled"
"with validation errors"
"with validation warnings"
"with validation errors and warnings"
)
)
(hash
editType="ttl"
variants=(array
"base"
"with helpText"
"with custom helper texts"
"with value"
"with value 0s"
"with value 1h"
"with validation errors"
"with hidden toggle"
)
)
(hash editType="yield" variants=(array "yielded content"))
)
as |FormFieldTypes|
}}
{{#each FormFieldTypes as |FormFieldType index|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
{{#let FormFieldType.editType FormFieldType.attrType FormFieldType.variants as |editType attrType variants|}}
<h4>{{#if FormFieldType.isHds}}{{/if}} Type "{{editType}}"</h4>
{{#each variants as |variant|}}
{{#let
(fn this.dynamicFormFieldOptionsModels editType)
(fn this.dynamicFormFieldOptionsPossibleValues editType variant)
(fn this.dynamicFormFieldOptionsFieldValue editType)
(fn this.dynamicFormFieldHelperTextEnabledValue editType variant)
(fn this.dynamicFormFieldHelperTextDisabledValue editType variant)
as |dynamicFormFieldOptionsModels dynamicFormFieldOptionsPossibleValues dynamicFormFieldOptionsFieldValue dynamicFormFieldHelperTextEnabledValue dynamicFormFieldHelperTextDisabledValue|
}}
<pre>{{capitalize variant}}</pre>
<FormField
class={{if FormFieldType.isHds "isHds"}}
data-test-field={{true}}
@attr={{(hash
name=(if
(or
(eq editType "input")
(eq editType "optionalText")
(eq editType "radio")
(eq editType "regex")
(eq editType "toggleButton")
)
(concat editType "-" variant)
editType
)
type=attrType
options=(hash
editType=(if (eq editType "input") undefined editType)
label=(unless (eq variant "base") (concat "This is the label of the '" editType "' type of field"))
isSectionHeader=(if (eq variant "with label as section header") true)
helpText=(if
(or
(eq variant "with helpText")
(eq variant "with helpText + subText")
(eq variant "with helpText + subText + docLink")
(eq variant "with subText + docLink")
(eq variant "with value + subText + docLink")
)
"This is the help text"
)
subText=(if
(or
(eq variant "with subText")
(eq variant "with helpText + subText")
(eq variant "with helpText + subText + docLink")
(eq variant "with subText + docLink")
(eq variant "with value + subText + docLink")
)
"This is the subtext"
)
defaultSubText=(if
(and (eq editType "optionalText") (eq variant "with default subText")) "This is the default subtext"
)
docLink=(if
(or (eq variant "with helpText + subText + docLink") (eq variant "with value + subText + docLink")) "/#"
)
example=(if (and (eq editType "json") (eq variant "with value + restore")) this.jsonExample)
noCopy=(if (and (eq editType "sensitive") (eq variant "with copy")) false true)
hideToggle=(if (and (eq editType "ttl") (eq variant "with hidden toggle")) true false)
helperTextEnabled=(dynamicFormFieldHelperTextEnabledValue)
helperTextDisabled=(dynamicFormFieldHelperTextDisabledValue)
readOnly=(if (eq variant "readonly") true false)
editDisabled=(if (eq variant "disabled") "Enabled state custom helper text")
noDefault=(if (and (eq editType "select") (eq variant "with no default")) true false)
placeholder=(if (and (eq editType "input") (eq variant "with placeholder")) "This is the placeholder text")
keyPlaceholder=(if (and (eq editType "kv") (eq variant "with key/value placeholders")) "Key placeholder")
valuePlaceholder=(if
(and (eq editType "kv") (eq variant "with key/value placeholders")) "Value placeholder"
)
isSingleRow=(if (and (eq editType "kv") (eq variant "single row")) true false)
isOppositeValue=(if
(and (eq editType "toggleButton") (eq variant "checked with opposite value")) true false
)
characterLimit=(if (and (eq editType "input") (eq variant "with character limit")) 10)
models=(dynamicFormFieldOptionsModels)
possibleValues=(dynamicFormFieldOptionsPossibleValues)
fieldValue=(dynamicFormFieldOptionsFieldValue)
sensitive=(if (eq editType "sensitive") true false)
)
)}}
@disabled={{if (eq variant "disabled") true false}}
@mode={{if (eq variant "readonly") "edit"}}
@model={{this.dynamicFormFieldModel editType variant}}
@modelValidations={{this.dynamicFormFieldModelValidations editType variant}}
>
{{#if (eq editType "yield")}}
<span>This is yielded content</span>
{{/if}}
</FormField>
{{/let}}
{{/each}}
{{/let}}
{{/each}}
{{/let}}
<hr />
<h3>FormSaveButtons</h3>
<pre>Save</pre>
<FormSaveButtons @saveButtonText="Save" />
<pre>Save / Loading state (isSaving=true)</pre>
<FormSaveButtons @saveButtonText="Save" @isSaving={{true}} />
<pre>Save + Cancel (onCancel)</pre>
<FormSaveButtons @saveButtonText="Save" @cancelButtonText="Cancel" @onCancel={{this.noop}} />
<pre>Save + Cancel (cancelLinkParams)</pre>
<FormSaveButtons @saveButtonText="Save" @cancelButtonText="Cancel" @cancelLinkParams={{array "vault.cluster.storage"}} />
<pre>Save (isSaving=true / saveButtonText="Loading") + Cancel</pre>
<FormSaveButtons @saveButtonText="Save" @isSaving={{true}} @cancelButtonText="Cancel" @onCancel={{this.noop}} />
<pre>Without top border (includeBox=false)</pre>
<FormSaveButtons @saveButtonText="Save" @cancelButtonText="Cancel" @onCancel={{this.noop}} @includeBox={{false}} />
<hr />
<h3>ReadonlyFormField</h3>
{{#let
(array
(hash
attrType="text"
variants=(array "base" "with label" "with helpText + subText" "with value" "with value + helpText + subText")
)
(hash
attrType="password"
variants=(array "base" "with label" "with helpText + subText" "with value" "with value + helpText + subText")
)
(hash
attrType="date"
variants=(array "base" "with label" "with helpText + subText" "with value" "with value + helpText + subText")
)
(hash
attrType="select"
variants=(array "base" "with label" "with helpText + subText" "with value" "with value + helpText + subText")
)
)
as |ReadonlyFormFieldTypes|
}}
{{#each ReadonlyFormFieldTypes as |ReadonlyFormFieldType index|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
{{#let ReadonlyFormFieldType.attrType ReadonlyFormFieldType.variants as |attrType variants|}}
<h4>Input type="{{attrType}}"</h4>
{{#each variants as |variant|}}
<pre>{{capitalize variant}}</pre>
<ReadonlyFormField
@value={{this.dynamicReadonlyFormFieldValue attrType variant}}
@attr={{(hash
name=(concat attrType "-" variant)
type=attrType
options=(hash
label=(unless (eq variant "base") (concat "This is the label of the readonly '" attrType "' type of field"))
helpText=(if
(or
(eq variant "with helpText")
(eq variant "with helpText + subText")
(eq variant "with value + helpText + subText")
)
"This is the help text"
)
subText=(if
(or
(eq variant "with subText")
(eq variant "with helpText + subText")
(eq variant "with value + helpText + subText")
)
"This is the subtext"
)
possibleValues=(if (eq attrType "select") (array "a" "b" "c"))
)
)}}
/>
{{/each}}
{{/let}}
{{/each}}
{{/let}}
<hr />
<h3>FormFieldGroups</h3>
<p>TODO</p>
<hr />
<h3>FormFieldGroupsLoop</h3>
<p>TODO</p>
<hr />
<h3>FormFieldGroupFromModel</h3>
<p>TODO</p>
<hr />
<h3>AutocompleteInput</h3>
{{#let (array "base" "with label" "with subText" "with placeholder" "with value") as |variants index|}}
{{#each variants as |variant|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
<pre>{{capitalize variant}}</pre>
<AutocompleteInput
@label={{unless (eq variant "base") "This is the label of the 'AutocompleteInput' type of input"}}
@subText={{if (or (eq variant "with subText") (eq variant "with value + subText")) "Type '$' to see autocomplete"}}
@placeholder={{if (eq variant "with placeholder") "This is the placeholder text"}}
@value={{if (eq variant "with value") "Lorem"}}
@optionsTrigger="$"
@options={{(array
(hash label="Lorem" value="lorem") (hash label="Ipsum" value="ipsum") (hash label="Dolor" value="dolor")
)}}
@onChange={{this.noop}}
/>
{{/each}}
{{/let}}
<hr />
<h3>CommandInput</h3>
<Console::CommandInput
@isFullscreen={{false}}
@isRunning={{false}}
@value="lorem"
@onValueUpdate={{this.noop}}
@onFullscreen={{this.noop}}
@onExecuteCommand={{this.noop}}
@onShiftCommand={{this.noop}}
/>
<hr />
<h3>EnableInput</h3>
<blockquote>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.</blockquote>
<pre>With generic input / with Input placeholder (@label)</pre>
<div {{style width="fit-content"}}>
<EnableInput @label="This is the label">
<Input @type="text" @value="This input is yielded" />
</EnableInput>
</div>
<pre>With generic input / with ReadonlyFormField placeholder (@attr)</pre>
<div {{style width="fit-content"}}>
<EnableInput
@attr={{(hash
name="EnableInputWithReadonlyFormField"
type="text"
options=(hash label="This is the label" helpText="This is the help text" subText="This is the subtext")
)}}
>
<Input @type="text" @value="This input is yielded" />
</EnableInput>
</div>
<hr />
<h3>✅ FilterInput</h3>
<pre>Base</pre>
<FilterInput />
<pre>With value</pre>
<FilterInput value="Lorem ipsum" />
<pre>With icon hidden</pre>
<FilterInput value="Lorem ipsum" @hideIcon={{true}} />
<hr />
<h3>✅ InputSearch</h3>
<blockquote>This component renders an input that fires a callback on "keyup" containing the input's value</blockquote>
<pre>Base</pre>
<InputSearch @id="input-search-base" />
<pre>With label</pre>
<InputSearch @id="input-search-with-label" @label="This is the label" />
<pre>With subText</pre>
<InputSearch @id="input-search-with-subtext" @label="This is the label" @subText="This is the subtext" />
<pre>With placeholder</pre>
<InputSearch @id="input-search-with-placeholder" @label="This is the label" @placeholder="This is the placeholder" />
<pre>With value</pre>
<InputSearch @id="input-search-with-value" @label="This is the label" @initialValue="Lorem ipsum" />
<hr />
<h3>KvSuggestionInput</h3>
<blockquote>Input component that fetches secrets at a provided mount path and displays them as suggestions in a dropdown.
As the user types the result set will be filtered providing suggestions for the user to select. After the input debounce
wait time (500ms), if the value ends in a slash, secrets will be fetched at that path. The new result set will then be
displayed in the dropdown as suggestions for the newly inputted path. Selecting a suggestion will append it to the input
value. This allows the user to build a full path to a secret for the provided mount. This is useful for helping the user
find deeply nested secrets given the path based policy system. If the user does not have list permission they are still
able to enter a path to a secret but will not see suggestions. Input is disabled when mount path is not provided</blockquote>
{{#let (array "base" "with label" "with subText" "no mountpath") as |variants index|}}
{{#each variants as |variant|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
<pre>{{capitalize variant}}</pre>
<KvSuggestionInput
@label={{unless (eq variant "base") "This is the label of the 'KvSuggestionInput'"}}
@subText={{if (eq variant "with subText") "This is the subtext"}}
@value="lorem-ipsum/"
@mountPath={{(unless (eq variant "no mountpath") "secret/")}}
@onChange={{this.noop}}
/>
{{/each}}
{{/let}}
<hr />
<h3>MaskedInput</h3>
<blockquote>MaskedInput components are textarea inputs where the input is hidden. They are used to enter sensitive
information like passwords.</blockquote>
{{#let
(array
"base"
"with label"
"with value"
"allow download"
"allow copy + download"
"display only"
"display only with value"
"display only with empty string as value"
)
as |variants index|
}}
{{#each variants as |variant|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
<pre>{{capitalize variant}}</pre>
<MaskedInput
@name={{concat "masked-input-" variant}}
@value={{if
(or
(eq variant "with value")
(eq variant "allow download")
(eq variant "allow copy + download")
(eq variant "display only with value")
)
"lorem ipsum"
(if (eq variant "display only with empty string as value") "")
}}
@allowCopy={{if (or (eq variant "allow copy") (eq variant "allow copy + download")) true false}}
@allowDownload={{if (or (eq variant "allow download") (eq variant "allow copy + download")) true false}}
@displayOnly={{if
(or
(eq variant "display only")
(eq variant "display only with value")
(eq variant "display only with empty string as value")
)
true
false
}}
@onChange={{this.noop}}
/>
{{/each}}
{{/let}}
<hr />
<h3>✅ NavigateInput</h3>
<blockquote>NavigateInput components are used to filter list data.</blockquote>
{{#let (array "base" "with placeholder" "with value") as |variants index|}}
{{#each variants as |variant|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
<pre>{{capitalize variant}}</pre>
<NavigateInput
@filter={{(if (eq variant "with value") "lorem ipsum")}}
@placeholder={{(if (eq variant "with placeholder") "This is the placeholder")}}
@filterDidChange={{this.noop}}
/>
{{/each}}
{{/let}}
<hr />
<h3>ObjectListInput</h3>
<blockquote>ObjectListInput components are used to render a variable number of text inputs in a single row with an "Add"
button at the end of the row. Clicking 'add' generates a new row of empty inputs. Each input field is generated by an
object in the @objectKeys array. Labels render above each column.</blockquote>
{{#let
(array "base" "with single set of values" "with multiple sets of values" "with validation errors")
as |variants index|
}}
{{#each variants as |variant|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
<pre>{{capitalize variant}}</pre>
<ObjectListInput
@objectKeys={{this.dynamicObjectListInputObjectKeys variant}}
@inputValue={{this.dynamicObjectListInputInputValue variant}}
@validationErrors={{this.dynamicObjectListInputValidationErrors variant}}
/>
{{/each}}
{{/let}}
<hr />
<h3>Select</h3>
{{#let (array "base" "with label" "with no default" "with selected value" "inline" "full width") as |variants index|}}
{{#each variants as |variant|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
<pre>{{capitalize variant}}</pre>
<Select
@label={{unless (eq variant "base") "This is the label of the 'Select'"}}
@selectedValue={{if (eq variant "with selected value") 123456}}
@labelAttribute="label"
@valueAttribute="value"
@options={{(array
(hash label="Lorem ipsum" value="lorem")
(hash label="123456" value=123456)
(hash label="Disabled option" value="disabled" isDisabled=true)
)}}
@isInline={{if (eq variant "inline") true false}}
@isFullwidth={{if (eq variant "full width") true false}}
@onChange={{this.noop}}
/>
{{/each}}
{{/let}}
<hr />
<h3>TextFile</h3>
<blockquote>TextFile components render a file upload input with the option to toggle a "Enter as text" button that changes
the input into a textarea</blockquote>
{{#let
(array "base" "base / upload only" "with label" "with helpText + subText + docLink" "with validation errors")
as |variants index|
}}
{{#each variants as |variant|}}
{{#unless (eq index 0)}}
<hr level="2" />
{{/unless}}
<pre>{{capitalize variant}}</pre>
<TextFile
@uploadOnly={{if (eq variant "base / upload only") true false}}
@label={{if (not (or (eq variant "base") (eq variant "base / upload only"))) "This is the label of the 'Select'"}}
@helpText={{if (eq variant "with helpText + subText + docLink") "This is the helptext"}}
@subText={{if (eq variant "with helpText + subText + docLink") "This is the subtext"}}
@docLink={{if (eq variant "with helpText + subText + docLink") "/#"}}
@validationError={{if (eq variant "with validation errors") "This is the validation error message"}}
/>
{{/each}}
{{/let}}
</div>

View file

@ -4,7 +4,7 @@
}}
{{! template-lint-configure simple-unless "warn" }}
<div class="field" data-test-field={{@attr.name}}>
<div class="field" data-test-field={{@attr.name}} ...attributes>
{{#if this.isHdsFormField}}
{{! •••••••••••••••••••••••••••••••••••••••••••••••••••••••• }}
{{! HDS COMPONENTS - START }}

View file

@ -11,7 +11,8 @@
{{#let (find-by "key" inputKey @objectKeys) as |field|}}
<div class="column">
{{#if (eq index 0)}}
<h2 data-test-object-list-label={{inputKey}}>{{field.label}}</h2>
{{! TODO! remove the class name here, it was added only for testing purposes, to avoid applying styles to it }}
<h2 class="todo-remove" data-test-object-list-label={{inputKey}}>{{field.label}}</h2>
{{/if}}
{{#let (get rowValidations field.key) as |inputError|}}
<Input