PopoverAnchoredDialog.java
package com.vaadin.demo.component.popover; import java.time.LocalDate; import java.time.ZoneId; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import com.vaadin.demo.domain.DataService; import com.vaadin.demo.domain.Person; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.checkbox.CheckboxGroup; import com.vaadin.flow.component.checkbox.CheckboxGroupVariant; import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.H3; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.popover.Popover; import com.vaadin.flow.component.popover.PopoverPosition; import com.vaadin.flow.data.renderer.LocalDateRenderer; import com.vaadin.flow.router.Route; @Route("popover-anchored-dialog") public class PopoverAnchoredDialog extends Div { public PopoverAnchoredDialog() { // tag::gridsnippet[] Grid<Person> grid = new Grid<>(Person.class, false); grid.addColumn(Person::getFirstName).setKey("firstName") .setHeader("First name"); grid.addColumn(Person::getLastName).setKey("lastName") .setHeader("Last name"); grid.addColumn(Person::getEmail).setKey("email").setHeader("Email"); grid.addColumn(person -> person.getAddress().getPhone()).setKey("phone") .setHeader("Phone"); grid.addColumn(new LocalDateRenderer<>( PopoverAnchoredDialog::getPersonBirthday, "yyyy-MM-dd")) .setKey("birthday").setHeader("Birthday"); grid.addColumn(Person::getProfession).setKey("profession") .setHeader("Profession"); // end::gridsnippet[] grid.setItems(DataService.getPeople()); H3 title = new H3("Employees"); Button button = new Button(VaadinIcon.GRID_H.create()); button.addThemeVariants(ButtonVariant.LUMO_ICON); button.setAriaLabel("Show / hide columns"); HorizontalLayout headerLayout = new HorizontalLayout(title, button); headerLayout.setAlignItems(FlexComponent.Alignment.BASELINE); headerLayout.setFlexGrow(1, title); Popover popover = new Popover(); popover.setModal(true); popover.setBackdropVisible(true); popover.setPosition(PopoverPosition.BOTTOM_END); popover.setTarget(button); Div heading = new Div("Configure columns"); heading.getStyle().set("font-weight", "600"); heading.getStyle().set("padding", "var(--lumo-space-xs)"); List<String> columns = List.of("firstName", "lastName", "email", "phone", "birthday", "profession"); // tag::gridsnippet[] CheckboxGroup<String> group = new CheckboxGroup<>(); group.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL); group.setItems(columns); group.setItemLabelGenerator((item) -> { String label = StringUtils .join(StringUtils.splitByCharacterTypeCamelCase(item), " "); return StringUtils.capitalize(label.toLowerCase()); }); group.addValueChangeListener((e) -> { columns.stream().forEach((key) -> { grid.getColumnByKey(key).setVisible(e.getValue().contains(key)); }); }); // end::gridsnippet[] Set<String> defaultColumns = Set.of("firstName", "lastName", "email", "profession"); group.setValue(defaultColumns); Button showAll = new Button("Show all", (e) -> { group.setValue(new HashSet<String>(columns)); }); showAll.addThemeVariants(ButtonVariant.LUMO_SMALL); Button reset = new Button("Reset", (e) -> { group.setValue(defaultColumns); }); reset.addThemeVariants(ButtonVariant.LUMO_SMALL); HorizontalLayout footer = new HorizontalLayout(showAll, reset); footer.setSpacing(false); footer.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN); popover.add(heading, group, footer); add(headerLayout, grid, popover); } private static LocalDate getPersonBirthday(Person person) { return person.getBirthday().toInstant().atZone(ZoneId.systemDefault()) .toLocalDate(); } }
popover-anchored-dialog.tsx
import '@vaadin/icons'; import React, { useEffect } from 'react'; import { useComputed, useSignal } from '@vaadin/hilla-react-signals'; import { Button, Checkbox, CheckboxGroup, Grid, GridColumn, HorizontalLayout, Icon, Popover, } from '@vaadin/react-components'; import { getPeople } from 'Frontend/demo/domain/DataService'; import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person'; type ColumnConfig = { label: string; key: string; visible: boolean }; const DEFAULT_COLUMNS: ColumnConfig[] = [ { 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 }, ]; function Example() { const items = useSignal<Person[]>([]); const columns = useSignal<ColumnConfig[]>([...DEFAULT_COLUMNS]); const visibleColumns = useComputed(() => columns.value.filter((column) => column.visible).map((column) => column.key) ); useEffect(() => { getPeople().then(({ people }) => { items.value = people; }); }, []); return ( <> <HorizontalLayout style={{ alignItems: 'baseline' }}> <h3 style={{ flex: 1 }}>Employees</h3> <Button id="toggle-columns" theme="icon" aria-label="Show / hide columns"> <Icon icon="vaadin:grid-h" /> </Button> </HorizontalLayout> <Popover for="toggle-columns" modal withBackdrop position="bottom-end"> <div style={{ fontWeight: '600', padding: 'var(--lumo-space-xs)' }}>Configure columns</div> <CheckboxGroup theme="vertical" value={visibleColumns.value}> {columns.value.map((item) => ( <Checkbox label={item.label} value={item.key} onChange={(event) => { const idx = columns.value.findIndex(({ key }) => key === event.target.value); columns.value = columns.value.map((column, index) => ({ ...column, visible: idx === index ? event.target.checked : column.visible, })); }} /> ))} </CheckboxGroup> <HorizontalLayout style={{ justifyContent: 'space-between' }}> <Button theme="small" onClick={() => { columns.value = columns.value.map((column) => ({ ...column, visible: true })); }} > Show all </Button> <Button theme="small" onClick={() => { columns.value = columns.value.map((column, idx) => ({ ...column, visible: DEFAULT_COLUMNS[idx].visible, })); }} > Reset </Button> </HorizontalLayout> </Popover> {/* tag::gridsnippet[] */} <Grid items={items.value}> {columns.value.map((item) => ( <GridColumn path={item.key} hidden={!item.visible} key={item.key} /> ))} </Grid> {/* end::gridsnippet[] */} </> ); }
popover-anchored-dialog.ts
import '@vaadin/button'; import '@vaadin/checkbox-group'; import '@vaadin/grid'; import '@vaadin/horizontal-layout'; import '@vaadin/icon'; import '@vaadin/icons'; import '@vaadin/popover'; import { html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; 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; } @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` <vaadin-horizontal-layout style="align-items: baseline"> <h3 style="flex: 1;">Employees</h3> <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> <!-- tag::gridsnippet[] --> <vaadin-grid .items="${this.items}"> ${this.gridColumns.map( (column) => html` <vaadin-grid-column path="${column.key}" .hidden="${!column.visible}" ></vaadin-grid-column> ` )} </vaadin-grid> <!-- end::gridsnippet[] --> `; } popoverRenderer() { const visibleColumns = this.gridColumns .filter((column) => column.visible) .map((column) => column.key); return html` <div style="font-weight: 600; padding: var(--lumo-space-xs);">Configure columns</div> <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 style="justify-content: space-between;"> <vaadin-button theme="small" @click="${this.showAllColumns}">Show all</vaadin-button> <vaadin-button theme="small" @click="${this.resetColumns}">Reset</vaadin-button> </vaadin-horizontal-layout> `; } 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, })); } }