Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: hide navigation button when toolbar variant of AppLayout has hideNavigation #2872

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions src/app-layout/__tests__/multi-layout-props.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';

import {
mergeMultiAppLayoutProps,
SharedMultiAppLayoutProps,
} from '../../../lib/components/app-layout/visual-refresh-toolbar/multi-layout';

describe('mergeMultiAppLayoutProps', () => {
const mockParentNavigationToggle = jest.fn();
const mockParentActiveDrawerChange = jest.fn();
const mockParentSplitPanelToggle = jest.fn();
const ownProps: SharedMultiAppLayoutProps = {
forceDeduplicationType: 'primary',
ariaLabels: {
navigation: 'Navigation',
drawers: 'Drawers',
},
navigation: <div>Navigation</div>,
navigationOpen: true,
navigationFocusRef: React.createRef(),
onNavigationToggle: mockParentNavigationToggle,
breadcrumbs: <div>Breadcrumbs</div>,
activeDrawerId: 'drawer1',
drawers: [
{
id: 'drawer1',
ariaLabels: { drawerName: 'Drawer 1' },
content: <div>Drawer 1 Content</div>,
},
],
onActiveDrawerChange: mockParentActiveDrawerChange,
drawersFocusRef: React.createRef(),
splitPanel: <div>Split Panel</div>,
splitPanelToggleProps: {
displayed: false,
active: false,
position: 'bottom',
controlId: 'test',
ariaLabel: 'test',
},
splitPanelFocusRef: React.createRef(),
onSplitPanelToggle: mockParentSplitPanelToggle,
};

const additionalPropsBase: Partial<SharedMultiAppLayoutProps>[] = [
{
ariaLabels: {
navigation: 'New Navigation',
},
drawers: [
{
id: 'drawer2',
ariaLabels: { drawerName: 'Drawer 2' },
content: <div>Drawer 2 Content</div>,
},
],
activeDrawerId: 'drawer2',
},
{
splitPanelToggleProps: {
displayed: false,
active: false,
position: 'bottom',
controlId: 'test',
ariaLabel: 'test',
},
},
];

it('should merge ownProps and additionalProps correctly', () => {
const result = mergeMultiAppLayoutProps(ownProps, additionalPropsBase);

expect(result).toEqual({
//asserting new aria labels overwrite existing yet preserve others
ariaLabels: {
navigation: 'New Navigation',
drawers: 'Drawers',
},
hasNavigation: true,
navigationOpen: true,
navigationFocusRef: ownProps.navigationFocusRef,
onNavigationToggle: mockParentNavigationToggle,
hasBreadcrumbsPortal: true,
hasSplitPanel: true,
splitPanelToggleProps: {
displayed: false,
active: false,
position: 'bottom',
controlId: 'test',
ariaLabel: 'test',
},
splitPanelFocusRef: ownProps.splitPanelFocusRef,
onSplitPanelToggle: mockParentSplitPanelToggle,
//asserting the ownProps drawer is not overwritten
activeDrawerId: ownProps.activeDrawerId,
drawers: ownProps.drawers,
drawersFocusRef: ownProps.drawersFocusRef,
onActiveDrawerChange: mockParentActiveDrawerChange,
});
});

it('should return null if no fields are defined, except ariaLabels', () => {
const result = mergeMultiAppLayoutProps({ ariaLabels: {} } as SharedMultiAppLayoutProps, []);

expect(result).toBeNull();
});
});
23 changes: 22 additions & 1 deletion src/app-layout/__tests__/multi-layout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,26 @@ describeEachAppLayout({ themes: ['refresh-toolbar'], sizes: ['desktop'] }, () =>
expect(isDrawerClosed(firstLayout.findNavigation())).toEqual(false);
});

test('merges tools from two instances', async () => {
test('navigationHide in primary is respected when navigation is defined when merging from two instances', async () => {
const { firstLayout, secondLayout } = await renderAsync(
<AppLayout
{...defaultAppLayoutProps}
data-testid="first"
navigation="testing nav"
navigationHide={true}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second layout using navigationHide is a more common case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the test name and added some assertions below to cover this case more thoroughly.

toolsHide={true}
content={
<AppLayout {...defaultAppLayoutProps} data-testid="second" navigationHide={true} tools="testing tools" />
}
/>
);
expect(firstLayout.findNavigation()).toBeFalsy();
expect(firstLayout.findNavigationToggle()).toBeFalsy();
expect(secondLayout.findNavigation()).toBeFalsy();
expect(secondLayout.findNavigationToggle()).toBeFalsy();
Comment on lines +91 to +94
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we test here?

Is it about navigationHide is respected, even if navigation is defined? Then we should call it in the test name

});

test('merges tools from two instances with where navigationHide is true in secondary', async () => {
const { firstLayout, secondLayout } = await renderAsync(
<AppLayout
{...defaultAppLayoutProps}
Expand All @@ -89,6 +108,8 @@ describeEachAppLayout({ themes: ['refresh-toolbar'], sizes: ['desktop'] }, () =>
expect(createWrapper().findAllByClassName(testUtilStyles.tools)).toHaveLength(1);

firstLayout.findToolsToggle().click();
expect(secondLayout.findNavigation()).toBeFalsy();
expect(secondLayout.findNavigationToggle()).toBeFalsy();
expect(isDrawerClosed(secondLayout.findTools())).toEqual(false);
});

Expand Down
13 changes: 8 additions & 5 deletions src/app-layout/visual-refresh-toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,11 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
focusSplitPanel: () => splitPanelFocusControl.refs.slider.current?.focus(),
}));

const resolvedNavigation = navigationHide ? null : navigation ?? <></>;
const resolvedStickyNotifications = !!stickyNotifications && !isMobile;
//navigation must be null if hidden so toolbar knows to hide the toggle button
const resolvedNavigation = navigationHide ? null : navigation ?? <></>;
//navigation must not be open if navigationHide is true
const resolvedNavigationOpen = !!resolvedNavigation && navigationOpen;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if no navigation provided, the fragment passed will be truthy, therefor resolvedNavigation will only be falsy if navigationHide is true

const {
maxDrawerSize,
maxSplitPanelSize,
Expand All @@ -248,7 +251,7 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
activeDrawerSize: activeDrawer ? activeDrawerSize : 0,
splitPanelSize,
minContentWidth,
navigationOpen: !!resolvedNavigation && navigationOpen,
navigationOpen: resolvedNavigationOpen,
navigationWidth,
placement,
splitPanelOpen,
Expand All @@ -261,7 +264,7 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
forceDeduplicationType,
ariaLabels: ariaLabelsWithDrawers,
navigation: resolvedNavigation,
navigationOpen,
navigationOpen: resolvedNavigationOpen,
onNavigationToggle,
navigationFocusRef: navigationFocusControl.refs.toggle,
breadcrumbs,
Expand Down Expand Up @@ -299,7 +302,7 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
breadcrumbs,
discoveredBreadcrumbs,
stickyNotifications: resolvedStickyNotifications,
navigationOpen,
navigationOpen: resolvedNavigationOpen,
navigation: resolvedNavigation,
navigationFocusControl,
activeDrawer,
Expand Down Expand Up @@ -393,7 +396,7 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
// delay rendering the content until registration of this instance is complete
content={registered ? content : null}
navigation={resolvedNavigation && <AppLayoutNavigation appLayoutInternals={appLayoutInternals} />}
navigationOpen={navigationOpen}
navigationOpen={resolvedNavigationOpen}
navigationWidth={navigationWidth}
tools={drawers && drawers.length > 0 && <AppLayoutDrawer appLayoutInternals={appLayoutInternals} />}
globalTools={
Expand Down
18 changes: 12 additions & 6 deletions src/app-layout/visual-refresh-toolbar/multi-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AppLayoutProps } from '../interfaces';
import { Focusable } from '../utils/use-focus-control';
import { SplitPanelToggleProps, ToolbarProps } from './toolbar';

interface SharedProps {
export interface SharedMultiAppLayoutProps {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only name changes for the interface and function

forceDeduplicationType?: 'primary' | 'secondary';
ariaLabels: AppLayoutProps.Labels | undefined;
navigation: React.ReactNode;
Expand Down Expand Up @@ -39,7 +39,10 @@ function checkAlreadyExists(value: boolean, propName: string) {
return false;
}

function mergeProps(ownProps: SharedProps, additionalProps: ReadonlyArray<Partial<SharedProps>>): ToolbarProps | null {
export function mergeMultiAppLayoutProps(
ownProps: SharedMultiAppLayoutProps,
additionalProps: ReadonlyArray<Partial<SharedMultiAppLayoutProps>>
): ToolbarProps | null {
const toolbar: ToolbarProps = {};
for (const props of [ownProps, ...additionalProps]) {
toolbar.ariaLabels = Object.assign(toolbar.ariaLabels ?? {}, props.ariaLabels);
Expand All @@ -50,6 +53,8 @@ function mergeProps(ownProps: SharedProps, additionalProps: ReadonlyArray<Partia
toolbar.onActiveDrawerChange = props.onActiveDrawerChange;
}
if (props.navigation && !checkAlreadyExists(!!toolbar.hasNavigation, 'navigation')) {
// there is never a case where navigation will exist and a toggle will not so toolbar
// can use the hasNavigation here to conditionally render the navigationToggle button
toolbar.hasNavigation = true;
toolbar.navigationOpen = props.navigationOpen;
toolbar.navigationFocusRef = props.navigationFocusRef;
Expand All @@ -69,13 +74,13 @@ function mergeProps(ownProps: SharedProps, additionalProps: ReadonlyArray<Partia
return Object.keys(toolbar).filter(key => key !== 'ariaLabels').length > 0 ? toolbar : null;
}

export function useMultiAppLayout(props: SharedProps) {
const [registration, setRegistration] = useState<RegistrationState<SharedProps> | null>(null);
export function useMultiAppLayout(props: SharedMultiAppLayoutProps) {
const [registration, setRegistration] = useState<RegistrationState<SharedMultiAppLayoutProps> | null>(null);
const { forceDeduplicationType } = props;

useLayoutEffect(() => {
return awsuiPluginsInternal.appLayoutWidget.register(forceDeduplicationType, props =>
setRegistration(props as RegistrationState<SharedProps>)
setRegistration(props as RegistrationState<SharedMultiAppLayoutProps>)
);
}, [forceDeduplicationType]);

Expand All @@ -87,6 +92,7 @@ export function useMultiAppLayout(props: SharedProps) {

return {
registered: !!registration?.type,
toolbarProps: registration?.type === 'primary' ? mergeProps(props, registration.discoveredProps) : null,
toolbarProps:
registration?.type === 'primary' ? mergeMultiAppLayoutProps(props, registration.discoveredProps) : null,
};
}
Loading