File

src/app/shared/components/template/components/button/button.component.ts

Description

A general-purpose button component

Extends

TemplateBaseComponent

Implements

OnInit

Metadata

Index

Properties

Properties

params
Type : Partial<IButtonParams>
Default value : {}

Button

Example

type name value parameter_list
button button_par_2_1 Style parameter Information style: information
button button_par_2_2 Style parameter Navigation style: navigation

button example

Google Sheet Demo
Live Preview Demo

Parameters

Parameter Value Description
disabled false (default) Enables the button
disabled true Disables the button and greys it out
style information (default) Button colour primary
style navigation Button colour secondary
style full (default) Button width determined by screen width
style flexible Button width equal to its content width
style medium (default) Button height medium
style short Button height short
style tall Button height tall
style standard (default) Button text colour white
style alternative Button text colour primary
style medium_round To be removed
style no_shadow To be removed
text_align left (default) Button text is left aligned within the button
text_align center Button text is centred within the button
text_align right Button text is right aligned within the button
button_align center (default) Horizontal alignment of the button on the screen is centred
button_align left Horizontal alignment of the button on the screen is left aligned
button_align right Horizontal alignment of the button on the screen is right aligned

import { Component, ElementRef, OnInit } from "@angular/core";
import {
  getStringParamFromTemplateRow,
  getBooleanParamFromTemplateRow,
} from "src/app/shared/utils";
import { TemplateBaseComponent } from "../base";

interface IButtonParams {
  /** TEMPLATE PARAMETER: "variant" */
  variant:
    | "alternative"
    | "card"
    | "card-portrait"
    | "flexible"
    | "full"
    | "information"
    | "medium"
    | "navigation"
    | "short"
    | "standard"
    | "tall";
  /** TEMPLATE PARAMETER: "style". Legacy, use "variant" instead. */
  style: string;
  /** TEMPLATE PARAMETER: "disabled". If true, button is disabled and greyed out */
  disabled: boolean;
  /** TEMPLATE PARAMETER: "text_align" */
  textAlign: "left" | "centre" | "right";
  /** TEMPLATE PARAMETER: "button_align" */
  buttonAlign: "left" | "centre" | "right";
  /** TEMPLATE PARAMETER: "icon". The path to an icon asset */
  icon: string;
  /** TEMPLATE PARAMETER: "image_asset". The path to an image asset */
  image: string;
}

/**
 * A general-purpose button component
 */
@Component({
  selector: "plh-button",
  templateUrl: "./button.component.html",
  styleUrls: ["./button.component.scss"],
})
export class TmplButtonComponent extends TemplateBaseComponent implements OnInit {
  params: Partial<IButtonParams> = {};
  /** @ignore */
  variantMap: { cardPortrait: boolean };

  /** @ignore */
  constructor(private elRef: ElementRef) {
    super();
  }

  ngOnInit() {
    this.getParams();
  }

  private getParams() {
    this.params.style = `${getStringParamFromTemplateRow(this._row, "style", "information")} ${
      this.isTwoColumns() ? "two_columns" : ""
    }` as any;
    this.params.variant = getStringParamFromTemplateRow(this._row, "variant", "")
      .split(",")
      .join(" ") as IButtonParams["variant"];
    this.populateVariantMap();
    this.params.disabled = getBooleanParamFromTemplateRow(this._row, "disabled", false);
    this.params.image = getStringParamFromTemplateRow(this._row, "image_asset", null);
    if (this._row.disabled) {
      this.params.disabled = true;
    }
    this.params.textAlign = getStringParamFromTemplateRow(this._row, "text_align", null) as any;
    this.params.buttonAlign = getStringParamFromTemplateRow(
      this._row,
      "button_align",
      "center"
    ) as any;
    this.params.icon = getStringParamFromTemplateRow(this._row, "icon", null);
  }

  /** Determine if the button is inside a display group with the style "two_columns" */
  private isTwoColumns(): boolean {
    const displayGroupElement = this.elRef.nativeElement.closest(".display-group-wrapper");
    if (displayGroupElement) {
      return displayGroupElement.classList.contains("two_columns");
    } else {
      return false;
    }
  }

  private populateVariantMap() {
    const variantArray = this.params.variant.split(" ");
    this.variantMap = {
      cardPortrait: variantArray.includes("card-portrait"),
    };
  }
}
<ng-container [ngSwitch]="variantMap.cardPortrait">
  <!-- Default variant -->
  <ion-button
    *ngSwitchDefault
    [class]="'full standard medium' + ' ' + params.style + ' ' + params.buttonAlign"
    [disabled]="params.disabled"
    (click)="triggerActions('click')"
    [attr.data-param-style]="params.style"
    [attr.data-variant]="params.variant"
    [attr.data-has-children]="_row.rows ? true : false"
  >
    <ion-icon *ngIf="params.icon" slot="start" src="{{ params.icon | plhAsset }}"></ion-icon>
    <span
      *ngIf="_row.value"
      [class]="'left text ' + params.textAlign"
      [innerHTML]="_row.value | markdown"
    >
    </span>
    <span *ngIf="_row.rows" class="children">
      <plh-template-component
        *ngFor="let childRow of _row.rows | filterDisplayComponent; trackBy: trackByRow"
        class="child"
        [row]="childRow"
        [parent]="parent"
        [attr.data-rowname]="_row.name"
      ></plh-template-component>
    </span>
  </ion-button>

  <!-- "card-portrait" variant. This variant is not achievable using an ion-button -->
  <div
    *ngSwitchCase="true"
    class="button-container"
    (click)="triggerActions('click')"
    [attr.data-variant]="params.variant"
    [attr.data-has-children]="_row.rows ? true : false"
  >
    <img *ngIf="params.image" src="{{ params.image | plhAsset }}" />
    <div *ngIf="_row.value" [class]="'button-text ' + params.textAlign">
      {{ _row.value }}
    </div>
    <span *ngIf="_row.rows" class="children">
      <plh-template-component
        *ngFor="let childRow of _row.rows | filterDisplayComponent; trackBy: trackByRow"
        class="child"
        [row]="childRow"
        [parent]="parent"
        [attr.data-rowname]="_row.name"
      ></plh-template-component>
    </span>
  </div>
</ng-container>

./button.component.scss

$background-primary: var(--button-background-primary, var(--gradient-primary-mid-vertical));
$background-secondary: var(--button-background-secondary, var(--gradient-secondary-mid-vertical));

:host {
  width: 100%;
  ion-button {
    height: 100%;
  }
  span {
    padding: var(--tiny-padding) 0;
    display: contents;
  }
}
// ensure relative position on parent if absolute position children nested inside
[data-has-children] {
  position: relative;
}
ion-button {
  font-size: var(--buttons-font-size-small);
  font-weight: var(--font-weight-standard);
  color: var(--ion-color-primary-contrast);
  text-transform: none;
  white-space: pre-line;
  min-height: var(--buttons-min-height);
  padding: var(--regular-margin) var(--tiny-padding) 0;
  --border-radius: var(--ion-border-radius-standard);
  --box-shadow: var(--ion-default-box-shadow);
  display: flex;
  width: fit-content;
  margin: 0 auto;
}

ion-button::part(native) {
  height: auto;
}

ion-button:disabled,
ion-button[disabled] {
  filter: brightness(55%);
}

/// nested_color style applies semi-transparent background to match container
ion-button[data-param-style~="nested_color"],
ion-button[data-variant~="nested_color"] {
  --background: linear-gradient(rgb(0 0 0 / 13%) 0%, rgb(0 0 0 / 20%) 100%);
}

ion-button[data-param-style~="card"],
ion-button[data-variant~="card"] {
  --background: white;
  --background-activated: var(--ion-color-gray-light);
  --padding-top: var(--tiny-padding);
  --padding-bottom: var(--tiny-padding);
  color: var(--ion-color-primary);
  font-weight: var(--font-weight-bold);
  border: 1px solid var(--ion-color-gray-light);
  border-radius: var(--ion-border-radius-secondary);
  min-height: var(--buttons-short-height);

  .text ::ng-deep {
    p {
      margin: var(--small-margin) 0;
    }
  }

  ion-icon {
    width: 32px;
    font-size: 32px;
  }

  .children {
    position: absolute;
    align-self: flex-end;
    right: 0;
    display: flex;
    align-items: center;
  }
}

.button-container[data-variant~="card-portrait"] {
  background-color: white;
  color: var(--ion-color-primary);
  font-size: var(--buttons-font-size-tiny);
  font-weight: var(--font-weight-bold);
  border: 1px solid var(--ion-color-gray-light);
  border-radius: var(--ion-border-radius-standard);
  min-height: var(--buttons-short-height);
  filter: drop-shadow(var(--ion-default-box-shadow));
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 1.5rem 0.5rem 0.5rem;
  // TODO: make a variable
  width: 160px;

  &:active {
    background-color: var(--ion-color-gray-light);
  }

  img {
    width: 85%;
  }

  .button-text {
    width: 100%;
    padding: 1rem 0 0.5rem;
    text-align: center;
    &.right {
      text-align: right;
    }
    &.left {
      text-align: left;
    }
    &.center {
      text-align: center;
    }
    &.centre {
      text-align: center;
    }
  }
  .children {
    position: absolute;
    align-self: flex-end;
    right: -4px;
    top: -12px;
    display: flex;
    align-items: center;
    padding: -8px;
  }
}

/// TODO CC-2021-12-19 - the code below should likely be refactored to param selectors and moved

.navigation {
  --background: #{$background-secondary};
}

.information {
  --background: #{$background-primary};
}

.make_me_smile {
  min-height: 80px !important;
  max-width: fit-content;
  --background: #{$background-primary};
  color: var(--ion-color-primary-contrast);
}

.get_me_going {
  min-height: 80px !important;
  max-width: fit-content;
  --background: #{$background-secondary};
  color: var(--ion-color-primary-contrast);
}

.options {
  --background: #{$background-primary};
  color: var(--ion-color-primary-contrast);
}

.standard {
  color: var(--ion-color-primary-contrast);
}
.alternative {
  color: var(--ion-color-primary);
}

.left ::ng-deep {
  margin: 0 auto 0 2px;
  text-align: left;
  p {
    margin: 0 0 0 2px;
  }
}
.right {
  margin: 0 2px 0 auto;
  text-align: right;
}
.center {
  text-align: center;
}
.centre {
  text-align: center;
}

// width
.full {
  width: 100%;
}
.flexible {
  width: fit-content;
  max-width: 100%;
}
// height
.medium {
  min-height: var(--buttons-medium-height);
}
.short {
  min-height: var(--buttons-short-height);
  font-size: var(--buttons-font-size-small);
}
.tall {
  min-height: var(--buttons-tall-height);
}

.medium_round {
  --border-radius: var(--ion-border-radius-secondary);
}
.no_shadow {
  --box-shadow: none;
}
.two_columns {
  max-width: 12rem;
  min-height: var(--buttons-min-height);
  font-size: calc(var(--buttons-font-size) * var(--scale-factor-btn));
  min-width: 5rem;
  @media (max-width: 375px) {
    max-width: 11rem;
  }
  @media (max-width: 320px) {
    max-width: 10rem;
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""