@@ -524,6 +540,7 @@ Map {
},
"hasBreadcrumbsPortal": true,
"hasNavigation": true,
+ "navigationCollapsible": undefined,
"navigationFocusRef": undefined,
"navigationOpen": true,
"onNavigationToggle": undefined,
@@ -555,6 +572,7 @@ Map {
"navigation":
,
+ "navigationCollapsedWidth": 54,
"navigationOpen": true,
"navigationWidth": 280,
"notifications":
@@ -625,6 +643,7 @@ Map {
],
"maxContentWidth": undefined,
"minContentWidth": 280,
+ "navigationCollapsedWidth": 54,
"navigationOpen": true,
"navigationWidth": 280,
"onDrawerChange": [Function],
@@ -655,6 +674,7 @@ Map {
"toolsToggle": undefined,
},
"hasNavigation": true,
+ "navigationCollapsible": undefined,
"navigationFocusRef": undefined,
"navigationOpen": true,
"onNavigationToggle": undefined,
@@ -695,6 +715,7 @@ Map {
],
"maxContentWidth": undefined,
"minContentWidth": 280,
+ "navigationCollapsedWidth": 54,
"navigationOpen": true,
"navigationWidth": 280,
"onDrawerChange": [Function],
@@ -725,6 +746,7 @@ Map {
"toolsToggle": undefined,
},
"hasNavigation": true,
+ "navigationCollapsible": undefined,
"navigationFocusRef": undefined,
"navigationOpen": true,
"onNavigationToggle": undefined,
@@ -765,6 +787,7 @@ Map {
],
"maxContentWidth": undefined,
"minContentWidth": 280,
+ "navigationCollapsedWidth": 54,
"navigationOpen": true,
"navigationWidth": 280,
"onDrawerChange": [Function],
@@ -795,6 +818,7 @@ Map {
"toolsToggle": undefined,
},
"hasNavigation": true,
+ "navigationCollapsible": undefined,
"navigationFocusRef": undefined,
"navigationOpen": true,
"onNavigationToggle": undefined,
@@ -835,6 +859,7 @@ Map {
],
"maxContentWidth": undefined,
"minContentWidth": 280,
+ "navigationCollapsedWidth": 54,
"navigationOpen": true,
"navigationWidth": 280,
"onDrawerChange": [Function],
@@ -865,6 +890,7 @@ Map {
"toolsToggle": undefined,
},
"hasNavigation": true,
+ "navigationCollapsible": undefined,
"navigationFocusRef": undefined,
"navigationOpen": true,
"onNavigationToggle": undefined,
@@ -905,6 +931,7 @@ Map {
],
"maxContentWidth": undefined,
"minContentWidth": 280,
+ "navigationCollapsedWidth": 54,
"navigationOpen": true,
"navigationWidth": 280,
"onDrawerChange": [Function],
diff --git a/src/app-layout/__tests__/navigation-collapsed.test.tsx b/src/app-layout/__tests__/navigation-collapsed.test.tsx
new file mode 100644
index 0000000000..828080fe21
--- /dev/null
+++ b/src/app-layout/__tests__/navigation-collapsed.test.tsx
@@ -0,0 +1,216 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React from 'react';
+
+import AppLayout from '../../../lib/components/app-layout';
+import customCssProps from '../../../lib/components/internal/generated/custom-css-properties';
+import { describeEachAppLayout, renderComponent } from './utils';
+
+import navStyles from '../../../lib/components/app-layout/visual-refresh-toolbar/navigation/styles.css.js';
+import skeletonStyles from '../../../lib/components/app-layout/visual-refresh-toolbar/skeleton/styles.css.js';
+import iconStyles from '../../../lib/components/icon/styles.css.js';
+
+jest.mock('@cloudscape-design/component-toolkit', () => ({
+ ...jest.requireActual('@cloudscape-design/component-toolkit'),
+ useContainerQuery: () => [1300, () => {}],
+}));
+
+describeEachAppLayout({ themes: ['refresh-toolbar'], sizes: ['desktop'] }, () => {
+ describe('collapsed rail visibility', () => {
+ test('navigation is visible when navigationCloseBehavior="collapse" and navigationOpen=false', () => {
+ const { wrapper } = renderComponent(
+
Nav content>} />
+ );
+ const navContainer = wrapper.findNavigation().getElement().closest(`.${skeletonStyles.navigation}`);
+ expect(navContainer).not.toHaveClass(skeletonStyles['panel-hidden']);
+ expect(navContainer).toHaveClass(skeletonStyles['navigation-collapsed']);
+ });
+
+ test('navigation is hidden when navigationCloseBehavior="hide" and navigationOpen=false', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ const navContainer = wrapper.findNavigation().getElement().closest(`.${skeletonStyles.navigation}`);
+ expect(navContainer).toHaveClass(skeletonStyles['panel-hidden']);
+ expect(navContainer).not.toHaveClass(skeletonStyles['navigation-collapsed']);
+ });
+ });
+
+ describe('toggle behavior', () => {
+ test('close button shows angle-right icon in collapsed state', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ const closeButton = wrapper.findNavigationClose().getElement();
+ expect(closeButton.querySelector(`.${iconStyles['name-angle-right']}`)).not.toBeNull();
+ });
+
+ test('close button shows angle-left icon in open state on desktop', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ const closeButton = wrapper.findNavigationClose().getElement();
+ expect(closeButton.querySelector(`.${iconStyles['name-angle-left']}`)).not.toBeNull();
+ });
+ });
+
+ describe('aria-expanded', () => {
+ test('close button has aria-expanded=false when navigation is closed', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ expect(wrapper.findNavigationClose().getElement()).toHaveAttribute('aria-expanded', 'false');
+ });
+
+ test('close button has aria-expanded=true when navigation is open', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ expect(wrapper.findNavigationClose().getElement()).toHaveAttribute('aria-expanded', 'true');
+ });
+ });
+
+ describe('aria-hidden', () => {
+ test('nav is not aria-hidden when collapsed (visible and interactive)', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ expect(wrapper.findNavigation().getElement().closest('nav')).toHaveAttribute('aria-hidden', 'false');
+ });
+
+ test('nav is aria-hidden when closed without collapsed', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ expect(wrapper.findNavigation().getElement().closest('nav')).toHaveAttribute('aria-hidden', 'true');
+ });
+ });
+
+ describe('navigationHide guard', () => {
+ test('navigation is fully hidden when navigationHide=true regardless of navigationCloseBehavior', () => {
+ const { wrapper } = renderComponent(
+ Nav>}
+ />
+ );
+ expect(wrapper.findNavigation()).toBeFalsy();
+ expect(wrapper.findNavigationToggle()).toBeFalsy();
+ });
+ });
+
+ describe('layout computation', () => {
+ test('sets navigationCollapsedWidth CSS property', () => {
+ const { wrapper } = renderComponent(
+ Nav>} />
+ );
+ const rootStyle = wrapper.getElement().style;
+ expect(rootStyle.getPropertyValue(customCssProps.navigationCollapsedWidth)).toBe('54px');
+ });
+
+ test('uses custom navigationCollapsedWidth when provided', () => {
+ const { wrapper } = renderComponent(
+ Nav>}
+ />
+ );
+ const rootStyle = wrapper.getElement().style;
+ expect(rootStyle.getPropertyValue(customCssProps.navigationCollapsedWidth)).toBe('80px');
+ });
+ });
+
+ describe('default values', () => {
+ test('navigationCollapsedWidth defaults to 54', () => {
+ const { wrapper } = renderComponent(
+ Nav>} />
+ );
+ const rootStyle = wrapper.getElement().style;
+ expect(rootStyle.getPropertyValue(customCssProps.navigationCollapsedWidth)).toBe('54px');
+ });
+ });
+
+ describe('navigationCollapsedWidth vs navigationWidth warning', () => {
+ let consoleWarnSpy: jest.SpyInstance;
+ beforeEach(() => {
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
+ });
+ afterEach(() => {
+ consoleWarnSpy?.mockRestore();
+ });
+
+ const warning =
+ '[AwsUi] [AppLayout] `navigationCollapsedWidth` should be smaller than `navigationWidth`. ' +
+ 'When collapsed width equals or exceeds the expanded width, the ARIA expanded/collapsed semantics become inverted.';
+
+ test('does not warn when collapsed width is smaller than navigation width', () => {
+ renderComponent(
+ Nav>}
+ />
+ );
+ expect(console.warn).not.toHaveBeenCalled();
+ });
+
+ test('does not warn when collapse behavior is not enabled, even if collapsed width is larger', () => {
+ renderComponent(Nav>} />);
+ expect(console.warn).not.toHaveBeenCalled();
+ });
+
+ // Kept last: it primes warnOnce's cache for this message, so it must not run before the negative cases above.
+ test('warns when collapsed width is not smaller than navigation width', () => {
+ renderComponent(
+ Nav>}
+ />
+ );
+ expect(console.warn).toHaveBeenCalledWith(warning);
+ });
+ });
+});
+
+describeEachAppLayout({ themes: ['refresh-toolbar'], sizes: ['mobile'] }, () => {
+ describe('mobile standard behavior (collapsed is desktop-only)', () => {
+ test('navigation uses panel-hidden when closed on mobile, even with navigationCloseBehavior="collapse"', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ const navContainer = wrapper.findNavigation().getElement().closest(`.${skeletonStyles.navigation}`);
+ expect(navContainer).toHaveClass(skeletonStyles['panel-hidden']);
+ });
+
+ test('navigation does not use navigation-collapsed class on mobile', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ const navContainer = wrapper.findNavigation().getElement().closest(`.${skeletonStyles.navigation}`);
+ expect(navContainer).not.toHaveClass(skeletonStyles['navigation-collapsed']);
+ });
+
+ test('navigation-container does not have is-navigation-collapsed class on mobile', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ const nav = wrapper.findNavigation().getElement().closest(`.${navStyles['navigation-container']}`);
+ expect(nav).not.toHaveClass(navStyles['is-navigation-collapsed']);
+ });
+
+ test('navigation opens full width on mobile with standard behavior', () => {
+ const { wrapper } = renderComponent(
+ Nav content>} />
+ );
+ const navContainer = wrapper.findNavigation().getElement().closest(`.${skeletonStyles.navigation}`);
+ expect(navContainer).not.toHaveClass(skeletonStyles['panel-hidden']);
+ });
+ });
+});
diff --git a/src/app-layout/__tests__/skeleton.test.tsx b/src/app-layout/__tests__/skeleton.test.tsx
index 9f179c2124..a3bb1eb7d9 100644
--- a/src/app-layout/__tests__/skeleton.test.tsx
+++ b/src/app-layout/__tests__/skeleton.test.tsx
@@ -1,13 +1,16 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
+import { render } from '@testing-library/react';
import AppLayout from '../../../lib/components/app-layout';
+import { BeforeMainSlotSkeleton } from '../../../lib/components/app-layout/visual-refresh-toolbar/skeleton/skeleton-parts';
import BreadcrumbGroup from '../../../lib/components/breadcrumb-group';
import { getFunnelKeySelector } from '../../../lib/components/internal/analytics/selectors';
import { describeEachAppLayout, renderComponent } from './utils';
import testutilStyles from '../../../lib/components/app-layout/test-classes/styles.selectors.js';
+import navStyles from '../../../lib/components/app-layout/visual-refresh-toolbar/navigation/styles.selectors.js';
import skeletonStyles from '../../../lib/components/app-layout/visual-refresh-toolbar/skeleton/styles.selectors.js';
import toolbarStyles from '../../../lib/components/app-layout/visual-refresh-toolbar/toolbar/styles.selectors.js';
@@ -116,3 +119,38 @@ describeEachAppLayout({ themes: ['refresh-toolbar'] }, () => {
});
});
});
+
+// The navigation branches of the skeleton depend on `toolbarProps.navigationCollapsible`, which the
+// full app layout does not provide while the widget is still loading (the skeleton is shown before
+// the state that computes it exists). These render the skeleton part directly to cover those branches.
+describe('BeforeMainSlotSkeleton navigation', () => {
+ const appLayoutProps = { navigation: nav content
} as any;
+
+ function renderSkeleton(toolbarProps: any) {
+ const { container } = render(
+
+ );
+ return container;
+ }
+
+ it('shows the collapsed rail when closed with collapse behavior', () => {
+ const container = renderSkeleton({ hasNavigation: true, navigationOpen: false, navigationCollapsible: true });
+ expect(container.querySelector(`.${skeletonStyles['navigation-collapsed']}`)).toBeTruthy();
+ expect(container.querySelector(`.${skeletonStyles['panel-hidden']}`)).toBeFalsy();
+ expect(container.querySelector(`.${navStyles['is-navigation-collapsed']}`)).toBeTruthy();
+ });
+
+ it('hides the panel when closed without collapse behavior', () => {
+ const container = renderSkeleton({ hasNavigation: true, navigationOpen: false, navigationCollapsible: false });
+ expect(container.querySelector(`.${skeletonStyles['panel-hidden']}`)).toBeTruthy();
+ expect(container.querySelector(`.${skeletonStyles['navigation-collapsed']}`)).toBeFalsy();
+ expect(container.querySelector(`.${navStyles['is-navigation-collapsed']}`)).toBeFalsy();
+ });
+
+ it('does not apply collapsed rail classes when the navigation is open', () => {
+ const container = renderSkeleton({ hasNavigation: true, navigationOpen: true, navigationCollapsible: true });
+ expect(container.querySelector(`.${skeletonStyles['navigation-collapsed']}`)).toBeFalsy();
+ expect(container.querySelector(`.${navStyles['is-navigation-collapsed']}`)).toBeFalsy();
+ expect(container.querySelector(`.${navStyles['is-navigation-open']}`)).toBeTruthy();
+ });
+});
diff --git a/src/app-layout/constants.scss b/src/app-layout/constants.scss
index 5ff3d1d6c1..04a4bcc032 100644
--- a/src/app-layout/constants.scss
+++ b/src/app-layout/constants.scss
@@ -35,4 +35,8 @@ $toolbar-z-index: 1000;
// Shared toolbar drawer component values
$toolbar-vertical-panel-icon-offset: 14px;
+// Default collapsed navigation rail width
+$navigation-collapsed-width-default: 54px;
+
+$ai-drawer-background: #161d26;
$ai-drawer-background: awsui.$color-background-layout-ai-drawer;
diff --git a/src/app-layout/index.tsx b/src/app-layout/index.tsx
index 5c492b8c85..166fcd24b6 100644
--- a/src/app-layout/index.tsx
+++ b/src/app-layout/index.tsx
@@ -28,6 +28,7 @@ const AppLayout = React.forwardRef(
headerSelector = '#b #h',
footerSelector = '#b #f',
navigationWidth = 280,
+ navigationCollapsedWidth = 54,
toolsWidth = 290,
maxContentWidth,
minContentWidth,
@@ -96,6 +97,7 @@ const AppLayout = React.forwardRef(
const props = {
contentType,
navigationWidth,
+ navigationCollapsedWidth,
toolsWidth,
navigationOpen,
onNavigationChange,
diff --git a/src/app-layout/interfaces.ts b/src/app-layout/interfaces.ts
index 753933908d..51ea4bff62 100644
--- a/src/app-layout/interfaces.ts
+++ b/src/app-layout/interfaces.ts
@@ -74,6 +74,23 @@ export interface BaseLayoutProps extends BaseComponentProps {
*/
navigationWidth?: number;
+ /**
+ * Determines whether the navigation panel collapses or hides completely when closed.
+ * Defaults to `hide` if not specified.
+ *
+ * When set to `collapse`, closing the navigation shows a collapsed icon-only navigation rail
+ * at `navigationCollapsedWidth` instead of hiding the panel. Use it together with the
+ * `collapsed` property of the side navigation component. On mobile viewports the rail
+ * is not available and the closed navigation is completely hidden.
+ */
+ navigationCloseBehavior?: 'collapse' | 'hide';
+
+ /**
+ * Width of the collapsed navigation rail in pixels.
+ * Only applies when `navigationCloseBehavior` is `collapse` and `navigationOpen` is `false`.
+ */
+ navigationCollapsedWidth?: number;
+
/**
* If `true`, the navigation drawer is not displayed at all.
*/
@@ -361,7 +378,13 @@ export namespace AppLayoutProps {
export type AppLayoutPropsWithDefaults = SomeRequired<
Omit,
- 'contentType' | 'navigationWidth' | 'toolsWidth' | 'minContentWidth' | 'navigationOpen' | 'onNavigationChange'
+ | 'contentType'
+ | 'navigationWidth'
+ | 'navigationCollapsedWidth'
+ | 'toolsWidth'
+ | 'minContentWidth'
+ | 'navigationOpen'
+ | 'onNavigationChange'
> & {
placement: {
insetBlockStart: number;
diff --git a/src/app-layout/visual-refresh-toolbar/compute-layout.ts b/src/app-layout/visual-refresh-toolbar/compute-layout.ts
index 3bc00b19ff..020ee167b2 100644
--- a/src/app-layout/visual-refresh-toolbar/compute-layout.ts
+++ b/src/app-layout/visual-refresh-toolbar/compute-layout.ts
@@ -7,6 +7,8 @@ import { shouldSplitPanelBeForcedToBottom } from '../split-panel/split-panel-for
interface HorizontalLayoutInput {
navigationOpen: boolean;
navigationWidth: number;
+ navigationCollapsible: boolean;
+ navigationCollapsedWidth: number;
placement: AppLayoutPropsWithDefaults['placement'];
minContentWidth: number;
activeDrawerSize: number;
@@ -23,6 +25,8 @@ export const CONTENT_PADDING = 2 * 24; // space-xl
export function computeHorizontalLayout({
navigationOpen,
navigationWidth,
+ navigationCollapsible,
+ navigationCollapsedWidth,
placement,
minContentWidth,
activeDrawerSize,
@@ -33,7 +37,7 @@ export function computeHorizontalLayout({
activeGlobalDrawersSizes,
activeAiDrawerSize,
}: HorizontalLayoutInput) {
- const activeNavigationWidth = navigationOpen ? navigationWidth : 0;
+ const activeNavigationWidth = navigationOpen ? navigationWidth : navigationCollapsible ? navigationCollapsedWidth : 0;
let resizableSpaceAvailable =
placement.inlineSize - minContentWidth - CONTENT_PADDING - activeNavigationWidth - activeAiDrawerSize;
diff --git a/src/app-layout/visual-refresh-toolbar/interfaces.ts b/src/app-layout/visual-refresh-toolbar/interfaces.ts
index fae99a33ad..9848cf74c5 100644
--- a/src/app-layout/visual-refresh-toolbar/interfaces.ts
+++ b/src/app-layout/visual-refresh-toolbar/interfaces.ts
@@ -109,6 +109,8 @@ export interface AppLayoutWidgetizedState extends AppLayoutInternals {
onActiveBottomDrawerResize: ({ id, size }: { id: string; size: number }) => void;
bottomDrawers: ReadonlyArray;
featureNotificationsProps?: FeatureNotificationsProps;
+ navigationCollapsible: boolean;
+ navigationCollapsedWidth: number;
}
// New widget interface
diff --git a/src/app-layout/visual-refresh-toolbar/navigation/index.tsx b/src/app-layout/visual-refresh-toolbar/navigation/index.tsx
index 5241cea55d..759387fc12 100644
--- a/src/app-layout/visual-refresh-toolbar/navigation/index.tsx
+++ b/src/app-layout/visual-refresh-toolbar/navigation/index.tsx
@@ -16,11 +16,13 @@ import styles from './styles.css.js';
interface AppLayoutNavigationImplementationProps {
appLayoutInternals: AppLayoutInternals;
bottomDrawerReportedSize?: number;
+ navigationCollapsible?: boolean;
}
export function AppLayoutNavigationImplementation({
appLayoutInternals,
bottomDrawerReportedSize,
+ navigationCollapsible,
}: AppLayoutNavigationImplementationProps) {
const {
ariaLabels,
@@ -40,6 +42,8 @@ export function AppLayoutNavigationImplementation({
isMobile ? 0 : (bottomDrawerReportedSize ?? 0)
);
+ const navigationCollapsed = navigationCollapsible && !navigationOpen && !isMobile;
+
// Close the Navigation drawer on mobile when a user clicks a link inside.
const onNavigationClick = (event: React.MouseEvent) => {
const hasLink = findUpUntil(
@@ -55,6 +59,7 @@ export function AppLayoutNavigationImplementation({
-
+
onNavigationToggle(false)}
+ ariaLabel={
+ navigationCollapsed
+ ? (ariaLabels?.navigationToggle ?? undefined)
+ : (ariaLabels?.navigationClose ?? undefined)
+ }
+ ariaExpanded={navigationCollapsible && !isMobile ? navigationOpen : undefined}
+ iconName={navigationCollapsed ? 'angle-right' : isMobile ? 'close' : 'angle-left'}
+ onClick={() => onNavigationToggle(!navigationOpen)}
variant="icon"
formAction="none"
className={testutilStyles['navigation-close']}
ref={navigationFocusControl.refs.close}
- analyticsAction="close"
+ analyticsAction={navigationCollapsed ? 'open' : 'close'}
/>
{navigation}
diff --git a/src/app-layout/visual-refresh-toolbar/navigation/styles.scss b/src/app-layout/visual-refresh-toolbar/navigation/styles.scss
index adabab1191..9be149d36a 100644
--- a/src/app-layout/visual-refresh-toolbar/navigation/styles.scss
+++ b/src/app-layout/visual-refresh-toolbar/navigation/styles.scss
@@ -8,6 +8,14 @@
@use '../../../internal/generated/custom-css-properties/index.scss' as custom-props;
@use '../../constants.scss' as constants;
+$collapsed-width: var(#{custom-props.$navigationCollapsedWidth}, constants.$navigation-collapsed-width-default);
+
+.hide-navigation {
+ position: absolute;
+ inset-inline-end: awsui.$space-m;
+ inset-block-start: constants.$toolbar-vertical-panel-icon-offset;
+}
+
// This wrapper clips the actual navigation content during the enter animation.
.navigation-container {
position: sticky;
@@ -20,13 +28,6 @@
display: flex;
flex-direction: column;
- // All content is hidden by the overflow-x property
- &:not(.is-navigation-open) {
- inline-size: 0px;
- // We need to hide the closed panel to make containing focusable elements not focusable anymore.
- display: none;
- }
-
& > .navigation {
flex-grow: 1;
block-size: 100%;
@@ -39,15 +40,45 @@
position: relative;
}
+ // Closed panel: collapsed to zero width and removed from the layout so its
+ // focusable children are no longer reachable. The .is-navigation-collapsed
+ // rule below overrides this to render the icon rail instead.
+ &:not(.is-navigation-open) {
+ inline-size: 0px;
+ display: none;
+ }
+
+ // Closed but showing the collapsed icon rail. This class is only ever set while
+ // the panel is closed, so it always overrides the rule above (equal specificity,
+ // later in source order).
+ &.is-navigation-collapsed {
+ display: flex;
+ align-items: center;
+ inline-size: $collapsed-width;
+
+ & > .navigation {
+ inline-size: $collapsed-width;
+ overflow-x: hidden;
+ }
+
+ // In the rail, replace the top-right close button with a centered expand toggle.
+ // stylelint-disable-next-line @cloudscape-design/no-implicit-descendant
+ & > .navigation > .hide-navigation {
+ display: none;
+
+ &.expand-navigation-toggle {
+ display: flex;
+ position: static;
+ align-items: center;
+ justify-content: center;
+ padding-block: awsui.$space-s;
+ }
+ }
+ }
+
// The Navigation drawer will take up the entire viewport on mobile
@include styles.media-breakpoint-down(styles.$breakpoint-x-small) {
#{custom-props.$navigationWidth}: 100vw;
z-index: constants.$drawer-z-index-mobile;
}
}
-
-.hide-navigation {
- position: absolute;
- inset-inline-end: awsui.$space-m;
- inset-block-start: constants.$toolbar-vertical-panel-icon-offset;
-}
diff --git a/src/app-layout/visual-refresh-toolbar/skeleton/skeleton-parts.tsx b/src/app-layout/visual-refresh-toolbar/skeleton/skeleton-parts.tsx
index d697bab89b..5c8e6a8fe1 100644
--- a/src/app-layout/visual-refresh-toolbar/skeleton/skeleton-parts.tsx
+++ b/src/app-layout/visual-refresh-toolbar/skeleton/skeleton-parts.tsx
@@ -35,14 +35,18 @@ export const BeforeMainSlotSkeleton = React.forwardRef
diff --git a/src/app-layout/visual-refresh-toolbar/skeleton/styles.scss b/src/app-layout/visual-refresh-toolbar/skeleton/styles.scss
index 92879161e7..65263ecd5f 100644
--- a/src/app-layout/visual-refresh-toolbar/skeleton/styles.scss
+++ b/src/app-layout/visual-refresh-toolbar/skeleton/styles.scss
@@ -146,12 +146,18 @@
@include desktop-only {
grid-area: navigation;
inline-size: var(#{custom-props.$navigationWidth});
- border-inline-end: awsui.$border-divider-section-width solid awsui.$color-border-layout;
+ border-inline-end: awsui.$border-width-layout-side-navigation solid awsui.$color-border-layout-side-navigation;
}
@include mobile-only {
z-index: constants.$drawer-z-index-mobile;
}
+
+ &.navigation-collapsed {
+ @include desktop-only {
+ inline-size: var(#{custom-props.$navigationCollapsedWidth}, constants.$navigation-collapsed-width-default);
+ }
+ }
}
.tools {
diff --git a/src/app-layout/visual-refresh-toolbar/state/interfaces.ts b/src/app-layout/visual-refresh-toolbar/state/interfaces.ts
index 6b0e7f38db..ae4fdcada4 100644
--- a/src/app-layout/visual-refresh-toolbar/state/interfaces.ts
+++ b/src/app-layout/visual-refresh-toolbar/state/interfaces.ts
@@ -9,6 +9,7 @@ import { SplitPanelToggleProps, ToolbarProps } from '../toolbar';
export interface SharedProps {
navigationOpen: boolean;
+ navigationCollapsible?: boolean;
ariaLabels: AppLayoutProps.Labels | undefined;
navigation: React.ReactNode;
onNavigationToggle?: (open: boolean) => void;
diff --git a/src/app-layout/visual-refresh-toolbar/state/props-merger.ts b/src/app-layout/visual-refresh-toolbar/state/props-merger.ts
index d903f780ff..68a84ad8c2 100644
--- a/src/app-layout/visual-refresh-toolbar/state/props-merger.ts
+++ b/src/app-layout/visual-refresh-toolbar/state/props-merger.ts
@@ -61,6 +61,7 @@ export const mergeProps: MergeProps = (ownProps, additionalProps) => {
if (props.navigation && !checkAlreadyExists(!!toolbar.hasNavigation, 'navigation')) {
toolbar.hasNavigation = true;
toolbar.navigationOpen = props.navigationOpen;
+ toolbar.navigationCollapsible = props.navigationCollapsible;
toolbar.navigationFocusRef = props.navigationFocusRef;
toolbar.onNavigationToggle = props.onNavigationToggle;
}
@@ -93,6 +94,7 @@ export const getPropsToMerge = (props: AppLayoutInternalProps, appLayoutState: A
ariaLabels: state ? state.ariaLabels : props.ariaLabels,
navigation: !props.navigationTriggerHide && !props.navigationHide,
navigationOpen: state ? state.navigationOpen : props.navigationOpen,
+ navigationCollapsible: state?.navigationCollapsible,
onNavigationToggle: state?.onNavigationToggle,
navigationFocusRef: state?.navigationFocusControl.refs.toggle,
activeDrawerId: state?.activeDrawer?.id ?? null,
diff --git a/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx b/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx
index e556a5df95..339f7fb362 100644
--- a/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx
+++ b/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import React, { ForwardedRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
-import { useMergeRefs, useStableCallback, useUniqueId } from '@cloudscape-design/component-toolkit/internal';
+import { useMergeRefs, useStableCallback, useUniqueId, warnOnce } from '@cloudscape-design/component-toolkit/internal';
import { SplitPanelSideToggleProps } from '../../../internal/context/split-panel-context';
import { fireNonCancelableEvent } from '../../../internal/events';
@@ -39,6 +39,8 @@ export const useAppLayout = (
ariaLabels,
navigationOpen,
navigationWidth,
+ navigationCloseBehavior,
+ navigationCollapsedWidth,
navigation,
navigationHide,
onNavigationChange,
@@ -65,6 +67,16 @@ export const useAppLayout = (
forwardRef: ForwardedRef
): AppLayoutState => {
const isMobile = useMobile();
+ const navigationCollapsible = navigationCloseBehavior === 'collapse';
+
+ if (navigationCollapsible && navigationCollapsedWidth !== undefined && navigationCollapsedWidth >= navigationWidth) {
+ warnOnce(
+ 'AppLayout',
+ '`navigationCollapsedWidth` should be smaller than `navigationWidth`. ' +
+ 'When collapsed width equals or exceeds the expanded width, the ARIA expanded/collapsed semantics become inverted.'
+ );
+ }
+
const splitPanelControlId = useUniqueId('split-panel');
const [toolbarState, setToolbarState] = useState<'show' | 'hide'>('show');
const [toolbarHeight, setToolbarHeight] = useState(0);
@@ -402,6 +414,8 @@ export const useAppLayout = (
minContentWidth,
navigationOpen: resolvedNavigationOpen,
navigationWidth,
+ navigationCollapsible,
+ navigationCollapsedWidth,
placement,
splitPanelOpen,
splitPanelPosition: splitPanelPreferences?.position,
@@ -577,7 +591,12 @@ export const useAppLayout = (
return;
}
- const activeNavigationWidth = !navigationHide && navigationOpen ? navigationWidth : 0;
+ const activeNavigationWidth =
+ !navigationHide && navigationOpen
+ ? navigationWidth
+ : !navigationHide && navigationCollapsible
+ ? navigationCollapsedWidth
+ : 0;
const scrollWidth = activeNavigationWidth + CONTENT_PADDING + totalActiveDrawersMinSize;
const hasHorizontalScroll = scrollWidth > placement.inlineSize;
if (hasHorizontalScroll) {
@@ -592,6 +611,8 @@ export const useAppLayout = (
totalActiveDrawersMinSize,
closeFirstDrawer,
isMobile,
+ navigationCollapsible,
+ navigationCollapsedWidth,
navigationHide,
navigationOpen,
navigationWidth,
@@ -650,6 +671,8 @@ export const useAppLayout = (
bottomDrawers,
bottomDrawersFocusControl,
featureNotificationsProps,
+ navigationCollapsible,
+ navigationCollapsedWidth,
},
};
};
diff --git a/src/app-layout/visual-refresh-toolbar/state/use-skeleton-slots-attributes.ts b/src/app-layout/visual-refresh-toolbar/state/use-skeleton-slots-attributes.ts
index 5a114a0f95..a1d1e9ebb4 100644
--- a/src/app-layout/visual-refresh-toolbar/state/use-skeleton-slots-attributes.ts
+++ b/src/app-layout/visual-refresh-toolbar/state/use-skeleton-slots-attributes.ts
@@ -28,6 +28,7 @@ export const useSkeletonSlotsAttributes = (
expandedDrawerId,
activeAiDrawer,
activeGlobalBottomDrawerId,
+ navigationCollapsedWidth,
} = appLayoutState.widgetizedState ?? {};
const { contentType, placement, maxContentWidth, navigationWidth, minContentWidth, disableContentPaddings } =
appLayoutProps;
@@ -51,6 +52,7 @@ export const useSkeletonSlotsAttributes = (
minBlockSize: isNested ? '100%' : `calc(100vh - ${placement.insetBlockStart + placement.insetBlockEnd}px)`,
[customCssProps.maxContentWidth]: isMaxWidth ? '100%' : maxContentWidth ? `${maxContentWidth}px` : '',
[customCssProps.navigationWidth]: `${navigationWidth}px`,
+ [customCssProps.navigationCollapsedWidth]: `${navigationCollapsedWidth}px`,
[customCssProps.toolsWidth]: `${activeDrawerSize}px`,
},
};
diff --git a/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx b/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx
index a144087a3b..054add8f6f 100644
--- a/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx
+++ b/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx
@@ -30,6 +30,7 @@ export interface ToolbarProps {
// navigation
hasNavigation?: boolean;
navigationOpen?: boolean;
+ navigationCollapsible?: boolean;
onNavigationToggle?: (open: boolean) => void;
navigationFocusRef?: React.Ref;
diff --git a/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss b/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss
index 8cdc710a6d..ea03777260 100644
--- a/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss
+++ b/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss
@@ -143,7 +143,7 @@ $toolbar-height: 42px;
> .universal-toolbar-nav {
grid-column: 2;
- padding-inline-start: awsui.$space-m;
+ padding-inline-start: awsui.$space-s;
padding-inline-end: awsui.$space-static-xxs;
}
@@ -171,7 +171,7 @@ $toolbar-height: 42px;
.drawers-mobile-triggers-container {
@include styles.styles-reset;
background-color: transparent;
- padding-inline: awsui.$space-m;
+ padding-inline: awsui.$space-s;
box-sizing: border-box;
overflow-y: hidden;
overflow-x: hidden;
diff --git a/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx b/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx
index fd05eec54e..791542ef91 100644
--- a/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx
+++ b/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx
@@ -34,6 +34,7 @@ export const BeforeMainSlotImplementationInternal = ({
const {
activeDrawer,
navigationOpen,
+ navigationCollapsible,
navigation,
expandedDrawerId,
setExpandedDrawerId,
@@ -117,7 +118,8 @@ export const BeforeMainSlotImplementationInternal = ({
diff --git a/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap b/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap
index f13a2d7c9c..6babf3aaf7 100644
--- a/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap
@@ -6,9 +6,9 @@ exports[`getDropdownStyles handles all possible style configurations 2`] = `unde
exports[`getDropdownStyles handles all possible style configurations 3`] = `
{
- "--awsui-dropdown-content-border-color-z17rmd": "rgb(0, 0, 0)",
- "--awsui-dropdown-content-border-radius-z17rmd": "8px",
- "--awsui-dropdown-content-border-width-z17rmd": "2px",
+ "--awsui-dropdown-content-border-color-esndbs": "rgb(0, 0, 0)",
+ "--awsui-dropdown-content-border-radius-esndbs": "8px",
+ "--awsui-dropdown-content-border-width-esndbs": "2px",
"background": "rgb(255, 255, 255)",
}
`;
diff --git a/src/input/__tests__/__snapshots__/styles.test.tsx.snap b/src/input/__tests__/__snapshots__/styles.test.tsx.snap
index c0202a1295..f1cacfd9b5 100644
--- a/src/input/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/input/__tests__/__snapshots__/styles.test.tsx.snap
@@ -2,30 +2,30 @@
exports[`getInputStyles handles all possible style configurations 1`] = `
{
- "--awsui-style-background-default-z17rmd": undefined,
- "--awsui-style-background-disabled-z17rmd": undefined,
- "--awsui-style-background-focus-z17rmd": undefined,
- "--awsui-style-background-hover-z17rmd": undefined,
- "--awsui-style-background-readonly-z17rmd": undefined,
- "--awsui-style-border-color-default-z17rmd": undefined,
- "--awsui-style-border-color-disabled-z17rmd": undefined,
- "--awsui-style-border-color-focus-z17rmd": undefined,
- "--awsui-style-border-color-hover-z17rmd": undefined,
- "--awsui-style-border-color-readonly-z17rmd": undefined,
- "--awsui-style-box-shadow-default-z17rmd": undefined,
- "--awsui-style-box-shadow-disabled-z17rmd": undefined,
- "--awsui-style-box-shadow-focus-z17rmd": undefined,
- "--awsui-style-box-shadow-hover-z17rmd": undefined,
- "--awsui-style-box-shadow-readonly-z17rmd": undefined,
- "--awsui-style-color-default-z17rmd": undefined,
- "--awsui-style-color-disabled-z17rmd": undefined,
- "--awsui-style-color-focus-z17rmd": undefined,
- "--awsui-style-color-hover-z17rmd": undefined,
- "--awsui-style-color-readonly-z17rmd": undefined,
- "--awsui-style-placeholder-color-z17rmd": undefined,
- "--awsui-style-placeholder-font-size-z17rmd": undefined,
- "--awsui-style-placeholder-font-style-z17rmd": undefined,
- "--awsui-style-placeholder-font-weight-z17rmd": undefined,
+ "--awsui-style-background-default-esndbs": undefined,
+ "--awsui-style-background-disabled-esndbs": undefined,
+ "--awsui-style-background-focus-esndbs": undefined,
+ "--awsui-style-background-hover-esndbs": undefined,
+ "--awsui-style-background-readonly-esndbs": undefined,
+ "--awsui-style-border-color-default-esndbs": undefined,
+ "--awsui-style-border-color-disabled-esndbs": undefined,
+ "--awsui-style-border-color-focus-esndbs": undefined,
+ "--awsui-style-border-color-hover-esndbs": undefined,
+ "--awsui-style-border-color-readonly-esndbs": undefined,
+ "--awsui-style-box-shadow-default-esndbs": undefined,
+ "--awsui-style-box-shadow-disabled-esndbs": undefined,
+ "--awsui-style-box-shadow-focus-esndbs": undefined,
+ "--awsui-style-box-shadow-hover-esndbs": undefined,
+ "--awsui-style-box-shadow-readonly-esndbs": undefined,
+ "--awsui-style-color-default-esndbs": undefined,
+ "--awsui-style-color-disabled-esndbs": undefined,
+ "--awsui-style-color-focus-esndbs": undefined,
+ "--awsui-style-color-hover-esndbs": undefined,
+ "--awsui-style-color-readonly-esndbs": undefined,
+ "--awsui-style-placeholder-color-esndbs": undefined,
+ "--awsui-style-placeholder-font-size-esndbs": undefined,
+ "--awsui-style-placeholder-font-style-esndbs": undefined,
+ "--awsui-style-placeholder-font-weight-esndbs": undefined,
"borderRadius": undefined,
"borderWidth": undefined,
"fontSize": undefined,
@@ -37,30 +37,30 @@ exports[`getInputStyles handles all possible style configurations 1`] = `
exports[`getInputStyles handles all possible style configurations 2`] = `
{
- "--awsui-style-background-default-z17rmd": undefined,
- "--awsui-style-background-disabled-z17rmd": undefined,
- "--awsui-style-background-focus-z17rmd": undefined,
- "--awsui-style-background-hover-z17rmd": undefined,
- "--awsui-style-background-readonly-z17rmd": undefined,
- "--awsui-style-border-color-default-z17rmd": undefined,
- "--awsui-style-border-color-disabled-z17rmd": undefined,
- "--awsui-style-border-color-focus-z17rmd": undefined,
- "--awsui-style-border-color-hover-z17rmd": undefined,
- "--awsui-style-border-color-readonly-z17rmd": undefined,
- "--awsui-style-box-shadow-default-z17rmd": undefined,
- "--awsui-style-box-shadow-disabled-z17rmd": undefined,
- "--awsui-style-box-shadow-focus-z17rmd": undefined,
- "--awsui-style-box-shadow-hover-z17rmd": undefined,
- "--awsui-style-box-shadow-readonly-z17rmd": undefined,
- "--awsui-style-color-default-z17rmd": undefined,
- "--awsui-style-color-disabled-z17rmd": undefined,
- "--awsui-style-color-focus-z17rmd": undefined,
- "--awsui-style-color-hover-z17rmd": undefined,
- "--awsui-style-color-readonly-z17rmd": undefined,
- "--awsui-style-placeholder-color-z17rmd": undefined,
- "--awsui-style-placeholder-font-size-z17rmd": undefined,
- "--awsui-style-placeholder-font-style-z17rmd": undefined,
- "--awsui-style-placeholder-font-weight-z17rmd": undefined,
+ "--awsui-style-background-default-esndbs": undefined,
+ "--awsui-style-background-disabled-esndbs": undefined,
+ "--awsui-style-background-focus-esndbs": undefined,
+ "--awsui-style-background-hover-esndbs": undefined,
+ "--awsui-style-background-readonly-esndbs": undefined,
+ "--awsui-style-border-color-default-esndbs": undefined,
+ "--awsui-style-border-color-disabled-esndbs": undefined,
+ "--awsui-style-border-color-focus-esndbs": undefined,
+ "--awsui-style-border-color-hover-esndbs": undefined,
+ "--awsui-style-border-color-readonly-esndbs": undefined,
+ "--awsui-style-box-shadow-default-esndbs": undefined,
+ "--awsui-style-box-shadow-disabled-esndbs": undefined,
+ "--awsui-style-box-shadow-focus-esndbs": undefined,
+ "--awsui-style-box-shadow-hover-esndbs": undefined,
+ "--awsui-style-box-shadow-readonly-esndbs": undefined,
+ "--awsui-style-color-default-esndbs": undefined,
+ "--awsui-style-color-disabled-esndbs": undefined,
+ "--awsui-style-color-focus-esndbs": undefined,
+ "--awsui-style-color-hover-esndbs": undefined,
+ "--awsui-style-color-readonly-esndbs": undefined,
+ "--awsui-style-placeholder-color-esndbs": undefined,
+ "--awsui-style-placeholder-font-size-esndbs": undefined,
+ "--awsui-style-placeholder-font-style-esndbs": undefined,
+ "--awsui-style-placeholder-font-weight-esndbs": undefined,
"borderRadius": undefined,
"borderWidth": undefined,
"fontSize": undefined,
@@ -72,30 +72,30 @@ exports[`getInputStyles handles all possible style configurations 2`] = `
exports[`getInputStyles handles all possible style configurations 3`] = `
{
- "--awsui-style-background-default-z17rmd": "#ffffff",
- "--awsui-style-background-disabled-z17rmd": "#f0f0f0",
- "--awsui-style-background-focus-z17rmd": "#ffffff",
- "--awsui-style-background-hover-z17rmd": "#fafafa",
- "--awsui-style-background-readonly-z17rmd": "#ffffff",
- "--awsui-style-border-color-default-z17rmd": "#cccccc",
- "--awsui-style-border-color-disabled-z17rmd": "#e0e0e0",
- "--awsui-style-border-color-focus-z17rmd": "#0073bb",
- "--awsui-style-border-color-hover-z17rmd": "#999999",
- "--awsui-style-border-color-readonly-z17rmd": "#e0e0e0",
- "--awsui-style-box-shadow-default-z17rmd": "none",
- "--awsui-style-box-shadow-disabled-z17rmd": "none",
- "--awsui-style-box-shadow-focus-z17rmd": "0 0 0 2px #0073bb",
- "--awsui-style-box-shadow-hover-z17rmd": "0 1px 2px rgba(0,0,0,0.1)",
- "--awsui-style-box-shadow-readonly-z17rmd": "none",
- "--awsui-style-color-default-z17rmd": "#000000",
- "--awsui-style-color-disabled-z17rmd": "#999999",
- "--awsui-style-color-focus-z17rmd": "#000000",
- "--awsui-style-color-hover-z17rmd": "#000000",
- "--awsui-style-color-readonly-z17rmd": "#000000",
- "--awsui-style-placeholder-color-z17rmd": "#999999",
- "--awsui-style-placeholder-font-size-z17rmd": "14px",
- "--awsui-style-placeholder-font-style-z17rmd": "italic",
- "--awsui-style-placeholder-font-weight-z17rmd": "400",
+ "--awsui-style-background-default-esndbs": "#ffffff",
+ "--awsui-style-background-disabled-esndbs": "#f0f0f0",
+ "--awsui-style-background-focus-esndbs": "#ffffff",
+ "--awsui-style-background-hover-esndbs": "#fafafa",
+ "--awsui-style-background-readonly-esndbs": "#ffffff",
+ "--awsui-style-border-color-default-esndbs": "#cccccc",
+ "--awsui-style-border-color-disabled-esndbs": "#e0e0e0",
+ "--awsui-style-border-color-focus-esndbs": "#0073bb",
+ "--awsui-style-border-color-hover-esndbs": "#999999",
+ "--awsui-style-border-color-readonly-esndbs": "#e0e0e0",
+ "--awsui-style-box-shadow-default-esndbs": "none",
+ "--awsui-style-box-shadow-disabled-esndbs": "none",
+ "--awsui-style-box-shadow-focus-esndbs": "0 0 0 2px #0073bb",
+ "--awsui-style-box-shadow-hover-esndbs": "0 1px 2px rgba(0,0,0,0.1)",
+ "--awsui-style-box-shadow-readonly-esndbs": "none",
+ "--awsui-style-color-default-esndbs": "#000000",
+ "--awsui-style-color-disabled-esndbs": "#999999",
+ "--awsui-style-color-focus-esndbs": "#000000",
+ "--awsui-style-color-hover-esndbs": "#000000",
+ "--awsui-style-color-readonly-esndbs": "#000000",
+ "--awsui-style-placeholder-color-esndbs": "#999999",
+ "--awsui-style-placeholder-font-size-esndbs": "14px",
+ "--awsui-style-placeholder-font-style-esndbs": "italic",
+ "--awsui-style-placeholder-font-weight-esndbs": "400",
"borderRadius": "4px",
"borderWidth": "1px",
"fontSize": "14px",
diff --git a/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap b/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap
index 512bdbb55b..6892bd53c0 100644
--- a/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap
@@ -42,11 +42,11 @@ exports[`getRootStyles handles all possible style configurations 2`] = `{}`;
exports[`getRootStyles handles all possible style configurations 3`] = `
{
- "--awsui-style-item-card-background-default-z17rmd": "#ffffff",
- "--awsui-style-item-card-border-color-default-z17rmd": "#e0e0e0",
- "--awsui-style-item-card-border-radius-z17rmd": "8px",
- "--awsui-style-item-card-border-width-default-z17rmd": "1px",
- "--awsui-style-item-card-box-shadow-default-z17rmd": "0 1px 3px rgba(0,0,0,0.1)",
+ "--awsui-style-item-card-background-default-esndbs": "#ffffff",
+ "--awsui-style-item-card-border-color-default-esndbs": "#e0e0e0",
+ "--awsui-style-item-card-border-radius-esndbs": "8px",
+ "--awsui-style-item-card-border-width-default-esndbs": "1px",
+ "--awsui-style-item-card-box-shadow-default-esndbs": "0 1px 3px rgba(0,0,0,0.1)",
"borderRadius": "8px",
}
`;
diff --git a/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap b/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap
index 37eb946e54..de0cbba2a9 100644
--- a/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap
@@ -20,17 +20,17 @@ exports[`getSegmentedControlRootStyles handles all possible style configurations
exports[`getSegmentedControlSegmentStyles handles all possible style configurations 1`] = `
{
- "--awsui-style-background-active-z17rmd": undefined,
- "--awsui-style-background-default-z17rmd": undefined,
- "--awsui-style-background-disabled-z17rmd": undefined,
- "--awsui-style-background-hover-z17rmd": undefined,
- "--awsui-style-color-active-z17rmd": undefined,
- "--awsui-style-color-default-z17rmd": undefined,
- "--awsui-style-color-disabled-z17rmd": undefined,
- "--awsui-style-color-hover-z17rmd": undefined,
- "--awsui-style-focus-ring-border-color-z17rmd": undefined,
- "--awsui-style-focus-ring-border-radius-z17rmd": undefined,
- "--awsui-style-focus-ring-border-width-z17rmd": undefined,
+ "--awsui-style-background-active-esndbs": undefined,
+ "--awsui-style-background-default-esndbs": undefined,
+ "--awsui-style-background-disabled-esndbs": undefined,
+ "--awsui-style-background-hover-esndbs": undefined,
+ "--awsui-style-color-active-esndbs": undefined,
+ "--awsui-style-color-default-esndbs": undefined,
+ "--awsui-style-color-disabled-esndbs": undefined,
+ "--awsui-style-color-hover-esndbs": undefined,
+ "--awsui-style-focus-ring-border-color-esndbs": undefined,
+ "--awsui-style-focus-ring-border-radius-esndbs": undefined,
+ "--awsui-style-focus-ring-border-width-esndbs": undefined,
"borderRadius": undefined,
"fontSize": undefined,
"paddingBlock": undefined,
@@ -40,17 +40,17 @@ exports[`getSegmentedControlSegmentStyles handles all possible style configurati
exports[`getSegmentedControlSegmentStyles handles all possible style configurations 2`] = `
{
- "--awsui-style-background-active-z17rmd": undefined,
- "--awsui-style-background-default-z17rmd": undefined,
- "--awsui-style-background-disabled-z17rmd": undefined,
- "--awsui-style-background-hover-z17rmd": undefined,
- "--awsui-style-color-active-z17rmd": undefined,
- "--awsui-style-color-default-z17rmd": undefined,
- "--awsui-style-color-disabled-z17rmd": undefined,
- "--awsui-style-color-hover-z17rmd": undefined,
- "--awsui-style-focus-ring-border-color-z17rmd": undefined,
- "--awsui-style-focus-ring-border-radius-z17rmd": undefined,
- "--awsui-style-focus-ring-border-width-z17rmd": undefined,
+ "--awsui-style-background-active-esndbs": undefined,
+ "--awsui-style-background-default-esndbs": undefined,
+ "--awsui-style-background-disabled-esndbs": undefined,
+ "--awsui-style-background-hover-esndbs": undefined,
+ "--awsui-style-color-active-esndbs": undefined,
+ "--awsui-style-color-default-esndbs": undefined,
+ "--awsui-style-color-disabled-esndbs": undefined,
+ "--awsui-style-color-hover-esndbs": undefined,
+ "--awsui-style-focus-ring-border-color-esndbs": undefined,
+ "--awsui-style-focus-ring-border-radius-esndbs": undefined,
+ "--awsui-style-focus-ring-border-width-esndbs": undefined,
"borderRadius": undefined,
"fontSize": undefined,
"paddingBlock": undefined,
@@ -60,17 +60,17 @@ exports[`getSegmentedControlSegmentStyles handles all possible style configurati
exports[`getSegmentedControlSegmentStyles handles all possible style configurations 3`] = `
{
- "--awsui-style-background-active-z17rmd": "#0073bb",
- "--awsui-style-background-default-z17rmd": "#ffffff",
- "--awsui-style-background-disabled-z17rmd": "#f0f0f0",
- "--awsui-style-background-hover-z17rmd": "#fafafa",
- "--awsui-style-color-active-z17rmd": "#ffffff",
- "--awsui-style-color-default-z17rmd": "#000000",
- "--awsui-style-color-disabled-z17rmd": "#999999",
- "--awsui-style-color-hover-z17rmd": "#000000",
- "--awsui-style-focus-ring-border-color-z17rmd": "#0073bb",
- "--awsui-style-focus-ring-border-radius-z17rmd": "8px",
- "--awsui-style-focus-ring-border-width-z17rmd": "2px",
+ "--awsui-style-background-active-esndbs": "#0073bb",
+ "--awsui-style-background-default-esndbs": "#ffffff",
+ "--awsui-style-background-disabled-esndbs": "#f0f0f0",
+ "--awsui-style-background-hover-esndbs": "#fafafa",
+ "--awsui-style-color-active-esndbs": "#ffffff",
+ "--awsui-style-color-default-esndbs": "#000000",
+ "--awsui-style-color-disabled-esndbs": "#999999",
+ "--awsui-style-color-hover-esndbs": "#000000",
+ "--awsui-style-focus-ring-border-color-esndbs": "#0073bb",
+ "--awsui-style-focus-ring-border-radius-esndbs": "8px",
+ "--awsui-style-focus-ring-border-width-esndbs": "2px",
"borderRadius": "6px",
"fontSize": "14px",
"paddingBlock": "8px",
diff --git a/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx b/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx
index 20b0570f3d..1388f9b481 100644
--- a/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx
+++ b/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx
@@ -218,15 +218,13 @@ describe('SideNavigation collapsed mode', () => {
});
describe('header', () => {
- it('hides header text in collapsed mode', () => {
+ it('does not render header when collapsed without logo', () => {
const wrapper = renderSideNavigation({
collapsed: true,
header: { href: '#/', text: 'Service name' },
items: [iconLink('Page', '#/page')],
});
- const headerLink = wrapper.findHeaderLink()!.getElement();
- expect(headerLink).toHaveAttribute('aria-label', 'Service name');
- expect(headerLink.textContent).not.toContain('Service name');
+ expect(wrapper.findHeaderLink()).toBeNull();
});
});
@@ -266,7 +264,7 @@ describe('SideNavigation collapsed mode', () => {
});
describe('header logo', () => {
- it('stretches img logo when collapsed', () => {
+ it('hides header with img logo when collapsed', () => {
const { container } = render(
{
items={[iconLink('Page', '#/page')]}
/>
);
- expect(container.querySelector('img')!.className).toContain('header-logo--stretched');
+ expect(container.querySelector('img')).toBeNull();
});
- it('stretches SVG logo when collapsed', () => {
+ it('hides header with SVG logo when collapsed', () => {
const { container } = render(
{
items={[iconLink('Page', '#/page')]}
/>
);
- expect(container.querySelector('[data-testid="svg-logo"]')!.parentElement!.className).toContain(
- 'header-logo--stretched'
- );
+ expect(container.querySelector('[data-testid="svg-logo"]')).toBeNull();
});
it('stretches SVG logo when no text is provided', () => {
@@ -349,5 +345,39 @@ describe('SideNavigation collapsed mode', () => {
fireEvent.mouseLeave(link);
expect(createWrapper().findTooltip()).toBeNull();
});
+
+ it('shows only one tooltip when one item is focused and another is hovered', () => {
+ const wrapper = renderSideNavigation({
+ collapsed: true,
+ items: [iconLink('Dashboard', '#/dashboard'), iconLink('Settings', '#/settings')],
+ });
+ const first = wrapper.findLinkByHref('#/dashboard')!.getElement();
+ const second = wrapper.findLinkByHref('#/settings')!.getElement();
+
+ // Keyboard-focus the first item, then move the pointer onto the second.
+ fireEvent.focus(first);
+ fireEvent.mouseEnter(second);
+
+ // Only the hovered item's tooltip should be visible.
+ expect(createWrapper().findAllTooltips().length).toBe(1);
+ expect(createWrapper().findTooltip()!.getElement()).toHaveTextContent('Settings');
+ });
+
+ it('keeps the newer tooltip when the previously focused item blurs', () => {
+ const wrapper = renderSideNavigation({
+ collapsed: true,
+ items: [iconLink('Dashboard', '#/dashboard'), iconLink('Settings', '#/settings')],
+ });
+ const first = wrapper.findLinkByHref('#/dashboard')!.getElement();
+ const second = wrapper.findLinkByHref('#/settings')!.getElement();
+
+ fireEvent.focus(first);
+ fireEvent.mouseEnter(second);
+ // The first item losing focus must not clear the second item's tooltip.
+ fireEvent.blur(first);
+
+ expect(createWrapper().findAllTooltips().length).toBe(1);
+ expect(createWrapper().findTooltip()!.getElement()).toHaveTextContent('Settings');
+ });
});
});
diff --git a/src/side-navigation/implementation.tsx b/src/side-navigation/implementation.tsx
index 66b3b6b682..10063b7e37 100644
--- a/src/side-navigation/implementation.tsx
+++ b/src/side-navigation/implementation.tsx
@@ -1,6 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import React, { useCallback, useEffect, useMemo } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import clsx from 'clsx';
import { useAppLayoutToolbarDesignEnabled } from '../app-layout/utils/feature-flags';
@@ -31,6 +31,10 @@ export function SideNavigationImplementation({
const baseProps = getBaseProps(props);
const isToolbar = useAppLayoutToolbarDesignEnabled();
const parentMap = useMemo(() => generateExpandableItemsMapping(items), [items]);
+ // In collapsed mode each item shows its label in a tooltip on focus/hover. Only one
+ // tooltip should ever be visible, so its owner (the item's `position`) is tracked once
+ // here and passed down; focusing or hovering an item claims it and hides any other.
+ const [activeTooltip, setActiveTooltip] = useState(null);
if (isDevelopment) {
// This code should be wiped in production anyway.
@@ -63,7 +67,12 @@ export function SideNavigationImplementation({
return (
{header && (
@@ -85,6 +94,8 @@ export function SideNavigationImplementation({
fireChange={onChangeHandler}
activeHref={activeHref}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
)}
diff --git a/src/side-navigation/parts.tsx b/src/side-navigation/parts.tsx
index 2d617dc1f5..42dcac5be1 100644
--- a/src/side-navigation/parts.tsx
+++ b/src/side-navigation/parts.tsx
@@ -36,6 +36,12 @@ interface BaseItemComponentProps {
) => void;
position?: string;
collapsed?: boolean;
+ // The `position` of the item whose collapsed-state tooltip is currently shown,
+ // or null. Owned once at the root and threaded through the tree so that only a
+ // single tooltip is ever visible — focusing or hovering an item claims the
+ // tooltip and hides any other. See NavigationItemsList in implementation.tsx.
+ activeTooltip?: string | null;
+ setActiveTooltip?: (position: string | null) => void;
}
interface HeaderProps extends BaseItemComponentProps {
@@ -65,41 +71,40 @@ export function Header({ definition, activeHref, fireFollow, collapsed }: Header
return (
<>
-
+ )}
+ {!collapsed && }
>
);
}
@@ -123,6 +128,8 @@ export function NavigationItemsList({
fireFollow,
position = '',
collapsed,
+ activeTooltip,
+ setActiveTooltip,
}: NavigationItemsListProps) {
const lists: Array- = [];
let currentListIndex = 0;
@@ -179,6 +186,8 @@ export function NavigationItemsList({
fireChange={fireChange}
position={childPosition}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
);
@@ -247,6 +256,8 @@ export function NavigationItemsList({
fireFollow={fireFollow}
position={itemPosition}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
),
@@ -269,6 +280,8 @@ export function NavigationItemsList({
fireFollow={fireFollow}
position={itemPosition}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
),
@@ -290,6 +303,8 @@ export function NavigationItemsList({
fireFollow={fireFollow}
position={itemPosition}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
),
@@ -311,6 +326,8 @@ export function NavigationItemsList({
fireFollow={fireFollow}
position={itemPosition}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
),
@@ -333,6 +350,8 @@ export function NavigationItemsList({
variant={variant}
position={itemPosition}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
),
@@ -446,29 +465,58 @@ const ItemIcon = React.forwardRef
(function ItemI
// Used in the collapsed state, where the visible labels are hidden, to give
// pointer and keyboard users a way to identify each item without relying on
// their browser's native title popup.
-function useCollapsedTooltip(label: React.ReactNode) {
- const [show, setShow] = useState(false);
+//
+// Which item's tooltip is visible is a single value owned at the root and shared
+// across all items (activeTooltip), so only one tooltip is ever shown. Focusing or
+// hovering an item claims the tooltip; leaving it releases the tooltip only if this
+// item still owns it, so that moving the pointer onto another item (which already
+// claimed the tooltip) doesn't get cancelled by the previous item's leave/blur.
+function useCollapsedTooltip({
+ label,
+ position,
+ activeTooltip,
+ setActiveTooltip,
+}: {
+ label: React.ReactNode;
+ position?: string;
+ activeTooltip?: string | null;
+ setActiveTooltip?: (position: string | null) => void;
+}) {
const triggerRef = useRef(null);
+ const id = position ?? '';
+ const show = activeTooltip === id;
+
+ const claim = () => setActiveTooltip?.(id);
+ const release = () => {
+ if (activeTooltip === id) {
+ setActiveTooltip?.(null);
+ }
+ };
const triggerProps = {
- onFocus: () => setShow(true),
- onBlur: () => setShow(false),
- onMouseEnter: () => setShow(true),
- onMouseLeave: () => setShow(false),
+ onFocus: claim,
+ onBlur: release,
+ onMouseEnter: claim,
+ onMouseLeave: release,
};
const tooltip = show ? (
- triggerRef.current} content={label} position="right" onEscape={() => setShow(false)} />
+ triggerRef.current} content={label} position="right" onEscape={release} />
) : null;
return { triggerRef, triggerProps, tooltip };
}
-function Link({ definition, activeHref, fireFollow, position, collapsed }: LinkProps) {
+function Link({ definition, activeHref, fireFollow, position, collapsed, activeTooltip, setActiveTooltip }: LinkProps) {
checkSafeUrl('SideNavigation', definition.href);
const isActive = definition.href === activeHref;
const i18n = useInternalI18n('link');
- const collapsedTooltip = useCollapsedTooltip(definition.text);
+ const collapsedTooltip = useCollapsedTooltip({
+ label: definition.text,
+ position,
+ activeTooltip,
+ setActiveTooltip,
+ });
const onClick = useCallback(
(event: React.MouseEvent) => {
@@ -618,7 +666,16 @@ interface LinkGroupProps extends BaseItemComponentProps {
definition: SideNavigationProps.LinkGroup;
}
-function LinkGroup({ definition, activeHref, fireFollow, fireChange, position, collapsed }: LinkGroupProps) {
+function LinkGroup({
+ definition,
+ activeHref,
+ fireFollow,
+ fireChange,
+ position,
+ collapsed,
+ activeTooltip,
+ setActiveTooltip,
+}: LinkGroupProps) {
checkSafeUrl('SideNavigation', definition.href);
return (
@@ -636,6 +693,8 @@ function LinkGroup({ definition, activeHref, fireFollow, fireChange, position, c
activeHref={activeHref}
position={position}
collapsed={collapsed}
+ activeTooltip={activeTooltip}
+ setActiveTooltip={setActiveTooltip}
/>
{!collapsed && (
);
}
diff --git a/src/side-navigation/styles.scss b/src/side-navigation/styles.scss
index ce45fd1ac4..42bd23c2b9 100644
--- a/src/side-navigation/styles.scss
+++ b/src/side-navigation/styles.scss
@@ -45,13 +45,6 @@ $expandable-icon-negative-margin: awsui.$space-l;
padding-inline-start: awsui.$space-panel-nav-left;
// Additional xl space to prevent text from overlapping the close button.
padding-inline-end: calc(#{awsui.$space-scaled-xxl} + #{awsui.$space-xl});
-
- &--collapsed {
- padding-inline: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- }
}
.header-link {
@@ -95,6 +88,10 @@ $expandable-icon-negative-margin: awsui.$space-l;
.items-control,
.list-container {
margin-block-start: calc(#{awsui.$space-panel-content-top} - #{$item-padding-block});
+ .root--collapsed > & {
+ margin-block-start: awsui.$space-xs;
+ }
+
// Toolbar removes margin and item padding for whatever comes first after the header
.with-toolbar > .divider-header + & {
margin-block-start: calc(-1 * #{$item-padding-block});
diff --git a/src/slider/__tests__/__snapshots__/styles.test.tsx.snap b/src/slider/__tests__/__snapshots__/styles.test.tsx.snap
index d449f3943d..454cea9ecd 100644
--- a/src/slider/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/slider/__tests__/__snapshots__/styles.test.tsx.snap
@@ -2,36 +2,36 @@
exports[`getSliderStyles handles all possible style configurations 1`] = `
{
- "--awsui-style-slider-handle-background-active-z17rmd": undefined,
- "--awsui-style-slider-handle-background-default-z17rmd": undefined,
- "--awsui-style-slider-handle-background-hover-z17rmd": undefined,
- "--awsui-style-slider-handle-border-radius-z17rmd": undefined,
- "--awsui-style-slider-range-background-active-z17rmd": undefined,
- "--awsui-style-slider-range-background-default-z17rmd": undefined,
- "--awsui-style-slider-track-background-color-z17rmd": undefined,
+ "--awsui-style-slider-handle-background-active-esndbs": undefined,
+ "--awsui-style-slider-handle-background-default-esndbs": undefined,
+ "--awsui-style-slider-handle-background-hover-esndbs": undefined,
+ "--awsui-style-slider-handle-border-radius-esndbs": undefined,
+ "--awsui-style-slider-range-background-active-esndbs": undefined,
+ "--awsui-style-slider-range-background-default-esndbs": undefined,
+ "--awsui-style-slider-track-background-color-esndbs": undefined,
}
`;
exports[`getSliderStyles handles all possible style configurations 2`] = `
{
- "--awsui-style-slider-handle-background-active-z17rmd": undefined,
- "--awsui-style-slider-handle-background-default-z17rmd": undefined,
- "--awsui-style-slider-handle-background-hover-z17rmd": undefined,
- "--awsui-style-slider-handle-border-radius-z17rmd": undefined,
- "--awsui-style-slider-range-background-active-z17rmd": undefined,
- "--awsui-style-slider-range-background-default-z17rmd": undefined,
- "--awsui-style-slider-track-background-color-z17rmd": undefined,
+ "--awsui-style-slider-handle-background-active-esndbs": undefined,
+ "--awsui-style-slider-handle-background-default-esndbs": undefined,
+ "--awsui-style-slider-handle-background-hover-esndbs": undefined,
+ "--awsui-style-slider-handle-border-radius-esndbs": undefined,
+ "--awsui-style-slider-range-background-active-esndbs": undefined,
+ "--awsui-style-slider-range-background-default-esndbs": undefined,
+ "--awsui-style-slider-track-background-color-esndbs": undefined,
}
`;
exports[`getSliderStyles handles all possible style configurations 3`] = `
{
- "--awsui-style-slider-handle-background-active-z17rmd": "#1d4ed8",
- "--awsui-style-slider-handle-background-default-z17rmd": "#3b82f6",
- "--awsui-style-slider-handle-background-hover-z17rmd": "#2563eb",
- "--awsui-style-slider-handle-border-radius-z17rmd": "50%",
- "--awsui-style-slider-range-background-active-z17rmd": "#2563eb",
- "--awsui-style-slider-range-background-default-z17rmd": "#3b82f6",
- "--awsui-style-slider-track-background-color-z17rmd": "#dbeafe",
+ "--awsui-style-slider-handle-background-active-esndbs": "#1d4ed8",
+ "--awsui-style-slider-handle-background-default-esndbs": "#3b82f6",
+ "--awsui-style-slider-handle-background-hover-esndbs": "#2563eb",
+ "--awsui-style-slider-handle-border-radius-esndbs": "50%",
+ "--awsui-style-slider-range-background-active-esndbs": "#2563eb",
+ "--awsui-style-slider-range-background-default-esndbs": "#3b82f6",
+ "--awsui-style-slider-track-background-color-esndbs": "#dbeafe",
}
`;
diff --git a/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap b/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap
index c57425d5e4..362c2bf9cb 100644
--- a/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap
@@ -2,21 +2,21 @@
exports[`getTabStyles transforms tab styles to CSS properties 1`] = `
{
- "--awsui-style-background-active-z17rmd": "#bfdbfe",
- "--awsui-style-background-default-z17rmd": "#dbeafe",
- "--awsui-style-background-disabled-z17rmd": "#f3f4f6",
- "--awsui-style-background-hover-z17rmd": "#eff6ff",
- "--awsui-style-border-color-active-z17rmd": "#1d4ed8",
- "--awsui-style-border-color-default-z17rmd": "#3b82f6",
- "--awsui-style-border-color-disabled-z17rmd": "#93c5fd",
- "--awsui-style-border-color-hover-z17rmd": "#2563eb",
- "--awsui-style-color-active-z17rmd": "#1e3a8a",
- "--awsui-style-color-default-z17rmd": "#1e40af",
- "--awsui-style-color-disabled-z17rmd": "#93c5fd",
- "--awsui-style-color-hover-z17rmd": "#1e40af",
- "--awsui-style-focus-ring-border-color-z17rmd": "#3b82f6",
- "--awsui-style-focus-ring-border-radius-z17rmd": "4px",
- "--awsui-style-focus-ring-border-width-z17rmd": "2px",
+ "--awsui-style-background-active-esndbs": "#bfdbfe",
+ "--awsui-style-background-default-esndbs": "#dbeafe",
+ "--awsui-style-background-disabled-esndbs": "#f3f4f6",
+ "--awsui-style-background-hover-esndbs": "#eff6ff",
+ "--awsui-style-border-color-active-esndbs": "#1d4ed8",
+ "--awsui-style-border-color-default-esndbs": "#3b82f6",
+ "--awsui-style-border-color-disabled-esndbs": "#93c5fd",
+ "--awsui-style-border-color-hover-esndbs": "#2563eb",
+ "--awsui-style-color-active-esndbs": "#1e3a8a",
+ "--awsui-style-color-default-esndbs": "#1e40af",
+ "--awsui-style-color-disabled-esndbs": "#93c5fd",
+ "--awsui-style-color-hover-esndbs": "#1e40af",
+ "--awsui-style-focus-ring-border-color-esndbs": "#3b82f6",
+ "--awsui-style-focus-ring-border-radius-esndbs": "4px",
+ "--awsui-style-focus-ring-border-width-esndbs": "2px",
"borderRadius": "4px",
"borderWidth": "2px",
"fontSize": "16px",
@@ -28,10 +28,10 @@ exports[`getTabStyles transforms tab styles to CSS properties 1`] = `
exports[`getTabStyles transforms tab styles to CSS properties 2`] = `
{
- "--awsui-style-tabs-active-indicator-border-radius-z17rmd": "2px",
- "--awsui-style-tabs-active-indicator-color-z17rmd": "#1d4ed8",
- "--awsui-style-tabs-active-indicator-width-z17rmd": "3px",
- "--awsui-style-tabs-separator-color-z17rmd": "#cbd5e1",
- "--awsui-style-tabs-separator-width-z17rmd": "2px",
+ "--awsui-style-tabs-active-indicator-border-radius-esndbs": "2px",
+ "--awsui-style-tabs-active-indicator-color-esndbs": "#1d4ed8",
+ "--awsui-style-tabs-active-indicator-width-esndbs": "3px",
+ "--awsui-style-tabs-separator-color-esndbs": "#cbd5e1",
+ "--awsui-style-tabs-separator-width-esndbs": "2px",
}
`;
diff --git a/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap b/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap
index abaa6411e3..3cefa2bb99 100644
--- a/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap
@@ -2,30 +2,30 @@
exports[`getTextFilterStyles handles all possible style configurations 1`] = `
{
- "--awsui-style-background-default-z17rmd": undefined,
- "--awsui-style-background-disabled-z17rmd": undefined,
- "--awsui-style-background-focus-z17rmd": undefined,
- "--awsui-style-background-hover-z17rmd": undefined,
- "--awsui-style-background-readonly-z17rmd": undefined,
- "--awsui-style-border-color-default-z17rmd": undefined,
- "--awsui-style-border-color-disabled-z17rmd": undefined,
- "--awsui-style-border-color-focus-z17rmd": undefined,
- "--awsui-style-border-color-hover-z17rmd": undefined,
- "--awsui-style-border-color-readonly-z17rmd": undefined,
- "--awsui-style-box-shadow-default-z17rmd": undefined,
- "--awsui-style-box-shadow-disabled-z17rmd": undefined,
- "--awsui-style-box-shadow-focus-z17rmd": undefined,
- "--awsui-style-box-shadow-hover-z17rmd": undefined,
- "--awsui-style-box-shadow-readonly-z17rmd": undefined,
- "--awsui-style-color-default-z17rmd": undefined,
- "--awsui-style-color-disabled-z17rmd": undefined,
- "--awsui-style-color-focus-z17rmd": undefined,
- "--awsui-style-color-hover-z17rmd": undefined,
- "--awsui-style-color-readonly-z17rmd": undefined,
- "--awsui-style-placeholder-color-z17rmd": undefined,
- "--awsui-style-placeholder-font-size-z17rmd": undefined,
- "--awsui-style-placeholder-font-style-z17rmd": undefined,
- "--awsui-style-placeholder-font-weight-z17rmd": undefined,
+ "--awsui-style-background-default-esndbs": undefined,
+ "--awsui-style-background-disabled-esndbs": undefined,
+ "--awsui-style-background-focus-esndbs": undefined,
+ "--awsui-style-background-hover-esndbs": undefined,
+ "--awsui-style-background-readonly-esndbs": undefined,
+ "--awsui-style-border-color-default-esndbs": undefined,
+ "--awsui-style-border-color-disabled-esndbs": undefined,
+ "--awsui-style-border-color-focus-esndbs": undefined,
+ "--awsui-style-border-color-hover-esndbs": undefined,
+ "--awsui-style-border-color-readonly-esndbs": undefined,
+ "--awsui-style-box-shadow-default-esndbs": undefined,
+ "--awsui-style-box-shadow-disabled-esndbs": undefined,
+ "--awsui-style-box-shadow-focus-esndbs": undefined,
+ "--awsui-style-box-shadow-hover-esndbs": undefined,
+ "--awsui-style-box-shadow-readonly-esndbs": undefined,
+ "--awsui-style-color-default-esndbs": undefined,
+ "--awsui-style-color-disabled-esndbs": undefined,
+ "--awsui-style-color-focus-esndbs": undefined,
+ "--awsui-style-color-hover-esndbs": undefined,
+ "--awsui-style-color-readonly-esndbs": undefined,
+ "--awsui-style-placeholder-color-esndbs": undefined,
+ "--awsui-style-placeholder-font-size-esndbs": undefined,
+ "--awsui-style-placeholder-font-style-esndbs": undefined,
+ "--awsui-style-placeholder-font-weight-esndbs": undefined,
"borderRadius": undefined,
"borderWidth": undefined,
"fontSize": undefined,
@@ -37,30 +37,30 @@ exports[`getTextFilterStyles handles all possible style configurations 1`] = `
exports[`getTextFilterStyles handles all possible style configurations 2`] = `
{
- "--awsui-style-background-default-z17rmd": undefined,
- "--awsui-style-background-disabled-z17rmd": undefined,
- "--awsui-style-background-focus-z17rmd": undefined,
- "--awsui-style-background-hover-z17rmd": undefined,
- "--awsui-style-background-readonly-z17rmd": undefined,
- "--awsui-style-border-color-default-z17rmd": undefined,
- "--awsui-style-border-color-disabled-z17rmd": undefined,
- "--awsui-style-border-color-focus-z17rmd": undefined,
- "--awsui-style-border-color-hover-z17rmd": undefined,
- "--awsui-style-border-color-readonly-z17rmd": undefined,
- "--awsui-style-box-shadow-default-z17rmd": undefined,
- "--awsui-style-box-shadow-disabled-z17rmd": undefined,
- "--awsui-style-box-shadow-focus-z17rmd": undefined,
- "--awsui-style-box-shadow-hover-z17rmd": undefined,
- "--awsui-style-box-shadow-readonly-z17rmd": undefined,
- "--awsui-style-color-default-z17rmd": undefined,
- "--awsui-style-color-disabled-z17rmd": undefined,
- "--awsui-style-color-focus-z17rmd": undefined,
- "--awsui-style-color-hover-z17rmd": undefined,
- "--awsui-style-color-readonly-z17rmd": undefined,
- "--awsui-style-placeholder-color-z17rmd": undefined,
- "--awsui-style-placeholder-font-size-z17rmd": undefined,
- "--awsui-style-placeholder-font-style-z17rmd": undefined,
- "--awsui-style-placeholder-font-weight-z17rmd": undefined,
+ "--awsui-style-background-default-esndbs": undefined,
+ "--awsui-style-background-disabled-esndbs": undefined,
+ "--awsui-style-background-focus-esndbs": undefined,
+ "--awsui-style-background-hover-esndbs": undefined,
+ "--awsui-style-background-readonly-esndbs": undefined,
+ "--awsui-style-border-color-default-esndbs": undefined,
+ "--awsui-style-border-color-disabled-esndbs": undefined,
+ "--awsui-style-border-color-focus-esndbs": undefined,
+ "--awsui-style-border-color-hover-esndbs": undefined,
+ "--awsui-style-border-color-readonly-esndbs": undefined,
+ "--awsui-style-box-shadow-default-esndbs": undefined,
+ "--awsui-style-box-shadow-disabled-esndbs": undefined,
+ "--awsui-style-box-shadow-focus-esndbs": undefined,
+ "--awsui-style-box-shadow-hover-esndbs": undefined,
+ "--awsui-style-box-shadow-readonly-esndbs": undefined,
+ "--awsui-style-color-default-esndbs": undefined,
+ "--awsui-style-color-disabled-esndbs": undefined,
+ "--awsui-style-color-focus-esndbs": undefined,
+ "--awsui-style-color-hover-esndbs": undefined,
+ "--awsui-style-color-readonly-esndbs": undefined,
+ "--awsui-style-placeholder-color-esndbs": undefined,
+ "--awsui-style-placeholder-font-size-esndbs": undefined,
+ "--awsui-style-placeholder-font-style-esndbs": undefined,
+ "--awsui-style-placeholder-font-weight-esndbs": undefined,
"borderRadius": undefined,
"borderWidth": undefined,
"fontSize": undefined,
@@ -72,30 +72,30 @@ exports[`getTextFilterStyles handles all possible style configurations 2`] = `
exports[`getTextFilterStyles handles all possible style configurations 3`] = `
{
- "--awsui-style-background-default-z17rmd": "#ffffff",
- "--awsui-style-background-disabled-z17rmd": "#f0f0f0",
- "--awsui-style-background-focus-z17rmd": "#ffffff",
- "--awsui-style-background-hover-z17rmd": "#fafafa",
- "--awsui-style-background-readonly-z17rmd": "#ffffff",
- "--awsui-style-border-color-default-z17rmd": "#cccccc",
- "--awsui-style-border-color-disabled-z17rmd": "#e0e0e0",
- "--awsui-style-border-color-focus-z17rmd": "#0073bb",
- "--awsui-style-border-color-hover-z17rmd": "#999999",
- "--awsui-style-border-color-readonly-z17rmd": "#e0e0e0",
- "--awsui-style-box-shadow-default-z17rmd": "none",
- "--awsui-style-box-shadow-disabled-z17rmd": "none",
- "--awsui-style-box-shadow-focus-z17rmd": "0 0 0 2px #0073bb",
- "--awsui-style-box-shadow-hover-z17rmd": "0 1px 2px rgba(0,0,0,0.1)",
- "--awsui-style-box-shadow-readonly-z17rmd": "none",
- "--awsui-style-color-default-z17rmd": "#000000",
- "--awsui-style-color-disabled-z17rmd": "#999999",
- "--awsui-style-color-focus-z17rmd": "#000000",
- "--awsui-style-color-hover-z17rmd": "#000000",
- "--awsui-style-color-readonly-z17rmd": "#000000",
- "--awsui-style-placeholder-color-z17rmd": "#999999",
- "--awsui-style-placeholder-font-size-z17rmd": "14px",
- "--awsui-style-placeholder-font-style-z17rmd": "italic",
- "--awsui-style-placeholder-font-weight-z17rmd": "400",
+ "--awsui-style-background-default-esndbs": "#ffffff",
+ "--awsui-style-background-disabled-esndbs": "#f0f0f0",
+ "--awsui-style-background-focus-esndbs": "#ffffff",
+ "--awsui-style-background-hover-esndbs": "#fafafa",
+ "--awsui-style-background-readonly-esndbs": "#ffffff",
+ "--awsui-style-border-color-default-esndbs": "#cccccc",
+ "--awsui-style-border-color-disabled-esndbs": "#e0e0e0",
+ "--awsui-style-border-color-focus-esndbs": "#0073bb",
+ "--awsui-style-border-color-hover-esndbs": "#999999",
+ "--awsui-style-border-color-readonly-esndbs": "#e0e0e0",
+ "--awsui-style-box-shadow-default-esndbs": "none",
+ "--awsui-style-box-shadow-disabled-esndbs": "none",
+ "--awsui-style-box-shadow-focus-esndbs": "0 0 0 2px #0073bb",
+ "--awsui-style-box-shadow-hover-esndbs": "0 1px 2px rgba(0,0,0,0.1)",
+ "--awsui-style-box-shadow-readonly-esndbs": "none",
+ "--awsui-style-color-default-esndbs": "#000000",
+ "--awsui-style-color-disabled-esndbs": "#999999",
+ "--awsui-style-color-focus-esndbs": "#000000",
+ "--awsui-style-color-hover-esndbs": "#000000",
+ "--awsui-style-color-readonly-esndbs": "#000000",
+ "--awsui-style-placeholder-color-esndbs": "#999999",
+ "--awsui-style-placeholder-font-size-esndbs": "14px",
+ "--awsui-style-placeholder-font-style-esndbs": "italic",
+ "--awsui-style-placeholder-font-weight-esndbs": "400",
"borderRadius": "4px",
"borderWidth": "1px",
"fontSize": "14px",
diff --git a/src/token/__tests__/__snapshots__/styles.test.tsx.snap b/src/token/__tests__/__snapshots__/styles.test.tsx.snap
index 00a21508b1..5f4f9d5c98 100644
--- a/src/token/__tests__/__snapshots__/styles.test.tsx.snap
+++ b/src/token/__tests__/__snapshots__/styles.test.tsx.snap
@@ -2,19 +2,19 @@
exports[`getTokenRootStyles handles all possible style configurations 1`] = `
{
- "--awsui-style-focus-ring-border-color-z17rmd": "#6366f1",
- "--awsui-style-focus-ring-border-radius-z17rmd": "12px",
- "--awsui-style-focus-ring-border-width-z17rmd": "2px",
- "--awsui-token-style-background-default-z17rmd": "#eef2ff",
- "--awsui-token-style-background-disabled-z17rmd": "#f1f5f9",
- "--awsui-token-style-background-read-only-z17rmd": "#f8fafc",
- "--awsui-token-style-border-color-default-z17rmd": "#c7d2fe",
- "--awsui-token-style-border-color-disabled-z17rmd": "#e2e8f0",
- "--awsui-token-style-border-color-read-only-z17rmd": "#cbd5e1",
- "--awsui-token-style-dismiss-color-default-z17rmd": "#6366f1",
- "--awsui-token-style-dismiss-color-disabled-z17rmd": "#cbd5e1",
- "--awsui-token-style-dismiss-color-hover-z17rmd": "#4338ca",
- "--awsui-token-style-dismiss-color-read-only-z17rmd": "#94a3b8",
+ "--awsui-style-focus-ring-border-color-esndbs": "#6366f1",
+ "--awsui-style-focus-ring-border-radius-esndbs": "12px",
+ "--awsui-style-focus-ring-border-width-esndbs": "2px",
+ "--awsui-token-style-background-default-esndbs": "#eef2ff",
+ "--awsui-token-style-background-disabled-esndbs": "#f1f5f9",
+ "--awsui-token-style-background-read-only-esndbs": "#f8fafc",
+ "--awsui-token-style-border-color-default-esndbs": "#c7d2fe",
+ "--awsui-token-style-border-color-disabled-esndbs": "#e2e8f0",
+ "--awsui-token-style-border-color-read-only-esndbs": "#cbd5e1",
+ "--awsui-token-style-dismiss-color-default-esndbs": "#6366f1",
+ "--awsui-token-style-dismiss-color-disabled-esndbs": "#cbd5e1",
+ "--awsui-token-style-dismiss-color-hover-esndbs": "#4338ca",
+ "--awsui-token-style-dismiss-color-read-only-esndbs": "#94a3b8",
"borderRadius": "24px",
"borderWidth": "2px",
"paddingBlock": "4px",
diff --git a/style-dictionary/utils/token-names.ts b/style-dictionary/utils/token-names.ts
index 421df0f115..2d883f3957 100644
--- a/style-dictionary/utils/token-names.ts
+++ b/style-dictionary/utils/token-names.ts
@@ -721,6 +721,7 @@ export type ColorsTokenName =
| 'colorBorderItemPlaceholder'
| 'colorBorderItemSelected'
| 'colorBorderLayout'
+ | 'colorBorderLayoutSideNavigation'
| 'colorBorderNotificationStackBar'
| 'colorBorderPanelHeader'
| 'colorBorderPopover'
@@ -1060,6 +1061,7 @@ export type BordersTokenName =
| 'borderWidthItemCard'
| 'borderWidthItemCardHighlighted'
| 'borderWidthItemSelected'
+ | 'borderWidthLayoutSideNavigation'
| 'borderWidthButton'
| 'borderWidthDropdown'
| 'borderWidthField'
diff --git a/style-dictionary/visual-refresh/borders.ts b/style-dictionary/visual-refresh/borders.ts
index 93c7822fbd..61ab37fd99 100644
--- a/style-dictionary/visual-refresh/borders.ts
+++ b/style-dictionary/visual-refresh/borders.ts
@@ -76,6 +76,7 @@ export const tokens: StyleDictionary.BordersDictionary = {
borderWidthItemCard: '{borderWidthCard}',
borderWidthItemCardHighlighted: '{borderWidthCardSelected}',
borderWidthItemSelected: '2px',
+ borderWidthLayoutSideNavigation: '{borderDividerSectionWidth}',
borderWidthBadge: '0px',
borderWidthPopover: '2px',
borderWidthToken: '2px',
diff --git a/style-dictionary/visual-refresh/colors.ts b/style-dictionary/visual-refresh/colors.ts
index c564718031..70657139e8 100644
--- a/style-dictionary/visual-refresh/colors.ts
+++ b/style-dictionary/visual-refresh/colors.ts
@@ -195,6 +195,7 @@ const tokens: StyleDictionary.ColorsDictionary = {
colorBorderItemPlaceholder: '{colorBorderItemSelected}',
colorBorderItemSelected: '{colorItemSelected}',
colorBorderLayout: { light: '{colorNeutral350}', dark: '{colorNeutral650}' },
+ colorBorderLayoutSideNavigation: '{colorBorderLayout}',
colorBorderNotificationStackBar: '{colorNeutral750}',
colorBorderPanelHeader: '{colorBorderDividerDefault}',
colorBorderPopover: '{colorBorderDropdownContainer}',
diff --git a/style-dictionary/visual-refresh/metadata/borders.ts b/style-dictionary/visual-refresh/metadata/borders.ts
index 5520eb4515..69fcda858f 100644
--- a/style-dictionary/visual-refresh/metadata/borders.ts
+++ b/style-dictionary/visual-refresh/metadata/borders.ts
@@ -10,6 +10,11 @@ const metadata: StyleDictionary.MetadataIndex = {
'The default system divider width - used for dividers between sections of content such as key/value pairs and tabs, for both full width and inset dividers.',
},
borderInvalidWidth: { description: 'Used for invalid input left border width.' },
+ borderWidthLayoutSideNavigation: {
+ description: 'The border width of the side navigation divider in the AppLayout and AppLayoutToolbar components.',
+ public: true,
+ themeable: true,
+ },
borderPanelHeaderWidth: { description: 'The split panel header bottom divider width.' },
borderPanelTopWidth: { description: 'The split panel top border width.' },
borderWidthItemSelected: {
diff --git a/style-dictionary/visual-refresh/metadata/colors.ts b/style-dictionary/visual-refresh/metadata/colors.ts
index ad8097e63c..111306eb2d 100644
--- a/style-dictionary/visual-refresh/metadata/colors.ts
+++ b/style-dictionary/visual-refresh/metadata/colors.ts
@@ -842,6 +842,11 @@ const metadata: StyleDictionary.MetadataIndex = {
public: false,
themeable: true,
},
+ colorBorderLayoutSideNavigation: {
+ description: 'The border color of the side navigation divider in the app layout.',
+ public: true,
+ themeable: true,
+ },
colorBorderTutorial: { description: 'The border color of tutorials in the tutorials list in the tutorial panel.' },
colorForegroundControlDefault: {
description: