Skip to content

Commit

Permalink
Merge pull request #1 from varghvi/main
Browse files Browse the repository at this point in the history
merging latest
  • Loading branch information
varghvi authored Nov 17, 2022
2 parents 1f4ab41 + 09ad428 commit ab8601c
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 42 deletions.
88 changes: 88 additions & 0 deletions pages/app-layout/with-wizard-and-table.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';
import { AppLayout, Box, Button, ColumnLayout, Container, Header, SpaceBetween, Table, Wizard } from '~components';
import { columnsConfig } from '../table/shared-configs';
import { generateItems, Instance } from '../table/generate-data';
import labels from './utils/labels';

import ScreenshotArea from '../utils/screenshot-area';

const items = generateItems(20);

export default function () {
const [activeStepIndex, setActiveStepIndex] = useState(0);

return (
<ScreenshotArea gutters={false}>
<AppLayout
ariaLabels={labels}
navigationHide={false}
content={
<Wizard
i18nStrings={{
stepNumberLabel: stepNumber => `Step ${stepNumber}`,
collapsedStepsLabel: (stepNumber, stepsCount) => `Step ${stepNumber} of ${stepsCount}`,
skipToButtonLabel: step => `Skip to ${step.title}`,
navigationAriaLabel: 'Steps',
cancelButton: 'Cancel',
previousButton: 'Previous',
nextButton: 'Next',
submitButton: 'Launch instance',
optional: 'optional',
}}
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
activeStepIndex={activeStepIndex}
allowSkipTo={true}
steps={[
{
title: 'Add storage',
content: (
<Box>
<Table<Instance>
header={
<Header
variant="awsui-h1-sticky"
description="Demo page with footer"
actions={<Button variant="primary">Create</Button>}
>
Sticky Scrollbar Example
</Header>
}
columnDefinitions={columnsConfig}
items={items}
/>
</Box>
),
isOptional: true,
},
{
title: 'Review and launch',
content: (
<SpaceBetween size="xs">
<Header variant="h3" actions={<Button onClick={() => setActiveStepIndex(0)}>Edit</Button>}>
Step 1: Instance type
</Header>
<Container header={<Header variant="h2">Container title</Header>}>
<ColumnLayout columns={2} variant="text-grid">
<div>
<Box variant="awsui-key-label">First field</Box>
<div>Value</div>
</div>
<div>
<Box variant="awsui-key-label">Second Field</Box>
<div>Value</div>
</div>
</ColumnLayout>
</Container>
</SpaceBetween>
),
},
]}
/>
}
contentType="wizard"
/>
</ScreenshotArea>
);
}
8 changes: 7 additions & 1 deletion pages/wizard/simple.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useState } from 'react';
import Wizard, { WizardProps } from '~components/wizard';
import Toggle from '~components/toggle';
import Button from '~components/button';
import Link from '~components/link';
import styles from './styles.scss';

import { i18nStrings } from './common';
Expand All @@ -27,9 +28,14 @@ const steps: WizardProps.Step[] = [
},
{
title: 'Step 3',
info: <Link variant="info">Info</Link>,
content: (
<div className={styles['step-content']}>
<div id="content-text">Content 3</div>
{Array.from(Array(15).keys()).map(key => (
<div key={key} className={styles['content-item']}>
Item {key}
</div>
))}
</div>
),
},
Expand Down
4 changes: 4 additions & 0 deletions pages/wizard/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@
height: 200px;
overflow: scroll;
}

.content-item {
height: 100px;
}
1 change: 1 addition & 0 deletions src/tiles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ $gutter: awsui.$space-grid-gutter;
box-sizing: border-box;
border: styles.$control-border-width solid awsui.$color-border-input-default;
border-radius: awsui.$border-radius-tiles;
background: awsui.$color-background-input-default;
padding: awsui.$space-xs awsui.$space-scaled-m;
display: flex;
flex-direction: column;
Expand Down
29 changes: 29 additions & 0 deletions src/wizard/__integ__/wizard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@ describe('Wizard keyboard navigation', () => {
await expect(page.getText('#content-text')).resolves.toBe('Content 1');
})
);

test(
'should focus on header after navigation to the next step',
setupTest(async page => {
await page.resetFocus();
await page.keys(['Tab', 'Tab', 'Space']);
await expect(page.getFocusedElementText()).resolves.toBe('Step 2');
})
);

test(
'should focus on header after navigation to the previous step',
setupTest(async page => {
await page.clickPrimaryButton();
await page.keys(['Shift', 'Tab', 'Space']);
await expect(page.getFocusedElementText()).resolves.toBe('Step 1');
})
);

test(
'header should receive focus only programmatically',
setupTest(async page => {
await page.resetFocus();
await page.keys(['Tab', 'Tab', 'Space']);
await expect(page.getFocusedElementText()).resolves.toBe('Step 2');
await page.keys(['Tab', 'Shift', 'Tab']);
await expect(page.getFocusedElementText()).resolves.not.toBe('Step 2');
})
);
});
});

Expand Down
17 changes: 0 additions & 17 deletions src/wizard/__tests__/wizard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -402,23 +402,6 @@ describe('Form', () => {
});
});

describe('Focus delegation', () => {
test('when previous button is not focused and unmounted no focus delegation occurs', () => {
const [wrapper] = renderDefaultWizard();
wrapper.findPrimaryButton()!.click();
wrapper.findPreviousButton()!.click();
expect(wrapper.findPrimaryButton()!.getElement()).not.toBe(document.activeElement);
});

test('when previous button is focused and unmounted the focus is delegated to the next button', () => {
const [wrapper] = renderDefaultWizard();
wrapper.findPrimaryButton()!.click();
wrapper.findPreviousButton()!.focus();
wrapper.findPreviousButton()!.click();
expect(wrapper.findPrimaryButton()!.getElement()).toBe(document.activeElement);
});
});

test('sets last step as active when activeStepIndex is out of bound, and raises warning', () => {
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
expect(consoleWarnSpy).not.toHaveBeenCalled();
Expand Down
21 changes: 1 addition & 20 deletions src/wizard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useEffect, useRef } from 'react';
import React, { useRef } from 'react';
import clsx from 'clsx';
import { getBaseProps } from '../internal/base-component';
import { fireNonCancelableEvent } from '../internal/events';
Expand All @@ -18,19 +18,6 @@ import { useVisualRefresh } from '../internal/hooks/use-visual-mode';

export { WizardProps };

const scrollToTop = (ref: React.RefObject<HTMLDivElement>) => {
const overflowRegex = /(auto|scroll)/;
let parent = ref?.current?.parentElement;
while (parent && !overflowRegex.test(getComputedStyle(parent).overflow)) {
parent = parent.parentElement;
}
if (parent) {
parent.scrollTop = 0;
} else {
window.scrollTo(window.pageXOffset, 0);
}
};

export default function Wizard({
steps,
activeStepIndex: controlledActiveStepIndex,
Expand Down Expand Up @@ -61,11 +48,6 @@ export default function Wizard({
const farthestStepIndex = useRef<number>(actualActiveStepIndex);
farthestStepIndex.current = Math.max(farthestStepIndex.current, actualActiveStepIndex);

const internalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
scrollToTop(internalRef);
}, [actualActiveStepIndex]);

const isVisualRefresh = useVisualRefresh();
const isLastStep = actualActiveStepIndex >= steps.length - 1;

Expand Down Expand Up @@ -101,7 +83,6 @@ export default function Wizard({
<div {...baseProps} className={clsx(styles.root, baseProps.className)} ref={ref}>
<div
className={clsx(styles.wizard, isVisualRefresh && styles.refresh, smallContainer && styles['small-container'])}
ref={internalRef}
>
<WizardNavigation
activeStepIndex={actualActiveStepIndex}
Expand Down
11 changes: 10 additions & 1 deletion src/wizard/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
.wizard.refresh {
column-gap: awsui.$space-xl;
display: grid;
grid-template-columns: auto 1fr;
grid-template-columns: auto minmax(0, 1fr);
grid-template-rows: auto 1fr;
row-gap: awsui.$space-scaled-xl;

Expand Down Expand Up @@ -257,6 +257,15 @@
}
}

.form-header-component {
&-wrapper {
outline: none;
@include focus-visible.when-visible {
@include styles.link-focus;
}
}
}

.form-header-component,
.navigation-link,
.navigation-link-item,
Expand Down
19 changes: 16 additions & 3 deletions src/wizard/wizard-form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import React, { useRef } from 'react';
import clsx from 'clsx';
import InternalForm from '../form/internal';
import InternalHeader from '../header/internal';
Expand All @@ -9,6 +9,8 @@ import WizardActions from './wizard-actions';
import { WizardProps } from './interfaces';
import WizardFormHeader from './wizard-form-header';
import styles from './styles.css.js';
import useFocusVisible from '../internal/hooks/focus-visible';
import { useEffectOnUpdate } from '../internal/hooks/use-effect-on-update';

interface WizardFormProps {
steps: ReadonlyArray<WizardProps.Step>;
Expand Down Expand Up @@ -43,6 +45,15 @@ export default function WizardForm({
const isLastStep = activeStepIndex >= steps.length - 1;
const skipToTargetIndex = findSkipToTargetIndex(steps, activeStepIndex);
const isMobile = useMobile();
const stepHeaderRef = useRef<HTMLDivElement | null>(null);

useEffectOnUpdate(() => {
if (stepHeaderRef && stepHeaderRef.current) {
stepHeaderRef.current?.focus();
}
}, [activeStepIndex]);

const focusVisible = useFocusVisible();

const showSkipTo = allowSkipTo && skipToTargetIndex !== -1;
const skipToButtonText =
Expand All @@ -63,8 +74,10 @@ export default function WizardForm({
{i18nStrings.collapsedStepsLabel(activeStepIndex + 1, steps.length)}
</div>
<InternalHeader className={styles['form-header-component']} variant="h1" description={description} info={info}>
{title}
{isOptional && <i>{` - ${i18nStrings.optional}`}</i>}
<span className={styles['form-header-component-wrapper']} tabIndex={-1} ref={stepHeaderRef} {...focusVisible}>
{title}
{isOptional && <i>{` - ${i18nStrings.optional}`}</i>}
</span>
</InternalHeader>
</WizardFormHeader>
<InternalForm
Expand Down

0 comments on commit ab8601c

Please sign in to comment.