mirror of
https://github.com/bitwarden/browser
synced 2026-02-24 00:23:17 +00:00
add dynamic border styles to dialog template
This commit is contained in:
@@ -6,9 +6,15 @@
|
||||
cdkTrapFocus
|
||||
cdkTrapFocusAutoCapture
|
||||
>
|
||||
@let showHeaderBorder = !isDrawer || bodyHasScrolledFrom?.top();
|
||||
<header
|
||||
class="tw-flex tw-justify-between tw-items-center tw-gap-4 tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
|
||||
[ngClass]="[isDrawer ? 'tw-p-6 tw-pb-4' : 'tw-p-4']"
|
||||
class="tw-flex tw-justify-between tw-items-center tw-gap-4 tw-border-0 tw-border-b tw-border-solid"
|
||||
[ngClass]="{
|
||||
'tw-p-4': !isDrawer,
|
||||
'tw-p-6 tw-pb-4': isDrawer,
|
||||
'tw-border-secondary-300': showHeaderBorder,
|
||||
'tw-border-transparent': !showHeaderBorder,
|
||||
}"
|
||||
>
|
||||
<h2
|
||||
bitDialogTitleContainer
|
||||
@@ -47,6 +53,7 @@
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
cdkScrollable
|
||||
[ngClass]="{
|
||||
'tw-p-4': !disablePadding && !isDrawer,
|
||||
'tw-px-6 tw-py-4': !disablePadding && isDrawer,
|
||||
@@ -60,9 +67,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@let showFooterBorder = !isDrawer || bodyHasScrolledFrom?.bottom();
|
||||
<footer
|
||||
class="tw-flex tw-flex-row tw-items-center tw-gap-2 tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-bg-background"
|
||||
[ngClass]="[isDrawer ? 'tw-px-6 tw-py-4' : 'tw-p-4']"
|
||||
[ngClass]="{
|
||||
'tw-px-6 tw-py-4': isDrawer,
|
||||
'tw-p-4': !isDrawer,
|
||||
'tw-border-secondary-300': showFooterBorder,
|
||||
'tw-border-transparent': !showFooterBorder,
|
||||
}"
|
||||
>
|
||||
<ng-content select="[bitDialogFooter]"></ng-content>
|
||||
</footer>
|
||||
|
||||
@@ -2,13 +2,23 @@
|
||||
// @ts-strict-ignore
|
||||
import { CdkTrapFocus } from "@angular/cdk/a11y";
|
||||
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||
import { CdkScrollable } from "@angular/cdk/scrolling";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, HostBinding, Input, inject } from "@angular/core";
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
HostBinding,
|
||||
Injector,
|
||||
Input,
|
||||
inject,
|
||||
viewChild,
|
||||
} from "@angular/core";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { BitIconButtonComponent } from "../../icon-button/icon-button.component";
|
||||
import { TypographyDirective } from "../../typography/typography.directive";
|
||||
import { ScrollState, hasScrolledFrom } from "../../utils/has-scrolled-from";
|
||||
import { fadeIn } from "../animations";
|
||||
import { DialogRef } from "../dialog.service";
|
||||
import { DialogCloseDirective } from "../directives/dialog-close.directive";
|
||||
@@ -30,10 +40,14 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai
|
||||
DialogCloseDirective,
|
||||
I18nPipe,
|
||||
CdkTrapFocus,
|
||||
CdkScrollable,
|
||||
],
|
||||
})
|
||||
export class DialogComponent {
|
||||
export class DialogComponent implements AfterViewInit {
|
||||
protected dialogRef = inject(DialogRef, { optional: true });
|
||||
private scrollableBody = viewChild.required(CdkScrollable);
|
||||
protected bodyHasScrolledFrom: ScrollState;
|
||||
private injector = inject(Injector);
|
||||
|
||||
/** Background color */
|
||||
@Input()
|
||||
@@ -75,15 +89,19 @@ export class DialogComponent {
|
||||
return ["tw-flex", "tw-flex-col"]
|
||||
.concat(
|
||||
this.width,
|
||||
!this.dialogRef.isDrawer
|
||||
? ["tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"]
|
||||
: ["tw-min-h-screen"],
|
||||
this.dialogRef?.isDrawer
|
||||
? ["tw-min-h-screen"]
|
||||
: ["tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"],
|
||||
)
|
||||
.flat();
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.bodyHasScrolledFrom = hasScrolledFrom(this.scrollableBody(), this.injector);
|
||||
}
|
||||
|
||||
handleEsc() {
|
||||
this.dialogRef.close();
|
||||
this.dialogRef?.close();
|
||||
}
|
||||
|
||||
get width() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CdkScrollable } from "@angular/cdk/scrolling";
|
||||
import { ChangeDetectionStrategy, Component, Signal, inject } from "@angular/core";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { map } from "rxjs";
|
||||
import { ChangeDetectionStrategy, Component, inject } from "@angular/core";
|
||||
|
||||
import { hasScrolledFrom } from "../utils/has-scrolled-from";
|
||||
|
||||
/**
|
||||
* Body container for `bit-drawer`
|
||||
@@ -14,7 +14,7 @@ import { map } from "rxjs";
|
||||
host: {
|
||||
class:
|
||||
"tw-p-4 tw-pt-0 tw-block tw-overflow-auto tw-border-solid tw-border tw-border-transparent tw-transition-colors tw-duration-200",
|
||||
"[class.tw-border-t-secondary-300]": "isScrolled()",
|
||||
"[class.tw-border-t-secondary-300]": "this.hasScrolledFrom.top()",
|
||||
},
|
||||
hostDirectives: [
|
||||
{
|
||||
@@ -24,13 +24,5 @@ import { map } from "rxjs";
|
||||
template: ` <ng-content></ng-content> `,
|
||||
})
|
||||
export class DrawerBodyComponent {
|
||||
private scrollable = inject(CdkScrollable);
|
||||
|
||||
/** TODO: share this utility with browser popup header? */
|
||||
protected isScrolled: Signal<boolean> = toSignal(
|
||||
this.scrollable
|
||||
.elementScrolled()
|
||||
.pipe(map(() => this.scrollable.measureScrollOffset("top") > 0)),
|
||||
{ initialValue: false },
|
||||
);
|
||||
protected hasScrolledFrom = hasScrolledFrom(inject(CdkScrollable));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component";
|
||||
standalone: true,
|
||||
imports: [KitchenSinkSharedModule],
|
||||
template: `
|
||||
<bit-dialog title="Dialog Title" [dialogSize]="dialogRef.isDrawer ? 'large' : 'large'">
|
||||
<bit-dialog title="Dialog Title" dialogSize="small">
|
||||
<ng-container bitDialogContent>
|
||||
<p bitTypography="body1">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
||||
|
||||
33
libs/components/src/utils/has-scrolled-from.ts
Normal file
33
libs/components/src/utils/has-scrolled-from.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { CdkScrollable } from "@angular/cdk/scrolling";
|
||||
import { Injector, Signal, inject, runInInjectionContext } from "@angular/core";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { map } from "rxjs";
|
||||
|
||||
export type ScrollState = {
|
||||
/** `true` when the scrollbar is not at the top-most position */
|
||||
top: Signal<boolean>;
|
||||
|
||||
/** `true` when the scrollbar is not at the bottom-most position */
|
||||
bottom: Signal<boolean>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a `CdkScrollable` instance has been scrolled
|
||||
* @param scrollable The element to check
|
||||
* @param injector An optional injector; needed if called from outside an injection context
|
||||
* @returns {ScrollState}
|
||||
*/
|
||||
export const hasScrolledFrom = (scrollable: CdkScrollable, injector?: Injector): ScrollState => {
|
||||
const _injector = injector ?? inject(Injector);
|
||||
const scrollState$ = scrollable.elementScrolled().pipe(
|
||||
map(() => ({
|
||||
top: scrollable.measureScrollOffset("top") > 0,
|
||||
bottom: scrollable.measureScrollOffset("bottom") > 0,
|
||||
})),
|
||||
);
|
||||
|
||||
return runInInjectionContext(_injector, () => ({
|
||||
top: toSignal(scrollState$.pipe(map(($) => $.top)), { initialValue: false }),
|
||||
bottom: toSignal(scrollState$.pipe(map(($) => $.bottom)), { initialValue: false }),
|
||||
}));
|
||||
};
|
||||
Reference in New Issue
Block a user