diff --git a/pages/table/sticky-scrollbar-in-container.page.tsx b/pages/table/sticky-scrollbar-in-container.page.tsx index 6981d7ba2d..27bf62f7c7 100644 --- a/pages/table/sticky-scrollbar-in-container.page.tsx +++ b/pages/table/sticky-scrollbar-in-container.page.tsx @@ -1,37 +1,62 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React from 'react'; +import React, { useContext } from 'react'; import { useCollection } from '@cloudscape-design/collection-hooks'; -import Header from '~components/header'; -import Table from '~components/table'; +import { Checkbox, Container, Header, Pagination, SpaceBetween, Table } from '~components'; +import AppContext, { AppContextType } from '../app/app-context'; import ScreenshotArea from '../utils/screenshot-area'; -import { generateItems, Instance } from './generate-data'; -import { columnsConfig } from './shared-configs'; +import { generateItems } from './generate-data'; +import { columnsConfig, paginationLabels } from './shared-configs'; + +type PageContext = React.Context< + AppContextType<{ + fitHeight?: boolean; + hasFooter?: boolean; + }> +>; const allItems = generateItems(); const PAGE_SIZE = 50; export default function App() { - const { items } = useCollection(allItems, { - pagination: { pageSize: PAGE_SIZE }, - sorting: {}, - }); + const { + urlParams: { fitHeight = true, hasFooter = false }, + setUrlParams, + } = useContext(AppContext as PageContext); + + const { items, paginationProps } = useCollection(allItems, { pagination: { pageSize: PAGE_SIZE }, sorting: {} }); + return ( -
- - header={ -
- Sticky Scrollbar Example -
- } - columnDefinitions={columnsConfig} - items={items} - /> -
+ +
+ + + Sticky Scrollbar Example + + } + footer={hasFooter ? : undefined} + columnDefinitions={columnsConfig} + items={items} + /> + + + + + setUrlParams({ fitHeight: detail.checked })}> + fitHeight + + setUrlParams({ hasFooter: detail.checked })}> + hasFooter + + + ); } diff --git a/src/container/styles.scss b/src/container/styles.scss index 287bc6d3f8..0a83899093 100644 --- a/src/container/styles.scss +++ b/src/container/styles.scss @@ -16,6 +16,7 @@ display: flex; flex-direction: column; block-size: 100%; + &.with-side-media { flex-direction: row; } @@ -37,6 +38,7 @@ border-end-start-radius: 0; border-block-end-width: 0; } + // Meld container top corners into preceding container &-stacked + &-stacked, &-stacked + &-stacked::before, @@ -44,6 +46,7 @@ border-start-start-radius: 0; border-start-end-radius: 0; } + // Replace container border with a divider &-stacked + &-stacked:not(.refresh)::before { @include shared.divider; @@ -55,6 +58,7 @@ &::before { inset-block-start: calc(-1 * #{awsui.$border-container-top-width}); } + &.variant-stacked::before { inset-block-start: calc(-1 * #{awsui.$border-divider-section-width}); } @@ -75,6 +79,7 @@ display: flex; flex-direction: column; inline-size: 100%; + &-fit-height { block-size: 100%; overflow: hidden; @@ -103,6 +108,7 @@ border-block: 0; border-inline: 0; } + // stylelint-enable @cloudscape-design/no-implicit-descendant, selector-max-type // reduce border-radius size to fill the visual gap between the parent border and image @@ -123,12 +129,14 @@ background-color: awsui.$color-background-container-header; border-start-start-radius: awsui.$border-radius-container; border-start-end-radius: awsui.$border-radius-container; + &.header-full-page { background-color: awsui.$color-background-layout-main; } &.header-with-media { background: none; + &:not(:empty) { border-block-end: none; } @@ -158,6 +166,7 @@ border-block: 0; border-inline: 0; } + &:not(.header-variant-cards) { box-shadow: awsui.$shadow-sticky-embedded; } @@ -191,6 +200,7 @@ &-variant-cards { // Border and shadows are applied with ::before and ::after elements (respectively) @include styles.container-style; + &:not(.header-sticky-enabled) { position: relative; } @@ -244,17 +254,21 @@ .content { flex: 1; + &-fit-height { overflow: auto; } + + // Using margins instead of paddings so that the content overflow works correctly. &.with-paddings { - padding-block: awsui.$space-scaled-l; - padding-inline: awsui.$space-container-horizontal; + margin-block: awsui.$space-scaled-l; + margin-inline: awsui.$space-container-horizontal; .header + & { - padding-block-start: awsui.$space-container-content-top; + margin-block-start: awsui.$space-container-content-top; + &.content-with-media { - padding-block-start: 0; + margin-block-start: 0; } } } diff --git a/src/table/__integ__/sticky-scrollbar.test.ts b/src/table/__integ__/sticky-scrollbar.test.ts index 2620cbf066..d008b6baab 100644 --- a/src/table/__integ__/sticky-scrollbar.test.ts +++ b/src/table/__integ__/sticky-scrollbar.test.ts @@ -4,23 +4,18 @@ import { BasePageObject } from '@cloudscape-design/browser-test-tools/page-objec import useBrowser from '@cloudscape-design/browser-test-tools/use-browser'; import createWrapper from '../../../lib/components/test-utils/selectors'; -const tableWrapper = createWrapper().findTable(); + import scrollbarStyles from '../../../lib/components/table/sticky-scrollbar/styles.selectors.js'; const scrollbarSelector = `.${scrollbarStyles['sticky-scrollbar-visible']}`; -class StickyScrollbarPage extends BasePageObject { - findVisibleScrollbar() { - return tableWrapper.find(scrollbarSelector).toSelector(); - } - findTable() { - return tableWrapper.toSelector(); - } -} +const tableWrapper = createWrapper().findTable(); +const containerWrapper = createWrapper().findContainer(); +const scrollbarWrapper = tableWrapper.find(scrollbarSelector); -const setupTest = (testFn: (page: StickyScrollbarPage) => Promise, isVisualRefresh?: boolean) => { +const setupTest = (testFn: (page: BasePageObject) => Promise, isVisualRefresh?: boolean) => { return useBrowser(async browser => { - const page = new StickyScrollbarPage(browser); + const page = new BasePageObject(browser); await page.setWindowSize({ width: 600, height: 400 }); await browser.url( `#/light/table/sticky-scrollbar?${isVisualRefresh ? 'visualRefresh=true' : 'visualRefresh=false'}` @@ -34,49 +29,62 @@ describe('Sticky scrollbar', () => { test( `is visible, when the table is in view, but it's bottom is not`, setupTest(async page => { - await expect(page.isExisting(await page.findVisibleScrollbar())).resolves.toEqual(true); + await expect(page.isExisting(scrollbarWrapper.toSelector())).resolves.toEqual(true); }) ); [false, true].forEach(visualRefresh => describe(`visualRefresh=${visualRefresh}`, () => { test( - `scrollbarWidth is equal to tableWidth`, + 'scrollbarWidth is equal to tableWidth', setupTest(async page => { - const { width: scrollbarWidth } = await page.getBoundingBox(await page.findVisibleScrollbar()); - const { width: tableWidth } = await page.getBoundingBox(await page.findTable()); + const { width: scrollbarWidth } = await page.getBoundingBox(scrollbarWrapper.toSelector()); + const { width: tableWidth } = await page.getBoundingBox(tableWrapper.toSelector()); const borderOffset = visualRefresh ? 2 : 0; expect(scrollbarWidth).toEqual(tableWidth - borderOffset); }, visualRefresh) ); + + test( + 'sticky scrollbar is at the bottom when rendered inside container with fit-height', + useBrowser(async browser => { + const page = new BasePageObject(browser); + await browser.url( + `#/light/table/sticky-scrollbar-in-container?visualRefresh=${visualRefresh}&fitHeight=true` + ); + const { bottom: containerBottom } = await page.getBoundingBox(containerWrapper.findContent().toSelector()); + const { bottom: scrollbarBottom } = await page.getBoundingBox(scrollbarWrapper.toSelector()); + expect(scrollbarBottom).toBe(containerBottom); + }) + ); }) ); test( - `is hidden, when page is resized and table fits into the screen`, + 'is hidden, when page is resized and table fits into the screen', setupTest(async page => { await page.setWindowSize({ width: 1600, height: 400 }); - await expect(page.isExisting(await page.findVisibleScrollbar())).resolves.toEqual(false); + await expect(page.isExisting(scrollbarWrapper.toSelector())).resolves.toEqual(false); }) ); test( - `appears when screen is resized`, + 'appears when screen is resized', setupTest(async page => { await page.setWindowSize({ width: 1600, height: 400 }); - await expect(page.isExisting(await page.findVisibleScrollbar())).resolves.toEqual(false); + await expect(page.isExisting(scrollbarWrapper.toSelector())).resolves.toEqual(false); await page.setWindowSize({ width: 600, height: 400 }); - await expect(page.isExisting(await page.findVisibleScrollbar())).resolves.toEqual(true); + await expect(page.isExisting(scrollbarWrapper.toSelector())).resolves.toEqual(true); }) ); test( - `scrollbar position updates when window resizes`, + 'scrollbar position updates when window resizes', setupTest(async page => { await page.setWindowSize({ width: 600, height: 600 }); - const { bottom: bottom1 } = await page.getBoundingBox(page.findVisibleScrollbar()); + const { bottom: bottom1 } = await page.getBoundingBox(scrollbarWrapper.toSelector()); expect(bottom1).toEqual((await page.getViewportSize()).height); await page.setWindowSize({ width: 600, height: 400 }); - const { bottom: bottom2 } = await page.getBoundingBox(page.findVisibleScrollbar()); + const { bottom: bottom2 } = await page.getBoundingBox(scrollbarWrapper.toSelector()); expect(bottom1 - bottom2).toBe(200); }) );