Skip to content

Commit 81aad44

Browse files
anezthesjouni
andauthored
refactor!: Base combo box styles (#8932)
Co-authored-by: Jouni Koivuviita <jouni@vaadin.com>
1 parent 107e4c9 commit 81aad44

File tree

11 files changed

+209
-54
lines changed

11 files changed

+209
-54
lines changed

dev/combo-box.html

+67-28
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,84 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
55
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Combo box</title>
7+
<title>Combo Box</title>
88
<script type="module" src="./common.js"></script>
9+
910
<script type="module">
10-
import '@vaadin/combo-box';
11-
import '@vaadin/combo-box/vaadin-combo-box-light.js';
12-
import '@vaadin/tooltip';
11+
import '@vaadin/combo-box/src/vaadin-lit-combo-box.js';
12+
import '@vaadin/icon/src/vaadin-lit-icon.js';
13+
import '@vaadin/vaadin-lumo-styles/vaadin-iconset.js';
14+
import '@vaadin/icons';
1315
</script>
1416
</head>
1517
<body>
16-
<vaadin-combo-box label="Country">
17-
<vaadin-tooltip slot="tooltip" text="Loading might take time"></vaadin-tooltip>
18-
</vaadin-combo-box>
18+
<section>
19+
<h2>Plain</h2>
20+
<vaadin-combo-box></vaadin-combo-box>
21+
<vaadin-combo-box placeholder="Placeholder"></vaadin-combo-box>
22+
</section>
1923

20-
<script type="module">
21-
const comboBox = document.querySelector('vaadin-combo-box');
24+
<section>
25+
<h2>Bells & Whistles</h2>
26+
<vaadin-combo-box
27+
label="Label"
28+
helper-text="Description for this field."
29+
clear-button-visible
30+
error-message="You need to write something in this field."
31+
required
32+
>
33+
<vaadin-icon icon="vaadin:search" slot="prefix"></vaadin-icon>
34+
</vaadin-combo-box>
35+
</section>
36+
37+
<section>
38+
<h2>States</h2>
39+
<vaadin-combo-box
40+
label="Read-only"
41+
helper-text="Description for this field."
42+
clear-button-visible
43+
error-message="You need to write something in this field."
44+
required
45+
readonly
46+
>
47+
<vaadin-icon icon="vaadin:search" slot="prefix"></vaadin-icon>
48+
</vaadin-combo-box>
2249

23-
/*
24-
const res = await fetch('https://demo.vaadin.com/demo-data/1.0/filtered-countries?count=200');
25-
const arr = await res.json();
26-
comboBox.items = arr.result;
27-
*/
50+
<vaadin-combo-box
51+
label="Disabled"
52+
helper-text="Description for this field."
53+
clear-button-visible
54+
error-message="You need to write something in this field."
55+
required
56+
disabled
57+
>
58+
<vaadin-icon icon="vaadin:search" slot="prefix"></vaadin-icon>
59+
</vaadin-combo-box>
60+
</section>
61+
62+
<script type="module">
63+
document.querySelectorAll('vaadin-combo-box').forEach((comboBox) => {
64+
comboBox.dataProvider = async (params, callback) => {
65+
const index = params.page * params.pageSize;
66+
const response = await fetch(
67+
`https://demo.vaadin.com/demo-data/1.0/filtered-countries?index=${index}&count=${params.pageSize}&filter=${params.filter}`,
68+
);
69+
if (response.ok) {
70+
const { result, size } = await response.json();
71+
// Emulate network latency for demo purpose
72+
setTimeout(() => {
73+
callback(result, size);
74+
}, 1000);
75+
}
76+
};
2877

29-
comboBox.dataProvider = async (params, callback) => {
30-
console.log(JSON.stringify(params));
31-
const index = params.page * params.pageSize;
32-
const response = await fetch(
33-
`https://demo.vaadin.com/demo-data/1.0/filtered-countries?index=${index}&count=${params.pageSize}&filter=${params.filter}`,
34-
);
35-
if (response.ok) {
36-
const { result, size } = await response.json();
37-
// Emulate network latency for demo purpose
38-
setTimeout(() => {
39-
callback(result, size);
40-
}, 100);
78+
if (!comboBox.placeholder) {
79+
comboBox.selectedItem = 'Andorra';
4180
}
42-
};
81+
});
4382
</script>
4483
</body>
4584
</html>

packages/combo-box/src/vaadin-lit-combo-box-item.js

+3-10
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
* Copyright (c) 2015 - 2025 Vaadin Ltd.
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
6-
import { css, html, LitElement } from 'lit';
6+
import { html, LitElement } from 'lit';
77
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
88
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
99
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10+
import { itemStyles } from '@vaadin/item/src/vaadin-item-styles.js';
1011
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
1112
import { ComboBoxItemMixin } from './vaadin-combo-box-item-mixin.js';
1213

@@ -25,15 +26,7 @@ export class ComboBoxItem extends ComboBoxItemMixin(ThemableMixin(DirMixin(Polyl
2526
}
2627

2728
static get styles() {
28-
return css`
29-
:host {
30-
display: block;
31-
}
32-
33-
:host([hidden]) {
34-
display: none;
35-
}
36-
`;
29+
return itemStyles;
3730
}
3831

3932
/** @protected */

packages/combo-box/src/vaadin-lit-combo-box-overlay.js

+39-1
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,53 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
1313
import { ComboBoxOverlayMixin } from './vaadin-combo-box-overlay-mixin.js';
1414

1515
const comboBoxOverlayStyles = css`
16-
#overlay {
16+
:host {
17+
--vaadin-item-checkmark-display: block;
18+
}
19+
20+
[part='overlay'] {
21+
position: relative;
1722
width: var(--vaadin-combo-box-overlay-width, var(--_vaadin-combo-box-overlay-default-width, auto));
1823
}
1924
25+
[part='loader'] {
26+
animation: spin 1s linear infinite;
27+
border: 2px solid;
28+
--_spinner-color: var(--vaadin-combo-box-spinner-color, var(--_vaadin-color-strong));
29+
border-color: var(--_spinner-color) var(--_spinner-color) hsl(from var(--_spinner-color) h s l / 0.2)
30+
hsl(from var(--_spinner-color) h s l / 0.2);
31+
forced-color-adjust: none; /* Retain border color */
32+
border-radius: var(--_vaadin-radius-full);
33+
box-sizing: border-box;
34+
display: none;
35+
height: var(--vaadin-icon-size, 1lh);
36+
inset: calc(var(--vaadin-item-overlay-padding, 4px) + 2px);
37+
inset-block-end: auto;
38+
inset-inline-start: auto;
39+
pointer-events: none;
40+
position: absolute;
41+
width: var(--vaadin-icon-size, 1lh);
42+
}
43+
2044
[part='content'] {
2145
display: flex;
2246
flex-direction: column;
2347
height: 100%;
2448
}
49+
50+
:host([loading]) [part='loader'] {
51+
display: block;
52+
}
53+
54+
:host([loading]) [part='content'] {
55+
--_items-min-height: calc(var(--vaadin-icon-size, 1lh) + 4px);
56+
}
57+
58+
@keyframes spin {
59+
to {
60+
rotate: 1turn;
61+
}
62+
}
2563
`;
2664

2765
/**

packages/combo-box/src/vaadin-lit-combo-box-scroller.js

+5-10
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,20 @@ export class ComboBoxScroller extends ComboBoxScrollerMixin(PolylitMixin(LitElem
2323
static get styles() {
2424
return css`
2525
:host {
26+
/* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */
27+
box-shadow: 0 0 0 white;
2628
display: block;
2729
min-height: 1px;
2830
overflow: auto;
29-
3031
/* Fixes item background from getting on top of scrollbars on Safari */
3132
transform: translate3d(0, 0, 0);
32-
33-
/* Enable momentum scrolling on iOS */
34-
-webkit-overflow-scrolling: touch;
35-
36-
/* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */
37-
box-shadow: 0 0 0 white;
3833
}
3934
4035
#selector {
41-
border-width: var(--_vaadin-combo-box-items-container-border-width);
42-
border-style: var(--_vaadin-combo-box-items-container-border-style);
43-
border-color: var(--_vaadin-combo-box-items-container-border-color, transparent);
36+
border: var(--vaadin-item-overlay-padding, 4px) solid transparent;
4437
position: relative;
38+
forced-color-adjust: none;
39+
min-height: var(--_items-min-height, auto);
4540
}
4641
`;
4742
}

packages/combo-box/src/vaadin-lit-combo-box.js

+19
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ class ComboBox extends ComboBoxDataProviderMixin(
4545
:host([opened]) {
4646
pointer-events: auto;
4747
}
48+
49+
[part='toggle-button'] {
50+
color: var(--_vaadin-color-subtle);
51+
}
52+
53+
[part='toggle-button']::before {
54+
background: currentColor;
55+
content: '';
56+
display: block;
57+
height: var(--vaadin-icon-size, 1lh);
58+
mask-image: var(--_vaadin-icon-chevron-down);
59+
width: var(--vaadin-icon-size, 1lh);
60+
}
61+
62+
@media (forced-colors: active) {
63+
[part='toggle-button']::before {
64+
background: CanvasText;
65+
}
66+
}
4867
`,
4968
];
5069
}

packages/combo-box/test/basic.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ describe('pre-opened', () => {
252252
const comboBox = fixtureSync(`<vaadin-combo-box opened items="[0]"></vaadin-combo-box>`);
253253
await nextRender();
254254
const expectedOverlayWidth = comboBox.clientWidth;
255-
const actualOverlayWidth = comboBox.$.overlay.$.content.clientWidth;
255+
const actualOverlayWidth = comboBox.$.overlay.$.overlay.offsetWidth;
256256
expect(actualOverlayWidth).to.eq(expectedOverlayWidth);
257257
});
258258

packages/combo-box/test/helpers.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ export const getAllItems = (comboBox) => {
4343
export const getViewportItems = (comboBox) => {
4444
const overlayRect = comboBox.$.overlay.$.content.getBoundingClientRect();
4545

46+
// Take the default 4px border width into account
47+
const scrollerTop = parseInt(getComputedStyle(comboBox._scroller.$.selector).borderTopWidth);
48+
4649
// Firefox can produce values like 19.199996948242188
47-
const top = Math.round(overlayRect.top);
50+
const top = Math.round(overlayRect.top) + scrollerTop;
4851
const bottom = Math.round(overlayRect.bottom);
4952

5053
return getAllItems(comboBox).filter((item) => {

packages/component-base/src/style-props.js

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ addGlobalThemeStyles(
4848
4949
/* Icons, used as mask-image */
5050
--_vaadin-icon-calendar: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" /></svg>');
51+
--_vaadin-icon-checkmark: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>');
52+
--_vaadin-icon-chevron-down: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" /></svg>');
5153
--_vaadin-icon-cross: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>');
5254
--_vaadin-icon-warn: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" /></svg>');
5355
}
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2017 - 2025 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import '@vaadin/component-base/src/style-props.js';
7+
import { css } from 'lit';
8+
9+
export const itemStyles = css`
10+
@layer base {
11+
:host {
12+
align-items: center;
13+
border-radius: var(--vaadin-item-border-radius, var(--_vaadin-radius-m));
14+
box-sizing: border-box;
15+
cursor: pointer;
16+
display: flex;
17+
gap: var(--vaadin-item-gap, 0 var(--_vaadin-gap-container-inline));
18+
height: var(--vaadin-item-height, auto);
19+
padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
20+
}
21+
22+
:host([focused]) {
23+
outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
24+
outline-offset: calc(var(--vaadin-focus-ring-width) / -1);
25+
}
26+
27+
:host([disabled]) {
28+
cursor: not-allowed;
29+
opacity: 0.5;
30+
}
31+
32+
:host([hidden]) {
33+
display: none !important;
34+
}
35+
36+
[part='checkmark'] {
37+
color: var(--vaadin-item-checkmark-color, var(--_vaadin-color-subtle));
38+
display: var(--vaadin-item-checkmark-display, none);
39+
visibility: hidden;
40+
}
41+
42+
[part='checkmark']::before {
43+
content: '';
44+
display: block;
45+
background: currentColor;
46+
height: var(--vaadin-icon-size, 1lh);
47+
mask-image: var(--_vaadin-icon-checkmark);
48+
width: var(--vaadin-icon-size, 1lh);
49+
}
50+
51+
:host([selected]) [part='checkmark'] {
52+
visibility: visible;
53+
}
54+
55+
[part='content'] {
56+
flex: 1;
57+
}
58+
}
59+
`;

packages/multi-select-combo-box/test/chips.test.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -547,13 +547,13 @@ describe('chips', () => {
547547

548548
const overlay = document.querySelector('vaadin-multi-select-combo-box-overlay');
549549
const overlayPart = overlay.$.overlay;
550-
const width = overlayPart.clientWidth;
550+
const width = overlayPart.offsetWidth;
551551
expect(width).to.equal(comboBox.clientWidth);
552552

553553
comboBox.selectedItems = ['apple', 'banana'];
554554
await nextRender();
555-
expect(overlayPart.clientWidth).to.be.lessThan(width);
556-
expect(overlayPart.clientWidth).to.be.equal(comboBox.clientWidth);
555+
expect(overlayPart.offsetWidth).to.be.lessThan(width);
556+
expect(overlayPart.offsetWidth).to.be.equal(comboBox.clientWidth);
557557
});
558558
});
559559

packages/vaadin-lumo-styles/style.js

+7
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ const globals = css`
111111
--vaadin-input-field-value-color: var(--lumo-body-text-color);
112112
--vaadin-input-field-value-font-size: var(--lumo-font-size-m);
113113
--vaadin-input-field-value-font-weight: 500;
114+
/* Item */
115+
--vaadin-item-border-radius: var(--lumo-border-radius-m);
116+
--vaadin-item-gap: 0;
117+
--vaadin-item-height: auto;
118+
--vaadin-item-overlay-padding: var(--lumo-space-xs);
119+
--vaadin-item-padding: 0.5em calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4) 0.5em
120+
var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4));
114121
}
115122
`;
116123

0 commit comments

Comments
 (0)