diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index 45160780be..d053ee38db 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -30,6 +30,7 @@ import("./numbered-list"); import("./overflow-dropdown"); import("./overflow-scroll"); import("./pagination"); +import("./popover"); import("./pw-strength-alert"); import("./relative-duration"); import("./search-combobox"); diff --git a/frontend/src/components/ui/popover.ts b/frontend/src/components/ui/popover.ts new file mode 100644 index 0000000000..f4ea9f153b --- /dev/null +++ b/frontend/src/components/ui/popover.ts @@ -0,0 +1,70 @@ +import { localized } from "@lit/localize"; +import SlTooltip from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.component.js"; +import slTooltipStyles from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.styles.js"; +import { css } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +/** + * Popovers are used to reveal supplementary information, like additional context or details. + * They're hidden until an anchor is activated, e.g. on hover. + * + * Popovers should be used to convey information in full sentences or complex HTML. + * To display titles, labels, and expand truncated text on hover, use ``. + * + * @attr {String} content + * @attr {String} placement + * @attr {String} trigger + * @attr {Boolean} open + */ +@customElement("btrix-popover") +@localized() +export class Popover extends SlTooltip { + @property({ type: Boolean, reflect: true }) + hoist = true; + + @property({ type: String, reflect: true }) + placement: SlTooltip["placement"] = "bottom"; + + static styles = [ + slTooltipStyles, + css` + :host { + --btrix-border: 1px solid var(--sl-panel-border-color); + --sl-tooltip-background-color: var(--sl-color-neutral-0); + --sl-tooltip-color: var(--sl-color-neutral-700); + --sl-tooltip-font-size: var(--sl-font-size-x-small); + --sl-tooltip-padding: var(--sl-spacing-small); + --sl-tooltip-line-height: var(--sl-line-height-dense); + } + + ::part(body) { + border: var(--btrix-border); + box-shadow: var(--sl-shadow-medium); + } + + ::part(arrow) { + z-index: 1; + } + + [placement^="bottom"]::part(arrow), + [placement^="left"]::part(arrow) { + border-top: var(--btrix-border); + } + + [placement^="bottom"]::part(arrow), + [placement^="right"]::part(arrow) { + border-left: var(--btrix-border); + } + + [placement^="top"]::part(arrow), + [placement^="right"]::part(arrow) { + border-bottom: var(--btrix-border); + } + + [placement^="top"]::part(arrow), + [placement^="left"]::part(arrow) { + border-right: var(--btrix-border); + } + `, + ]; +} diff --git a/frontend/src/stories/components/Popover.stories.ts b/frontend/src/stories/components/Popover.stories.ts new file mode 100644 index 0000000000..b3a61b7ac6 --- /dev/null +++ b/frontend/src/stories/components/Popover.stories.ts @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; + +import { renderComponent, type RenderProps } from "./Popover"; + +const meta = { + title: "Components/Popover", + component: "btrix-popover", + tags: ["autodocs"], + decorators: (story) => + html`
${story()}
`, + render: renderComponent, + argTypes: { + anchor: { table: { disable: true } }, + slottedContent: { table: { disable: true } }, + }, + args: { + content: "Popover content", + anchor: html`Hover me`, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: {}, +}; + +export const Open: Story = { + args: { + open: true, + anchor: html`Always open`, + }, +}; + +export const TopPlacement: Story = { + args: { + open: true, + placement: "top", + anchor: html`Popover displays below`, + }, +}; + +export const LeftPlacement: Story = { + args: { + open: true, + placement: "left", + anchor: html`Popover displays left`, + }, +}; + +export const RightPlacement: Story = { + args: { + open: true, + placement: "right", + anchor: html`Popover displays right`, + }, +}; + +export const HTMLContent: Story = { + args: { + open: true, + anchor: html`HTML Content`, + slottedContent: html` +
Popover Title
+
+

+ This popover has HTML content for displaying informative text or + additional details when the anchor is activated. +

+ `, + }, +}; diff --git a/frontend/src/stories/components/Popover.ts b/frontend/src/stories/components/Popover.ts new file mode 100644 index 0000000000..12cf427d79 --- /dev/null +++ b/frontend/src/stories/components/Popover.ts @@ -0,0 +1,33 @@ +import { html, nothing, type TemplateResult } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import type { Popover } from "@/components/ui/popover"; + +import "@/components/ui/popover"; + +export type RenderProps = Popover & { + anchor: TemplateResult; + slottedContent: TemplateResult; +}; + +export const renderComponent = ({ + content, + placement, + open, + anchor, + slottedContent, +}: Partial) => { + return html` + + ${anchor} + ${slottedContent + ? html`
${slottedContent}
` + : nothing} +
+ `; +}; diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index 4a2321dd73..8684c0afce 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -258,6 +258,7 @@ } /* Style tooltip with white background */ + /* TODO Replace instances with `` */ sl-tooltip.invert-tooltip { --sl-tooltip-arrow-size: 0; --sl-tooltip-background-color: var(--sl-color-neutral-50);