Skip to content

Commit 391f9aa

Browse files
web-padawanjouni
andauthored
experiment: add popover base styles and visual tests (#9638)
Co-authored-by: Jouni Koivuviita <jouni@vaadin.com>
1 parent 87f7270 commit 391f9aa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+365
-5
lines changed

dev/popover.html

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,68 @@
1212
import '@vaadin/button';
1313
import '@vaadin/horizontal-layout';
1414
import '@vaadin/popover';
15+
import '@vaadin/radio-group';
1516
import '@vaadin/text-field';
1617
</script>
1718
</head>
1819

1920
<body>
20-
<vaadin-button id="button">Discount</vaadin-button>
21+
<style>
22+
#wrapper {
23+
width: 100%;
24+
height: 350px;
25+
display: flex;
26+
justify-content: center;
27+
align-items: center;
28+
margin: 1rem;
29+
}
2130

22-
<vaadin-popover for="button" position="bottom-start"></vaadin-popover>
31+
#target {
32+
width: 150px;
33+
height: 150px;
34+
display: flex;
35+
justify-content: center;
36+
align-items: center;
37+
border: solid 1px #e2e2e2;
38+
}
39+
</style>
40+
41+
<vaadin-radio-group id="positionGroup" label="Tooltip Position" theme="horizontal" value="bottom">
42+
<vaadin-radio-button value="bottom-start" label="Bottom Start"></vaadin-radio-button>
43+
<vaadin-radio-button value="bottom" label="Bottom"></vaadin-radio-button>
44+
<vaadin-radio-button value="bottom-end" label="Bottom End"></vaadin-radio-button>
45+
<vaadin-radio-button value="start-top" label="Start Top"></vaadin-radio-button>
46+
<vaadin-radio-button value="start" label="Start"></vaadin-radio-button>
47+
<vaadin-radio-button value="start-bottom" label="Start Bottom"></vaadin-radio-button>
48+
<vaadin-radio-button value="top-start" label="Top Start"></vaadin-radio-button>
49+
<vaadin-radio-button value="top" label="Top"></vaadin-radio-button>
50+
<vaadin-radio-button value="top-end" label="Top End"></vaadin-radio-button>
51+
<vaadin-radio-button value="end-top" label="End Top"></vaadin-radio-button>
52+
<vaadin-radio-button value="end" label="End"></vaadin-radio-button>
53+
<vaadin-radio-button value="end-bottom" label="End Bottom"></vaadin-radio-button>
54+
</vaadin-radio-group>
55+
56+
<div id="wrapper">
57+
<div id="target">Save</div>
58+
<vaadin-popover for="target" theme="arrow"></vaadin-popover>
59+
</div>
2360

2461
<script type="module">
2562
const popover = document.querySelector('vaadin-popover');
63+
const radioGroup = document.querySelector('#positionGroup');
2664

27-
// popover.trigger = ['hover', 'focus'];
65+
radioGroup.addEventListener('change', (e) => {
66+
popover.position = e.target.value;
67+
});
2868

2969
popover.renderer = (root) => {
3070
if (root.firstChild) {
3171
return;
3272
}
3373

3474
const layout = document.createElement('vaadin-horizontal-layout');
35-
layout.setAttribute('theme', 'spacing-s');
36-
layout.style.alignItems = 'baseline';
75+
layout.setAttribute('theme', 'spacing');
76+
layout.style.alignItems = 'end';
3777

3878
const field = document.createElement('vaadin-text-field');
3979
field.label = 'Discount code';

packages/popover/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
"lit.d.ts",
2424
"lit.js",
2525
"src",
26+
"!src/styles/*-base-styles.d.ts",
27+
"!src/styles/*-base-styles.js",
2628
"theme",
2729
"vaadin-*.d.ts",
2830
"vaadin-*.js",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2024 - 2025 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import type { CSSResult } from 'lit';
7+
8+
export const popoverOverlayStyles: CSSResult;
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2024 - 2025 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import { css } from 'lit';
7+
import { overlayStyles } from '@vaadin/overlay/src/styles/vaadin-overlay-base-styles.js';
8+
9+
const popoverOverlay = css`
10+
@layer base {
11+
:host {
12+
--_arrow-size: var(--vaadin-popover-arrow-size, 0.5rem);
13+
--_default-offset: 0.25rem;
14+
--_rtl-multiplier: 1;
15+
--_border-width: var(--vaadin-popover-border-width, var(--vaadin-overlay-border-width, 1px));
16+
}
17+
18+
[part='overlay']:focus-visible {
19+
outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
20+
}
21+
22+
:host([dir='rtl']) {
23+
--_rtl-multiplier: -1;
24+
}
25+
26+
:host([modeless][with-backdrop]) [part='backdrop'] {
27+
pointer-events: none;
28+
}
29+
30+
:host([position^='top'][top-aligned]) [part='overlay'],
31+
:host([position^='bottom'][top-aligned]) [part='overlay'] {
32+
margin-top: var(--vaadin-popover-offset-top, var(--_default-offset));
33+
}
34+
35+
[part='overlay'] {
36+
position: relative;
37+
overflow: visible;
38+
max-height: 100%;
39+
border: var(--_border-width) solid
40+
var(--vaadin-popover-border-color, var(--vaadin-overlay-border-color, var(--vaadin-border-color)));
41+
background: var(--vaadin-popover-background, var(--vaadin-overlay-background, var(--vaadin-background-color)));
42+
box-shadow: var(
43+
--vaadin-popover-box-shadow,
44+
var(--vaadin-overlay-box-shadow, 0 8px 24px -4px rgba(0, 0, 0, 0.3))
45+
);
46+
}
47+
48+
[part='content'] {
49+
overflow: auto;
50+
overscroll-behavior: contain;
51+
box-sizing: border-box;
52+
max-height: 100%;
53+
padding: var(--vaadin-popover-padding, var(--vaadin-padding));
54+
}
55+
56+
:host([theme~='no-padding']) [part='content'] {
57+
padding: 0;
58+
}
59+
60+
/* Increase the area of the popover so the pointer can go from the target directly to it. */
61+
[part='overlay']::before {
62+
position: absolute;
63+
content: '';
64+
inset-block: calc(var(--vaadin-popover-offset-top, var(--_default-offset)) * -1)
65+
calc(var(--vaadin-popover-offset-bottom, var(--_default-offset)) * -1);
66+
inset-inline: calc(var(--vaadin-popover-offset-start, var(--_default-offset)) * -1)
67+
calc(var(--vaadin-popover-offset-end, var(--_default-offset)) * -1);
68+
z-index: -1;
69+
pointer-events: auto;
70+
}
71+
72+
:host([position^='top'][bottom-aligned]) [part='overlay'],
73+
:host([position^='bottom'][bottom-aligned]) [part='overlay'] {
74+
margin-bottom: var(--vaadin-popover-offset-bottom, var(--_default-offset));
75+
}
76+
77+
:host([position^='start'][start-aligned]) [part='overlay'],
78+
:host([position^='end'][start-aligned]) [part='overlay'] {
79+
margin-inline-start: var(--vaadin-popover-offset-start, var(--_default-offset));
80+
}
81+
82+
:host([position^='start'][end-aligned]) [part='overlay'],
83+
:host([position^='end'][end-aligned]) [part='overlay'] {
84+
margin-inline-end: var(--vaadin-popover-offset-end, var(--_default-offset));
85+
}
86+
87+
[part='arrow'] {
88+
display: none;
89+
}
90+
91+
:host([theme~='arrow']) {
92+
--_default-offset: calc(0.25rem + var(--_arrow-size) / 2);
93+
}
94+
95+
:host([theme~='arrow']) [part='arrow'] {
96+
display: block;
97+
position: absolute;
98+
background: inherit;
99+
border: inherit;
100+
border-start-start-radius: var(--vaadin-popover-arrow-border-radius, 0);
101+
outline: inherit;
102+
box-shadow: inherit;
103+
width: var(--_arrow-size);
104+
height: var(--_arrow-size);
105+
rotate: 45deg;
106+
--o: 20px; /* clip-path outset, how far outward it extends to reveal the outline and box shadow */
107+
--b: var(--_border-width);
108+
/* We need this elaborate clip-path to allow the arrow bg and border to cover
109+
the overlay border but prevent the outline and box-shadow from covering it */
110+
clip-path: polygon(
111+
calc(var(--o) * -1) calc(var(--o) * -1),
112+
calc(100% + var(--o) - var(--b)) calc(var(--o) * -1),
113+
calc(100% - var(--b) * 1.4) 0,
114+
100% 0,
115+
calc(100% - var(--b)) var(--b),
116+
calc(100% - var(--b)) calc(var(--b) + var(--ff, 0px)),
117+
calc(var(--b) + var(--ff, 0px)) calc(100% - var(--b)),
118+
calc(var(--b)) calc(100% - var(--b)),
119+
0 100%,
120+
0 calc(100% - var(--b) * 1.4),
121+
calc(var(--o) * -1) calc(100% + var(--o) - var(--b))
122+
);
123+
}
124+
125+
/* Firefox renders a blurry edge for a diagonal clip-path + rotation,
126+
so we need to extend the clip-path slightly further on the diagonal */
127+
@supports (-moz-appearance: none) {
128+
:host([theme~='arrow']) [part='arrow'] {
129+
--ff: 1px;
130+
}
131+
}
132+
133+
/* bottom / top */
134+
:host([theme~='arrow']:is([position^='bottom'], [position^='top'])[start-aligned]) [part='arrow'] {
135+
inset-inline-start: calc(var(--_arrow-size) * 2);
136+
}
137+
138+
:host([theme~='arrow']:is([position^='bottom'], [position^='top'])[end-aligned]) [part='arrow'] {
139+
inset-inline-end: calc(var(--_arrow-size) * 2);
140+
}
141+
142+
:host([theme~='arrow']:is([position^='bottom'], [position^='top'])[arrow-centered]) [part='arrow'] {
143+
inset-inline-start: 50%;
144+
}
145+
146+
/* bottom */
147+
:host([theme~='arrow']:is([position^='bottom'], [position^='top'])[top-aligned]) [part='arrow'] {
148+
top: 0;
149+
translate: calc(-50% * var(--_rtl-multiplier)) -50%;
150+
}
151+
152+
:host([theme~='arrow']:is([position^='bottom'], [position^='top'])[end-aligned][top-aligned]) [part='arrow'] {
153+
translate: calc(50% * var(--_rtl-multiplier)) -50%;
154+
}
155+
156+
/* top */
157+
:host([theme~='arrow']:is([position^='bottom'], [position^='top'])[bottom-aligned]) [part='arrow'] {
158+
bottom: 0;
159+
rotate: 225deg;
160+
translate: calc(-50% * var(--_rtl-multiplier)) 50%;
161+
}
162+
163+
:host([theme~='arrow']:is([position^='bottom'], [position^='top'])[end-aligned][bottom-aligned]) [part='arrow'] {
164+
translate: calc(50% * var(--_rtl-multiplier)) 50%;
165+
}
166+
167+
/* start / end */
168+
:host([theme~='arrow']:is([position^='start'], [position^='end'])[top-aligned]) [part='arrow'] {
169+
rotate: -45deg;
170+
top: calc(var(--_arrow-size) * 2);
171+
}
172+
173+
:host([theme~='arrow']:is([position^='start'], [position^='end'])[bottom-aligned]) [part='arrow'] {
174+
rotate: -45deg;
175+
bottom: calc(var(--_arrow-size) * 2);
176+
}
177+
178+
:host([theme~='arrow']:is([position='start'], [position='end'])[top-aligned]) [part='arrow'] {
179+
top: 50%;
180+
}
181+
182+
:host([dir='rtl'][theme~='arrow']:is([position^='start'], [position^='end'])) [part='arrow'] {
183+
scale: -1;
184+
}
185+
186+
/* end */
187+
:host([theme~='arrow']:is([position^='start'], [position^='end'])[start-aligned]) [part='arrow'] {
188+
inset-inline-start: 0;
189+
translate: calc(-50% * var(--_rtl-multiplier)) -50%;
190+
}
191+
192+
:host([theme~='arrow']:is([position^='start'], [position^='end'])[start-aligned][bottom-aligned]) [part='arrow'] {
193+
translate: calc(-50% * var(--_rtl-multiplier)) 50%;
194+
}
195+
196+
/* start */
197+
:host([theme~='arrow']:is([position^='start'], [position^='end'])[end-aligned]) [part='arrow'] {
198+
rotate: 135deg;
199+
inset-inline-end: 0;
200+
translate: calc(50% * var(--_rtl-multiplier)) -50%;
201+
}
202+
203+
:host([theme~='arrow']:is([position^='start'], [position^='end'])[end-aligned][bottom-aligned]) [part='arrow'] {
204+
translate: calc(50% * var(--_rtl-multiplier)) 50%;
205+
}
206+
207+
@media (forced-colors: active) {
208+
:host {
209+
--_border-width: 3px;
210+
}
211+
}
212+
}
213+
`;
214+
215+
export const popoverOverlayStyles = [overlayStyles, popoverOverlay];

0 commit comments

Comments
 (0)