Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add Popover documentation and live examples #3587

Merged
merged 12 commits into from
Sep 4, 2024
Merged
565 changes: 565 additions & 0 deletions articles/components/popover/index.adoc

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions articles/components/popover/styling.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Styling
description: Styling API reference for the Popover component.
order: 50
---
= Styling


include::../_styling-section-theming-props.adoc[tag=style-properties]

[cols="1,2,2"]
|===
| Feature | Property | Default Value

|Arrow size
|`--vaadin-popover-arrow-size`
|`0.5rem`

|Top offset
|`--vaadin-popover-offset-top`
|`var(--lumo-space-xs)`

|Bottom offset
|`--vaadin-popover-offset-bottom`
|`var(--lumo-space-xs)`

|Start offset
|`--vaadin-popover-offset-start`
|`var(--lumo-space-xs)`

|End offset
|`--vaadin-popover-offset-end`
|`var(--lumo-space-xs)`

|===


include::../_styling-section-intros.adoc[tag=selectors]


Root element:: `vaadin-popover-overlay`


=== States

Non-modal:: `vaadin-popover-overlay+++<wbr>+++**[modeless]**`


=== Parts

Modality curtain (backdrop):: `vaadin-popover-overlay+++<wbr>+++**::part(backdrop)**`
Popover surface:: `vaadin-popover-overlay+++<wbr>+++**::part(overlay)**`
Content wrapper:: `vaadin-popover-overlay+++<wbr>+++**::part(content)**`
Arrow element:: `vaadin-popover-overlay+++<wbr>+++**::part(arrow)**`

=== Style Variants

Arrow:: `vaadin-popover+++<wbr>+++**[theme~="arrow"]**`
125 changes: 125 additions & 0 deletions frontend/demo/component/popover/popover-anchored-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import 'Frontend/demo/init'; // hidden-source-line

import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/button';
import '@vaadin/checkbox-group';
import '@vaadin/grid';
import '@vaadin/horizontal-layout';
import '@vaadin/icon';
import '@vaadin/icons';
import '@vaadin/popover';
import type { CheckboxChangeEvent } from '@vaadin/checkbox';
import { popoverRenderer } from '@vaadin/popover/lit.js';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
import { applyTheme } from 'Frontend/generated/theme';

const DEFAULT_COLUMNS = [
{ label: 'First name', key: 'firstName', visible: true },
{ label: 'Last name', key: 'lastName', visible: true },
{ label: 'Email', key: 'email', visible: true },
{ label: 'Phone', key: 'address.phone', visible: false },
{ label: 'Birthday', key: 'birthday', visible: false },
{ label: 'Profession', key: 'profession', visible: true },
];

@customElement('popover-anchored-dialog')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}

// tag::snippet[]
@state()
private items: Person[] = [];

@state()
private gridColumns = [...DEFAULT_COLUMNS];

protected override async firstUpdated() {
const { people } = await getPeople();
this.items = people;
}

protected override render() {
return html`
<!-- tag::snippet[] -->
<vaadin-horizontal-layout style="align-items: baseline">
<strong style="flex: 1;">Employees</strong>
<vaadin-button id="toggle-columns" theme="icon" aria-label="Show / hide columns">
<vaadin-icon icon="vaadin:grid-h"></vaadin-icon>
</vaadin-button>
</vaadin-horizontal-layout>

<vaadin-popover
for="toggle-columns"
modal
with-backdrop
position="bottom-end"
${popoverRenderer(this.popoverRenderer, [this.gridColumns])}
></vaadin-popover>
<!-- end::snippet[] -->

<vaadin-grid .items="${this.items}">
${this.gridColumns.map(
(column) => html`
<vaadin-grid-column
path="${column.key}"
.hidden="${!column.visible}"
></vaadin-grid-column>
`
)}
</vaadin-grid>
`;
}

// tag::snippet[]
popoverRenderer() {
const visibleColumns = this.gridColumns
.filter((column) => column.visible)
.map((column) => column.key);

return html`
<h4 style="margin: 0">Configure columns</h4>
<vaadin-checkbox-group theme="vertical" .value="${visibleColumns}">
${this.gridColumns.map(
(column) => html`
<vaadin-checkbox
.label="${column.label}"
.value="${column.key}"
@change="${this.onCheckboxChange}"
></vaadin-checkbox>
`
)}
</vaadin-checkbox-group>
<vaadin-horizontal-layout theme="spacing-xs">
<vaadin-button @click="${this.showAllColumns}">Show all</vaadin-button>
<vaadin-button @click="${this.resetColumns}">Reset</vaadin-button>
</vaadin-horizontal-layout>
`;
}
// end::snippet[]

onCheckboxChange(event: CheckboxChangeEvent) {
const idx = this.gridColumns.findIndex(({ key }) => key === event.target.value);
this.gridColumns = this.gridColumns.map((column, index) => ({
...column,
visible: idx === index ? event.target.checked : column.visible,
}));
}

showAllColumns() {
this.gridColumns = this.gridColumns.map((column) => ({ ...column, visible: true }));
}

resetColumns() {
this.gridColumns = this.gridColumns.map((column, idx) => ({
...column,
visible: DEFAULT_COLUMNS[idx].visible,
}));
}
}
39 changes: 39 additions & 0 deletions frontend/demo/component/popover/popover-arrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'Frontend/demo/init'; // hidden-source-line

import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import '@vaadin/button';
import '@vaadin/icon';
import '@vaadin/popover';
import '@vaadin/vaadin-lumo-styles/vaadin-iconset.js';
import { popoverRenderer } from '@vaadin/popover/lit.js';
import { applyTheme } from 'Frontend/generated/theme';

@customElement('popover-arrow')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}

protected override render() {
return html`
<vaadin-button id="target" aria-label="notifications" theme="icon">
<vaadin-icon icon="lumo:bell"></vaadin-icon>
</vaadin-button>
<!-- tag::snippet[] -->
<vaadin-popover
for="target"
theme="arrow"
${popoverRenderer(this.popoverRenderer)}
></vaadin-popover>
<!-- end::snippet[] -->
`;
}

popoverRenderer() {
return html`<div>No new notifications</div>`;
}
}
157 changes: 157 additions & 0 deletions frontend/demo/component/popover/popover-dropdown-field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import 'Frontend/demo/init'; // hidden-source-line

import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/date-picker';
import '@vaadin/horizontal-layout';
import '@vaadin/icon';
import '@vaadin/popover';
import '@vaadin/select';
import '@vaadin/text-field';
import '@vaadin/vaadin-lumo-styles/vaadin-iconset.js';
import type { DatePickerChangeEvent } from '@vaadin/date-picker';
import type { PopoverOpenedChangedEvent, PopoverTrigger } from '@vaadin/popover';
import type { SelectChangeEvent } from '@vaadin/select';
import { popoverRenderer } from '@vaadin/popover/lit.js';
import { applyTheme } from 'Frontend/generated/theme';
import { formatISO, subMonths, subWeeks, subYears } from 'date-fns';

@customElement('popover-dropdown-field')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}

@state()
range = '';

@state()
from = '';

@state()
to = '';

@state()
opened = false;

@state()
trigger: PopoverTrigger[] = ['click', 'focus'];

@state()
presets = [
{ label: 'Today', value: 'today' },
{ label: 'Last week', value: 'last-week' },
{ label: 'Last month', value: 'last-month' },
{ label: 'Year to date', value: 'year-to-date' },
{ label: 'Last year', value: 'last-year' },
{ label: 'Past 5 years', value: 'past-5-years' },
];

protected override render() {
return html`
<vaadin-text-field
id="range-field"
label="Search date range"
style="width: 340px"
.value="${this.from && this.to ? `${this.from} − ${this.to}` : ''}"
>
<vaadin-icon icon="lumo:dropdown" slot="suffix"></vaadin-icon>
</vaadin-text-field>
<!-- tag::snippet[] -->
<vaadin-popover
for="range-field"
.trigger="${this.trigger}"
focus-delay="0"
modal
content-width="325px"
position="bottom-start"
accessible-name="Select a date range"
.opened="${this.opened}"
@opened-changed="${this.onOpenedChanged}"
${popoverRenderer(this.popoverRenderer, [this.from, this.to])}
></vaadin-popover>
<!-- end::snippet[] -->
`;
}

// tag::snippet[]
popoverRenderer() {
return html`
<vaadin-select
label="Common ranges"
.items="${this.presets}"
placeholder="Select preset"
style="width: 100%"
.value="${this.range}"
@change="${this.onRangeChange}"
></vaadin-select>
<vaadin-horizontal-layout theme="spacing-s" style="align-items: baseline">
<vaadin-date-picker
label="From"
style="width: 150px"
.value="${this.from}"
@change="${this.onFromChange}"
></vaadin-date-picker>
<div>−</div>
<vaadin-date-picker
label="To"
style="width: 150px"
.value="${this.to}"
@change="${this.onToChange}"
></vaadin-date-picker>
</vaadin-horizontal-layout>
`;
}
// end::snippet[]

onFromChange(event: DatePickerChangeEvent) {
this.range = '';
this.from = event.target.value;
}

onToChange(event: DatePickerChangeEvent) {
this.range = '';
this.to = event.target.value;
}

onOpenedChanged(event: PopoverOpenedChangedEvent) {
this.opened = event.detail.value;
}

onRangeChange(event: SelectChangeEvent) {
this.range = event.target.value;
this.to = this.formatDate(new Date());

switch (event.target.value) {
case 'today':
this.from = this.formatDate(new Date());
break;
case 'last-week':
this.from = this.formatDate(subWeeks(new Date(), 1));
break;
case 'last-month':
this.from = this.formatDate(subMonths(new Date(), 1));
break;
case 'year-to-date':
this.from = this.formatDate(new Date(new Date().getFullYear(), 0, 1));
break;
case 'last-year':
this.from = this.formatDate(subYears(new Date(), 1));
break;
case 'past-5-years':
this.from = this.formatDate(subYears(new Date(), 5));
break;
default:
// Do nothing
}

this.opened = false;
}

formatDate(date: Date) {
return formatISO(date, { representation: 'date' });
}
}
Loading
Loading