Skip to content

Commit

Permalink
feat: Add support for headerVariant in toolbar (#2821)
Browse files Browse the repository at this point in the history
Co-authored-by: Boris Serdiuk <serdiuk@amazon.com>
  • Loading branch information
fralongo and just-boris authored Oct 8, 2024
1 parent 72c5f77 commit ba1929a
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 70 deletions.
77 changes: 77 additions & 0 deletions pages/app-layout/landing-page.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext } from 'react';

import AppLayout from '~components/app-layout';
import Box from '~components/box';
import Container from '~components/container';
import Grid from '~components/grid';
import Header from '~components/header';

import AppContext, { AppContextType } from '../app/app-context';
import ScreenshotArea from '../utils/screenshot-area';
import { Containers, Notifications } from './utils/content-blocks';
import labels from './utils/labels';

import styles from './landing-page.scss';

type PageContext = React.Context<AppContextType<{ stickyNotifications: boolean }>>;

export default function () {
const { urlParams } = useContext(AppContext as PageContext);
return (
<ScreenshotArea gutters={false}>
<AppLayout
headerVariant="high-contrast"
disableContentPaddings={true}
stickyNotifications={urlParams.stickyNotifications}
notifications={<Notifications />}
ariaLabels={labels}
content={
<Box margin={{ bottom: 'l' }}>
<div className={styles.header}>
<Box padding={{ vertical: 'xxxl', horizontal: 's' }}>
<Grid
gridDefinition={[
{ colspan: { xl: 6, l: 5, s: 6, xxs: 10 }, offset: { l: 2, xxs: 1 } },
{ colspan: { xl: 2, l: 3, s: 4, xxs: 10 }, offset: { s: 0, xxs: 1 } },
]}
>
<div className={styles['header-title']}>
<Box variant="h1" fontWeight="heavy" fontSize="display-l" color="inherit">
Service name
</Box>
<Box fontWeight="light" padding={{ bottom: 's' }} fontSize="display-l" color="inherit">
Name sub-title
</Box>
<Box variant="p" fontWeight="light">
<span className={styles['header-sub-title']}>Some information about this service</span>
</Box>
</div>
</Grid>
</Box>
</div>
<Box padding={{ top: 'xxxl', horizontal: 's' }}>
<Grid
gridDefinition={[
{ colspan: { xl: 6, l: 5, s: 6, xxs: 10 }, offset: { l: 2, xxs: 1 } },
{ colspan: { xl: 2, l: 3, s: 4, xxs: 10 }, offset: { s: 0, xxs: 1 } },
]}
>
<div>
<Box variant="h1" tagOverride="h2" padding={{ bottom: 's', top: 'n' }}>
Features
</Box>
<Containers />
</div>
<Container header={<Header>Getting started</Header>}>
Some information to learn about this service
</Container>
</Grid>
</Box>
</Box>
}
/>
</ScreenshotArea>
);
}
18 changes: 18 additions & 0 deletions pages/app-layout/landing-page.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

@use '~design-tokens' as awsui;

.header {
background-color: awsui.$color-background-home-header;
}

.header-title {
color: awsui.$color-text-home-header-default;
}

.header-sub-title {
color: awsui.$color-text-home-header-secondary;
}
124 changes: 69 additions & 55 deletions src/app-layout/__tests__/header-variant.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
import React from 'react';

import { findUpUntil } from '@cloudscape-design/component-toolkit/dom';

import AppLayout from '../../../lib/components/app-layout';
import { useMobile } from '../../../lib/components/internal/hooks/use-mobile';
import { useVisualRefresh } from '../../../lib/components/internal/hooks/use-visual-mode';
import { highContrastHeaderClassName } from '../../../lib/components/internal/utils/content-header-utils';
import { renderComponent } from './utils';
import { describeEachAppLayout, renderComponent } from './utils';

import visualRefreshStyles from '../../../lib/components/app-layout/visual-refresh/styles.css.js';
import toolbarSkeletonStyles from '../../../lib/components/app-layout/visual-refresh-toolbar/skeleton/styles.css.js';

jest.mock('../../../lib/components/internal/hooks/use-visual-mode', () => ({
useVisualRefresh: jest.fn().mockReturnValue(false),
Expand All @@ -18,63 +19,76 @@ jest.mock('../../../lib/components/internal/hooks/use-mobile', () => ({
useMobile: jest.fn().mockReturnValue(false),
}));

jest.mock('@cloudscape-design/component-toolkit/internal', () => ({
...jest.requireActual('@cloudscape-design/component-toolkit/internal'),
isMotionDisabled: jest.fn().mockReturnValue(true),
useDensityMode: jest.fn().mockReturnValue('comfortable'),
useReducedMotion: jest.fn().mockReturnValue(true),
}));
const hasHighContrastContext = (element: HTMLElement) =>
findUpUntil(element, el => el.classList.contains(highContrastHeaderClassName));

beforeEach(() => {
(useVisualRefresh as jest.Mock).mockReturnValue(true);
});
afterEach(() => {
(useVisualRefresh as jest.Mock).mockReset();
});
describe('headerVariant - desktop', () => {
test('default', () => {
const { wrapper } = renderComponent(<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" />);
expect(wrapper.findNotifications()!.getElement()).not.toHaveClass(highContrastHeaderClassName);
expect(wrapper.findBreadcrumbs()!.getElement()).not.toHaveClass(highContrastHeaderClassName);
expect(wrapper.findByClassName(visualRefreshStyles.background)!.getElement()).not.toHaveClass(
highContrastHeaderClassName
);
});
describeEachAppLayout({ themes: ['refresh', 'refresh-toolbar'], sizes: ['desktop'] }, ({ theme }) => {
describe('headerVariant', () => {
test('default', () => {
const { wrapper } = renderComponent(<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" />);
expect(hasHighContrastContext(wrapper.findNotifications()!.getElement())).toBeFalsy();
expect(hasHighContrastContext(wrapper.findBreadcrumbs()!.getElement())).toBeFalsy();
if (theme === 'refresh') {
expect(
hasHighContrastContext(wrapper.findByClassName(visualRefreshStyles.background)!.getElement())
).toBeFalsy();
} else {
expect(
hasHighContrastContext(wrapper.findByClassName(toolbarSkeletonStyles['toolbar-container'])!.getElement())
).toBeFalsy();
}
});

test('high-contrast', () => {
const { wrapper } = renderComponent(
<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" headerVariant="high-contrast" />
);
expect(wrapper.findNotifications()!.getElement()).toHaveClass(highContrastHeaderClassName);
expect(wrapper.findBreadcrumbs()!.getElement()).toHaveClass(highContrastHeaderClassName);
expect(wrapper.findByClassName(visualRefreshStyles.background)!.getElement()).toHaveClass(
highContrastHeaderClassName
);
test('high-contrast', () => {
const { wrapper } = renderComponent(
<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" headerVariant="high-contrast" />
);
expect(hasHighContrastContext(wrapper.findNotifications()!.getElement())).toBeTruthy();
expect(hasHighContrastContext(wrapper.findBreadcrumbs()!.getElement())).toBeTruthy();
if (theme === 'refresh') {
// For refresh toolbar, high-contrast header is not implemented in contentHeader slot, or in conjunction with ContentLayout
expect(
hasHighContrastContext(wrapper.findByClassName(visualRefreshStyles.background)!.getElement())
).toBeTruthy();
} else {
expect(
hasHighContrastContext(wrapper.findByClassName(toolbarSkeletonStyles['toolbar-container'])!.getElement())
).toBeTruthy();
}
});
});
});

describe('headerVariant - mobile', () => {
beforeEach(() => {
(useMobile as jest.Mock).mockReturnValue(true);
});
afterEach(() => {
(useMobile as jest.Mock).mockReset();
});
test('default', () => {
const { wrapper } = renderComponent(
<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" content="aaa" />
);
expect(wrapper.findByClassName(visualRefreshStyles['mobile-toolbar'])!.getElement()).not.toHaveClass(
highContrastHeaderClassName
);
});
describeEachAppLayout({ themes: ['refresh', 'refresh-toolbar'], sizes: ['mobile'] }, ({ theme }) => {
describe('headerVariant', () => {
test('default', () => {
const { wrapper } = renderComponent(
<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" content="aaa" />
);
if (theme === 'refresh') {
expect(wrapper.findByClassName(visualRefreshStyles['mobile-toolbar'])!.getElement()).not.toHaveClass(
highContrastHeaderClassName
);
} else {
expect(
hasHighContrastContext(wrapper.findByClassName(toolbarSkeletonStyles['toolbar-container'])!.getElement())
).toBeFalsy();
}
});

test('high-contrast', () => {
const { wrapper } = renderComponent(
<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" headerVariant="high-contrast" />
);
expect(wrapper.findByClassName(visualRefreshStyles['mobile-toolbar'])!.getElement()).toHaveClass(
highContrastHeaderClassName
);
test('high-contrast', () => {
const { wrapper } = renderComponent(
<AppLayout notifications="Notifications" breadcrumbs="Breadcrumbs" headerVariant="high-contrast" />
);
if (theme === 'refresh') {
expect(wrapper.findByClassName(visualRefreshStyles['mobile-toolbar'])!.getElement()).toHaveClass(
highContrastHeaderClassName
);
} else {
expect(
hasHighContrastContext(wrapper.findByClassName(toolbarSkeletonStyles['toolbar-container'])!.getElement())
).toBeTruthy();
}
});
});
});
1 change: 1 addition & 0 deletions src/app-layout/visual-refresh-toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
<AppLayoutNotifications appLayoutInternals={appLayoutInternals}>{notifications}</AppLayoutNotifications>
)
}
headerVariant={headerVariant}
contentHeader={contentHeader}
// delay rendering the content until registration of this instance is complete
content={registered ? content : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import clsx from 'clsx';

import { useResizeObserver } from '@cloudscape-design/component-toolkit/internal';

import { highContrastHeaderClassName } from '../../../internal/utils/content-header-utils';
import { createWidgetizedComponent } from '../../../internal/widgets';
import { AppLayoutInternals } from '../interfaces';
import { NotificationsSlot } from '../skeleton/slot-wrappers';
Expand Down Expand Up @@ -34,7 +35,11 @@ export function AppLayoutNotificationsImplementation({
return (
<NotificationsSlot
ref={ref}
className={clsx(stickyNotifications && styles['sticky-notifications'])}
className={clsx(
appLayoutInternals.headerVariant === 'high-contrast' && highContrastHeaderClassName,
stickyNotifications && styles['sticky-notifications'],
appLayoutInternals.headerVariant !== 'high-contrast' && styles['sticky-notifications-with-background']
)}
style={{
insetBlockStart: stickyNotifications ? verticalOffsets.notifications : undefined,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
.sticky-notifications {
position: sticky;
z-index: 850;
background-color: awsui.$color-background-layout-main;
&-with-background {
background-color: awsui.$color-background-layout-main;
}
}
14 changes: 13 additions & 1 deletion src/app-layout/visual-refresh-toolbar/skeleton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import clsx from 'clsx';

import customCssProps from '../../../internal/generated/custom-css-properties';
import { highContrastHeaderClassName } from '../../../internal/utils/content-header-utils';
import { AppLayoutPropsWithDefaults } from '../../interfaces';

import sharedStyles from '../../resize/styles.css.js';
Expand All @@ -16,6 +17,7 @@ interface SkeletonLayoutProps
extends Pick<
AppLayoutPropsWithDefaults,
| 'notifications'
| 'headerVariant'
| 'contentHeader'
| 'content'
| 'contentType'
Expand All @@ -41,6 +43,7 @@ interface SkeletonLayoutProps
export function SkeletonLayout({
style,
notifications,
headerVariant,
contentHeader,
content,
navigation,
Expand Down Expand Up @@ -89,6 +92,14 @@ export function SkeletonLayout({
</div>
)}
<main className={clsx(styles['main-landmark'], anyPanelOpen && styles['unfocusable-mobile'])}>
{notifications && (
<div
className={clsx(
styles['notifications-background'],
headerVariant === 'high-contrast' && highContrastHeaderClassName
)}
></div>
)}
{notifications}
<div className={clsx(styles.main, { [styles['main-disable-paddings']]: disableContentPaddings })} style={style}>
{contentHeader && <div className={styles['content-header']}>{contentHeader}</div>}
Expand All @@ -110,7 +121,8 @@ export function SkeletonLayout({
styles.tools,
!toolsOpen && styles['panel-hidden'],
sharedStyles['with-motion'],
navigationOpen && !toolsOpen && styles['unfocusable-mobile']
navigationOpen && !toolsOpen && styles['unfocusable-mobile'],
toolsOpen && styles['tools-open']
)}
>
{tools}
Expand Down
27 changes: 18 additions & 9 deletions src/app-layout/visual-refresh-toolbar/skeleton/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
}
}

@mixin grid-column-full-content-width {
grid-column: 1 / -1;
@include desktop-only {
grid-column: 2 / span 4;
}
}

.root {
@include styles.styles-reset;
color: awsui.$color-text-body-default;
Expand Down Expand Up @@ -105,7 +112,9 @@
/* stylelint-disable plugin/no-unsupported-browser-features */
&:not(:has(> [data-testid])) {
inline-size: var(#{custom-props.$toolsWidth});
border-inline-start: awsui.$border-divider-section-width solid awsui.$color-border-layout;
&.tools-open {
border-inline-start: awsui.$border-divider-section-width solid awsui.$color-border-layout;
}
}
}
}
Expand All @@ -130,10 +139,7 @@
z-index: 840;
align-self: end;
grid-area: main;
grid-column: 1 / -1;
@include desktop-only {
grid-column: 2 / span 3;
}
@include grid-column-full-content-width;
}

.panel-hidden {
Expand Down Expand Up @@ -161,6 +167,12 @@
}
}

.notifications-background {
background: awsui.$color-background-layout-main;
grid-area: notifications;
@include grid-column-full-content-width;
}

.main-landmark {
// does not participate in the layout, rendered for accessibility grouping
display: contents;
Expand All @@ -172,10 +184,7 @@
margin-block-end: awsui.$space-layout-content-bottom;
&-disable-paddings {
margin-block: 0;
grid-column: 1 / -1;
@include desktop-only {
grid-column: 2 / span 3;
}
@include grid-column-full-content-width;
}
}

Expand Down
Loading

0 comments on commit ba1929a

Please sign in to comment.