Skip to content

Commit 222b99f

Browse files
authored
feat: Reduce blurriness of container shadows against dark header background in VR (#702)
1 parent 112a9cb commit 222b99f

File tree

18 files changed

+118
-102
lines changed

18 files changed

+118
-102
lines changed

src/__integ__/__snapshots__/themes.test.ts.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3409,7 +3409,7 @@ Object {
34093409
"motion-keyframes-status-icon-error": "awsui-status-icon-error-35003c",
34103410
"shadow-container": "none",
34113411
"shadow-container-active": "0px 1px 1px 1px #192534, 0px 6px 36px #000716",
3412-
"shadow-container-stacked": "-1px 1px 1px 0px #192534, 1px 1px 1px 0px #192534, 0px 9px 8px -7px rgb(0 7 22), 8px 0px 8px -7px rgb(0 7 22), -8px 0px 8px -7px rgb(0 7 22)",
3412+
"shadow-container-stacked": "0px 9px 8px -7px rgb(0 7 22 / 60%), 8px 0px 8px -7px rgb(0 7 22 / 60%), -8px 0px 8px -7px rgb(0 7 22 / 60%)",
34133413
"shadow-dropdown": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
34143414
"shadow-dropup": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
34153415
"shadow-flash-collapsed": "0px 4px 4px rgba(0, 0, 0, 0.25)",
@@ -3993,9 +3993,9 @@ Object {
39933993
"motion-keyframes-fade-out": "awsui-fade-out-35003c",
39943994
"motion-keyframes-scale-popup": "awsui-scale-popup-35003c",
39953995
"motion-keyframes-status-icon-error": "awsui-status-icon-error-35003c",
3996-
"shadow-container": "0px 0px 1px 1px #192534, 0px 1px 8px 2px rgba(0, 7, 22, 1)",
3996+
"shadow-container": "0px 1px 8px 2px rgba(0, 7, 22, 0.6)",
39973997
"shadow-container-active": "0px 1px 1px 1px #192534, 0px 6px 36px #000716",
3998-
"shadow-container-stacked": "-1px 1px 1px 0px #192534, 1px 1px 1px 0px #192534, 0px 9px 8px -7px rgb(0 7 22), 8px 0px 8px -7px rgb(0 7 22), -8px 0px 8px -7px rgb(0 7 22)",
3998+
"shadow-container-stacked": "0px 9px 8px -7px rgb(0 7 22 / 60%), 8px 0px 8px -7px rgb(0 7 22 / 60%), -8px 0px 8px -7px rgb(0 7 22 / 60%)",
39993999
"shadow-dropdown": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
40004000
"shadow-dropup": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
40014001
"shadow-flash-collapsed": "0px 4px 4px rgba(0, 0, 0, 0.25)",

src/app-layout/__integ__/app-layout-sticky-elements.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ test(
6767
await page.windowScrollTo({ top: 200 });
6868
const { bottom: pageHeaderBottom } = await page.getBoundingBox('header');
6969
const { top: tableHeaderTop } = await page.getBoundingBox(page.findStickyTableHeader().toSelector());
70-
expect(tableHeaderTop).toEqual(pageHeaderBottom - 1);
70+
expect(tableHeaderTop).toEqual(pageHeaderBottom);
7171
}
7272
)
7373
);

src/cards/__integ__/sticky-header.test.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { BasePageObject, ElementRect } from '@cloudscape-design/browser-test-too
44
import useBrowser from '@cloudscape-design/browser-test-tools/use-browser';
55
import createWrapper, { CardsWrapper, ContainerWrapper } from '../../../lib/components/test-utils/selectors';
66

7-
const CONTAINER_ROOT_BORDER = 1;
8-
97
export default class StickyHeaderCardsPage extends BasePageObject {
108
wrapper = new CardsWrapper(createWrapper('body').find(`.${CardsWrapper.rootSelector}`).getElement());
119
containerWrapper = new ContainerWrapper(this.wrapper.find(`.${ContainerWrapper.rootSelector}`).getElement());
@@ -47,7 +45,6 @@ describe('Cards Sticky Header', () => {
4745
const toggleVerticalOffsetBtn = '#toggle-vertical-offset-btn';
4846
const overflowParentPageHeight = 300;
4947
const verticalOffset = 50;
50-
const containerBorder = 1;
5148

5249
test(
5350
'non-sticky header is not visible when scrolling',
@@ -57,7 +54,7 @@ describe('Cards Sticky Header', () => {
5754

5855
const headerRect = await page.getBoundingBox(page.findCardsHeader().toSelector());
5956
const overflowRect = await page.getBoundingBox(overflowParent);
60-
expect(contains(overflowRect, headerRect, { top: CONTAINER_ROOT_BORDER })).toBe(false);
57+
expect(contains(overflowRect, headerRect)).toBe(false);
6158
})
6259
);
6360

@@ -68,7 +65,7 @@ describe('Cards Sticky Header', () => {
6865

6966
const headerRect = await page.getBoundingBox(page.findCardsHeader().toSelector());
7067
const overflowRect = await page.getBoundingBox(overflowParent);
71-
expect(contains(overflowRect, headerRect, { top: CONTAINER_ROOT_BORDER })).toBe(true);
68+
expect(contains(overflowRect, headerRect)).toBe(true);
7269
})
7370
);
7471

@@ -93,9 +90,7 @@ describe('Cards Sticky Header', () => {
9390

9491
await page.click(scrollTopToBtn);
9592

96-
expect(page.getElementScroll(overflowParent)).resolves.toEqual(
97-
expect.objectContaining({ top: verticalOffset + containerBorder })
98-
);
93+
expect(page.getElementScroll(overflowParent)).resolves.toEqual(expect.objectContaining({ top: verticalOffset }));
9994
})
10095
);
10196

@@ -107,9 +102,7 @@ describe('Cards Sticky Header', () => {
107102

108103
await page.click(scrollTopToBtn);
109104

110-
expect(page.getElementScroll(overflowParent)).resolves.toEqual(
111-
expect.objectContaining({ top: verticalOffset + containerBorder })
112-
);
105+
expect(page.getElementScroll(overflowParent)).resolves.toEqual(expect.objectContaining({ top: verticalOffset }));
113106
})
114107
);
115108

@@ -123,7 +116,7 @@ describe('Cards Sticky Header', () => {
123116
const headerTop = (await page.getBoundingBox(page.findCardsHeader().toSelector())).top;
124117
const overflowParentTop = (await page.getBoundingBox(overflowParent)).top;
125118
const diff = headerTop - overflowParentTop;
126-
expect(diff).toEqual(verticalOffset - containerBorder);
119+
expect(diff).toEqual(verticalOffset);
127120
})
128121
);
129122

src/cards/motion.scss

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88

99
.card-inner {
1010
@include styles.with-motion {
11-
transition-property: background-color, border-top-color, border-bottom-color, border-left-color, border-right-color;
11+
transition-property: background-color;
12+
transition-duration: awsui.$motion-duration-transition-show-paced;
13+
transition-timing-function: awsui.$motion-easing-transition-show-paced;
14+
}
15+
}
16+
17+
.card-inner::before {
18+
@include styles.with-motion {
19+
transition-property: border-top-color, border-right-color, border-bottom-color, border-left-color;
1220
transition-duration: awsui.$motion-duration-transition-show-paced;
1321
transition-timing-function: awsui.$motion-easing-transition-show-paced;
1422
}

src/cards/styles.scss

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,9 @@
8484
calc(#{awsui.$space-card-horizontal} - #{awsui.$border-item-width})
8585
calc(#{awsui.$space-scaled-l} - #{awsui.$border-item-width});
8686
}
87-
// reset border color to prevent it from flashing black during selection animation
88-
border-color: transparent;
8987
@include styles.container-shadow;
90-
// container shadow comes with a top border, need to make it the same as the selection width
91-
border-width: awsui.$border-item-width;
9288
width: 100%;
9389
min-width: 0;
94-
box-sizing: border-box;
9590
}
9691
&-header {
9792
@include styles.font-heading-m;
@@ -107,12 +102,9 @@
107102
}
108103
&-selected {
109104
> .card-inner {
110-
border: awsui.$border-item-width solid awsui.$color-border-item-selected;
111105
background-color: awsui.$color-background-item-selected;
112-
padding: awsui.$space-scaled-l calc(#{awsui.$space-card-horizontal} - #{awsui.$border-item-width})
113-
calc(#{awsui.$space-scaled-l} - #{awsui.$border-item-width});
114-
> .card-header > .selection-control {
115-
margin-right: calc(-1 * #{awsui.$border-item-width});
106+
&::before {
107+
border: awsui.$border-item-width solid awsui.$color-border-item-selected;
116108
}
117109
}
118110
}

src/container/__integ__/awsui-applayout-sticky.test.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ const stickyToggleSelector = createWrapper().findFlashbar().findItems().get(1).f
1616
const headerSelector = '#b #h';
1717
const scrollableDivSelector = '#scrollable-div';
1818

19-
const CONTAINER_ROOT_BORDER = 1;
20-
2119
class AppLayoutStickyPage extends BasePageObject {
2220
async isNotificationVisible() {
2321
const elements = await this.browser.$$(appLayoutWrapper.findNotifications().toSelector());
@@ -47,11 +45,11 @@ test(
4745
setupTest({}, async page => {
4846
const { top: containerTopBefore } = await page.getBoundingBox(containerHeaderSelector);
4947
const { bottom: flashBarBottomBefore } = await page.getBoundingBox(flashBarSelector);
50-
expect(containerTopBefore).toBeGreaterThan(flashBarBottomBefore - CONTAINER_ROOT_BORDER);
48+
expect(containerTopBefore).toBeGreaterThan(flashBarBottomBefore);
5149
await page.windowScrollTo({ top: 200 });
5250
const { top: containerTopAfter } = await page.getBoundingBox(containerHeaderSelector);
5351
const { bottom: flashBarBottomAfter } = await page.getBoundingBox(flashBarSelector);
54-
expect(containerTopAfter).toEqual(flashBarBottomAfter - CONTAINER_ROOT_BORDER);
52+
expect(containerTopAfter).toEqual(flashBarBottomAfter);
5553
})
5654
);
5755

@@ -61,11 +59,11 @@ test(
6159
await page.toggleStickiness();
6260
const { top: containerTopBefore } = await page.getBoundingBox(containerHeaderSelector);
6361
const { bottom: headerBottomBefore } = await page.getBoundingBox(headerSelector);
64-
expect(containerTopBefore).toBeGreaterThan(headerBottomBefore - CONTAINER_ROOT_BORDER);
62+
expect(containerTopBefore).toBeGreaterThan(headerBottomBefore);
6563
await page.windowScrollTo({ top: 200 });
6664
const { top: containerTopAfter } = await page.getBoundingBox(containerHeaderSelector);
6765
const { bottom: headerBottomAfter } = await page.getBoundingBox(headerSelector);
68-
expect(containerTopAfter).toEqual(headerBottomAfter - CONTAINER_ROOT_BORDER);
66+
expect(containerTopAfter).toEqual(headerBottomAfter);
6967
})
7068
);
7169

@@ -74,13 +72,13 @@ test(
7472
setupTest({}, async page => {
7573
const { top: containerHeaderTopBefore } = await page.getBoundingBox(containerInsideDivHeaderSelector);
7674
const { top: scrollableDivTopBefore } = await page.getBoundingBox(scrollableDivSelector);
77-
expect(containerHeaderTopBefore - CONTAINER_ROOT_BORDER).toEqual(scrollableDivTopBefore);
75+
expect(containerHeaderTopBefore).toEqual(scrollableDivTopBefore);
7876

7977
await page.elementScrollTo(scrollableDivSelector, { top: 50 });
8078

8179
const { top: containerHeaderTopAfter } = await page.getBoundingBox(containerInsideDivHeaderSelector);
8280
const { top: scrollableDivTopAfter } = await page.getBoundingBox(scrollableDivSelector);
83-
expect(containerHeaderTopAfter).toEqual(scrollableDivTopAfter - CONTAINER_ROOT_BORDER);
81+
expect(containerHeaderTopAfter).toEqual(scrollableDivTopAfter);
8482
})
8583
);
8684

src/container/__integ__/awsui-legacy-applayout-sticky.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ const containerWrapper = appLayoutWrapper.findContentRegion().findContainer();
1212
const containerHeaderSelector = containerWrapper.findHeader().toSelector();
1313
const flashBarSelector = createWrapper().findFlashbar().toSelector();
1414

15-
const CLASSIC_STICKY_OFFSET_SPACE = -1; // Container border (1px) offset
16-
const VISUAL_REFRESH_STICKY_OFFSET_SPACE = 4; // space-xxs (container border offset is 0px)
15+
const CLASSIC_STICKY_OFFSET_SPACE = 0; // No borders on flashbars or additional padding below
16+
const VISUAL_REFRESH_STICKY_OFFSET_SPACE = 4; // space-xxs - from $offsetTopWithNotifications additional padding
1717

1818
class AppLayoutLegacyStickyPage extends BasePageObject {
1919
async areNotificationsVisible() {

src/container/internal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ export default function InternalContainer({
8888
baseProps.className,
8989
styles.root,
9090
styles[`variant-${variant}`],
91-
fitHeight && styles['root-fit-height']
91+
fitHeight && styles['fit-height'],
92+
isSticky && [styles['sticky-enabled']]
9293
)}
9394
ref={mergedRef}
9495
>

src/container/styles.scss

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,55 @@
1010
.root {
1111
@include styles.styles-reset;
1212
word-wrap: break-word;
13+
position: relative;
1314

14-
&-fit-height {
15+
&.fit-height {
1516
display: flex;
1617
flex-direction: column;
17-
overflow: hidden;
1818
height: 100%;
1919
}
2020

2121
&.variant {
2222
&-default,
2323
&-stacked {
24+
// Border and shadows are applied with ::before and ::after elements (respectively)
2425
@include shared.borders-and-shadows;
2526
background-color: awsui.$color-background-container-content;
2627
}
2728

28-
&-stacked:not(:last-child) {
29+
// Meld container bottom corners into next adjoining container
30+
&-stacked:not(:last-child),
31+
&-stacked:not(:last-child)::before,
32+
&-stacked:not(:last-child)::after {
2933
border-bottom-right-radius: 0;
3034
border-bottom-left-radius: 0;
3135
}
32-
33-
&-stacked + &-stacked {
34-
@include shared.divider;
36+
// Meld container top corners into preceding container
37+
&-stacked + &-stacked,
38+
&-stacked + &-stacked::before,
39+
&-stacked + &-stacked::after {
3540
border-top-left-radius: 0;
3641
border-top-right-radius: 0;
42+
}
43+
// Replace container border with a divider
44+
&-stacked + &-stacked::before {
45+
@include shared.divider;
46+
}
47+
// Reset container shadow to prevent unwanted overflow
48+
&-stacked + &-stacked::after {
3749
box-shadow: awsui.$shadow-container-stacked;
3850
}
3951
}
52+
53+
// To ensure the top border/divider is visible on sticky elements which have a higher z-index
54+
&.sticky-enabled {
55+
&::before {
56+
top: calc(-1 * #{awsui.$border-container-top-width});
57+
}
58+
&.variant-stacked::before {
59+
top: calc(-1 * #{awsui.$border-divider-section-width});
60+
}
61+
}
4062
}
4163

4264
.header {
@@ -59,9 +81,13 @@
5981
}
6082

6183
&-stuck {
62-
box-shadow: awsui.$shadow-sticky-embedded;
63-
border: 0;
6484
border-radius: 0;
85+
&::before {
86+
border: 0;
87+
}
88+
&:not(.header-variant-cards) {
89+
box-shadow: awsui.$shadow-sticky-embedded;
90+
}
6591
}
6692

6793
&-dynamic-height.header-stuck {
@@ -83,20 +109,14 @@
83109
}
84110

85111
&-variant-cards {
86-
@include shared.borders-and-shadows;
87-
88-
&:not(:empty) {
89-
// bottom shadow does not appear in IE11 due to the presence of background color
90-
// Adding a bottom border
91-
border-bottom: 1px solid #d5dbdb;
92-
93-
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
94-
@supports (--css-variable-support-check: #000) {
95-
border-bottom: 0;
96-
}
112+
&:not(.header-sticky-enabled) {
113+
position: relative;
97114
}
115+
@include shared.borders-and-shadows;
98116

99-
&.header-stuck {
117+
&.header-stuck::after,
118+
&.header-stuck::before {
119+
border: 0;
100120
border-top-left-radius: 0;
101121
border-top-right-radius: 0;
102122
}
@@ -130,7 +150,7 @@ the default white background of the container component.
130150
}
131151

132152
.content {
133-
.root-fit-height > & {
153+
.fit-height > & {
134154
flex: 1;
135155
overflow: auto;
136156
}

src/container/use-sticky-header.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
import { RefObject, useState, useLayoutEffect, useCallback, useEffect, createContext, useMemo } from 'react';
3+
import { RefObject, useState, useLayoutEffect, useCallback, useEffect, createContext } from 'react';
44
import { useAppLayoutContext } from '../internal/context/app-layout-context';
55
import { useMobile } from '../internal/hooks/use-mobile';
66
import { findUpUntil, supportsStickyPosition } from '../internal/utils/dom';
@@ -20,18 +20,6 @@ export const useStickyHeader = (
2020
__stickyHeader?: boolean,
2121
__stickyOffset?: number
2222
) => {
23-
const currentRootRef = rootRef.current;
24-
const currentHeaderRef = headerRef.current;
25-
const totalBorder = useMemo(() => {
26-
const containerRootBorder = currentRootRef
27-
? parseInt(getComputedStyle(currentRootRef).getPropertyValue('border-top-width'), 10)
28-
: 0;
29-
const headerBorder = currentHeaderRef
30-
? parseInt(getComputedStyle(currentHeaderRef).getPropertyValue('border-top-width'), 10)
31-
: 0;
32-
return containerRootBorder + headerBorder;
33-
}, [currentRootRef, currentHeaderRef]);
34-
3523
// We reach into AppLayoutContext in case sticky header needs to be offset down by the height
3624
// of other sticky elements positioned on top of the view.
3725
const { stickyOffsetTop } = useAppLayoutContext();
@@ -62,7 +50,7 @@ export const useStickyHeader = (
6250
* body scroll then we will use that property. When a component is used outside AppLayout, we fall back
6351
* to the default offset calculated in AppLayoutDomContext.
6452
*/
65-
let computedOffset = `${effectiveStickyOffset - totalBorder}px`;
53+
let computedOffset = `${effectiveStickyOffset}px`;
6654
if (isRefresh && !hasInnerOverflowParents) {
6755
computedOffset = `var(${customCssProps.offsetTopWithNotifications}, ${computedOffset})`;
6856
}
@@ -81,13 +69,13 @@ export const useStickyHeader = (
8169
if (rootRef.current && headerRef.current) {
8270
const rootTop = rootRef.current.getBoundingClientRect().top;
8371
const headerTop = headerRef.current.getBoundingClientRect().top;
84-
if (rootTop + totalBorder < headerTop) {
72+
if (rootTop < headerTop) {
8573
setIsStuck(true);
8674
} else {
8775
setIsStuck(false);
8876
}
8977
}
90-
}, [rootRef, headerRef, totalBorder]);
78+
}, [rootRef, headerRef]);
9179
useEffect(() => {
9280
if (isSticky) {
9381
window.addEventListener('scroll', checkIfStuck, true);

0 commit comments

Comments
 (0)