mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
[PM-2452] Popover Component (#5889)
* setup popover component template and basic story * add a11y features * add multiple positions for the popover * add stories for open right and left * prevent panel from hugging edges of screen * fix typo * add popover arrow depending on position * add buttons to stories * add figma preview * move toward directive approach * add all positions * add header input * add close functionality * make standalone component * add a11y import * add all stories * add story controls/args * add module of standalone components * gracefully handle text wrap and align close button to top for longer headings * update semantic html * add story for open state * use bitIconButton * adjust styles * add public close method * setup walkthrough mode * add walkthrough mode * revert to before walkthrough service added * add triggerRef to stories * change property name * add Escape key to close events * add initially open state * add docs * minor reformatting --------- Co-authored-by: William Martin <contact@willmartian.com>
This commit is contained in:
parent
c2e03d2cdc
commit
a4303fac59
@ -19,6 +19,7 @@ export * from "./menu";
|
||||
export * from "./multi-select";
|
||||
export * from "./navigation";
|
||||
export * from "./no-items";
|
||||
export * from "./popover";
|
||||
export * from "./progress";
|
||||
export * from "./radio-button";
|
||||
export * from "./search";
|
||||
|
150
libs/components/src/popover/default-positions.ts
Normal file
150
libs/components/src/popover/default-positions.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { ConnectedPosition } from "@angular/cdk/overlay";
|
||||
|
||||
const ORIGIN_OFFSET_PX = 6;
|
||||
const OVERLAY_OFFSET_PX = 24;
|
||||
|
||||
export type PositionIdentifier =
|
||||
| "right-start"
|
||||
| "right-center"
|
||||
| "right-end"
|
||||
| "left-start"
|
||||
| "left-center"
|
||||
| "left-end"
|
||||
| "below-start"
|
||||
| "below-center"
|
||||
| "below-end"
|
||||
| "above-start"
|
||||
| "above-center"
|
||||
| "above-end";
|
||||
|
||||
export interface DefaultPosition extends ConnectedPosition {
|
||||
id: PositionIdentifier;
|
||||
}
|
||||
|
||||
export const defaultPositions: DefaultPosition[] = [
|
||||
/**
|
||||
* The order of these positions matters. The Popover component will use
|
||||
* the first position that fits within the viewport.
|
||||
*/
|
||||
|
||||
// Popover opens to right of trigger
|
||||
{
|
||||
id: "right-start",
|
||||
offsetX: ORIGIN_OFFSET_PX,
|
||||
offsetY: -OVERLAY_OFFSET_PX,
|
||||
originX: "end",
|
||||
originY: "center",
|
||||
overlayX: "start",
|
||||
overlayY: "top",
|
||||
panelClass: ["bit-popover-right", "bit-popover-right-start"],
|
||||
},
|
||||
{
|
||||
id: "right-center",
|
||||
offsetX: ORIGIN_OFFSET_PX,
|
||||
originX: "end",
|
||||
originY: "center",
|
||||
overlayX: "start",
|
||||
overlayY: "center",
|
||||
panelClass: ["bit-popover-right", "bit-popover-right-center"],
|
||||
},
|
||||
{
|
||||
id: "right-end",
|
||||
offsetX: ORIGIN_OFFSET_PX,
|
||||
offsetY: OVERLAY_OFFSET_PX,
|
||||
originX: "end",
|
||||
originY: "center",
|
||||
overlayX: "start",
|
||||
overlayY: "bottom",
|
||||
panelClass: ["bit-popover-right", "bit-popover-right-end"],
|
||||
},
|
||||
// ... to left of trigger
|
||||
{
|
||||
id: "left-start",
|
||||
offsetX: -ORIGIN_OFFSET_PX,
|
||||
offsetY: -OVERLAY_OFFSET_PX,
|
||||
originX: "start",
|
||||
originY: "center",
|
||||
overlayX: "end",
|
||||
overlayY: "top",
|
||||
panelClass: ["bit-popover-left", "bit-popover-left-start"],
|
||||
},
|
||||
{
|
||||
id: "left-center",
|
||||
offsetX: -ORIGIN_OFFSET_PX,
|
||||
originX: "start",
|
||||
originY: "center",
|
||||
overlayX: "end",
|
||||
overlayY: "center",
|
||||
panelClass: ["bit-popover-left", "bit-popover-left-center"],
|
||||
},
|
||||
{
|
||||
id: "left-end",
|
||||
offsetX: -ORIGIN_OFFSET_PX,
|
||||
offsetY: OVERLAY_OFFSET_PX,
|
||||
originX: "start",
|
||||
originY: "center",
|
||||
overlayX: "end",
|
||||
overlayY: "bottom",
|
||||
panelClass: ["bit-popover-left", "bit-popover-left-end"],
|
||||
},
|
||||
// ... below trigger
|
||||
{
|
||||
id: "below-center",
|
||||
offsetY: ORIGIN_OFFSET_PX,
|
||||
originX: "center",
|
||||
originY: "bottom",
|
||||
overlayX: "center",
|
||||
overlayY: "top",
|
||||
panelClass: ["bit-popover-below", "bit-popover-below-center"],
|
||||
},
|
||||
{
|
||||
id: "below-start",
|
||||
offsetX: -OVERLAY_OFFSET_PX,
|
||||
offsetY: ORIGIN_OFFSET_PX,
|
||||
originX: "center",
|
||||
originY: "bottom",
|
||||
overlayX: "start",
|
||||
overlayY: "top",
|
||||
panelClass: ["bit-popover-below", "bit-popover-below-start"],
|
||||
},
|
||||
{
|
||||
id: "below-end",
|
||||
offsetX: OVERLAY_OFFSET_PX,
|
||||
offsetY: ORIGIN_OFFSET_PX,
|
||||
originX: "center",
|
||||
originY: "bottom",
|
||||
overlayX: "end",
|
||||
overlayY: "top",
|
||||
panelClass: ["bit-popover-below", "bit-popover-below-end"],
|
||||
},
|
||||
// ... above trigger
|
||||
{
|
||||
id: "above-center",
|
||||
offsetY: -ORIGIN_OFFSET_PX,
|
||||
originX: "center",
|
||||
originY: "top",
|
||||
overlayX: "center",
|
||||
overlayY: "bottom",
|
||||
panelClass: ["bit-popover-above", "bit-popover-above-center"],
|
||||
},
|
||||
{
|
||||
id: "above-start",
|
||||
offsetX: -OVERLAY_OFFSET_PX,
|
||||
offsetY: -ORIGIN_OFFSET_PX,
|
||||
originX: "center",
|
||||
originY: "top",
|
||||
overlayX: "start",
|
||||
overlayY: "bottom",
|
||||
panelClass: ["bit-popover-above", "bit-popover-above-start"],
|
||||
},
|
||||
{
|
||||
id: "above-end",
|
||||
offsetX: OVERLAY_OFFSET_PX,
|
||||
offsetY: -ORIGIN_OFFSET_PX,
|
||||
originX: "center",
|
||||
originY: "top",
|
||||
overlayX: "end",
|
||||
overlayY: "bottom",
|
||||
panelClass: ["bit-popover-above", "bit-popover-above-end"],
|
||||
},
|
||||
];
|
1
libs/components/src/popover/index.ts
Normal file
1
libs/components/src/popover/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./popover.module";
|
131
libs/components/src/popover/popover-trigger-for.directive.ts
Normal file
131
libs/components/src/popover/popover-trigger-for.directive.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { Overlay, OverlayConfig, OverlayRef } from "@angular/cdk/overlay";
|
||||
import { TemplatePortal } from "@angular/cdk/portal";
|
||||
import {
|
||||
AfterViewInit,
|
||||
Directive,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
OnDestroy,
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { Observable, Subscription, filter, mergeWith } from "rxjs";
|
||||
|
||||
import { defaultPositions } from "./default-positions";
|
||||
import { PopoverComponent } from "./popover.component";
|
||||
|
||||
@Directive({
|
||||
selector: "[bitPopoverTriggerFor]",
|
||||
standalone: true,
|
||||
exportAs: "popoverTrigger",
|
||||
})
|
||||
export class PopoverTriggerForDirective implements OnDestroy, AfterViewInit {
|
||||
@Input()
|
||||
@HostBinding("attr.aria-expanded")
|
||||
popoverOpen = false;
|
||||
|
||||
@Input("bitPopoverTriggerFor")
|
||||
popover: PopoverComponent;
|
||||
|
||||
@Input("position")
|
||||
position: string;
|
||||
|
||||
private overlayRef: OverlayRef;
|
||||
private closedEventsSub: Subscription;
|
||||
|
||||
get positions() {
|
||||
if (!this.position) {
|
||||
return defaultPositions;
|
||||
}
|
||||
|
||||
const preferredPosition = defaultPositions.find((position) => position.id === this.position);
|
||||
|
||||
if (preferredPosition) {
|
||||
return [preferredPosition, ...defaultPositions];
|
||||
}
|
||||
|
||||
return defaultPositions;
|
||||
}
|
||||
|
||||
get defaultPopoverConfig(): OverlayConfig {
|
||||
return {
|
||||
hasBackdrop: true,
|
||||
backdropClass: "cdk-overlay-transparent-backdrop",
|
||||
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
||||
positionStrategy: this.overlay
|
||||
.position()
|
||||
.flexibleConnectedTo(this.elementRef)
|
||||
.withPositions(this.positions)
|
||||
.withLockedPosition(true)
|
||||
.withFlexibleDimensions(false)
|
||||
.withPush(true),
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef<HTMLElement>,
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private overlay: Overlay
|
||||
) {}
|
||||
|
||||
@HostListener("click")
|
||||
togglePopover() {
|
||||
if (this.popoverOpen) {
|
||||
this.closePopover();
|
||||
} else {
|
||||
this.openPopover();
|
||||
}
|
||||
}
|
||||
|
||||
private openPopover() {
|
||||
this.popoverOpen = true;
|
||||
this.overlayRef = this.overlay.create(this.defaultPopoverConfig);
|
||||
|
||||
const templatePortal = new TemplatePortal(this.popover.templateRef, this.viewContainerRef);
|
||||
|
||||
this.overlayRef.attach(templatePortal);
|
||||
this.closedEventsSub = this.getClosedEvents().subscribe(() => {
|
||||
this.destroyPopover();
|
||||
});
|
||||
}
|
||||
|
||||
private getClosedEvents(): Observable<any> {
|
||||
const detachments = this.overlayRef.detachments();
|
||||
const escKey = this.overlayRef
|
||||
.keydownEvents()
|
||||
.pipe(filter((event: KeyboardEvent) => event.key === "Escape"));
|
||||
const backdrop = this.overlayRef.backdropClick();
|
||||
const popoverClosed = this.popover.closed;
|
||||
|
||||
return detachments.pipe(mergeWith(escKey, backdrop, popoverClosed));
|
||||
}
|
||||
|
||||
private destroyPopover() {
|
||||
if (this.overlayRef == null || !this.popoverOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.popoverOpen = false;
|
||||
this.disposeAll();
|
||||
}
|
||||
|
||||
private disposeAll() {
|
||||
this.closedEventsSub?.unsubscribe();
|
||||
this.overlayRef?.dispose();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (this.popoverOpen) {
|
||||
this.openPopover();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.disposeAll();
|
||||
}
|
||||
|
||||
closePopover() {
|
||||
this.destroyPopover();
|
||||
}
|
||||
}
|
49
libs/components/src/popover/popover.component.css
Normal file
49
libs/components/src/popover/popover.component.css
Normal file
@ -0,0 +1,49 @@
|
||||
.bit-popover-arrow {
|
||||
@apply tw-absolute tw-z-10 tw-h-4 tw-w-4 tw-rotate-45 tw-border-solid tw-bg-background;
|
||||
}
|
||||
|
||||
.bit-popover-right .bit-popover-arrow {
|
||||
@apply tw-left-1 -tw-translate-x-1/2 tw-rounded-bl-sm tw-border-b tw-border-l tw-border-b-secondary-300 tw-border-l-secondary-300;
|
||||
}
|
||||
|
||||
.bit-popover-left .bit-popover-arrow {
|
||||
@apply tw-right-1 tw-translate-x-1/2 tw-rounded-tr-sm tw-border-r tw-border-t tw-border-r-secondary-300 tw-border-t-secondary-300;
|
||||
}
|
||||
|
||||
.bit-popover-right-start .bit-popover-arrow,
|
||||
.bit-popover-left-start .bit-popover-arrow {
|
||||
@apply tw-top-6 -tw-translate-y-1/2;
|
||||
}
|
||||
|
||||
.bit-popover-right-center .bit-popover-arrow,
|
||||
.bit-popover-left-center .bit-popover-arrow {
|
||||
@apply tw-top-1/2 -tw-translate-y-1/2;
|
||||
}
|
||||
|
||||
.bit-popover-right-end .bit-popover-arrow,
|
||||
.bit-popover-left-end .bit-popover-arrow {
|
||||
@apply tw-bottom-6 tw-translate-y-1/2;
|
||||
}
|
||||
|
||||
.bit-popover-below .bit-popover-arrow {
|
||||
@apply tw-top-1 -tw-translate-y-1/2 tw-rounded-tl-sm tw-border-l tw-border-t tw-border-l-secondary-300 tw-border-t-secondary-300;
|
||||
}
|
||||
|
||||
.bit-popover-above .bit-popover-arrow {
|
||||
@apply tw-bottom-1 tw-translate-y-1/2 tw-rounded-br-sm tw-border-b tw-border-r tw-border-b-secondary-300 tw-border-r-secondary-300;
|
||||
}
|
||||
|
||||
.bit-popover-below-start .bit-popover-arrow,
|
||||
.bit-popover-above-start .bit-popover-arrow {
|
||||
@apply tw-left-6 -tw-translate-x-1/2;
|
||||
}
|
||||
|
||||
.bit-popover-below-center .bit-popover-arrow,
|
||||
.bit-popover-above-center .bit-popover-arrow {
|
||||
@apply tw-left-1/2 -tw-translate-x-1/2;
|
||||
}
|
||||
|
||||
.bit-popover-below-end .bit-popover-arrow,
|
||||
.bit-popover-above-end .bit-popover-arrow {
|
||||
@apply tw-right-6 tw-translate-x-1/2;
|
||||
}
|
26
libs/components/src/popover/popover.component.html
Normal file
26
libs/components/src/popover/popover.component.html
Normal file
@ -0,0 +1,26 @@
|
||||
<ng-template>
|
||||
<section cdkTrapFocus cdkTrapFocusAutoCapture class="tw-relative" role="dialog" aria-modal="true">
|
||||
<div class="tw-overflow-hidden tw-rounded-md tw-border tw-border-solid tw-border-secondary-300">
|
||||
<div
|
||||
class="tw-relative tw-z-20 tw-w-72 tw-break-words tw-bg-background tw-pb-4 tw-pt-2 tw-text-main"
|
||||
>
|
||||
<div class="tw-mb-1 tw-mr-2 tw-flex tw-items-start tw-justify-between tw-gap-4 tw-pl-4">
|
||||
<h2 class="tw-mb-0 tw-mt-1 tw-text-base tw-font-semibold">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-close"
|
||||
[attr.title]="'close' | i18n"
|
||||
[attr.aria-label]="'close' | i18n"
|
||||
(click)="closed.emit()"
|
||||
></button>
|
||||
</div>
|
||||
<div class="tw-px-4">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bit-popover-arrow"></div>
|
||||
</section>
|
||||
</ng-template>
|
18
libs/components/src/popover/popover.component.ts
Normal file
18
libs/components/src/popover/popover.component.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { A11yModule } from "@angular/cdk/a11y";
|
||||
import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from "@angular/core";
|
||||
|
||||
import { IconButtonModule } from "../icon-button/icon-button.module";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "bit-popover",
|
||||
imports: [A11yModule, IconButtonModule, SharedModule],
|
||||
templateUrl: "./popover.component.html",
|
||||
exportAs: "popoverComponent",
|
||||
})
|
||||
export class PopoverComponent {
|
||||
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
|
||||
@Input() title = "";
|
||||
@Output() closed = new EventEmitter();
|
||||
}
|
88
libs/components/src/popover/popover.mdx
Normal file
88
libs/components/src/popover/popover.mdx
Normal file
@ -0,0 +1,88 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./popover.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Popover
|
||||
|
||||
A popover is a page overlay that is triggered by a selecting a button. It displays interactive
|
||||
content.
|
||||
|
||||
Popovers remain actively open until a user dismisses it in one of the following ways:
|
||||
|
||||
- Presses the Esc key
|
||||
- Presses the close "x" button in the Popover
|
||||
- Presses a button within the Popover triggering close
|
||||
- Clicks outside of the Popover
|
||||
|
||||
Popovers are used to provide the user with additional context about an interaction or page. We
|
||||
primarily use popovers when a user clicks on an icon-button with a question icon. This launches a
|
||||
popover that provides the user with in app help text.
|
||||
|
||||
Note: Popovers are not tooltips. Use tooltips to show a short text to respondents when they hover
|
||||
over a word or icon. Use popovers to show a longer text, or when you want to link to an external web
|
||||
page.
|
||||
|
||||
<Primary />
|
||||
|
||||
## Open on Page Load
|
||||
|
||||
A Popover can be set to initially open on page load by setting `[popoverOpen]="true"` on the trigger
|
||||
element, like so:
|
||||
|
||||
```html
|
||||
<button [bitPopoverTriggerFor]="myPopover" [popoverOpen]="true">Open Popover</button>
|
||||
```
|
||||
|
||||
## Positions
|
||||
|
||||
The Popover component uses the following list of default "positions" to determine where to position
|
||||
the Popover overlay.
|
||||
|
||||
1. right-start ---> "Open the Popover to the RIGHT of the trigger and align the START of the Popover
|
||||
with the trigger"
|
||||
2. right-center
|
||||
3. right-end
|
||||
4. left-start
|
||||
5. left-center
|
||||
6. left-end
|
||||
7. below-start
|
||||
8. below-center
|
||||
9. below-end
|
||||
10. above-start
|
||||
11. above-center
|
||||
12. above-end
|
||||
|
||||
The order here matters. If position 1 fits within the viewport, it will be used. If it does not, the
|
||||
Popover component will try position 2, and so forth. This cascading behavior ensures that if the
|
||||
user resizes the screen, the Popover component will find the best way to reposition itself.
|
||||
|
||||
### Example
|
||||
|
||||
Suppose you have a trigger element on the right side of the page. The `right-start` position will
|
||||
not work because there is not enough space to open the Popover to the right. The same is true for
|
||||
`right-center` and `right-end`.
|
||||
|
||||
The first position that "fits" is `left-start`, and therefore that is where the Popover will open.
|
||||
|
||||
<Story of={stories.LeftStart} />
|
||||
|
||||
### Manually Setting a Position
|
||||
|
||||
You can manually set the initial position of the Popover by binding a `[position]` input on the
|
||||
Popover's trigger element, such as:
|
||||
|
||||
```html
|
||||
<button [bitPopoverTriggerFor]="myPopover" [position]="'above-end'">Open Popover</button>
|
||||
```
|
||||
|
||||
<Story of={stories.AboveEnd} />
|
||||
|
||||
Note that if the user resizes the page and the Popover no longer fits in the viewport, the Popover
|
||||
component will fall back to the list of default positions to find the best position.
|
||||
|
||||
To test this out, open the Popopver in the example above and then slowly resize your browser window
|
||||
horizontally to make it smaller. When the Popover no longer fits the `above-end` position, it will
|
||||
jump down below the trigger, using `below-center`, because that is the first position that fits
|
||||
based on the list of default positions.
|
10
libs/components/src/popover/popover.module.ts
Normal file
10
libs/components/src/popover/popover.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PopoverTriggerForDirective } from "./popover-trigger-for.directive";
|
||||
import { PopoverComponent } from "./popover.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [PopoverComponent, PopoverTriggerForDirective],
|
||||
exports: [PopoverComponent, PopoverTriggerForDirective],
|
||||
})
|
||||
export class PopoverModule {}
|
408
libs/components/src/popover/popover.stories.ts
Normal file
408
libs/components/src/popover/popover.stories.ts
Normal file
@ -0,0 +1,408 @@
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { ButtonModule } from "../button";
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||
|
||||
import { PopoverTriggerForDirective } from "./popover-trigger-for.directive";
|
||||
import { PopoverModule } from "./popover.module";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Popover",
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [PopoverModule, ButtonModule, IconButtonModule, SharedModule],
|
||||
providers: [
|
||||
{
|
||||
provide: I18nService,
|
||||
useFactory: () => {
|
||||
return new I18nMockService({
|
||||
close: "Close",
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1717-15868",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
position: {
|
||||
options: [
|
||||
"right-start",
|
||||
"right-center",
|
||||
"right-end",
|
||||
"left-start",
|
||||
"left-center",
|
||||
"left-end",
|
||||
"below-start",
|
||||
"below-center",
|
||||
"below-end",
|
||||
"above-start",
|
||||
"above-center",
|
||||
"above-end",
|
||||
],
|
||||
control: { type: "select" },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
position: "right-start",
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
type Story = StoryObj<PopoverTriggerForDirective>;
|
||||
|
||||
const popoverContent = `
|
||||
<bit-popover [title]="'Example Title'" #myPopover>
|
||||
<div>Lorem ipsum dolor <a href="#">adipisicing elit</a>.</div>
|
||||
<ul class="tw-mt-2 tw-mb-0 tw-pl-4">
|
||||
<li>Dolor sit amet consectetur</li>
|
||||
<li>Esse labore veniam tempora</li>
|
||||
<li>Adipisicing elit ipsum <a href="#">iustolaborum</a></li>
|
||||
</ul>
|
||||
<button bitButton class="tw-mt-3" (click)="triggerRef.closePopover()">Close</button>
|
||||
</bit-popover>
|
||||
`;
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const Open: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<bit-popover [title]="'Example Title'" #myPopover="popoverComponent">
|
||||
<div>Lorem ipsum dolor <a href="#">adipisicing elit</a>.</div>
|
||||
<ul class="tw-mt-2 tw-mb-0 tw-pl-4">
|
||||
<li>Dolor sit amet consectetur</li>
|
||||
<li>Esse labore veniam tempora</li>
|
||||
<li>Adipisicing elit ipsum <a href="#">iustolaborum</a></li>
|
||||
</ul>
|
||||
</bit-popover>
|
||||
|
||||
<div class="tw-h-40">
|
||||
<div class="cdk-overlay-pane bit-popover-right bit-popover-right-start">
|
||||
<ng-container *ngTemplateOutlet="myPopover.templateRef"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const InitiallyOpen: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
[popoverOpen]="true"
|
||||
#triggerRef="popoverTrigger"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const RightStart: Story = {
|
||||
args: {
|
||||
position: "right-start",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const RightCenter: Story = {
|
||||
args: {
|
||||
position: "right-center",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const RightEnd: Story = {
|
||||
args: {
|
||||
position: "right-end",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const LeftStart: Story = {
|
||||
args: {
|
||||
position: "left-start",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const LeftCenter: Story = {
|
||||
args: {
|
||||
position: "left-center",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
export const LeftEnd: Story = {
|
||||
args: {
|
||||
position: "left-end",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const BelowStart: Story = {
|
||||
args: {
|
||||
position: "below-start",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const BelowCenter: Story = {
|
||||
args: {
|
||||
position: "below-center",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const BelowEnd: Story = {
|
||||
args: {
|
||||
position: "below-end",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const AboveStart: Story = {
|
||||
args: {
|
||||
position: "above-start",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const AboveCenter: Story = {
|
||||
args: {
|
||||
position: "above-center",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const AboveEnd: Story = {
|
||||
args: {
|
||||
position: "above-end",
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<div class="tw-mt-32 tw-flex tw-justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-border-none tw-bg-transparent tw-text-primary-500"
|
||||
[bitPopoverTriggerFor]="myPopover"
|
||||
#triggerRef="popoverTrigger"
|
||||
[position]="'${args.position}'"
|
||||
>
|
||||
<i class="bwi bwi-question-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
${popoverContent}
|
||||
`,
|
||||
}),
|
||||
};
|
@ -160,6 +160,7 @@
|
||||
--tw-ring-offset-color: #002b36;
|
||||
}
|
||||
|
||||
@import "./popover/popover.component.css";
|
||||
@import "./search/search.component.css";
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user