Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: inline header or footer, via 'sticky' display group param #2564

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
<ion-toolbar>
@if (config.title) {
<!-- use mode="ios" to centre title within toolbar -->
<ion-title mode="ios">
{{ config.title }}
</ion-title>
}
@if (config.showCloseButton) {
<ion-buttons slot="end">
<ion-button (click)="close()">
<ion-icon name="close"></ion-icon>
</ion-button>
</ion-buttons>
}
</ion-toolbar>
@if (config.header) {
<ion-toolbar>
@if (config.title) {
<!-- use mode="ios" to centre title within toolbar -->
<ion-title mode="ios">
{{ config.title }}
</ion-title>
}
@if (config.showCloseButton) {
<ion-buttons slot="end">
<ion-button (click)="close()">
<ion-icon name="close"></ion-icon>
</ion-button>
</ion-buttons>
}
</ion-toolbar>
}
<ion-content>
<plh-template-container
class="template-container"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export interface INavStackConfig {
templateName: string;
title?: string;
showCloseButton?: boolean;
// Show default nav-stack header
header?: boolean;
}

/** The logic for nav-stack open/dismiss exists in the nav-stack service */
Expand Down
7 changes: 5 additions & 2 deletions src/app/feature/nav-stack/nav-stack.actions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { IActionHandler } from "src/app/shared/components/template/services/instance/template-action.registry";
import { NavStackService } from "./nav-stack.service";
import { INavStackConfig } from "./components/nav-stack/nav-stack.component";

interface INavStackActionParams {
template: string;
title: string;
show_close_button: boolean;
header: boolean;
}

export class NavStackActionFactory {
Expand All @@ -14,12 +16,13 @@ export class NavStackActionFactory {
const [actionId] = args;
const childActions = {
open: async () => {
const { template, title, show_close_button = true } = params;
const { template, title, show_close_button = true, header = true } = params;
const navStackConfig = {
templateName: template,
title,
showCloseButton: show_close_button,
};
header,
} as INavStackConfig;
this.service.pushNavStack(navStackConfig);
},
close_top: async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/shared/components/template/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import { TmplTextBoxComponent } from "./text-box/text-box.component";
import { TmplTextComponent } from "./text/text.component";
import { TmplTextBubbleComponent } from "./text-bubble/text-bubble.component";
import { TmplTileComponent } from "./tile-component/tile-component.component";
import { TmplTitleComponent } from "./title";
import { TmplTitleComponent } from "./title/title.component";
import { TmplTimerComponent } from "./timer/timer.component";
import { TmplToggleBarComponent } from "./toggle-bar/toggle-bar";
import { TmplVideoComponent } from "./video";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<div
#displayGroupWrapper
(click)="clickDisplayGroup()"
class="display-group-wrapper"
[attr.data-param-style]="params.style"
[attr.data-rowname]="_row.name"
[attr.data-variant]="params.variant"
[attr.data-sticky]="params.sticky"
[style.marginBottom.px]="params.offset"
[ngSwitch]="type"
[style]="_row.style_list | styleList"
Expand All @@ -28,3 +30,10 @@
<!-- Form layout -->
<plh-tmpl-form *ngSwitchCase="'form'" [inputRow]="_row" [parent]="parent"></plh-tmpl-form>
</div>
<!-- If sticky, add inline element so that rest of content doesn't shift behind -->
@if (params.sticky === "top") {
<div class="sticky-top-placeholder" [style.height.px]="stickyHeight()"></div>
}
@if (params.sticky === "bottom") {
<div class="sticky-bottom-placeholder" [style.height.px]="stickyHeight()"></div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@
display: flex;
align-items: center;
}

// When the display group is "sticky", style it as an inline header/footer
.display-group-wrapper {
&[data-sticky="top"],
&[data-sticky="bottom"] {
background-color: var(--ion-background-color);
position: fixed;
left: 0;
width: 100vw;
display: flex;
justify-content: center;
}

&[data-sticky="top"] {
top: 0;
}

&[data-sticky="bottom"] {
bottom: 0;
}
}
/// In flex box components should try to fill height, but not width (leave to flex property)
.display-group-wrapper > plh-template-component {
height: 100%;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Component, OnInit } from "@angular/core";
import {
AfterViewInit,
Component,
ElementRef,
OnDestroy,
OnInit,
signal,
ViewChild,
} from "@angular/core";
import { TemplateBaseComponent } from "../../base";
import { getNumberParamFromTemplateRow, getStringParamFromTemplateRow } from "../../../../../utils";

Expand All @@ -9,33 +17,53 @@ interface IDisplayGroupParams {
style: "form" | "default" | string | null;
/** TEMPLATE PARAMETER: "offset". Add a custom bottom margin */
offset: number;
/** TEMPLATE PARAMETER: "sticky". Set to "top" or "bottom" to make the display group a sticky inline header/footer */
sticky: "top" | "bottom" | null;
}

@Component({
selector: "plh-tmpl-display-group",
templateUrl: "./display-group.component.html",
styleUrls: ["../../tmpl-components-common.scss", "./display-group.component.scss"],
})
export class TmplDisplayGroupComponent extends TemplateBaseComponent implements OnInit {
export class TmplDisplayGroupComponent
extends TemplateBaseComponent
implements OnInit, AfterViewInit, OnDestroy
{
params: Partial<IDisplayGroupParams> = {};
bgColor: string;
type: "form" | "dashed_box" | "default";

@ViewChild("displayGroupWrapper") displayGroupWrapper: ElementRef;
private resizeObserver: ResizeObserver;
stickyHeight = signal<number>(0);

ngOnInit() {
this.getParams();
}

clickDisplayGroup() {
ngAfterViewInit() {
if (this.params.sticky) {
this.initResizeObserver();
}
}

public clickDisplayGroup() {
this.triggerActions("click");
}

getParams() {
private getParams() {
this.params.style = getStringParamFromTemplateRow(this._row, "style", "row");
this.params.offset = getNumberParamFromTemplateRow(this._row, "offset", 0);
this.params.variant = getStringParamFromTemplateRow(this._row, "variant", "")
.split(",")
.join(" ")
.concat(" " + this.params.style) as IDisplayGroupParams["variant"];
this.params.sticky = getStringParamFromTemplateRow(
this._row,
"sticky",
null
) as IDisplayGroupParams["sticky"];
this.type = this.getTypeFromStyles();
}

Expand All @@ -45,4 +73,38 @@ export class TmplDisplayGroupComponent extends TemplateBaseComponent implements
return "dashed_box";
return "default";
}

/** Observe the height of the display group wrapper element and update the height of the placeholder accordingly */
private initResizeObserver() {
if (this.displayGroupWrapper) {
this.resizeObserver = new ResizeObserver((entries) => {
let containerPadding = 0;
// In the case of a sticky header, the top padding/margin of the main app content and template container need to be accounted for,
// now that the display group sits at the very top of the content window outside of the main content and template container
if (this.params.sticky === "top") {
containerPadding = this.getTotalContainerTopPadding();
}
const entry = entries[0];
this.stickyHeight.set(entry.contentRect.height - containerPadding);
});

this.resizeObserver.observe(this.displayGroupWrapper.nativeElement);
}
}

private getTotalContainerTopPadding() {
const computedStyles = getComputedStyle(this.displayGroupWrapper.nativeElement);
const ionContentPaddingStart =
parseFloat(computedStyles.getPropertyValue("--padding-start")) || 0;
const templateContainerMarginEm = computedStyles.getPropertyValue("--row-margin-top").trim();
const fontSize = parseFloat(computedStyles.fontSize) || 0;
const templateContainerMarginPx = parseFloat(templateContainerMarginEm) * fontSize || 0;
return ionContentPaddingStart + templateContainerMarginPx;
}

ngOnDestroy() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
}
43 changes: 0 additions & 43 deletions src/app/shared/components/template/components/title.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="title-wrapper" [class]="params.style" [attr.data-variant]="params.variant">
<h1
[style.textAlign]="params.textAlign"
[class]="'tiny standard' + ' ' + params.style"
[innerHTML]="_row.value | markdown"
></h1>
<ion-icon
*ngIf="params.help"
name="help-circle-outline"
class="title-help"
[pTooltip]="params.help"
[tooltipPosition]="params.tooltipPosition"
tooltipEvent="click"
></ion-icon>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,12 @@
color: var(--ion-color-primary);
@include mixins.small-square;
}

&[data-variant~="header"] {
padding: var(--regular-padding);
h1 {
width: 100%;
text-align: center !important;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Component, OnInit } from "@angular/core";
import { TemplateBaseComponent } from ".././base";
import { ITemplateRowProps } from "../../models";
import { getStringParamFromTemplateRow } from "../../../../utils";

interface ITitleParams {
/** TEMPLATE PARAMETER: "help". Tooltip text */
help: string | null;
/** TEMPLATE PARAMETER: "tooltip_position". Default "right". */
tooltipPosition: string;
/** TEMPLATE PARAMETER: "text_align". Default "left"". */
textAlign: "left" | "right" | "center";
/** TEMPLATE PARAMETER: "style". */
style: string | null;
/** TEMPLATE PARAMETER: "variant". */
variant: "" | "header";
}

@Component({
selector: "plh-tmpl-title",
templateUrl: "./title.component.html",
styleUrls: ["./title.component.scss"],
})
export class TmplTitleComponent extends TemplateBaseComponent implements ITemplateRowProps, OnInit {
params: Partial<ITitleParams> = {};

ngOnInit() {
this.getParams();
}

getParams() {
this.params.help = getStringParamFromTemplateRow(this._row, "help", null);
this.params.tooltipPosition = getStringParamFromTemplateRow(
this._row,
"tooltip_position",
"right"
);
this.params.style = getStringParamFromTemplateRow(this._row, "style", "tiny standard");
this.params.textAlign = getStringParamFromTemplateRow(
this._row,
"text_align",
"left"
) as ITitleParams["textAlign"];
this.params.variant = getStringParamFromTemplateRow(this._row, "variant", "")
.split(",")
.join(" ") as ITitleParams["variant"];
}
}
2 changes: 1 addition & 1 deletion src/app/shared/components/template/template-component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ plh-template-container > plh-template-component:not([data-hidden="true"]) {
plh-template-container
> plh-template-component:not([data-hidden="true"])
~ plh-template-component:not([data-hidden="true"]) {
margin-top: 1em;
margin-top: var(--row-margin-top);
}

/// Ensure standalone containers and components fill all available space
Expand Down
1 change: 1 addition & 0 deletions src/theme/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
--safe-area-height: calc(100vh - var(--ion-safe-area-top, 0) - var(--ion-safe-area-bottom, 0));

// Margins //
--row-margin-top: 1em; // used in downstream calculations, e.g. sticky display-group
--tiny-margin: 5px;
--small-margin: 10px;
--regular-margin: 15px;
Expand Down
Loading