diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5d8fa8c87..6ae7f8eb10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,8 +13,74 @@ permissions: contents: read jobs: + unitTest: + name: Components unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + - run: npm install --force + - run: npm run build + - run: npm run test:unit + + integTest: + name: Components integ tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + - run: npm install --force + - run: npm run build + - run: npm run test:integ + + a11yTest: + name: Components a11y tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + - run: npm install --force + - run: npm run build + - run: npm run test:a11y + release: - uses: cloudscape-design/.github/.github/workflows/release.yml@main - secrets: inherit - with: - publish-packages: lib/components,lib/design-tokens,lib/style-dictionary,lib/components-themeable,lib/dev-pages,lib/components-definitions + concurrency: release-${{ github.ref }} + runs-on: ubuntu-latest + needs: + - unitTest + - integTest + - a11yTest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + - run: npm install --force + - run: npm run build + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.AWS_CODEARTIFACT_ROLE }} + aws-region: us-west-2 + - name: Login and configure codeartifact + env: + CODE_ARTIFACT_REPO: ${{ startsWith(github.ref_name, 'dev-v3-') && format('AwsUI-Artifacts-{0}', github.ref_name) || 'github-artifacts' }} + run: | + echo Logging into repository $CODE_ARTIFACT_REPO + aws codeartifact login --tool npm --repository $CODE_ARTIFACT_REPO --domain awsui --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} --region us-west-2 --namespace @cloudscape-design + + - name: Release package to private CodeArtifact + uses: cloudscape-design/.github/.github/actions/release-package@main + with: + publish-packages: lib/components,lib/design-tokens,lib/style-dictionary,lib/components-themeable,lib/dev-pages,lib/components-definitions diff --git a/pages/alert/simple.page.tsx b/pages/alert/simple.page.tsx index dce91d30b0..def7d24120 100644 --- a/pages/alert/simple.page.tsx +++ b/pages/alert/simple.page.tsx @@ -1,7 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React, { useState } from 'react'; -import Alert from '~components/alert'; +import React, { useEffect, useRef, useState } from 'react'; +import Alert, { AlertProps } from '~components/alert'; +import Button from '~components/button'; import Link from '~components/link'; import ScreenshotArea from '../utils/screenshot-area'; import SpaceBetween from '~components/space-between'; @@ -12,10 +13,19 @@ import messages from '~components/i18n/messages/all.en'; export default function AlertScenario() { const [visible, setVisible] = useState(true); + const alertRef = useRef(null); + + useEffect(() => { + if (visible) { + alertRef.current?.focus(); + } + }, [visible]); + return (

Simple alert

+
@@ -27,6 +37,7 @@ export default function AlertScenario() { buttonText="Button text" type="warning" onDismiss={() => setVisible(false)} + ref={alertRef} > Content
diff --git a/pages/app-layout/full-page-table-with-variable-header-height.page.tsx b/pages/app-layout/full-page-table-with-variable-header-height.page.tsx new file mode 100644 index 0000000000..850da6009a --- /dev/null +++ b/pages/app-layout/full-page-table-with-variable-header-height.page.tsx @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; +import AppLayout from '~components/app-layout'; +import labels from './utils/labels'; +import Table from '~components/table'; +import { generateItems, Instance } from '../table/generate-data'; +import { columnsConfig } from '../table/shared-configs'; +import ExpandableSection from '~components/expandable-section'; +import Header from '~components/header'; + +const items = generateItems(20); + +export default function () { + return ( + + header={ + <> +
Header that changes size when scrolling
+ +
Content
+
+ + } + stickyHeader={true} + variant="full-page" + columnDefinitions={columnsConfig} + items={items} + /> + } + /> + ); +} diff --git a/pages/app-layout/with-sticky-columns.page.tsx b/pages/app-layout/with-sticky-columns.page.tsx index d5c3a1ea3e..fb0a385b0b 100644 --- a/pages/app-layout/with-sticky-columns.page.tsx +++ b/pages/app-layout/with-sticky-columns.page.tsx @@ -66,6 +66,7 @@ export default function () { diff --git a/pages/cards/hooks.page.tsx b/pages/cards/hooks.page.tsx index f1bbe30a87..fcde6ac35b 100644 --- a/pages/cards/hooks.page.tsx +++ b/pages/cards/hooks.page.tsx @@ -14,7 +14,11 @@ import { EmptyState, getMatchesCountText, paginationLabels, pageSizeOptions } fr import ScreenshotArea from '../utils/screenshot-area'; export const cardDefinition: CardsProps.CardDefinition = { - header: item => {item.id}, + header: item => ( + + {item.id} + + ), sections: [ { id: 'type', diff --git a/pages/cards/permutations.page.tsx b/pages/cards/permutations.page.tsx index 0548481561..417f1e64bb 100644 --- a/pages/cards/permutations.page.tsx +++ b/pages/cards/permutations.page.tsx @@ -9,6 +9,7 @@ import Cards, { CardsProps } from '~components/cards/index'; import createPermutations from '../utils/permutations'; import PermutationsView from '../utils/permutations-view'; import ScreenshotArea from '../utils/screenshot-area'; +import { Link } from '~components'; interface Item { number: number; @@ -27,7 +28,7 @@ function createSimpleItems(count: number) { } const cardDefinition: CardsProps.CardDefinition = { - header: item => item.text, + header: item => (item.number === 2 ? {item.text} : item.text), sections: [ { id: 'description', diff --git a/pages/help-panel/permutations.page.tsx b/pages/help-panel/permutations.page.tsx index da065a84e7..cbe03dd361 100644 --- a/pages/help-panel/permutations.page.tsx +++ b/pages/help-panel/permutations.page.tsx @@ -6,6 +6,7 @@ import Box from '~components/box'; import Icon from '~components/icon'; import ScreenshotArea from '../utils/screenshot-area'; import styles from './styles.scss'; +import { Link } from '~components'; const mainContent = (
@@ -97,7 +98,8 @@ const mainContent = (
diff --git a/src/table/__tests__/empty-state.test.tsx b/src/table/__tests__/empty-state.test.tsx index 98348e8846..09e481e36a 100644 --- a/src/table/__tests__/empty-state.test.tsx +++ b/src/table/__tests__/empty-state.test.tsx @@ -27,10 +27,9 @@ jest.mock('../../../lib/components/table/sticky-columns', () => ({ })); const mockStickyStateModel = { - isEnabled: false, store: jest.fn(), style: { - wrapper: '', + wrapper: undefined, }, refs: { table: jest.fn(), diff --git a/src/table/internal.tsx b/src/table/internal.tsx index 1509ac8bc1..0b9129ee33 100644 --- a/src/table/internal.tsx +++ b/src/table/internal.tsx @@ -38,6 +38,7 @@ import { useMobile } from '../internal/hooks/use-mobile'; import { useContainerQuery } from '@cloudscape-design/component-toolkit'; import { getTableRoleProps, getTableRowRoleProps } from './table-role'; import { useCellEditing } from './use-cell-editing'; +import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context'; const SELECTION_COLUMN_WIDTH = 54; const selectionColumnId = Symbol('selection-column-id'); @@ -229,229 +230,233 @@ const InternalTable = React.forwardRef( (toolsHeaderWrapper?.current as HTMLDivElement | null)?.getBoundingClientRect().height ?? 0; return ( - - - {hasHeader && ( -
+ + + + {hasHeader && (
- +
+ +
+
+ )} + {stickyHeader && ( + + )} + + } + disableHeaderPaddings={true} + disableContentPaddings={true} + variant={toContainerVariant(computedVariant)} + __disableFooterPaddings={true} + __disableFooterDivider={true} + __disableStickyMobile={false} + footer={ + hasFooter ? ( +
+
+ {footer && {footer}} + {hasFooterPagination &&
{pagination}
}
- )} - {stickyHeader && ( - - )} - - } - disableHeaderPaddings={true} - disableContentPaddings={true} - variant={toContainerVariant(computedVariant)} - __disableFooterPaddings={true} - __disableFooterDivider={true} - __disableStickyMobile={false} - footer={ - hasFooter ? ( -
-
- {footer && {footer}} - {hasFooterPagination &&
{pagination}
} -
-
- ) : null - } - __stickyHeader={stickyHeader} - __mobileStickyOffset={toolsHeaderHeight} - __stickyOffset={stickyHeaderVerticalOffset} - {...focusMarkers.root} - > -
- {!!renderAriaLive && !!firstIndex && ( - - {renderAriaLive({ totalItemsCount, firstIndex, lastIndex: firstIndex + items.length - 1 })} - - )} -
- stickyHeaderRef.current?.setFocus(component)} - {...theadProps} - /> - - {loading || items.length === 0 ? ( - - - - ) : ( - items.map((item, rowIndex) => { - const firstVisible = rowIndex === 0; - const lastVisible = rowIndex === items.length - 1; - const isEven = rowIndex % 2 === 0; - const isSelected = !!selectionType && isItemSelected(item); - const isPrevSelected = !!selectionType && !firstVisible && isItemSelected(items[rowIndex - 1]); - const isNextSelected = !!selectionType && !lastVisible && isItemSelected(items[rowIndex + 1]); - return ( - { - // When an element inside table row receives focus we want to adjust the scroll. - // However, that behaviour is unwanted when the focus is received as result of a click - // as it causes the click to never reach the target element. - if (!currentTarget.contains(getMouseDownTarget())) { - stickyHeaderRef.current?.scrollToRow(currentTarget); - } - }} - {...focusMarkers.item} - onClick={onRowClickHandler && onRowClickHandler.bind(null, rowIndex, item)} - onContextMenu={onRowContextMenuHandler && onRowContextMenuHandler.bind(null, rowIndex, item)} - {...getTableRowRoleProps({ tableRole, firstIndex, rowIndex })} + {!!renderAriaLive && !!firstIndex && ( + + + {renderAriaLive({ totalItemsCount, firstIndex, lastIndex: firstIndex + items.length - 1 })} + + + )} +
-
- {loading ? ( - - {loadingText} - - ) : ( -
{empty}
- )} -
-
+ stickyHeaderRef.current?.setFocus(component)} + {...theadProps} + /> + + {loading || items.length === 0 ? ( + + + + ) : ( + items.map((item, rowIndex) => { + const firstVisible = rowIndex === 0; + const lastVisible = rowIndex === items.length - 1; + const isEven = rowIndex % 2 === 0; + const isSelected = !!selectionType && isItemSelected(item); + const isPrevSelected = !!selectionType && !firstVisible && isItemSelected(items[rowIndex - 1]); + const isNextSelected = !!selectionType && !lastVisible && isItemSelected(items[rowIndex + 1]); + return ( + { + // When an element inside table row receives focus we want to adjust the scroll. + // However, that behaviour is unwanted when the focus is received as result of a click + // as it causes the click to never reach the target element. + if (!currentTarget.contains(getMouseDownTarget())) { + stickyHeaderRef.current?.scrollToRow(currentTarget); + } + }} + {...focusMarkers.item} + onClick={onRowClickHandler && onRowClickHandler.bind(null, rowIndex, item)} + onContextMenu={onRowContextMenuHandler && onRowContextMenuHandler.bind(null, rowIndex, item)} + {...getTableRowRoleProps({ tableRole, firstIndex, rowIndex })} + > + {selectionType !== undefined && ( + cellEditing.startEdit({ rowIndex, colIndex })} - onEditEnd={editCancelled => - cellEditing.completeEdit({ rowIndex, colIndex }, editCancelled) - } - submitEdit={cellEditing.submitEdit} - hasFooter={hasFooter} - stripedRows={stripedRows} + wrapLines={false} isEvenRow={isEven} - columnId={column.id ?? colIndex} + stripedRows={stripedRows} + hasSelection={hasSelection} + hasFooter={hasFooter} stickyState={stickyState} - isVisualRefresh={isVisualRefresh} + columnId={selectionColumnId} tableRole={tableRole} - /> - ); - })} - - ); - }) - )} - -
- {selectionType !== undefined && ( - - - - )} - {visibleColumnDefinitions.map((column, colIndex) => { - const isEditing = cellEditing.checkEditing({ rowIndex, colIndex }); - const successfulEdit = cellEditing.checkLastSuccessfulEdit({ rowIndex, colIndex }); - const isEditable = !!column.editConfig && !cellEditing.isLoading; - return ( - + {loading ? ( + + {loadingText} + + ) : ( +
{empty}
+ )} + +
- {resizableColumns && } -
- - - + > + + + )} + {visibleColumnDefinitions.map((column, colIndex) => { + const isEditing = cellEditing.checkEditing({ rowIndex, colIndex }); + const successfulEdit = cellEditing.checkLastSuccessfulEdit({ rowIndex, colIndex }); + const isEditable = !!column.editConfig && !cellEditing.isLoading; + return ( + cellEditing.startEdit({ rowIndex, colIndex })} + onEditEnd={editCancelled => + cellEditing.completeEdit({ rowIndex, colIndex }, editCancelled) + } + submitEdit={cellEditing.submitEdit} + hasFooter={hasFooter} + stripedRows={stripedRows} + isEvenRow={isEven} + columnId={column.id ?? colIndex} + stickyState={stickyState} + isVisualRefresh={isVisualRefresh} + tableRole={tableRole} + /> + ); + })} + + ); + }) + )} + + + {resizableColumns && } + + + + + ); } ) as TableForwardRefType; diff --git a/src/table/sticky-columns/__tests__/use-sticky-columns.test.tsx b/src/table/sticky-columns/__tests__/use-sticky-columns.test.tsx index 4ac8178dfc..43d751e4ab 100644 --- a/src/table/sticky-columns/__tests__/use-sticky-columns.test.tsx +++ b/src/table/sticky-columns/__tests__/use-sticky-columns.test.tsx @@ -32,7 +32,7 @@ function createMockTable( return { wrapper, table, cells }; } -test('isEnabled is false, wrapper styles is empty and wrapper listener is not attached when feature is off', () => { +test('wrapper styles is empty and wrapper listener is not attached when feature is off', () => { const tableWrapper = document.createElement('div'); const addTableWrapperOnScrollSpy = jest.spyOn(tableWrapper, 'addEventListener'); const { result } = renderHook(() => @@ -40,12 +40,11 @@ test('isEnabled is false, wrapper styles is empty and wrapper listener is not at ); result.current.refs.wrapper(tableWrapper); - expect(result.current.isEnabled).toBe(false); expect(result.current.style.wrapper).not.toBeDefined(); expect(addTableWrapperOnScrollSpy).not.toHaveBeenCalled(); }); -test('isEnabled is true, wrapper styles is not empty and wrapper listener is attached when feature is on', () => { +test('wrapper styles is not empty and wrapper listener is attached when feature is on', () => { const tableWrapper = document.createElement('div'); const addTableWrapperOnScrollSpy = jest.spyOn(tableWrapper, 'addEventListener'); const { result } = renderHook(() => @@ -53,7 +52,6 @@ test('isEnabled is true, wrapper styles is not empty and wrapper listener is att ); result.current.refs.wrapper(tableWrapper); - expect(result.current.isEnabled).toBe(true); expect(result.current.style.wrapper).toEqual({ scrollPaddingLeft: 0, scrollPaddingRight: 0 }); expect(addTableWrapperOnScrollSpy).toHaveBeenCalledWith('scroll', expect.any(Function)); }); diff --git a/src/table/sticky-columns/index.ts b/src/table/sticky-columns/index.ts index 5452a44a30..ab09629b39 100644 --- a/src/table/sticky-columns/index.ts +++ b/src/table/sticky-columns/index.ts @@ -1,9 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -export { - useStickyColumns, - useStickyCellStyles, - StickyColumnsModel, - StickyColumnsCellState, -} from './use-sticky-columns'; +export { StickyColumnsCellState } from './interfaces'; +export { useStickyColumns, useStickyCellStyles, StickyColumnsModel } from './use-sticky-columns'; diff --git a/src/table/sticky-columns/interfaces.ts b/src/table/sticky-columns/interfaces.ts new file mode 100644 index 0000000000..140e65e819 --- /dev/null +++ b/src/table/sticky-columns/interfaces.ts @@ -0,0 +1,33 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export interface StickyColumnsProps { + visibleColumns: readonly PropertyKey[]; + stickyColumnsFirst: number; + stickyColumnsLast: number; +} + +export interface StickyColumnsState { + cellState: Record; + wrapperState: StickyColumnsWrapperState; +} + +// Cell state is used to apply respective styles and offsets to sticky cells. +export interface StickyColumnsCellState { + padLeft: boolean; + lastLeft: boolean; + lastRight: boolean; + offset: { left?: number; right?: number }; +} + +// Scroll padding is applied to table's wrapper so that the table scrolls when focus goes behind sticky column. +export interface StickyColumnsWrapperState { + scrollPaddingLeft: number; + scrollPaddingRight: number; +} + +export interface CellOffsets { + offsets: Map; + stickyWidthLeft: number; + stickyWidthRight: number; +} diff --git a/src/table/sticky-columns/use-sticky-columns.ts b/src/table/sticky-columns/use-sticky-columns.ts index fdc540c080..4955613f43 100644 --- a/src/table/sticky-columns/use-sticky-columns.ts +++ b/src/table/sticky-columns/use-sticky-columns.ts @@ -6,19 +6,20 @@ import AsyncStore from '../../area-chart/async-store'; import { useStableEventHandler } from '../../internal/hooks/use-stable-event-handler'; import { useResizeObserver } from '../../internal/hooks/container-queries'; import clsx from 'clsx'; +import { + CellOffsets, + StickyColumnsCellState, + StickyColumnsProps, + StickyColumnsState, + StickyColumnsWrapperState, +} from './interfaces'; +import { isCellStatesEqual, isWrapperStatesEqual, updateCellOffsets } from './utils'; // We allow the table to have a minimum of 148px of available space besides the sum of the widths of the sticky columns // This value is an UX recommendation and is approximately 1/3 of our smallest breakpoint (465px) const MINIMUM_SCROLLABLE_SPACE = 148; -interface StickyColumnsProps { - visibleColumns: readonly PropertyKey[]; - stickyColumnsFirst: number; - stickyColumnsLast: number; -} - export interface StickyColumnsModel { - isEnabled: boolean; store: StickyColumnsStore; style: { wrapper?: React.CSSProperties; @@ -30,25 +31,6 @@ export interface StickyColumnsModel { }; } -export interface StickyColumnsState { - cellState: Record; - wrapperState: StickyColumnsWrapperState; -} - -// Cell state is used to apply respective styles and offsets to sticky cells. -export interface StickyColumnsCellState { - padLeft: boolean; - lastLeft: boolean; - lastRight: boolean; - offset: { left?: number; right?: number }; -} - -// Scroll padding is applied to table's wrapper so that the table scrolls when focus goes behind sticky column. -export interface StickyColumnsWrapperState { - scrollPaddingLeft: number; - scrollPaddingRight: number; -} - export function useStickyColumns({ visibleColumns, stickyColumnsFirst, @@ -142,7 +124,6 @@ export function useStickyColumns({ }, []); return { - isEnabled: hasStickyColumns, store, style: { // Provide wrapper styles as props so that a re-render won't cause invalidation. @@ -169,7 +150,6 @@ export function useStickyCellStyles({ columnId, getClassName, }: UseStickyCellStylesProps): StickyCellStyles { - const cellRef = useRef(null) as React.MutableRefObject; const setCell = stickyColumns.refs.cell; // unsubscribeRef to hold the function to unsubscribe from the store's updates @@ -177,15 +157,14 @@ export function useStickyCellStyles({ // refCallback updates the cell ref and sets up the store subscription const refCallback = useCallback( - node => { + cellElement => { if (unsubscribeRef.current) { // Unsubscribe before we do any updates to avoid leaving any subscriptions hanging unsubscribeRef.current(); } // Update cellRef and the store's state to point to the new DOM node - cellRef.current = node; - setCell(columnId, node); + setCell(columnId, cellElement); // Update cell styles imperatively to avoid unnecessary re-renders. const selector = (state: StickyColumnsState) => state.cellState[columnId]; @@ -196,7 +175,6 @@ export function useStickyCellStyles({ } const className = getClassName(state); - const cellElement = cellRef.current; if (cellElement) { Object.keys(className).forEach(key => { if (className[key]) { @@ -212,7 +190,7 @@ export function useStickyCellStyles({ // If the node is not null (i.e., the table cell is being mounted or updated, not unmounted), // set up a new subscription to the store's updates - if (node) { + if (cellElement) { unsubscribeRef.current = stickyColumns.store.subscribe(selector, (newState, prevState) => { updateCellStyles(selector(newState), selector(prevState)); }); @@ -233,23 +211,6 @@ export function useStickyCellStyles({ }; } -function isCellStatesEqual(s1: null | StickyColumnsCellState, s2: null | StickyColumnsCellState): boolean { - if (s1 && s2) { - return ( - s1.padLeft === s2.padLeft && - s1.lastLeft === s2.lastLeft && - s1.lastRight === s2.lastRight && - s1.offset.left === s2.offset.left && - s1.offset.right === s2.offset.right - ); - } - return s1 === s2; -} - -function isWrapperStatesEqual(s1: StickyColumnsWrapperState, s2: StickyColumnsWrapperState): boolean { - return s1.scrollPaddingLeft === s2.scrollPaddingLeft && s1.scrollPaddingRight === s2.scrollPaddingRight; -} - interface UpdateCellStylesProps { wrapper: HTMLElement; table: HTMLElement; @@ -260,9 +221,11 @@ interface UpdateCellStylesProps { } export default class StickyColumnsStore extends AsyncStore { - private cellOffsets = new Map(); - private stickyWidthLeft = 0; - private stickyWidthRight = 0; + private cellOffsets: CellOffsets = { + offsets: new Map(), + stickyWidthLeft: 0, + stickyWidthRight: 0, + }; private isStuckToTheLeft = false; private isStuckToTheRight = false; private padLeft = false; @@ -273,14 +236,17 @@ export default class StickyColumnsStore extends AsyncStore { public updateCellStyles(props: UpdateCellStylesProps) { const hasStickyColumns = props.stickyColumnsFirst + props.stickyColumnsLast > 0; - const hadStickyColumns = this.cellOffsets.size > 0; + const hadStickyColumns = this.cellOffsets.offsets.size > 0; if (hasStickyColumns || hadStickyColumns) { this.updateScroll(props); this.updateCellOffsets(props); this.set(() => ({ cellState: this.generateCellStyles(props), - wrapperState: { scrollPaddingLeft: this.stickyWidthLeft, scrollPaddingRight: this.stickyWidthRight }, + wrapperState: { + scrollPaddingLeft: this.cellOffsets.stickyWidthLeft, + scrollPaddingRight: this.cellOffsets.stickyWidthRight, + }, })); } } @@ -321,8 +287,8 @@ export default class StickyColumnsStore extends AsyncStore { // Determine the offset of the sticky column using the `cellOffsets` state object const isFirstColumn = index === 0; - const stickyColumnOffsetLeft = this.cellOffsets.get(columnId)?.first ?? 0; - const stickyColumnOffsetRight = this.cellOffsets.get(columnId)?.last ?? 0; + const stickyColumnOffsetLeft = this.cellOffsets.offsets.get(columnId)?.first ?? 0; + const stickyColumnOffsetRight = this.cellOffsets.offsets.get(columnId)?.last ?? 0; acc[columnId] = { padLeft: isFirstColumn && this.padLeft, @@ -338,31 +304,7 @@ export default class StickyColumnsStore extends AsyncStore { }; private updateCellOffsets = (props: UpdateCellStylesProps): void => { - const firstColumnsWidths: number[] = []; - for (let i = 0; i < props.visibleColumns.length; i++) { - const element = props.cells[props.visibleColumns[i]]; - const cellWidth = element.getBoundingClientRect().width ?? 0; - firstColumnsWidths[i] = (firstColumnsWidths[i - 1] ?? 0) + cellWidth; - } - - const lastColumnsWidths: number[] = []; - for (let i = props.visibleColumns.length - 1; i >= 0; i--) { - const element = props.cells[props.visibleColumns[i]]; - const cellWidth = element.getBoundingClientRect().width ?? 0; - lastColumnsWidths[i] = (lastColumnsWidths[i + 1] ?? 0) + cellWidth; - } - lastColumnsWidths.reverse(); - - this.stickyWidthLeft = firstColumnsWidths[props.stickyColumnsFirst - 1] ?? 0; - this.stickyWidthRight = lastColumnsWidths[props.stickyColumnsLast - 1] ?? 0; - this.cellOffsets = props.visibleColumns.reduce( - (map, columnId, columnIndex) => - map.set(columnId, { - first: firstColumnsWidths[columnIndex - 1] ?? 0, - last: lastColumnsWidths[props.visibleColumns.length - 1 - columnIndex - 1] ?? 0, - }), - new Map() - ); + this.cellOffsets = updateCellOffsets(props.cells, props); }; private isEnabled = (props: UpdateCellStylesProps): boolean => { @@ -378,7 +320,7 @@ export default class StickyColumnsStore extends AsyncStore { return false; } - const totalStickySpace = this.stickyWidthLeft + this.stickyWidthRight; + const totalStickySpace = this.cellOffsets.stickyWidthLeft + this.cellOffsets.stickyWidthRight; const tablePaddingLeft = parseFloat(getComputedStyle(props.table).paddingLeft) || 0; const tablePaddingRight = parseFloat(getComputedStyle(props.table).paddingRight) || 0; const hasEnoughScrollableSpace = diff --git a/src/table/sticky-columns/utils.ts b/src/table/sticky-columns/utils.ts new file mode 100644 index 0000000000..c734eae9fc --- /dev/null +++ b/src/table/sticky-columns/utils.ts @@ -0,0 +1,52 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { CellOffsets, StickyColumnsCellState, StickyColumnsProps, StickyColumnsWrapperState } from './interfaces'; + +export function isCellStatesEqual(s1: null | StickyColumnsCellState, s2: null | StickyColumnsCellState): boolean { + if (s1 && s2) { + return ( + s1.padLeft === s2.padLeft && + s1.lastLeft === s2.lastLeft && + s1.lastRight === s2.lastRight && + s1.offset.left === s2.offset.left && + s1.offset.right === s2.offset.right + ); + } + return s1 === s2; +} + +export function isWrapperStatesEqual(s1: StickyColumnsWrapperState, s2: StickyColumnsWrapperState): boolean { + return s1.scrollPaddingLeft === s2.scrollPaddingLeft && s1.scrollPaddingRight === s2.scrollPaddingRight; +} + +export function updateCellOffsets(cells: Record, props: StickyColumnsProps): CellOffsets { + const totalColumns = props.visibleColumns.length; + + const firstColumnsWidths: number[] = []; + for (let i = 0; i < Math.min(totalColumns, props.stickyColumnsFirst); i++) { + const element = cells[props.visibleColumns[i]]; + const cellWidth = element.getBoundingClientRect().width ?? 0; + firstColumnsWidths[i] = (firstColumnsWidths[i - 1] ?? 0) + cellWidth; + } + + const lastColumnsWidths: number[] = []; + for (let i = 0; i < Math.min(totalColumns, props.stickyColumnsLast); i++) { + const element = cells[props.visibleColumns[totalColumns - 1 - i]]; + const cellWidth = element.getBoundingClientRect().width ?? 0; + lastColumnsWidths[i] = (lastColumnsWidths[i - 1] ?? 0) + cellWidth; + } + + const stickyWidthLeft = firstColumnsWidths[props.stickyColumnsFirst - 1] ?? 0; + const stickyWidthRight = lastColumnsWidths[props.stickyColumnsLast - 1] ?? 0; + const offsets = props.visibleColumns.reduce( + (map, columnId, columnIndex) => + map.set(columnId, { + first: firstColumnsWidths[columnIndex - 1] ?? 0, + last: lastColumnsWidths[totalColumns - 1 - columnIndex - 1] ?? 0, + }), + new Map() + ); + + return { offsets, stickyWidthLeft, stickyWidthRight }; +} diff --git a/src/tutorial-panel/components/tutorial-detail-view/congratulation-screen.tsx b/src/tutorial-panel/components/tutorial-detail-view/congratulation-screen.tsx index 2fd685bff8..fcbdc40ed8 100644 --- a/src/tutorial-panel/components/tutorial-detail-view/congratulation-screen.tsx +++ b/src/tutorial-panel/components/tutorial-detail-view/congratulation-screen.tsx @@ -42,7 +42,7 @@ export function CongratulationScreen({ children, onFeedbackClick, i18nStrings }:
{onFeedbackClick && ( - + {i18nStrings.feedbackLinkText} )} diff --git a/src/tutorial-panel/components/tutorial-list/index.tsx b/src/tutorial-panel/components/tutorial-list/index.tsx index 81bf1aee2b..ba5ca17372 100644 --- a/src/tutorial-panel/components/tutorial-list/index.tsx +++ b/src/tutorial-panel/components/tutorial-list/index.tsx @@ -173,6 +173,7 @@ function Tutorial({ externalIconAriaLabel={i18nStrings.labelLearnMoreExternalIcon} ariaLabel={i18nStrings.labelLearnMoreLink} external={true} + variant="primary" > {i18nStrings.learnMoreLinkText} diff --git a/src/wizard/wizard-navigation.tsx b/src/wizard/wizard-navigation.tsx index 72f3aa9e77..f9bf43296a 100644 --- a/src/wizard/wizard-navigation.tsx +++ b/src/wizard/wizard-navigation.tsx @@ -206,6 +206,7 @@ function NavigationStepClassic({ i18nStrings, index, onStepClick, onSkipToClick, evt.preventDefault(); status === Statuses.Visited ? onStepClick(index) : onSkipToClick(index); }} + variant="primary" > {step.title} diff --git a/style-dictionary/classic/colors.ts b/style-dictionary/classic/colors.ts index 9739338c75..1240111630 100644 --- a/style-dictionary/classic/colors.ts +++ b/style-dictionary/classic/colors.ts @@ -115,11 +115,10 @@ const tokens: StyleDictionary.ColorsDictionary = { colorTextLayoutToggleHover: { light: '{colorBlue600}', dark: '{colorBlue500}' }, colorTextLayoutToggleSelected: { light: '{colorWhite}', dark: '{colorGrey900}' }, colorTextLinkDefault: { dark: '{colorBlue400}' }, - colorTextLinkHover: '{colorTextLinkDefault}', + colorTextLinkHover: { light: '{colorBlue700}', dark: '{colorBlue300}' }, colorTextLinkInvertedHover: '{colorTextNotificationDefault}', colorTextLinkButtonUnderline: 'currentColor', colorTextLinkButtonUnderlineHover: 'currentColor', - colorTextLinkPrimaryUnderline: 'transparent', colorTextPaginationPageNumberActiveDisabled: '{colorTextBodySecondary}', colorTextPaginationPageNumberDefault: { dark: '{colorTextInteractiveDefault}' }, colorTextSegmentActive: { dark: '{colorGrey800}' }, diff --git a/style-dictionary/classic/typography.ts b/style-dictionary/classic/typography.ts index 382f89ba72..3a1f3a15b5 100644 --- a/style-dictionary/classic/typography.ts +++ b/style-dictionary/classic/typography.ts @@ -37,9 +37,6 @@ const tokens: StyleDictionary.TypographyDictionary = { fontBoxValueLargeWeight: '300', fontLinkButtonLetterSpacing: 'normal', fontLinkButtonWeight: '400', - fontLinkPrimaryDecoration: 'none', - fontLinkPrimaryLetterSpacing: '0.005em', - fontLinkPrimaryWeight: '700', fontPanelHeaderLineHeight: '{fontHeadingLLineHeight}', fontPanelHeaderSize: '{fontHeadingLSize}', fontSmoothingWebkit: 'auto', diff --git a/style-dictionary/utils/token-names.ts b/style-dictionary/utils/token-names.ts index 1baba23c9e..5fa9583465 100644 --- a/style-dictionary/utils/token-names.ts +++ b/style-dictionary/utils/token-names.ts @@ -416,7 +416,6 @@ export type ColorsTokenName = | 'colorTextLinkDefault' | 'colorTextLinkHover' | 'colorTextLinkInvertedHover' - | 'colorTextLinkPrimaryUnderline' | 'colorTextNotificationDefault' | 'colorTextNotificationStackBar' | 'colorTextNotificationYellow' @@ -480,9 +479,6 @@ export type TypographyTokenName = | 'fontBoxValueLargeWeight' | 'fontLinkButtonLetterSpacing' | 'fontLinkButtonWeight' - | 'fontLinkPrimaryDecoration' - | 'fontLinkPrimaryLetterSpacing' - | 'fontLinkPrimaryWeight' | 'fontPanelHeaderLineHeight' | 'fontPanelHeaderSize' | 'fontSmoothingWebkit' diff --git a/style-dictionary/visual-refresh/colors.ts b/style-dictionary/visual-refresh/colors.ts index 9011fd7eff..50318b7f9c 100644 --- a/style-dictionary/visual-refresh/colors.ts +++ b/style-dictionary/visual-refresh/colors.ts @@ -214,7 +214,6 @@ const tokens: StyleDictionary.ColorsDictionary = { colorTextLinkInvertedHover: '{colorWhite}', colorTextLinkButtonUnderline: 'transparent', colorTextLinkButtonUnderlineHover: 'transparent', - colorTextLinkPrimaryUnderline: '{colorTextLinkDefault}', colorTextNotificationDefault: '{colorGrey100}', colorTextNotificationStackBar: '{colorWhite}', colorTextNotificationYellow: '{colorGrey900}', diff --git a/style-dictionary/visual-refresh/typography.ts b/style-dictionary/visual-refresh/typography.ts index d9ac99f20c..72688e0570 100644 --- a/style-dictionary/visual-refresh/typography.ts +++ b/style-dictionary/visual-refresh/typography.ts @@ -42,9 +42,6 @@ export const tokens: StyleDictionary.TypographyDictionary = { fontBoxValueLargeWeight: '700', fontLinkButtonLetterSpacing: '{fontButtonLetterSpacing}', fontLinkButtonWeight: '{fontButtonWeight}', - fontLinkPrimaryDecoration: 'underline', - fontLinkPrimaryLetterSpacing: '"inherit"', - fontLinkPrimaryWeight: '"inherit"', fontPanelHeaderLineHeight: '{fontHeadingMLineHeight}', fontPanelHeaderSize: '{fontHeadingMSize}', fontSmoothingWebkit: 'antialiased',