Skip to content

Commit 1b0c1eb

Browse files
authored
chore(components): add border to the post-popovercontainer arrow element for for HCM (#6623)
## 📄 Description This PR adds a visible border to the `post-popovercontainer` arrow element for High Contrast Mode to improve accessibility. It adds a `dynamic-placement` state that exposes the computed placement from Floating UI, allowing the arrow to be styled dynamically based on its position. ## 🔮 Design review - [ ] Design review done - [ ] No design review needed ## 📝 Checklist - ✅ My code follows the style guidelines of this project - 🛠️ I have performed a self-review of my own code - 📄 I have made corresponding changes to the documentation - ⚠️ My changes generate no new warnings or errors - ✔️ New and existing unit tests pass locally with my changes
1 parent 9b73038 commit 1b0c1eb

File tree

3 files changed

+69
-18
lines changed

3 files changed

+69
-18
lines changed

.changeset/some-seas-invent.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@swisspost/design-system-components': patch
3+
---
4+
5+
Added High Contrast border around the arrow element used in tooltip and popover components.

packages/components/src/components/post-popovercontainer/post-popovercontainer.scss

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@use 'sass:meta';
22
@use 'sass:math';
3+
34
@use '@swisspost/design-system-styles/variables/color';
45
@use '@swisspost/design-system-styles/variables/commons';
56
@use '@swisspost/design-system-styles/variables/spacing';
@@ -8,6 +9,8 @@
89
@use '@swisspost/design-system-styles/variables/elevation';
910
@use '@swisspost/design-system-styles/functions/color' as color-fn;
1011
@use '@swisspost/design-system-styles/mixins/utilities';
12+
@use '@swisspost/design-system-styles/functions/tokens';
13+
@use '@swisspost/design-system-styles/tokens/elements';
1114

1215
// Puts polyfilled styles in a separate layer so they are easy to override
1316
// Can be removed as soon as popover is supported by all major browsers
@@ -106,35 +109,61 @@
106109
drop-shadow(8px 0px 12px hsla(225, 7%, 11%, 0.15));
107110
}
108111

112+
$arrow-rotation: 45deg;
109113
.arrow {
110114
$arrow-size: 0.5825rem;
115+
--arrow-size: #{$arrow-size};
111116
position: absolute;
112117
width: $arrow-size;
113118
height: $arrow-size;
114119
background-color: inherit;
115-
rotate: 45deg;
120+
rotate: $arrow-rotation;
116121
pointer-events: none;
117122
z-index: -1;
118123

119-
// Create transparent border to be styled by and for the high contrast mode
120-
&.top {
121-
border-block-start: 2px solid transparent;
122-
border-inline-start: 2px solid transparent;
124+
&[dynamic-placement='top'] {
125+
border-bottom-right-radius: 2px;
123126
}
124-
125-
&.right {
126-
border-block-start: 2px solid transparent;
127-
border-inline-end: 2px solid transparent;
127+
&[dynamic-placement='right'] {
128+
border-bottom-left-radius: 2px;
128129
}
129-
130-
&.left {
131-
border-block-end: 2px solid transparent;
132-
border-inline-start: 2px solid transparent;
130+
&[dynamic-placement='bottom'] {
131+
border-top-left-radius: 2px;
132+
}
133+
&[dynamic-placement='left'] {
134+
border-top-right-radius: 2px;
133135
}
134136

135-
&.bottom {
136-
border-block-end: 2px solid transparent;
137-
border-inline-end: 2px solid transparent;
137+
$angle-rad: math.div($arrow-rotation, 180deg) * 3.14; // convert deg to rad
138+
$border-width-rem: 0.125rem;
139+
140+
// the actual offset when the 2px border is rotated by 45deg
141+
$offset: -(math.cos($angle-rad) * $border-width-rem) - $arrow-size / 2;
142+
143+
@include utilities.high-contrast-mode {
144+
&[dynamic-placement='top'] {
145+
border-block-end: 2px solid transparent;
146+
border-inline-end: 2px solid transparent;
147+
bottom: #{$offset} !important;
148+
}
149+
150+
&[dynamic-placement='left'] {
151+
border-block-start: 2px solid transparent;
152+
border-inline-end: 2px solid transparent;
153+
right: #{$offset} !important;
154+
}
155+
156+
&[dynamic-placement='right'] {
157+
border-block-end: 2px solid transparent;
158+
border-inline-start: 2px solid transparent;
159+
left: #{$offset} !important;
160+
}
161+
162+
&[dynamic-placement='bottom'] {
163+
border-block-start: 2px solid transparent;
164+
border-inline-start: 2px solid transparent;
165+
top: #{$offset} !important;
166+
}
138167
}
139168
}
140169
}

packages/components/src/components/post-popovercontainer/post-popovercontainer.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Prop,
99
h,
1010
Watch,
11+
State,
1112
} from '@stencil/core';
1213

1314
import { IS_BROWSER, checkEmptyOrOneOf, checkEmptyOrType } from '@/utils';
@@ -129,6 +130,7 @@ export class PostPopovercontainer {
129130
*/
130131
@Prop() manualClose: boolean = false;
131132

133+
@State() dynamicPlacement?: string;
132134
/**
133135
* Enables a safespace through which the cursor can be moved without the popover being disabled
134136
*/
@@ -346,21 +348,35 @@ export class PostPopovercontainer {
346348
private async calculatePosition() {
347349
const { x, y, middlewareData, placement } = await this.computeMainPosition();
348350
const currentPlacement = placement.split('-')[0];
349-
351+
this.dynamicPlacement = currentPlacement;
350352
// Position popover
351353
this.host.style.left = `${x}px`;
352354
this.host.style.top = `${y}px`;
353355

354356
// Position arrow if enabled
355357
if (this.arrow && middlewareData.arrow) {
356358
const { x: arrowX, y: arrowY } = middlewareData.arrow;
359+
357360
const staticSide = PostPopovercontainer.STATIC_SIDES[currentPlacement];
358361

362+
const rootFontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
363+
364+
// Calculate dynamically the half side which provides the static side offset
365+
const arrowSizeValue = getComputedStyle(this.arrowRef)
366+
.getPropertyValue('--arrow-size')
367+
.trim();
368+
369+
const arrowSizePx = arrowSizeValue.endsWith('rem')
370+
? Number.parseFloat(arrowSizeValue) * rootFontSize
371+
: Number.parseFloat(arrowSizeValue);
372+
373+
const halfSide = -0.5 * arrowSizePx;
374+
359375
if (staticSide) {
360376
Object.assign(this.arrowRef.style, {
361377
left: arrowX ? `${arrowX}px` : '',
362378
top: arrowY ? `${arrowY}px` : '',
363-
[staticSide]: '-5px',
379+
[staticSide]: `${halfSide}px`,
364380
});
365381
}
366382
}
@@ -488,6 +504,7 @@ export class PostPopovercontainer {
488504
<div class="popover-content">
489505
{this.arrow && (
490506
<span
507+
dynamic-placement={this.dynamicPlacement}
491508
class="arrow"
492509
ref={el => {
493510
this.arrowRef = el;

0 commit comments

Comments
 (0)