1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-21 20:03:43 +00:00

[CL-947] drawer width variants (#18156)

* allow drawer to adapt to size input

* add new drawer sizes

* move logic back to dialog component

* convert width to computed signal

* fix template error and remove duplicate class

* use normal const object

* ensure dialogSize undefined is handled
This commit is contained in:
Bryan Cunningham
2026-01-05 08:59:11 -05:00
committed by GitHub
parent 1208b42ab9
commit f341b00092
3 changed files with 129 additions and 24 deletions

View File

@@ -31,7 +31,11 @@ interface Animal {
<button class="tw-mr-2" bitButton type="button" (click)="openDialogNonDismissable()">
Open Non-Dismissable Dialog
</button>
<button bitButton type="button" (click)="openDrawer()">Open Drawer</button>
<button class="tw-mr-2" bitButton type="button" (click)="openDrawer()">Open Drawer</button>
<button class="tw-mr-2" bitButton size="small" type="button" (click)="openSmallDrawer()">
Open Small Drawer
</button>
<button bitButton type="button" (click)="openLargeDrawer()">Open Large Drawer</button>
</bit-layout>
`,
imports: [ButtonModule, LayoutComponent],
@@ -63,13 +67,29 @@ class StoryDialogComponent {
},
});
}
openSmallDrawer() {
this.dialogService.openDrawer(SmallDrawerContentComponent, {
data: {
animal: "panda",
},
});
}
openLargeDrawer() {
this.dialogService.openDrawer(LargeDrawerContentComponent, {
data: {
animal: "panda",
},
});
}
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
template: `
<bit-dialog title="Dialog Title" dialogSize="large">
<bit-dialog title="Dialog Title">
<span bitDialogContent>
Dialog body text goes here.
<br />
@@ -100,7 +120,6 @@ class StoryDialogContentComponent {
template: `
<bit-dialog
title="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore"
dialogSize="large"
>
<span bitDialogContent>
Dialog body text goes here.
@@ -125,6 +144,64 @@ class NonDismissableContentComponent {
}
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
template: `
<bit-dialog title="Small Drawer" dialogSize="small">
<span bitDialogContent>
Dialog body text goes here.
<br />
Animal: {{ animal }}
</span>
<ng-container bitDialogFooter>
<button type="button" bitButton buttonType="primary" (click)="dialogRef.close()">
Save
</button>
<button type="button" bitButton buttonType="secondary" bitDialogClose>Cancel</button>
</ng-container>
</bit-dialog>
`,
imports: [DialogModule, ButtonModule],
})
class SmallDrawerContentComponent {
dialogRef = inject(DialogRef);
private data = inject<Animal>(DIALOG_DATA);
get animal() {
return this.data?.animal;
}
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
template: `
<bit-dialog title="Large Drawer" dialogSize="large">
<span bitDialogContent>
Dialog body text goes here.
<br />
Animal: {{ animal }}
</span>
<ng-container bitDialogFooter>
<button type="button" bitButton buttonType="primary" (click)="dialogRef.close()">
Save
</button>
<button type="button" bitButton buttonType="secondary" bitDialogClose>Cancel</button>
</ng-container>
</bit-dialog>
`,
imports: [DialogModule, ButtonModule],
})
class LargeDrawerContentComponent {
dialogRef = inject(DialogRef);
private data = inject<Animal>(DIALOG_DATA);
get animal() {
return this.data?.animal;
}
}
export default {
title: "Component Library/Dialogs/Service",
component: StoryDialogComponent,
@@ -206,3 +283,21 @@ export const Drawer: Story = {
await userEvent.click(button);
},
};
export const DrawerSmall: Story = {
play: async (context) => {
const canvas = context.canvasElement;
const button = getAllByRole(canvas, "button")[3];
await userEvent.click(button);
},
};
export const DrawerLarge: Story = {
play: async (context) => {
const canvas = context.canvasElement;
const button = getAllByRole(canvas, "button")[4];
await userEvent.click(button);
},
};

View File

@@ -2,7 +2,7 @@
<section
class="tw-flex tw-w-full tw-flex-col tw-self-center tw-overflow-hidden tw-border tw-border-solid tw-border-secondary-100 tw-bg-background tw-text-main"
[ngClass]="[
width,
width(),
isDrawer ? 'tw-h-full tw-border-t-0' : 'tw-rounded-t-xl md:tw-rounded-xl tw-shadow-lg',
]"
cdkTrapFocus

View File

@@ -26,6 +26,20 @@ import { DialogRef } from "../dialog.service";
import { DialogCloseDirective } from "../directives/dialog-close.directive";
import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive";
type DialogSize = "small" | "default" | "large";
const dialogSizeToWidth = {
small: "md:tw-max-w-sm",
default: "md:tw-max-w-xl",
large: "md:tw-max-w-3xl",
} as const;
const drawerSizeToWidth = {
small: "md:tw-max-w-sm",
default: "md:tw-max-w-lg",
large: "md:tw-max-w-2xl",
} as const;
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
@@ -71,7 +85,7 @@ export class DialogComponent {
/**
* Dialog size, more complex dialogs should use large, otherwise default is fine.
*/
readonly dialogSize = input<"small" | "default" | "large">("default");
readonly dialogSize = input<DialogSize>("default");
/**
* Title to show in the dialog's header
@@ -100,21 +114,31 @@ export class DialogComponent {
private readonly animationCompleted = signal(false);
protected readonly width = computed(() => {
const size = this.dialogSize() ?? "default";
const isDrawer = this.dialogRef?.isDrawer;
if (isDrawer) {
return drawerSizeToWidth[size];
}
return dialogSizeToWidth[size];
});
protected readonly classes = computed(() => {
// `tw-max-h-[90vh]` is needed to prevent dialogs from overlapping the desktop header
const baseClasses = ["tw-flex", "tw-flex-col", "tw-w-screen"];
const sizeClasses = this.dialogRef?.isDrawer
? ["tw-h-full", "md:tw-w-[23rem]"]
: ["md:tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"];
const sizeClasses = this.dialogRef?.isDrawer ? ["tw-h-full"] : ["md:tw-p-4", "tw-max-h-[90vh]"];
const size = this.dialogSize() ?? "default";
const animationClasses =
this.disableAnimations() || this.animationCompleted() || this.dialogRef?.isDrawer
? []
: this.dialogSize() === "small"
: size === "small"
? ["tw-animate-slide-down"]
: ["tw-animate-slide-up", "md:tw-animate-slide-down"];
return [...baseClasses, this.width, ...sizeClasses, ...animationClasses];
return [...baseClasses, this.width(), ...sizeClasses, ...animationClasses];
});
handleEsc(event: Event) {
@@ -124,20 +148,6 @@ export class DialogComponent {
}
}
get width() {
switch (this.dialogSize()) {
case "small": {
return "md:tw-max-w-sm";
}
case "large": {
return "md:tw-max-w-3xl";
}
default: {
return "md:tw-max-w-xl";
}
}
}
onAnimationEnd() {
this.animationCompleted.set(true);
}