Skip to content

Commit 267620c

Browse files
feat: add tooltip support to vaadin-side-nav-item (#10008)
Co-authored-by: Serhii Kulykov <[email protected]>
1 parent 47c3586 commit 267620c

File tree

7 files changed

+181
-9
lines changed

7 files changed

+181
-9
lines changed

dev/side-nav.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import '@vaadin/icon';
1111
import '@vaadin/icons';
1212
import '@vaadin/side-nav';
13+
import '@vaadin/tooltip';
1314
import '@vaadin/radio-group';
1415

1516
const icons = [
@@ -121,8 +122,12 @@
121122

122123
<vaadin-side-nav-item slot="children" expanded>
123124
Layouts
125+
<vaadin-tooltip text="Different types of layouts" slot="tooltip"></vaadin-tooltip>
124126

125-
<vaadin-side-nav-item path="/dev/app-layout.html" slot="children"> App Layout </vaadin-side-nav-item>
127+
<vaadin-side-nav-item path="/dev/app-layout.html" slot="children">
128+
App Layout
129+
<vaadin-tooltip text="The application layout" slot="tooltip"></vaadin-tooltip>
130+
</vaadin-side-nav-item>
126131
<vaadin-side-nav-item path="/dev/form-layout.html" slot="children"> Form Layout </vaadin-side-nav-item>
127132
<vaadin-side-nav-item path="/dev/master-detail-layout.html" slot="children">
128133
Master-Detail Layout

packages/side-nav/src/styles/vaadin-side-nav-item-base-styles.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import '@vaadin/component-base/src/styles/style-props.js';
77
import { css } from 'lit';
8+
import { screenReaderOnly } from '@vaadin/a11y-base/src/styles/sr-only-styles.js';
89
import { sharedStyles } from './vaadin-side-nav-shared-base-styles.js';
910

1011
const sideNavItem = css`
@@ -119,4 +120,4 @@ const sideNavItem = css`
119120
}
120121
`;
121122

122-
export const sideNavItemStyles = [sharedStyles, sideNavItem];
123+
export const sideNavItemStyles = [sharedStyles, screenReaderOnly, sideNavItem];

packages/side-nav/src/vaadin-side-nav-item.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
99
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
1010
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
1111
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
12+
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
1213
import { matchPaths } from '@vaadin/component-base/src/url-utils.js';
1314
import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
1415
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
@@ -174,6 +175,11 @@ class SideNavItem extends SideNavChildrenMixin(
174175
type: Boolean,
175176
value: false,
176177
},
178+
179+
/** @private */
180+
__tooltipText: {
181+
type: String,
182+
},
177183
};
178184
}
179185

@@ -246,7 +252,7 @@ class SideNavItem extends SideNavChildrenMixin(
246252
/** @protected */
247253
render() {
248254
return html`
249-
<div part="content" @click="${this._onContentClick}">
255+
<div id="content" part="content" @click="${this._onContentClick}">
250256
<a
251257
id="link"
252258
?disabled="${this.disabled}"
@@ -259,6 +265,7 @@ class SideNavItem extends SideNavChildrenMixin(
259265
>
260266
<slot name="prefix"></slot>
261267
<slot></slot>
268+
<div class="sr-only">${this.__tooltipText}</div>
262269
<slot name="suffix"></slot>
263270
</a>
264271
<button
@@ -274,9 +281,29 @@ class SideNavItem extends SideNavChildrenMixin(
274281
<slot name="children"></slot>
275282
</ul>
276283
<div hidden id="i18n">${this.__effectiveI18n.toggle}</div>
284+
<slot name="tooltip"></slot>
277285
`;
278286
}
279287

288+
/** @protected */
289+
ready() {
290+
super.ready();
291+
292+
this._tooltipController = new TooltipController(this);
293+
this._tooltipController.setTarget(this.$.content);
294+
this._tooltipController.setAriaTarget(null);
295+
this._tooltipController.addEventListener('tooltip-changed', (event) => {
296+
const { node } = event.detail;
297+
if (node) {
298+
this.__tooltipText = node.textContent.trim();
299+
node.setAttribute('aria-hidden', 'true');
300+
} else {
301+
this.__tooltipText = '';
302+
}
303+
});
304+
this.addController(this._tooltipController);
305+
}
306+
280307
/** @private */
281308
_onButtonClick(event) {
282309
// Prevent the event from being handled

packages/side-nav/test/dom/__snapshots__/side-nav-item.test.snap.js

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ snapshots["vaadin-side-nav-item item path"] =
176176
/* end snapshot vaadin-side-nav-item item path */
177177

178178
snapshots["vaadin-side-nav-item shadow default"] =
179-
`<div part="content">
179+
`<div
180+
id="content"
181+
part="content"
182+
>
180183
<a
181184
aria-current="false"
182185
id="link"
@@ -187,6 +190,8 @@ snapshots["vaadin-side-nav-item shadow default"] =
187190
</slot>
188191
<slot>
189192
</slot>
193+
<div class="sr-only">
194+
</div>
190195
<slot name="suffix">
191196
</slot>
192197
</a>
@@ -213,11 +218,16 @@ snapshots["vaadin-side-nav-item shadow default"] =
213218
>
214219
Toggle child items
215220
</div>
221+
<slot name="tooltip">
222+
</slot>
216223
`;
217224
/* end snapshot vaadin-side-nav-item shadow default */
218225

219226
snapshots["vaadin-side-nav-item shadow expanded"] =
220-
`<div part="content">
227+
`<div
228+
id="content"
229+
part="content"
230+
>
221231
<a
222232
aria-current="false"
223233
id="link"
@@ -228,6 +238,8 @@ snapshots["vaadin-side-nav-item shadow expanded"] =
228238
</slot>
229239
<slot>
230240
</slot>
241+
<div class="sr-only">
242+
</div>
231243
<slot name="suffix">
232244
</slot>
233245
</a>
@@ -253,11 +265,16 @@ snapshots["vaadin-side-nav-item shadow expanded"] =
253265
>
254266
Toggle child items
255267
</div>
268+
<slot name="tooltip">
269+
</slot>
256270
`;
257271
/* end snapshot vaadin-side-nav-item shadow expanded */
258272

259273
snapshots["vaadin-side-nav-item shadow current"] =
260-
`<div part="content">
274+
`<div
275+
id="content"
276+
part="content"
277+
>
261278
<a
262279
aria-current="page"
263280
href=""
@@ -269,6 +286,8 @@ snapshots["vaadin-side-nav-item shadow current"] =
269286
</slot>
270287
<slot>
271288
</slot>
289+
<div class="sr-only">
290+
</div>
272291
<slot name="suffix">
273292
</slot>
274293
</a>
@@ -294,11 +313,16 @@ snapshots["vaadin-side-nav-item shadow current"] =
294313
>
295314
Toggle child items
296315
</div>
316+
<slot name="tooltip">
317+
</slot>
297318
`;
298319
/* end snapshot vaadin-side-nav-item shadow current */
299320

300321
snapshots["vaadin-side-nav-item shadow path"] =
301-
`<div part="content">
322+
`<div
323+
id="content"
324+
part="content"
325+
>
302326
<a
303327
aria-current="false"
304328
href="path"
@@ -310,6 +334,8 @@ snapshots["vaadin-side-nav-item shadow path"] =
310334
</slot>
311335
<slot>
312336
</slot>
337+
<div class="sr-only">
338+
</div>
313339
<slot name="suffix">
314340
</slot>
315341
</a>
@@ -336,11 +362,16 @@ snapshots["vaadin-side-nav-item shadow path"] =
336362
>
337363
Toggle child items
338364
</div>
365+
<slot name="tooltip">
366+
</slot>
339367
`;
340368
/* end snapshot vaadin-side-nav-item shadow path */
341369

342370
snapshots["vaadin-side-nav-item shadow null path"] =
343-
`<div part="content">
371+
`<div
372+
id="content"
373+
part="content"
374+
>
344375
<a
345376
aria-current="false"
346377
id="link"
@@ -351,6 +382,8 @@ snapshots["vaadin-side-nav-item shadow null path"] =
351382
</slot>
352383
<slot>
353384
</slot>
385+
<div class="sr-only">
386+
</div>
354387
<slot name="suffix">
355388
</slot>
356389
</a>
@@ -377,11 +410,16 @@ snapshots["vaadin-side-nav-item shadow null path"] =
377410
>
378411
Toggle child items
379412
</div>
413+
<slot name="tooltip">
414+
</slot>
380415
`;
381416
/* end snapshot vaadin-side-nav-item shadow null path */
382417

383418
snapshots["vaadin-side-nav-item shadow i18n"] =
384-
`<div part="content">
419+
`<div
420+
id="content"
421+
part="content"
422+
>
385423
<a
386424
aria-current="false"
387425
id="link"
@@ -392,6 +430,8 @@ snapshots["vaadin-side-nav-item shadow i18n"] =
392430
</slot>
393431
<slot>
394432
</slot>
433+
<div class="sr-only">
434+
</div>
395435
<slot name="suffix">
396436
</slot>
397437
</a>
@@ -418,6 +458,8 @@ snapshots["vaadin-side-nav-item shadow i18n"] =
418458
>
419459
Toggle children
420460
</div>
461+
<slot name="tooltip">
462+
</slot>
421463
`;
422464
/* end snapshot vaadin-side-nav-item shadow i18n */
423465

packages/vaadin-lumo-styles/src/components/side-nav-item.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,17 @@
167167
color: var(--vaadin-selection-color-text, var(--lumo-primary-text-color));
168168
border-radius: var(--lumo-border-radius-m);
169169
}
170+
171+
.sr-only {
172+
border: 0 !important;
173+
clip: rect(1px, 1px, 1px, 1px) !important;
174+
clip-path: inset(50%) !important;
175+
height: 1px !important;
176+
margin: -1px !important;
177+
overflow: hidden !important;
178+
padding: 0 !important;
179+
position: absolute !important;
180+
width: 1px !important;
181+
white-space: nowrap !important;
182+
}
170183
}

test/integration/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@vaadin/popover": "25.0.0-alpha16",
3838
"@vaadin/radio-group": "25.0.0-alpha16",
3939
"@vaadin/select": "25.0.0-alpha16",
40+
"@vaadin/side-nav": "25.0.0-alpha16",
4041
"@vaadin/tabs": "25.0.0-alpha16",
4142
"@vaadin/test-runner-commands": "25.0.0-alpha16",
4243
"@vaadin/testing-helpers": "^2.0.0",
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect } from '@vaadin/chai-plugins';
2+
import { fixtureSync, nextRender } from '@vaadin/testing-helpers';
3+
import './not-animated-styles.js';
4+
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
5+
import { Tooltip } from '@vaadin/tooltip/src/vaadin-tooltip.js';
6+
import { mouseenter, mouseleave } from '@vaadin/tooltip/test/helpers.js';
7+
8+
describe('side-nav-item with tooltip', () => {
9+
let item, tooltip, tooltipOverlay, srOnly;
10+
11+
before(() => {
12+
Tooltip.setDefaultFocusDelay(0);
13+
Tooltip.setDefaultHoverDelay(0);
14+
Tooltip.setDefaultHideDelay(0);
15+
});
16+
17+
beforeEach(async () => {
18+
item = fixtureSync(`
19+
<vaadin-side-nav-item path="/parent">
20+
Parent
21+
<vaadin-tooltip slot="tooltip" text="Tooltip text"></vaadin-tooltip>
22+
<vaadin-side-nav-item path="/parent/child" slot="children">Child</vaadin-side-nav-item>
23+
</vaadin-side-nav-item>
24+
`);
25+
await nextRender();
26+
tooltip = item.querySelector('vaadin-tooltip');
27+
tooltipOverlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay');
28+
srOnly = item.shadowRoot.querySelector('.sr-only');
29+
});
30+
31+
it('should set tooltip target to the item content part', () => {
32+
expect(tooltip.target).to.equal(item.$.content);
33+
});
34+
35+
it('should set tooltip ariaTarget to null', () => {
36+
expect(tooltip.ariaTarget).to.be.null;
37+
});
38+
39+
it('should set aria-hidden: none on the tooltip', () => {
40+
expect(tooltip.getAttribute('aria-hidden')).to.equal('true');
41+
});
42+
43+
it('should toggle tooltip on item content mouseenter', () => {
44+
mouseenter(item.$.content);
45+
expect(tooltipOverlay.opened).to.be.true;
46+
47+
mouseleave(item.$.content);
48+
expect(tooltipOverlay.opened).to.be.false;
49+
});
50+
51+
it('should not show tooltip on child item mouseenter', async () => {
52+
item.click();
53+
const child = item.querySelector('vaadin-side-nav-item');
54+
mouseenter(child.$.content);
55+
await nextRender();
56+
expect(tooltipOverlay.opened).to.be.not.ok;
57+
});
58+
59+
it('should use tooltip text for the sr-only element text content', async () => {
60+
expect(srOnly.textContent).to.equal(tooltip.text);
61+
62+
tooltip.text = 'Other text';
63+
await nextRender();
64+
65+
expect(srOnly.textContent).to.equal('Other text');
66+
});
67+
68+
it('should use tooltip generator for the sr-only element text content', async () => {
69+
expect(srOnly.textContent).to.equal(tooltip.text);
70+
71+
tooltip.generator = () => 'Other text';
72+
await nextRender();
73+
74+
expect(srOnly.textContent).to.equal('Other text');
75+
});
76+
77+
it('should clear the sr-only element content when tooltip is removed', async () => {
78+
tooltip.remove();
79+
await nextRender();
80+
81+
expect(srOnly.textContent).to.equal('');
82+
});
83+
});

0 commit comments

Comments
 (0)