mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 19:47:54 +00:00
Button Conversion Part 1 (#23633)
* adds codemod for transforming button element to hds component * runs button codemod on kmip and kubernetes enginges * manully updates kuberenetes roles button * runs button codemod on ldap engine * manually updates remaining ldap buttons * updates button codemod to check if all child nodes were included in text arg construction * runs button codemod on kv engine * adds comment for future kv button update * Update ui/lib/kv/addon/components/page/secret/details.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * updates remaining instance of toolbar-link class on button and adds class name transform to button codemod * adds display inline override to hds button * updates hds button display override to inline-flex * updates ldap account check in button to tertiary * updates ldap library check out icon to tertiary and adds icon --------- Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
This commit is contained in:
@@ -383,6 +383,7 @@ a.button.disabled {
|
||||
// Existing class on <Hds::Button> component, modifying to match existing UI Structure buttons
|
||||
.hds-button {
|
||||
font-weight: $font-weight-semibold; // TODO consult design on font weight after button class audit
|
||||
display: inline-flex; // temporarily fixes existing button alignment until we adopt Hds::ButtonSet
|
||||
// for toolbar-button must pass arg @color="secondary"
|
||||
&.toolbar-button {
|
||||
color: $black;
|
||||
|
||||
@@ -86,14 +86,13 @@
|
||||
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless">
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text={{this.saveButtonText}}
|
||||
@icon={{if this.save.isRunning "loading"}}
|
||||
type="submit"
|
||||
data-test-edit-form-submit
|
||||
class="button is-primary {{if this.save.isRunning 'loading'}}"
|
||||
disabled={{this.save.isRunning}}
|
||||
>
|
||||
{{this.saveButtonText}}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
{{#if this.cancelLink}}
|
||||
<div class="control">
|
||||
|
||||
@@ -64,14 +64,14 @@
|
||||
Configuration values can be inferred from the pod and your local environment variables.
|
||||
</p>
|
||||
<div>
|
||||
<button
|
||||
class="button has-top-margin-s {{if this.fetchInferred.isRunning 'is-loading'}}"
|
||||
type="button"
|
||||
<Hds::Button
|
||||
@text="Get config values"
|
||||
@color="secondary"
|
||||
@icon={{if this.fetchInferred.isRunning "loading"}}
|
||||
class="has-top-margin-s"
|
||||
disabled={{this.fetchInferred.isRunning}}
|
||||
{{on "click" (perform this.fetchInferred)}}
|
||||
>
|
||||
Get config values
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -79,24 +79,15 @@
|
||||
<hr class="has-background-gray-200 has-top-margin-l" />
|
||||
|
||||
<div class="has-top-margin-s has-bottom-margin-s is-flex">
|
||||
<button
|
||||
data-test-config-save
|
||||
class="button is-primary"
|
||||
type="button"
|
||||
disabled={{this.isDisabled}}
|
||||
{{on "click" (perform this.save)}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
<Hds::Button @text="Save" data-test-config-save disabled={{this.isDisabled}} {{on "click" (perform this.save)}} />
|
||||
<Hds::Button
|
||||
@text="Back"
|
||||
@color="secondary"
|
||||
class="has-left-margin-xs"
|
||||
data-test-config-cancel
|
||||
class="button has-left-margin-xs"
|
||||
type="button"
|
||||
disabled={{or this.save.isRunning this.fetchInferred.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
/>
|
||||
{{#if this.alert}}
|
||||
<AlertInline @type="danger" @paddingTop={{true}} @message={{this.alert}} @mimicRefresh={{true}} data-test-alert />
|
||||
{{/if}}
|
||||
|
||||
@@ -46,9 +46,7 @@
|
||||
</div>
|
||||
|
||||
<div class="has-top-margin-l">
|
||||
<button class="button is-primary" type="button" data-test-generate-credentials-done {{on "click" this.cancel}}>
|
||||
Done
|
||||
</button>
|
||||
<Hds::Button @text="Done" data-test-generate-credentials-done {{on "click" this.cancel}} />
|
||||
</div>
|
||||
{{else}}
|
||||
<div data-test-generate-credentials>
|
||||
@@ -99,23 +97,21 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="has-top-margin-l">
|
||||
<button
|
||||
class="button is-primary {{if this.fetchCredentials.isRunning 'is-loading'}}"
|
||||
<Hds::Button
|
||||
@text="Generate credentials"
|
||||
@icon={{if this.fetchCredentials.isRunning "loading"}}
|
||||
type="submit"
|
||||
disabled={{this.fetchCredentials.isRunning}}
|
||||
data-test-generate-credentials-button
|
||||
>
|
||||
Generate credentials
|
||||
</button>
|
||||
<button
|
||||
class="button has-left-margin-xs"
|
||||
type="button"
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Back"
|
||||
@color="secondary"
|
||||
class="has-left-margin-xs"
|
||||
disabled={{this.fetchCredentials.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
data-test-generate-credentials-back
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -33,15 +33,14 @@
|
||||
@fallbackComponent="input-search"
|
||||
@onChange={{this.selectRole}}
|
||||
/>
|
||||
<button
|
||||
class="button has-left-margin-s"
|
||||
type="button"
|
||||
<Hds::Button
|
||||
@text="Generate"
|
||||
@color="secondary"
|
||||
class="has-left-margin-s"
|
||||
disabled={{not this.selectedRole}}
|
||||
{{on "click" this.generateCredential}}
|
||||
data-test-generate-credential-button
|
||||
>
|
||||
Generate
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</OverviewCard>
|
||||
</div>
|
||||
|
||||
@@ -116,10 +116,14 @@
|
||||
@valueUpdated={{fn (mut template.rules)}}
|
||||
@helpText={{sanitized-html this.roleRulesHelpText}}
|
||||
>
|
||||
<button type="button" class="toolbar-link" {{on "click" this.resetRoleRules}} data-test-restore-example>
|
||||
Restore example
|
||||
<Icon @name="reload" />
|
||||
</button>
|
||||
<Hds::Button
|
||||
@icon="reload"
|
||||
@text="Restore example"
|
||||
@color="secondary"
|
||||
class="toolbar-button"
|
||||
{{on "click" this.resetRoleRules}}
|
||||
data-test-restore-example
|
||||
/>
|
||||
</JsonEditor>
|
||||
{{/let}}
|
||||
</div>
|
||||
@@ -141,16 +145,13 @@
|
||||
<hr class="is-marginless has-background-gray-200" />
|
||||
|
||||
<div class="has-top-margin-l has-bottom-margin-s">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text="Save"
|
||||
@icon={{if this.save.isRunning "loading"}}
|
||||
type="submit"
|
||||
form="role"
|
||||
class="button is-primary {{if this.save.isRunning 'is-loading'}}"
|
||||
disabled={{or (not @model.generationPreference) this.save.isRunning}}
|
||||
data-test-save
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="button has-left-margin-s" data-test-cancel {{on "click" this.cancel}}>
|
||||
Back
|
||||
</button>
|
||||
/>
|
||||
<Hds::Button @text="Back" @color="secondary" class="has-left-margin-s" data-test-cancel {{on "click" this.cancel}} />
|
||||
</div>
|
||||
@@ -43,9 +43,7 @@
|
||||
<Item.menu as |Menu|>
|
||||
{{#if role.rolesPath.isLoading}}
|
||||
<li class="action">
|
||||
<button disabled type="button" class="link button is-loading is-transparent">
|
||||
loading
|
||||
</button>
|
||||
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="action">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
{{#each @metadata.sortedVersions as |versionData|}}
|
||||
<li data-test-version={{versionData.version}} class="action">
|
||||
{{#if @onSelect}}
|
||||
{{! TODO Hds::Button manual update }}
|
||||
<button
|
||||
disabled={{or versionData.destroyed versionData.isSecretDeleted}}
|
||||
{{on "click" (fn @onSelect versionData.version D.actions)}}
|
||||
|
||||
@@ -32,9 +32,13 @@
|
||||
@placeholder="secret/"
|
||||
data-test-view-secret
|
||||
/>
|
||||
<button type="submit" class="button is-secondary" disabled={{not this.secretPath}} data-test-get-secret-detail>
|
||||
{{this.buttonText}}
|
||||
</button>
|
||||
<Hds::Button
|
||||
@text={{this.buttonText}}
|
||||
@color="secondary"
|
||||
type="submit"
|
||||
disabled={{not this.secretPath}}
|
||||
data-test-get-secret-detail
|
||||
/>
|
||||
</form>
|
||||
{{#if @failedDirectoryQuery}}
|
||||
<AlertInline @type="danger" @message="You do not have the required permissions or the directory does not exist." />
|
||||
|
||||
@@ -23,9 +23,13 @@
|
||||
</:toolbarFilters>
|
||||
<:toolbarActions>
|
||||
{{#if this.showUndelete}}
|
||||
<button data-test-kv-delete="undelete" type="button" class="toolbar-link" {{on "click" this.undelete}}>
|
||||
Undelete
|
||||
</button>
|
||||
<Hds::Button
|
||||
@text="Undelete"
|
||||
@color="secondary"
|
||||
class="toolbar-button"
|
||||
data-test-kv-delete="undelete"
|
||||
{{on "click" this.undelete}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if this.showDelete}}
|
||||
<KvDeleteModal
|
||||
|
||||
@@ -70,23 +70,21 @@
|
||||
</div>
|
||||
<div class="box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text="Save"
|
||||
@icon={{if this.save.isRunning "loading"}}
|
||||
type="submit"
|
||||
class="button is-primary {{if this.save.isRunning 'is-loading'}}"
|
||||
disabled={{this.save.isRunning}}
|
||||
data-test-kv-save
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button has-left-margin-s"
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
@color="secondary"
|
||||
class="has-left-margin-s"
|
||||
disabled={{this.save.isRunning}}
|
||||
{{on "click" this.onCancel}}
|
||||
data-test-kv-cancel
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
{{#if this.invalidFormAlert}}
|
||||
<AlertInline
|
||||
|
||||
@@ -13,23 +13,21 @@
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless">
|
||||
<div class="has-top-padding-s">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text="Update"
|
||||
@icon={{if this.save.isRunning "loading"}}
|
||||
type="submit"
|
||||
class="button is-primary {{if this.save.isRunning 'is-loading'}}"
|
||||
disabled={{this.save.isRunning}}
|
||||
data-test-kv-save
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button has-left-margin-s"
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
@color="secondary"
|
||||
class="has-left-margin-s"
|
||||
disabled={{this.save.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
data-test-kv-cancel
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
/>
|
||||
{{#if this.invalidFormAlert}}
|
||||
<div class="control">
|
||||
<AlertInline
|
||||
|
||||
@@ -40,23 +40,21 @@
|
||||
</div>
|
||||
<div class="box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text="Save"
|
||||
@icon={{if this.save.isRunning "loading"}}
|
||||
type="submit"
|
||||
class="button is-primary {{if this.save.isRunning 'is-loading'}}"
|
||||
disabled={{this.save.isRunning}}
|
||||
data-test-kv-save
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button has-left-margin-s"
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
@color="secondary"
|
||||
class="has-left-margin-s"
|
||||
disabled={{this.save.isRunning}}
|
||||
{{on "click" this.onCancel}}
|
||||
data-test-kv-cancel
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
{{#if this.invalidFormAlert}}
|
||||
<AlertInline
|
||||
|
||||
@@ -15,16 +15,14 @@
|
||||
<Body.Td data-test-checked-out-library={{Body.data.account}}>{{Body.data.library}}</Body.Td>
|
||||
{{/if}}
|
||||
<Body.Td>
|
||||
<button
|
||||
type="button"
|
||||
class="text-button has-text-primary has-text-weight-semibold"
|
||||
<Hds::Button
|
||||
@icon="queue"
|
||||
@text="Check-in"
|
||||
@color="tertiary"
|
||||
disabled={{this.disableCheckIn Body.data.library}}
|
||||
data-test-checked-out-account-action={{Body.data.account}}
|
||||
{{on "click" (fn (mut this.selectedStatus) Body.data)}}
|
||||
>
|
||||
<Icon @name="queue" />
|
||||
Check-in
|
||||
</button>
|
||||
/>
|
||||
</Body.Td>
|
||||
</Body.Tr>
|
||||
</:body>
|
||||
|
||||
@@ -47,24 +47,21 @@
|
||||
<hr class="has-background-gray-200 has-top-margin-l" />
|
||||
|
||||
<div class="has-top-margin-l has-bottom-margin-l is-flex">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text="Save"
|
||||
data-test-config-save
|
||||
class="button is-primary"
|
||||
type="submit"
|
||||
disabled={{or this.save.isRunning (not @model.schema)}}
|
||||
{{on "click" (perform this.save)}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Back"
|
||||
@color="secondary"
|
||||
class="has-left-margin-xs"
|
||||
data-test-config-cancel
|
||||
class="button has-left-margin-xs"
|
||||
type="button"
|
||||
disabled={{or this.save.isRunning this.fetchInferred.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
/>
|
||||
{{#if this.invalidFormMessage}}
|
||||
<AlertInline
|
||||
@type="danger"
|
||||
|
||||
@@ -44,9 +44,7 @@
|
||||
<Item.menu as |Menu|>
|
||||
{{#if library.libraryPath.isLoading}}
|
||||
<li class="action">
|
||||
<button disabled type="button" class="link button is-loading is-transparent">
|
||||
loading
|
||||
</button>
|
||||
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="action">
|
||||
|
||||
@@ -39,12 +39,9 @@
|
||||
</div>
|
||||
|
||||
<div class="has-top-margin-xl has-bottom-margin-l">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text="Done"
|
||||
data-test-done
|
||||
class="button is-primary"
|
||||
type="button"
|
||||
{{on "click" (transition-to "vault.cluster.secrets.backend.ldap.libraries.library.details.accounts")}}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
@@ -21,18 +21,20 @@
|
||||
<hr class="has-background-gray-200 has-top-margin-l" />
|
||||
|
||||
<div class="has-top-margin-l has-bottom-margin-l is-flex">
|
||||
<button data-test-save class="button is-primary" type="submit" disabled={{this.save.isRunning}}>
|
||||
{{if @model.isNew "Create library" "Save"}}
|
||||
</button>
|
||||
<button
|
||||
<Hds::Button
|
||||
@text={{if @model.isNew "Create library" "Save"}}
|
||||
data-test-save
|
||||
type="submit"
|
||||
disabled={{this.save.isRunning}}
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
@color="secondary"
|
||||
class="has-left-margin-xs"
|
||||
data-test-cancel
|
||||
class="button has-left-margin-xs"
|
||||
type="button"
|
||||
disabled={{this.save.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
/>
|
||||
{{#if this.invalidFormMessage}}
|
||||
<AlertInline
|
||||
@type="danger"
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
<div class="is-flex-between">
|
||||
<h3 class="is-size-5 has-text-weight-semibold">All accounts</h3>
|
||||
{{#if @library.canCheckOut}}
|
||||
<button
|
||||
type="button"
|
||||
class="button is-link"
|
||||
<Hds::Button
|
||||
@text="Check-out"
|
||||
@color="tertiary"
|
||||
@icon="arrow-up-right"
|
||||
data-test-check-out
|
||||
{{on "click" (fn (mut this.showCheckOutPrompt) true)}}
|
||||
>
|
||||
Check-out
|
||||
</button>
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -53,15 +53,14 @@
|
||||
@fallbackComponent="input-search"
|
||||
@onChange={{this.selectRole}}
|
||||
/>
|
||||
<button
|
||||
class="button has-left-margin-s"
|
||||
type="button"
|
||||
<Hds::Button
|
||||
@text="Get credentials"
|
||||
@color="secondary"
|
||||
class="has-left-margin-s"
|
||||
disabled={{not this.selectedRole}}
|
||||
{{on "click" this.generateCredentials}}
|
||||
data-test-generate-credential-button
|
||||
>
|
||||
Get credentials
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</OverviewCard>
|
||||
</div>
|
||||
|
||||
@@ -45,18 +45,20 @@
|
||||
<hr class="has-background-gray-200 has-top-margin-l" />
|
||||
|
||||
<div class="has-top-margin-l has-bottom-margin-l is-flex">
|
||||
<button data-test-save class="button is-primary" type="submit" disabled={{this.save.isRunning}}>
|
||||
{{if @model.isNew "Create role" "Save"}}
|
||||
</button>
|
||||
<button
|
||||
<Hds::Button
|
||||
@text={{if @model.isNew "Create role" "Save"}}
|
||||
data-test-save
|
||||
type="submit"
|
||||
disabled={{this.save.isRunning}}
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Cancel"
|
||||
@color="secondary"
|
||||
class="has-left-margin-xs"
|
||||
data-test-cancel
|
||||
class="button has-left-margin-xs"
|
||||
type="button"
|
||||
disabled={{this.save.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
/>
|
||||
{{#if this.invalidFormMessage}}
|
||||
<AlertInline
|
||||
@type="danger"
|
||||
|
||||
@@ -56,13 +56,10 @@
|
||||
</div>
|
||||
|
||||
<div class="has-top-margin-xl has-bottom-margin-l">
|
||||
<button
|
||||
<Hds::Button
|
||||
@text="Done"
|
||||
data-test-done
|
||||
class="button is-primary"
|
||||
type="button"
|
||||
{{on "click" (transition-to "vault.cluster.secrets.backend.ldap.roles.role.details")}}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -51,9 +51,7 @@
|
||||
<Item.menu as |Menu|>
|
||||
{{#if role.rolePath.isLoading}}
|
||||
<li class="action">
|
||||
<button disabled type="button" class="link button is-loading is-transparent">
|
||||
loading
|
||||
</button>
|
||||
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="action">
|
||||
|
||||
284
ui/scripts/codemods/hds/button.js
Executable file
284
ui/scripts/codemods/hds/button.js
Executable file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* codemod to transform button html element to Hds::Button component
|
||||
* transformation is skipped if is-ghost or is-transparent is found in class list
|
||||
* if loading or is-loading is found to be a conditionally applied class the loading icon will be conditionally applied instead
|
||||
* if the text arg cannot be built from the child nodes (chained if block or multiple nodes that cannot be easily combined) the transformation will be skipped
|
||||
* classes relevant to the legacy button will be removed (see classesToRemove array)
|
||||
* html onclick event handler will be replaced with the {{on "click"}} modifier
|
||||
*
|
||||
* example execution from ui directory:
|
||||
** -> npx ember-template-recast ./app/templates -t ./scripts/codemods/hds/button.js
|
||||
* for best results run prettier after:
|
||||
** -> npx ember-template-recast ./app/templates -t ./scripts/codemods/hds/button.js && npx prettier --config .prettierrc.js --write ./app/templates
|
||||
*/
|
||||
|
||||
class Transforms {
|
||||
// button classes that will be removed from attribute
|
||||
classesToRemove = [
|
||||
'button',
|
||||
'is-compact',
|
||||
'is-danger',
|
||||
'is-danger-outlined',
|
||||
'is-flat',
|
||||
'is-icon',
|
||||
'is-loading',
|
||||
'is-link',
|
||||
'is-primary',
|
||||
'tool-tip-trigger',
|
||||
'is-secondary',
|
||||
];
|
||||
classesToTransform = [{ current: 'toolbar-link', updated: 'toolbar-button' }];
|
||||
|
||||
constructor(node, builders) {
|
||||
this.node = node;
|
||||
this.attrs = [];
|
||||
this.modifiers = [...node.modifiers];
|
||||
this.builders = builders;
|
||||
this.hasIcon = false;
|
||||
this.hasText = false;
|
||||
}
|
||||
|
||||
shouldTransform() {
|
||||
// buttons that have the is-ghost and/or is-transparent class will not be transformed
|
||||
// these usages have unclear mappings to tertiary buttons and in some cases will be replaced with Hds::Interactive
|
||||
const classAttr = this.node.attributes.find((attr) => attr.name === 'class');
|
||||
if (classAttr) {
|
||||
const shouldTransform = (chars) => {
|
||||
return chars.includes('is-ghost') || chars.includes('is-transparent') ? false : true;
|
||||
};
|
||||
if (classAttr.value.type === 'ConcatStatement') {
|
||||
for (const part of classAttr.value.parts) {
|
||||
if (part.type === 'TextNode' && !shouldTransform(part.chars)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return shouldTransform(classAttr.value.chars);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
addAttr(name, value) {
|
||||
this.attrs.push(this.builders.attr(name, value));
|
||||
}
|
||||
|
||||
filterClassTextNode(value) {
|
||||
// map color related classes to @color args
|
||||
let color = 'secondary'; // currently the default for .button class
|
||||
for (const colorClass of ['is-primary', 'is-danger', 'is-danger-outlined']) {
|
||||
if (value.chars.includes(colorClass)) {
|
||||
color = colorClass === 'is-primary' ? null : 'critical';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (color) {
|
||||
this.addAttr('@color', this.builders.text(color));
|
||||
}
|
||||
// remove button related classes no longer needed
|
||||
// map unused classes to new ones
|
||||
const classArray = value.chars.split(' ');
|
||||
const chars = classArray
|
||||
.filter((className) => !this.classesToRemove.includes(className))
|
||||
.map((className) => {
|
||||
const transform = this.classesToTransform.find((classHash) => classHash.current === className);
|
||||
return transform?.updated || className;
|
||||
})
|
||||
.join(' ');
|
||||
return chars ? { ...value, chars } : null;
|
||||
}
|
||||
|
||||
convertIsLoadingMustache(part, filteredParts) {
|
||||
let isLoading = false;
|
||||
const filteredParams = part.params.map((param) => {
|
||||
if (param.type === 'StringLiteral' && param.value.includes('loading')) {
|
||||
// rebuild param since icon name is loading and class name could be is-loading
|
||||
isLoading = true;
|
||||
return this.builders.string('loading');
|
||||
}
|
||||
return param;
|
||||
});
|
||||
if (isLoading) {
|
||||
this.addAttr('@icon', this.builders.mustache('if', filteredParams));
|
||||
} else {
|
||||
filteredParts.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
filterClassConcatStatement(attr) {
|
||||
const filteredParts = [];
|
||||
attr.value.parts.forEach((part) => {
|
||||
if (part.type === 'TextNode') {
|
||||
const value = this.filterClassTextNode(part);
|
||||
if (value) {
|
||||
filteredParts.push(value);
|
||||
}
|
||||
} else if (part.type === 'MustacheStatement') {
|
||||
this.convertIsLoadingMustache(part, filteredParts);
|
||||
} else {
|
||||
filteredParts.push(part);
|
||||
}
|
||||
});
|
||||
if (filteredParts.length) {
|
||||
return filteredParts.length === 1 ? filteredParts[0] : { ...attr.value, parts: filteredParts };
|
||||
}
|
||||
}
|
||||
|
||||
filterClasses(attr) {
|
||||
if (attr.name === 'class') {
|
||||
let attrValue = attr.value;
|
||||
const { type } = attrValue;
|
||||
if (type === 'ConcatStatement') {
|
||||
attrValue = this.filterClassConcatStatement(attr);
|
||||
} else if (type === 'TextNode') {
|
||||
attrValue = this.filterClassTextNode(attr.value);
|
||||
}
|
||||
if (attrValue) {
|
||||
this.addAttr('class', attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
convertOnClick(attr) {
|
||||
const params = [this.builders.string('click')];
|
||||
if (!attr.value.params.length) {
|
||||
params.push(attr.value.path);
|
||||
} else {
|
||||
params.push(this.builders.sexpr(attr.value.path, attr.value.params));
|
||||
}
|
||||
const onClickModifier = this.builders.elementModifier('on', params);
|
||||
this.modifiers.push(onClickModifier);
|
||||
}
|
||||
|
||||
filterAttributes() {
|
||||
this.node.attributes.forEach((attr) => {
|
||||
if (attr.name === 'class') {
|
||||
return this.filterClasses(attr);
|
||||
} else if (attr.name === 'onclick') {
|
||||
return this.convertOnClick(attr);
|
||||
} else if (attr.name === 'type' && attr.value.chars === 'button') {
|
||||
// remove type="button" attribute since it is default
|
||||
return;
|
||||
}
|
||||
this.attrs.push(attr);
|
||||
});
|
||||
}
|
||||
|
||||
textToString(node) {
|
||||
// filter out escape charaters like \n and whitespace from TextNode and rebuild as StringLiteral
|
||||
const text = decodeURI(node.chars).trim();
|
||||
if (text) {
|
||||
return this.builders.string(text);
|
||||
}
|
||||
}
|
||||
|
||||
filterTextNode(node, parts) {
|
||||
if (node.type === 'TextNode') {
|
||||
const text = this.textToString(node);
|
||||
if (text) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
convertBlockStatementNode(node, parts) {
|
||||
// convert if/else block statement to inline if mustache
|
||||
if (node.type === 'BlockStatement' && node.path.original === 'if' && !node.inverse.chained) {
|
||||
// only deal with text nodes -- more complex expressions should be converted to getter on component
|
||||
const program = node.program.body;
|
||||
const ifValueNode = program.length === 1 && program[0].type === 'TextNode' ? program[0] : null;
|
||||
const inverse = node.inverse.body;
|
||||
const elseValueNode = inverse.length === 1 && inverse[0].type === 'TextNode' ? inverse[0] : null;
|
||||
|
||||
if (ifValueNode && elseValueNode) {
|
||||
const params = [...node.params, this.textToString(ifValueNode), this.textToString(elseValueNode)];
|
||||
parts.push(this.builders.mustache(node.path, params));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
convertIconNode(node) {
|
||||
if (node.tag === 'Icon') {
|
||||
const nameAttr = node.attributes.find((attr) => attr.name === '@name');
|
||||
this.addAttr('@icon', this.builders.string(nameAttr.value.chars));
|
||||
// Hds::Button has @iconPosition arg when used with text
|
||||
// it seems most usages with button are leading which is default and recommended
|
||||
this.hasIcon = true;
|
||||
}
|
||||
}
|
||||
|
||||
pushAcceptedNodes(node, parts) {
|
||||
// some nodes may not need conversion and can be added to the @text assembly as is
|
||||
const acceptedNodes = ['MustacheStatement'];
|
||||
if (acceptedNodes.includes(node.type)) {
|
||||
parts.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
childNodesToArgs() {
|
||||
// convert child nodes to a format supported by an attr value for @text arg
|
||||
const parts = [];
|
||||
this.node.children.forEach((node) => {
|
||||
// following methods are used to build the @text arg
|
||||
this.filterTextNode(node, parts);
|
||||
this.convertBlockStatementNode(node, parts);
|
||||
this.pushAcceptedNodes(node, parts);
|
||||
// we also need to set the icon related args
|
||||
this.convertIconNode(node);
|
||||
});
|
||||
|
||||
// filter out ignored text nodes (\n) and compare with out compiled parts
|
||||
// if the lengths do not match then we were unable to transform a part and we must abort text build
|
||||
const relevantParts = this.node.children.filter((node) => {
|
||||
if (node.type === 'TextNode' && !this.textToString(node)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (parts.length && relevantParts.length === parts.length) {
|
||||
const value = parts.length === 1 ? parts[0] : this.builders.concat(parts);
|
||||
this.addAttr('@text', value);
|
||||
this.hasText = true;
|
||||
} else if (this.hasIcon) {
|
||||
// if there was an icon node but no text we need to add the @isIconOnly arg
|
||||
this.addAttr('@isIconOnly', this.builders.mustache(this.builders.boolean(true)));
|
||||
}
|
||||
}
|
||||
|
||||
buildElement() {
|
||||
if (this.hasText || this.hasIcon) {
|
||||
return this.builders.element(
|
||||
{ name: 'Hds::Button', selfClosing: true },
|
||||
{ attrs: this.attrs, modifiers: this.modifiers }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (env) => {
|
||||
const { builders } = env.syntax;
|
||||
|
||||
return {
|
||||
ElementNode(node) {
|
||||
if (node.tag === 'button') {
|
||||
try {
|
||||
const transforms = new Transforms(node, builders);
|
||||
if (transforms.shouldTransform()) {
|
||||
transforms.childNodesToArgs();
|
||||
transforms.filterAttributes();
|
||||
return transforms.buildElement();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`\nError caught transforming button in ${env.filePath}\n`, error); // eslint-disable-line
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user