src/app/shared/components/template/components/button/button.component.ts
A general-purpose button component
OnInit
selector | plh-button |
styleUrls | ./button.component.scss |
templateUrl | ./button.component.html |
Properties |
params |
Type : Partial<IButtonParams>
|
Default value : {}
|
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 |
Google Sheet Demo
Live Preview Demo
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;
}
}