Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/__integ__/__snapshots__/themes.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3409,7 +3409,7 @@ Object {
"motion-keyframes-status-icon-error": "awsui-status-icon-error-35003c",
"shadow-container": "none",
"shadow-container-active": "0px 1px 1px 1px #192534, 0px 6px 36px #000716",
"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)",
"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%)",
"shadow-dropdown": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
"shadow-dropup": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
"shadow-flash-collapsed": "0px 4px 4px rgba(0, 0, 0, 0.25)",
Expand Down Expand Up @@ -3993,9 +3993,9 @@ Object {
"motion-keyframes-fade-out": "awsui-fade-out-35003c",
"motion-keyframes-scale-popup": "awsui-scale-popup-35003c",
"motion-keyframes-status-icon-error": "awsui-status-icon-error-35003c",
"shadow-container": "0px 0px 1px 1px #192534, 0px 1px 8px 2px rgba(0, 7, 22, 1)",
"shadow-container": "0px 1px 8px 2px rgba(0, 7, 22, 0.6)",
"shadow-container-active": "0px 1px 1px 1px #192534, 0px 6px 36px #000716",
"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)",
"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%)",
"shadow-dropdown": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
"shadow-dropup": "0px 4px 20px 1px rgba(0, 7, 22, 1)",
"shadow-flash-collapsed": "0px 4px 4px rgba(0, 0, 0, 0.25)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ test(
await page.windowScrollTo({ top: 200 });
const { bottom: pageHeaderBottom } = await page.getBoundingBox('header');
const { top: tableHeaderTop } = await page.getBoundingBox(page.findStickyTableHeader().toSelector());
expect(tableHeaderTop).toEqual(pageHeaderBottom - 1);
expect(tableHeaderTop).toEqual(pageHeaderBottom);
}
)
);
17 changes: 5 additions & 12 deletions src/cards/__integ__/sticky-header.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { BasePageObject, ElementRect } from '@cloudscape-design/browser-test-too
import useBrowser from '@cloudscape-design/browser-test-tools/use-browser';
import createWrapper, { CardsWrapper, ContainerWrapper } from '../../../lib/components/test-utils/selectors';

const CONTAINER_ROOT_BORDER = 1;

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

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

const headerRect = await page.getBoundingBox(page.findCardsHeader().toSelector());
const overflowRect = await page.getBoundingBox(overflowParent);
expect(contains(overflowRect, headerRect, { top: CONTAINER_ROOT_BORDER })).toBe(false);
expect(contains(overflowRect, headerRect)).toBe(false);
})
);

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

const headerRect = await page.getBoundingBox(page.findCardsHeader().toSelector());
const overflowRect = await page.getBoundingBox(overflowParent);
expect(contains(overflowRect, headerRect, { top: CONTAINER_ROOT_BORDER })).toBe(true);
expect(contains(overflowRect, headerRect)).toBe(true);
})
);

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

await page.click(scrollTopToBtn);

expect(page.getElementScroll(overflowParent)).resolves.toEqual(
expect.objectContaining({ top: verticalOffset + containerBorder })
);
expect(page.getElementScroll(overflowParent)).resolves.toEqual(expect.objectContaining({ top: verticalOffset }));
})
);

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

await page.click(scrollTopToBtn);

expect(page.getElementScroll(overflowParent)).resolves.toEqual(
expect.objectContaining({ top: verticalOffset + containerBorder })
);
expect(page.getElementScroll(overflowParent)).resolves.toEqual(expect.objectContaining({ top: verticalOffset }));
})
);

Expand All @@ -123,7 +116,7 @@ describe('Cards Sticky Header', () => {
const headerTop = (await page.getBoundingBox(page.findCardsHeader().toSelector())).top;
const overflowParentTop = (await page.getBoundingBox(overflowParent)).top;
const diff = headerTop - overflowParentTop;
expect(diff).toEqual(verticalOffset - containerBorder);
expect(diff).toEqual(verticalOffset);
})
);

Expand Down
10 changes: 9 additions & 1 deletion src/cards/motion.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@

.card-inner {
@include styles.with-motion {
transition-property: background-color, border-top-color, border-bottom-color, border-left-color, border-right-color;
transition-property: background-color;
transition-duration: awsui.$motion-duration-transition-show-paced;
transition-timing-function: awsui.$motion-easing-transition-show-paced;
}
}

.card-inner::before {
@include styles.with-motion {
transition-property: border-top-color, border-right-color, border-bottom-color, border-left-color;
transition-duration: awsui.$motion-duration-transition-show-paced;
transition-timing-function: awsui.$motion-easing-transition-show-paced;
}
Expand Down
12 changes: 2 additions & 10 deletions src/cards/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,9 @@
calc(#{awsui.$space-card-horizontal} - #{awsui.$border-item-width})
calc(#{awsui.$space-scaled-l} - #{awsui.$border-item-width});
}
// reset border color to prevent it from flashing black during selection animation
border-color: transparent;
@include styles.container-shadow;
// container shadow comes with a top border, need to make it the same as the selection width
border-width: awsui.$border-item-width;
width: 100%;
min-width: 0;
box-sizing: border-box;
}
&-header {
@include styles.font-heading-m;
Expand All @@ -107,12 +102,9 @@
}
&-selected {
> .card-inner {
border: awsui.$border-item-width solid awsui.$color-border-item-selected;
background-color: awsui.$color-background-item-selected;
padding: awsui.$space-scaled-l calc(#{awsui.$space-card-horizontal} - #{awsui.$border-item-width})
calc(#{awsui.$space-scaled-l} - #{awsui.$border-item-width});
> .card-header > .selection-control {
margin-right: calc(-1 * #{awsui.$border-item-width});
&::before {
border: awsui.$border-item-width solid awsui.$color-border-item-selected;
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions src/container/__integ__/awsui-applayout-sticky.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const stickyToggleSelector = createWrapper().findFlashbar().findItems().get(1).f
const headerSelector = '#b #h';
const scrollableDivSelector = '#scrollable-div';

const CONTAINER_ROOT_BORDER = 1;

class AppLayoutStickyPage extends BasePageObject {
async isNotificationVisible() {
const elements = await this.browser.$$(appLayoutWrapper.findNotifications().toSelector());
Expand Down Expand Up @@ -47,11 +45,11 @@ test(
setupTest({}, async page => {
const { top: containerTopBefore } = await page.getBoundingBox(containerHeaderSelector);
const { bottom: flashBarBottomBefore } = await page.getBoundingBox(flashBarSelector);
expect(containerTopBefore).toBeGreaterThan(flashBarBottomBefore - CONTAINER_ROOT_BORDER);
expect(containerTopBefore).toBeGreaterThan(flashBarBottomBefore);
await page.windowScrollTo({ top: 200 });
const { top: containerTopAfter } = await page.getBoundingBox(containerHeaderSelector);
const { bottom: flashBarBottomAfter } = await page.getBoundingBox(flashBarSelector);
expect(containerTopAfter).toEqual(flashBarBottomAfter - CONTAINER_ROOT_BORDER);
expect(containerTopAfter).toEqual(flashBarBottomAfter);
})
);

Expand All @@ -61,11 +59,11 @@ test(
await page.toggleStickiness();
const { top: containerTopBefore } = await page.getBoundingBox(containerHeaderSelector);
const { bottom: headerBottomBefore } = await page.getBoundingBox(headerSelector);
expect(containerTopBefore).toBeGreaterThan(headerBottomBefore - CONTAINER_ROOT_BORDER);
expect(containerTopBefore).toBeGreaterThan(headerBottomBefore);
await page.windowScrollTo({ top: 200 });
const { top: containerTopAfter } = await page.getBoundingBox(containerHeaderSelector);
const { bottom: headerBottomAfter } = await page.getBoundingBox(headerSelector);
expect(containerTopAfter).toEqual(headerBottomAfter - CONTAINER_ROOT_BORDER);
expect(containerTopAfter).toEqual(headerBottomAfter);
})
);

Expand All @@ -74,13 +72,13 @@ test(
setupTest({}, async page => {
const { top: containerHeaderTopBefore } = await page.getBoundingBox(containerInsideDivHeaderSelector);
const { top: scrollableDivTopBefore } = await page.getBoundingBox(scrollableDivSelector);
expect(containerHeaderTopBefore - CONTAINER_ROOT_BORDER).toEqual(scrollableDivTopBefore);
expect(containerHeaderTopBefore).toEqual(scrollableDivTopBefore);

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

const { top: containerHeaderTopAfter } = await page.getBoundingBox(containerInsideDivHeaderSelector);
const { top: scrollableDivTopAfter } = await page.getBoundingBox(scrollableDivSelector);
expect(containerHeaderTopAfter).toEqual(scrollableDivTopAfter - CONTAINER_ROOT_BORDER);
expect(containerHeaderTopAfter).toEqual(scrollableDivTopAfter);
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const containerWrapper = appLayoutWrapper.findContentRegion().findContainer();
const containerHeaderSelector = containerWrapper.findHeader().toSelector();
const flashBarSelector = createWrapper().findFlashbar().toSelector();

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

class AppLayoutLegacyStickyPage extends BasePageObject {
async areNotificationsVisible() {
Expand Down
3 changes: 2 additions & 1 deletion src/container/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export default function InternalContainer({
baseProps.className,
styles.root,
styles[`variant-${variant}`],
fitHeight && styles['root-fit-height']
fitHeight && styles['fit-height'],
isSticky && [styles['sticky-enabled']]
)}
ref={mergedRef}
>
Expand Down
62 changes: 41 additions & 21 deletions src/container/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,55 @@
.root {
@include styles.styles-reset;
word-wrap: break-word;
position: relative;

&-fit-height {
&.fit-height {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
}

&.variant {
&-default,
&-stacked {
// Border and shadows are applied with ::before and ::after elements (respectively)
@include shared.borders-and-shadows;
background-color: awsui.$color-background-container-content;
}

&-stacked:not(:last-child) {
// Meld container bottom corners into next adjoining container
&-stacked:not(:last-child),
&-stacked:not(:last-child)::before,
&-stacked:not(:last-child)::after {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}

&-stacked + &-stacked {
@include shared.divider;
// Meld container top corners into preceding container
&-stacked + &-stacked,
&-stacked + &-stacked::before,
&-stacked + &-stacked::after {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
// Replace container border with a divider
&-stacked + &-stacked::before {
@include shared.divider;
}
// Reset container shadow to prevent unwanted overflow
&-stacked + &-stacked::after {
box-shadow: awsui.$shadow-container-stacked;
}
}

// To ensure the top border/divider is visible on sticky elements which have a higher z-index
&.sticky-enabled {
&::before {
top: calc(-1 * #{awsui.$border-container-top-width});
}
&.variant-stacked::before {
top: calc(-1 * #{awsui.$border-divider-section-width});
}
}
}

.header {
Expand All @@ -59,9 +81,13 @@
}

&-stuck {
box-shadow: awsui.$shadow-sticky-embedded;
border: 0;
border-radius: 0;
&::before {
border: 0;
}
&:not(.header-variant-cards) {
box-shadow: awsui.$shadow-sticky-embedded;
}
}

&-dynamic-height.header-stuck {
Expand All @@ -83,20 +109,14 @@
}

&-variant-cards {
@include shared.borders-and-shadows;

&:not(:empty) {
// bottom shadow does not appear in IE11 due to the presence of background color
// Adding a bottom border
border-bottom: 1px solid #d5dbdb;

/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
@supports (--css-variable-support-check: #000) {
border-bottom: 0;
}
&:not(.header-sticky-enabled) {
position: relative;
}
@include shared.borders-and-shadows;

&.header-stuck {
&.header-stuck::after,
&.header-stuck::before {
border: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
Expand Down Expand Up @@ -130,7 +150,7 @@ the default white background of the container component.
}

.content {
.root-fit-height > & {
.fit-height > & {
flex: 1;
overflow: auto;
}
Expand Down
20 changes: 4 additions & 16 deletions src/container/use-sticky-header.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { RefObject, useState, useLayoutEffect, useCallback, useEffect, createContext, useMemo } from 'react';
import { RefObject, useState, useLayoutEffect, useCallback, useEffect, createContext } from 'react';
import { useAppLayoutContext } from '../internal/context/app-layout-context';
import { useMobile } from '../internal/hooks/use-mobile';
import { findUpUntil, supportsStickyPosition } from '../internal/utils/dom';
Expand All @@ -20,18 +20,6 @@ export const useStickyHeader = (
__stickyHeader?: boolean,
__stickyOffset?: number
) => {
const currentRootRef = rootRef.current;
const currentHeaderRef = headerRef.current;
const totalBorder = useMemo(() => {
const containerRootBorder = currentRootRef
? parseInt(getComputedStyle(currentRootRef).getPropertyValue('border-top-width'), 10)
: 0;
const headerBorder = currentHeaderRef
? parseInt(getComputedStyle(currentHeaderRef).getPropertyValue('border-top-width'), 10)
: 0;
return containerRootBorder + headerBorder;
}, [currentRootRef, currentHeaderRef]);

// We reach into AppLayoutContext in case sticky header needs to be offset down by the height
// of other sticky elements positioned on top of the view.
const { stickyOffsetTop } = useAppLayoutContext();
Expand Down Expand Up @@ -62,7 +50,7 @@ export const useStickyHeader = (
* body scroll then we will use that property. When a component is used outside AppLayout, we fall back
* to the default offset calculated in AppLayoutDomContext.
*/
let computedOffset = `${effectiveStickyOffset - totalBorder}px`;
let computedOffset = `${effectiveStickyOffset}px`;
if (isRefresh && !hasInnerOverflowParents) {
computedOffset = `var(${customCssProps.offsetTopWithNotifications}, ${computedOffset})`;
}
Expand All @@ -81,13 +69,13 @@ export const useStickyHeader = (
if (rootRef.current && headerRef.current) {
const rootTop = rootRef.current.getBoundingClientRect().top;
const headerTop = headerRef.current.getBoundingClientRect().top;
if (rootTop + totalBorder < headerTop) {
if (rootTop < headerTop) {
setIsStuck(true);
} else {
setIsStuck(false);
}
}
}, [rootRef, headerRef, totalBorder]);
}, [rootRef, headerRef]);
useEffect(() => {
if (isSticky) {
window.addEventListener('scroll', checkIfStuck, true);
Expand Down
Loading