upp-panel
A structured panel component that organizes content into three distinct regions: header, content, and footer. The panel supports configurable sizing and optional border lines, making it suitable for dashboards, forms, and card-style layouts.
When to Use
Use upp-panel whenever you need a bordered container with a fixed header, scrollable content area, and an optional footer. Typical use cases include dashboard cards, detail views, and form containers. Panels can be placed side by side and independently sized.
Demo
Source Code
- HTML
- TypeScript
- SCSS
<h2>upp-panel — Dashboard Cards</h2>
<p class="demo-description">Three <code>upp-panel</code> cards forming a mini dashboard. Each has a header, metric content, and footer with actions.</p>
<div class="demo-controls">
<ion-button size="small" (click)="toggleLines()">Lines: {{ panelLines }}</ion-button>
<ion-button size="small" (click)="cycleSize(0)">Sales: {{ panelSizes[0] }}</ion-button>
<ion-button size="small" (click)="cycleSize(1)">Orders: {{ panelSizes[1] }}</ion-button>
<ion-button size="small" (click)="cycleSize(2)">Alerts: {{ panelSizes[2] }}</ion-button>
</div>
<div class="panel-row">
<div class="panel-col" *ngFor="let card of cards; let i = index">
<div class="panel-wrapper">
<upp-panel [size]="panelSizes[i]" [lines]="panelLines">
<upp-panel-header>
<upp-header [color]="card.color" menu="hide" height="40px">
<upp-header-title>{{ card.title }}</upp-header-title>
<upp-header-button>
<ion-icon [name]="card.icon"></ion-icon>
</upp-header-button>
</upp-header>
</upp-panel-header>
<upp-panel-content>
<div class="metric">
<span class="metric-value">{{ card.value }}</span>
<span class="metric-label">{{ card.label }}</span>
</div>
</upp-panel-content>
<upp-panel-footer>
<ion-item lines="none" button detail>
<ion-icon name="arrow-forward-outline" slot="start" color="medium"></ion-icon>
<ion-label color="medium">View details</ion-label>
</ion-item>
</upp-panel-footer>
</upp-panel>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'demo-upp-panel',
templateUrl: './demo-upp-panel.html',
styleUrls: ['../demo-common.scss', './demo-upp-panel.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DemoUppPanelComponent {
panelLines: 'show' | 'hide' = 'hide';
panelSizes: Array<'small' | 'auto' | 'large'> = ['small', 'auto', 'large'];
cards = [
{ title: "Today's Sales", value: '€1,245', label: 'Revenue today', color: 'success', icon: 'cash-outline' },
{ title: 'Active Orders', value: '38', label: 'Orders in progress', color: 'primary', icon: 'receipt-outline' },
{ title: 'Low Stock Alerts', value: '7', label: 'Items below threshold', color: 'warning', icon: 'alert-circle-outline' }
];
toggleLines() {
this.panelLines = this.panelLines === 'show' ? 'hide' : 'show';
}
cycleSize(index: number) {
const sizes: Array<'small' | 'auto' | 'large'> = ['small', 'auto', 'large'];
const current = sizes.indexOf(this.panelSizes[index]);
this.panelSizes[index] = sizes[(current + 1) % sizes.length];
}
}
:host {
display: block;
padding: 16px;
}
.panel-row {
display: flex;
gap: 12px;
}
.panel-col {
flex: 1;
min-width: 0;
}
.panel-wrapper {
height: 260px;
}
.metric {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px 16px;
text-align: center;
}
.metric-value {
font-size: 36px;
font-weight: 700;
line-height: 1.1;
color: var(--ion-color-dark, #222);
}
.metric-label {
margin-top: 8px;
font-size: 13px;
color: var(--ion-color-medium, #888);
}
API Reference
upp-panel
The main panel container. Use the sub-components to define header, content, and footer regions.
| Property | Type | Default | Description |
|---|---|---|---|
size | 'small' | 'auto' | 'large' | 'auto' | Controls the panel size. 'small' renders a compact layout, 'large' a wider one, and 'auto' adapts to the available space. |
lines | 'show' | 'hide' | 'hide' | Controls whether a visible border is drawn around the panel. |
The component applies CSS classes dynamically based on these inputs (--small, --auto, --large, --border-show, --border-hide).
upp-panel-header
Content-projection wrapper for the panel header. Typically contains a upp-header or a title element. Rendered at the top of the panel with a fixed height.
No inputs or outputs.
upp-panel-content
Content-projection wrapper for the main body of the panel. This region fills the available vertical space between the header and footer and can scroll if the content overflows.
Each instance makes direct projected children (inside .panel-content-inner) fill the body height when visible: > *:not([hidden]) get a flex column chain (display: flex; flex: 1 1 auto; min-height: 0; height: 100%). Use [hidden] to switch tab roots without duplicate layout SCSS in features. Nested upp-panel-content inside a tab applies the same rule only to its children (component encapsulation).
Opt-out when several visible siblings must not share height: add host class --no-fill-children on that upp-panel-content.
No inputs or outputs.
Spacing below the header
The panel exposes a CSS custom property on the host:
| Variable | Default | Description |
|---|---|---|
--upp-panel-content-offset-top | 0px | Padding applied to the inner content wrapper (.panel-content-inner) when upp-panel-header is immediately followed by upp-panel-content. |
When the first visible block in the panel body is a lead banner (upp-form-warning, upp-form-action inside upp-form-section fill="clear", or similar), leave at least 10px between the header and that block. Either set --upp-panel-content-offset-top: 10px on upp-panel, or add margin-top: 10px on the first section (for example fiscal-setup-section, account-block-section, service-lead-section). Do not stack both without need. Mid-form warnings preceded by titled sections do not require this offset.
upp-panel-footer
Content-projection wrapper for the panel footer. Rendered at the bottom of the panel with a fixed height. Useful for action buttons, status indicators, or summary information.
No inputs or outputs.