src/components/sq-overlay-base/sq-overlay-base.component.ts
Overlay base component for displaying side panel dialogs. Extends SqDialogCore for shared functionality.
Can be used directly in templates or opened programmatically via SqModalService.
Example :Direct usage in template:
```html
<sq-overlay-base [open]="isOpen" direction="right" width="500px" (dialogClose)="onClose()">
<ng-template #headerOverlay>
<h2>Panel Title</h2>
</ng-template>
<p>Panel content goes here</p>
<ng-template #footerOverlay>
<button (click)="onClose()">Close</button>
</ng-template>
</sq-overlay-base>```html
Programmatic usage via service:
```typescript
const ref = this.modalService.openOverlay({
direction: 'right',
width: '500px',
body: MyContentComponent,
data: { items: [...] }
});ref.afterClosed().subscribe(result => { console.log('Overlay closed with:', result); });
Example :
| selector | sq-overlay-base |
| standalone | true |
| imports |
NgClass
NgTemplateOutlet
SqClickOutsideDirective
SqDataTestDirective
|
| styleUrls | ./sq-overlay-base.component.scss |
| templateUrl | ./sq-overlay-base.component.html |
| borderless | |
Type : boolean
|
|
Default value : false
|
|
|
Whether to hide borders in the overlay. |
|
| direction | |
Type : OverlayDirectionType
|
|
Default value : 'right'
|
|
|
Direction from which the overlay slides in. |
|
| height | |
Type : string
|
|
Default value : '300px'
|
|
|
Height of the overlay panel (used for top/bottom directions). |
|
| width | |
Type : string
|
|
Default value : '475px'
|
|
|
Width of the overlay panel (used for left/right directions). |
|
| backdrop | |
Type : "static" |
|
|
Default value : 'static'
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:71
|
|
|
Backdrop behavior.
|
|
| bodyContent | |
Type : TemplateRef<any> | Type<any>
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:119
|
|
|
Custom body content (TemplateRef or Component type). If a Component is passed, its headerTemplate and footerTemplate properties will be used. |
|
| cancelText | |
Type : string
|
|
Default value : 'Cancelar'
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:141
|
|
|
Text for the cancel button in the default footer. |
|
| confirmText | |
Type : string
|
|
Default value : 'Confirmar'
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:146
|
|
|
Text for the confirm button in the default footer. |
|
| contentData | |
Type : any
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:130
|
|
|
Data to pass to dynamic content components. |
|
| contentOutputs | |
Type : literal type
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:136
|
|
|
Output handlers for the body component (when body is a Component). Keys must match the body component's @Output() EventEmitter names. |
|
| customClass | |
Type : string
|
|
Default value : ''
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:91
|
|
|
Additional CSS class(es) to apply to the dialog container. |
|
| dataTest | |
Type : string
|
|
Default value : 'sq-dialog'
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:96
|
|
|
Data-test attribute for testing purposes. |
|
| dialogRef | |
Type : SqDialogRef<any | any>
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:102
|
|
|
Reference to the dialog for programmatic control. Set by SqModalService when opening programmatically. |
|
| footerContent | |
Type : TemplateRef<any>
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:125
|
|
|
Custom footer content (string or TemplateRef). If body is a Component with footerTemplate property, it will be used automatically. |
|
| headerContent | |
Type : string | TemplateRef<any>
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:113
|
|
|
Custom header content. Can be a string (used as title) or TemplateRef. If body is a Component with headerTemplate property, it will be used automatically. |
|
| id | |
Type : string
|
|
Default value : `sq-dialog-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:59
|
|
|
Unique identifier for the dialog instance. |
|
| open | |
Type : boolean
|
|
Default value : false
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:64
|
|
|
Controls whether the dialog is open or closed. |
|
| showCloseButton | |
Type : boolean
|
|
Default value : true
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:76
|
|
|
Whether to show the close button. |
|
| showFooterInput | |
Type : boolean
|
|
Default value : true
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:86
|
|
|
Whether to show the footer section. |
|
| showHeaderInput | |
Type : boolean
|
|
Default value : true
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:81
|
|
|
Whether to show the header section. |
|
| dialogClose | |
Type : EventEmitter
|
|
|
Inherited from
SqDialogCore
|
|
|
Defined in
SqDialogCore:155
|
|
|
Emitted when the dialog is closed. |
|
| getDialogClasses |
getDialogClasses()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:96
|
|
Get CSS classes for the overlay container.
Returns :
Record<string, boolean>
Object with CSS class names as keys and boolean values |
| getModalDialogClasses |
getModalDialogClasses()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:111
|
|
Get CSS classes for the modal-dialog element.
Returns :
Record<string, boolean>
Object with CSS class names as keys and boolean values |
| Private injectWidthCss |
injectWidthCss()
|
|
Inject dynamic CSS for overlay size based on direction. For left/right: applies width For top/bottom: applies height
Returns :
void
|
| Private isHorizontalDirection |
isHorizontalDirection()
|
|
Check if the overlay direction is horizontal (left or right).
Returns :
boolean
True if direction is left or right |
| Protected onDialogClose |
onDialogClose()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:149
|
|
Hook called when the overlay closes.
Returns :
void
|
| Protected onDialogOpen |
onDialogOpen()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:132
|
|
Hook called when the overlay opens.
Returns :
void
|
| Private removeWidthCss |
removeWidthCss()
|
|
Remove the dynamic CSS for overlay size.
Returns :
void
|
| applyContentDataToBody |
applyContentDataToBody()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:615
|
|
Re-applies contentData to the body component's inputs. Usa setInput para que ngOnChanges seja disparado no body.
Returns :
void
|
| closeDialog |
closeDialog()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:404
|
|
Programmatically close the dialog. Used by SqModalService.
Returns :
void
|
| Protected destroyContentComponents |
destroyContentComponents()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:633
|
|
Destroy all dynamically created content components.
Returns :
void
|
| Protected extractTemplatesFromBodyComponent | ||||||||
extractTemplatesFromBodyComponent(componentInstance: any)
|
||||||||
|
Inherited from
SqDialogCore
|
||||||||
|
Defined in
SqDialogCore:599
|
||||||||
|
Extract headerTemplate and footerTemplate from body component if they exist.
Parameters :
Returns :
void
|
| Protected isTemplateRef | ||||||||
isTemplateRef(content: TemplateRef
|
||||||||
|
Inherited from
SqDialogCore
|
||||||||
|
Defined in
SqDialogCore:648
|
||||||||
|
Type guard to check if content is a TemplateRef.
Parameters :
Returns :
TemplateRef<any>
True if content is a TemplateRef |
| Async ngOnChanges | ||||||||
ngOnChanges(changes: SimpleChanges)
|
||||||||
|
Inherited from
SqDialogCore
|
||||||||
|
Defined in
SqDialogCore:289
|
||||||||
|
Responds to input changes, opening or closing the dialog based on 'open' property.
Parameters :
Returns :
Promise<void>
|
| ngOnDestroy |
ngOnDestroy()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:302
|
|
Cleans up resources when the component is destroyed.
Returns :
void
|
| Protected observeRouter |
observeRouter()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:488
|
|
Subscribe to router events to close on navigation.
Returns :
void
|
| onCancel |
onCancel()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:461
|
|
Handle cancel button click in default footer. Closes without returning a result (void/undefined).
Returns :
void
|
| onClickOutside |
onClickOutside()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:451
|
|
Handle click outside the dialog content.
Returns :
void
|
| onConfirm |
onConfirm()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:473
|
|
Handle confirm button click in default footer.
Closes returning
Returns :
void
|
| Protected onKeydown | ||||||||
onKeydown(event: KeyboardEvent)
|
||||||||
|
Inherited from
SqDialogCore
|
||||||||
|
Defined in
SqDialogCore:506
|
||||||||
|
Handle keyboard events.
Parameters :
Returns :
void
|
| Async openDialog |
openDialog()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:344
|
|
Programmatically open the dialog. Used by SqModalService.
Returns :
Promise<void>
|
| Protected renderContent | ||||||||||||||||
renderContent(content: TemplateRef
|
||||||||||||||||
|
Inherited from
SqDialogCore
|
||||||||||||||||
|
Defined in
SqDialogCore:538
|
||||||||||||||||
|
Render a single content item (TemplateRef or Component).
Parameters :
Returns :
void
|
| Protected renderDynamicContent |
renderDynamicContent()
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:524
|
|
Render dynamic content (TemplateRef or Component) into containers. Only body supports Component type. Header and footer support only string/TemplateRef. If body is a Component with headerTemplate/footerTemplate properties, they will be extracted.
Returns :
void
|
| finishOpening |
Default value : false
|
|
Whether the overlay has finished opening animation. |
| Optional footerTemplate |
Type : TemplateRef<ElementRef>
|
Decorators :
@ContentChild('footerOverlay')
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:84
|
|
Footer template from content projection. |
| Optional headerTemplate |
Type : TemplateRef<ElementRef>
|
Decorators :
@ContentChild('headerOverlay')
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:79
|
|
Header template from content projection. |
| Protected styleId |
Default value : `overlay-style-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:54
|
|
Unique style ID for dynamic CSS injection. |
| Protected Optional bodyComponentFooterTemplate |
Type : TemplateRef<any>
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:203
|
|
Footer template extracted from body component (if any). |
| Protected Optional bodyComponentHeaderTemplate |
Type : TemplateRef<any>
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:198
|
|
Header template extracted from body component (if any). |
| Optional bodyContainer |
Type : ViewContainerRef
|
Decorators :
@ViewChild('bodyContainer', {read: ViewContainerRef})
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:169
|
|
Container for dynamic body content. |
| Protected contentComponentRefs |
Type : literal type
|
Default value : {}
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:241
|
|
References to dynamically created content components. |
| Protected contentOutputSubscriptions |
Type : Subscription[]
|
Default value : []
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:250
|
|
Subscriptions to body component outputs (for cleanup on destroy). |
| Optional dialogElement |
Type : ElementRef<HTMLElement>
|
Decorators :
@ViewChild('dialogElement')
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:164
|
|
Reference to the dialog container element. |
| Protected document |
Default value : inject(DOCUMENT)
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:40
|
|
Reference to the document object. |
| Protected getWindow |
Default value : inject(GetWindow)
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:50
|
|
Window helper service for browser APIs. |
| hasFooter |
Default value : false
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:193
|
|
Whether the dialog has footer content. |
| hasHeader |
Default value : false
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:188
|
|
Whether the dialog has header content. |
| Protected isClosing |
Default value : false
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:223
|
|
Flag to prevent duplicate close operations. |
| Protected localized |
Type : URL
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:213
|
|
URL where the dialog was opened (for route change detection). |
| managedByService |
Default value : false
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:230
|
|
Whether this dialog is managed by SqModalService. When true, DOM cleanup is handled by the service. |
| Protected modalNumber |
Type : number
|
Default value : 0
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:183
|
|
Number of open modals (for stacking z-index). |
| Protected Optional modals |
Type : HTMLCollectionOf<Element>
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:178
|
|
Collection of open modal elements in the DOM. |
| Protected router |
Default value : inject(Router)
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:45
|
|
Angular Router for navigation events. |
| Protected Optional routerSubscription |
Type : Subscription
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:218
|
|
Subscription to router events. |
| Protected Optional scrollY |
Type : number
|
|
Inherited from
SqDialogCore
|
|
Defined in
SqDialogCore:208
|
|
Original scroll position to restore on close. |
| showFooter |
getshowFooter()
|
|
Override showFooter for overlay - only show when template or component is provided. Unlike modal, overlay does not have default footer buttons.
Returns :
boolean
|
import { NgClass, NgTemplateOutlet } from '@angular/common';
import { Component, ContentChild, ElementRef, Input, TemplateRef } from '@angular/core';
import { SqDialogCore } from '../../classes/sq-dialog-core.class';
import { SqClickOutsideDirective } from '../../directives/sq-click-outside/sq-click-outside.directive';
import { SqDataTestDirective } from '../../directives/sq-data-test/sq-data-test.directive';
import { OverlayDirectionType } from '../../interfaces/modal.interface';
/**
* Overlay base component for displaying side panel dialogs.
* Extends SqDialogCore for shared functionality.
*
* Can be used directly in templates or opened programmatically via SqModalService.
*
* @example
* Direct usage in template:
* ```html
* <sq-overlay-base [open]="isOpen" direction="right" width="500px" (dialogClose)="onClose()">
* <ng-template #headerOverlay>
* <h2>Panel Title</h2>
* </ng-template>
* <p>Panel content goes here</p>
* <ng-template #footerOverlay>
* <button (click)="onClose()">Close</button>
* </ng-template>
* </sq-overlay-base>
* ```
*
* @example
* Programmatic usage via service:
* ```typescript
* const ref = this.modalService.openOverlay({
* direction: 'right',
* width: '500px',
* body: MyContentComponent,
* data: { items: [...] }
* });
*
* ref.afterClosed().subscribe(result => {
* console.log('Overlay closed with:', result);
* });
* ```
*/
@Component({
selector: 'sq-overlay-base',
templateUrl: './sq-overlay-base.component.html',
styleUrls: ['./sq-overlay-base.component.scss'],
standalone: true,
imports: [NgClass, NgTemplateOutlet, SqClickOutsideDirective, SqDataTestDirective],
})
export class SqOverlayBaseComponent extends SqDialogCore {
/**
* Unique style ID for dynamic CSS injection.
*/
protected override styleId = `overlay-style-${Date.now()}-${Math.random().toString(36).substring(7)}`;
/**
* Direction from which the overlay slides in.
*/
@Input() direction: OverlayDirectionType = 'right';
/**
* Width of the overlay panel (used for left/right directions).
*/
@Input() width = '475px';
/**
* Height of the overlay panel (used for top/bottom directions).
*/
@Input() height = '300px';
/**
* Whether to hide borders in the overlay.
*/
@Input() borderless = false;
/**
* Header template from content projection.
*/
@ContentChild('headerOverlay') override headerTemplate?: TemplateRef<ElementRef>;
/**
* Footer template from content projection.
*/
@ContentChild('footerOverlay') override footerTemplate?: TemplateRef<ElementRef>;
/**
* Whether the overlay has finished opening animation.
*/
finishOpening = false;
/**
* Get CSS classes for the overlay container.
*
* @returns Object with CSS class names as keys and boolean values
*/
override getDialogClasses(): Record<string, boolean> {
return {
modal: true,
overlay: true,
open: this.open,
[this.direction]: true,
[this.customClass]: !!this.customClass,
};
}
/**
* Get CSS classes for the modal-dialog element.
*
* @returns Object with CSS class names as keys and boolean values
*/
override getModalDialogClasses(): Record<string, boolean> {
return {
'modal-dialog': true,
[this.direction]: true,
opened: this.finishOpening,
};
}
/**
* Override showFooter for overlay - only show when template or component is provided.
* Unlike modal, overlay does not have default footer buttons.
*
* @returns True if footer should be displayed
*/
override get showFooter(): boolean {
return !!this.effectiveFooterTemplate || this.hasFooterComponent;
}
/**
* Hook called when the overlay opens.
*/
protected override onDialogOpen(): void {
// Update header/footer detection for content-projected templates
this.hasHeader = this.showHeader;
this.hasFooter = this.showFooter;
// Inject dynamic width CSS
this.injectWidthCss();
// Set finish opening flag after a short delay for animation
setTimeout(() => {
this.finishOpening = true;
}, 10);
}
/**
* Hook called when the overlay closes.
*/
protected override onDialogClose(): void {
this.finishOpening = false;
this.removeWidthCss();
}
/**
* Check if the overlay direction is horizontal (left or right).
*
* @returns True if direction is left or right
*/
private isHorizontalDirection(): boolean {
return this.direction === 'left' || this.direction === 'right';
}
/**
* Inject dynamic CSS for overlay size based on direction.
* For left/right: applies width
* For top/bottom: applies height
*/
private injectWidthCss(): void {
const isHorizontal = this.isHorizontalDirection();
const sizeProperty = isHorizontal ? 'width' : 'height';
const sizeValue = isHorizontal ? this.width : this.height;
// Use specific selector with direction class to override the width: 0 / height: 0
const css = `
.overlay.open .modal-dialog.${this.direction}.opened {
${sizeProperty}: ${sizeValue} !important;
}
`;
const head = this.document.getElementsByTagName('head')[0];
let style = this.document.getElementById(this.styleId);
if (!style) {
style = this.document.createElement('style');
style.setAttribute('id', this.styleId);
style.appendChild(this.document.createTextNode(css));
head.appendChild(style);
} else {
style.innerHTML = '';
style.appendChild(this.document.createTextNode(css));
}
}
/**
* Remove the dynamic CSS for overlay size.
*/
private removeWidthCss(): void {
const style = this.document.getElementById(this.styleId);
if (style?.parentNode) {
style.parentNode.removeChild(style);
}
}
}
<div [id]="id" [ngClass]="getDialogClasses()" #dialogElement [dataTest]="dataTest">
<div
class="modal-dialog"
[ngClass]="getModalDialogClasses()"
[clickOutsideEnabled]="!!(open && backdrop !== 'static' && dialogElement)"
(clickOutside)="onClickOutside()"
>
<div [dataTest]="dataTest + '-content'" class="modal-content scrollbar">
<!-- Header -->
@if (showHeader) {
<div
[dataTest]="dataTest + '-header'"
class="modal-header"
[ngClass]="{
borderless: borderless,
}"
>
<!-- String header (title) -->
@if (headerTitle) {
<h3 class="modal-title">{{ headerTitle }}</h3>
}
<!-- Template header (from content projection, input, or body component) -->
@if (effectiveHeaderTemplate) {
<ng-container *ngTemplateOutlet="effectiveHeaderTemplate; context: templateContext"></ng-container>
}
<!-- Close button -->
@if (showCloseButton) {
<button type="button" class="close button-close" aria-label="Close" (click)="dialogClose.emit()">
<i class="fa-solid fa-xmark"></i>
</button>
}
</div>
}
<!-- Body -->
<div [dataTest]="dataTest + '-body'" class="modal-body scrollbar" [ngClass]="{ 'without-footer': !showFooter }">
<!-- Template body (from input) -->
@if (bodyTemplate) {
<ng-container *ngTemplateOutlet="bodyTemplate; context: templateContext"></ng-container>
}
<!-- Component body (dynamic) -->
@if (hasBodyComponent) {
<ng-container #bodyContainer></ng-container>
}
<!-- Content projection (default slot) -->
<ng-content></ng-content>
</div>
<!-- Footer -->
@if (showFooter) {
<div [dataTest]="dataTest + '-footer'" class="modal-footer" [ngClass]="{ borderless: borderless }">
<!-- Template footer (from content projection, input, or body component) -->
@if (effectiveFooterTemplate) {
<ng-container *ngTemplateOutlet="effectiveFooterTemplate; context: templateContext"></ng-container>
}
</div>
}
</div>
</div>
</div>
./sq-overlay-base.component.scss
:host {
display: contents;
}
.overlay {
padding: 0;
border-radius: 0;
&.left {
justify-content: flex-start;
}
&.right {
justify-content: flex-end;
}
&.top {
align-items: flex-start;
}
&.bottom {
align-items: flex-end;
}
.modal-dialog {
margin: 0;
position: fixed;
max-width: 100dvw;
max-height: 100dvh;
transition:
width 0.3s ease-out,
height 0.3s ease-out !important;
transform: translate3d(0, 0, 0) !important;
&.left,
&.right {
height: 100%;
width: 0;
top: 0;
// On mobile, lateral overlays expand to full or near-full width
@media (max-width: 576px) {
&.opened {
width: 100dvw !important;
}
}
}
&.left {
left: 0;
}
&.right {
right: 0;
}
&.top,
&.bottom {
width: 100%;
height: 0;
left: 0;
}
&.top {
top: 0;
}
&.bottom {
bottom: 0;
}
.modal-content {
height: 100%;
border-radius: 0;
border: none;
}
}
}
// Header adjustments for overlay
.modal-header {
min-height: 60px;
display: flex;
h3.modal-title {
margin: 0;
font-size: 1.25rem;
// Slightly smaller title on mobile
@media (max-width: 576px) {
font-size: 1.125rem;
}
}
.button-close {
position: unset;
height: 30px;
line-height: 30px;
opacity: .7;
}
&.without-header {
border-bottom: none;
}
// Reduce padding on mobile
@media (max-width: 576px) {
min-height: 50px;
padding: 0.75rem 1rem;
}
}
// Body scroll behavior
.modal-body {
flex: 1;
overflow-y: auto;
&.without-footer {
height: calc(100dvh - 60px);
}
// Adjust padding on mobile
@media (max-width: 576px) {
padding: 0.75rem 1rem;
&.without-footer {
height: calc(100dvh - 50px);
}
}
}
// Footer adjustments
.modal-footer {
min-height: 60px;
// Reduce padding on mobile
@media (max-width: 576px) {
min-height: 50px;
padding: 0.5rem 1rem;
gap: 0.5rem;
}
// Stack buttons on very small screens
@media (max-width: 360px) {
flex-direction: column;
width: 100%;
sq-button,
button {
width: 100%;
}
}
}
// Borderless variant
.borderless {
border-color: transparent;
}