Skip to content

Commit

Permalink
code review
Browse files Browse the repository at this point in the history
  • Loading branch information
sirineJ committed Jan 23, 2025
1 parent cc0049e commit 6b3c420
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const openCalendar = async ({
}) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button', {
name: 'Change date',
name: 'Change date, August 28, 2017',
});

await userEvent.click(button);
Expand Down Expand Up @@ -114,6 +114,7 @@ export const Optional = (args: DateInputProps) => <DateInput {...args} />;
Optional.args = {
...baseArgs,
optionalLabel: 'optional',
defaultValue: '2017-08-28',
};
Optional.play = openCalendar;

Expand Down
8 changes: 4 additions & 4 deletions packages/circuit-ui/components/DateInput/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,12 @@ export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
}
const [isClosing, setIsClosing] = useState(false);

const handleModalCloseEnd = useCallback(() => {
const handleDialogCloseEnd = useCallback(() => {
setIsClosing(false);
closeCalendar?.();
}, [closeCalendar]);

const handleModalCloseStart = useCallback(() => {
const handleDialogCloseStart = useCallback(() => {
setIsClosing(true);
}, []);

Expand Down Expand Up @@ -482,8 +482,8 @@ export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
open={open}
isModal={isMobile}
hideCloseButton={!isMobile}
onCloseStart={handleModalCloseStart}
onCloseEnd={handleModalCloseEnd}
onCloseStart={handleDialogCloseStart}
onCloseEnd={handleDialogCloseEnd}
className={clsx(
classes.dialog,
isClosing ? outAnimation : inAnimation,
Expand Down
36 changes: 18 additions & 18 deletions packages/circuit-ui/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,24 @@ export const Dialog = forwardRef<HTMLDialogElement, DialogProps>(
);

// Focus Management
useEffect(() => {
// save the opening element to restore focus after the dialog closes
if (open) {
if (document.activeElement instanceof HTMLElement) {
lastFocusedElementRef.current = document.activeElement;
}
}
return () => {
// restore focus to the opening element
if (lastFocusedElementRef.current) {
setTimeout(
() => lastFocusedElementRef.current?.focus(),
animationDurationRef.current,
);
}
};
}, [open, animationDurationRef]);

useEffect(() => {
const dialogElement = dialogRef.current;
let timeoutId: NodeJS.Timeout;
Expand All @@ -341,24 +359,6 @@ export const Dialog = forwardRef<HTMLDialogElement, DialogProps>(
};
}, [open, initialFocusRef, hideCloseButton, animationDurationRef.current]);

useEffect(() => {
// save the opening element to restore focus after the dialog closes
if (open) {
if (document.activeElement instanceof HTMLElement) {
lastFocusedElementRef.current = document.activeElement;
}
}
return () => {
// restore focus to the opening element
if (lastFocusedElementRef.current) {
setTimeout(
() => lastFocusedElementRef.current?.focus(),
animationDurationRef.current,
);
}
};
}, [open, animationDurationRef]);

useEffect(() => {
dialogRef.current?.style.setProperty(
'--dialog-animation-duration',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('MobileNavigation', () => {

const baseProps = {
open: true,
onCloseEnd: vi.fn(),
onClose: vi.fn(),
closeButtonLabel: 'Close navigation modal',
primaryNavigationLabel: 'Primary',
};
Expand Down Expand Up @@ -128,7 +128,7 @@ describe('MobileNavigation', () => {

await userEvent.click(primaryLinkEl);

expect(baseProps.onCloseEnd).toHaveBeenCalledTimes(1);
expect(baseProps.onClose).toHaveBeenCalledTimes(1);
expect(onClick).toHaveBeenCalledTimes(1);
});

Expand Down Expand Up @@ -177,7 +177,7 @@ describe('MobileNavigation', () => {

await userEvent.click(secondaryLinkEl);

expect(baseProps.onCloseEnd).toHaveBeenCalledTimes(1);
expect(baseProps.onClose).toHaveBeenCalledTimes(1);
expect(onClick).toHaveBeenCalledTimes(1);
});

Expand Down
17 changes: 17 additions & 0 deletions packages/circuit-ui/components/SidePanel/SidePanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
box-shadow: none;
}

.wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
padding: 0;
padding: env(safe-area-inset-top) env(safe-area-inset-right)
env(safe-area-inset-bottom) env(safe-area-inset-left);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}

@media (max-width: 767px) {
.base {
top: 0;
Expand Down Expand Up @@ -35,6 +48,10 @@
margin-left: 1px;
}

.wrapper {
padding-left: 0;
}

.content {
padding: 0 var(--cui-spacings-giga);
}
Expand Down
20 changes: 20 additions & 0 deletions packages/circuit-ui/components/SidePanel/SidePanel.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ describe('SidePanel', () => {
await waitFor(() => expect(onClose).toHaveBeenCalled());
});

describe('when the panel is not stacked', () => {
it('should not show the back button', () => {
renderComponent();

expect(
screen.queryByTitle(baseProps.backButtonLabel as string),
).toBeNull();
});
});

describe('when the panel is stacked', () => {
it('should show the back button', () => {
const onBack = vi.fn();
Expand Down Expand Up @@ -145,6 +155,16 @@ describe('SidePanel', () => {
});
});

describe('when the panel is on desktop resolution', () => {
it('should describe the side panel as modal', () => {
const { rerender } = render(<SidePanel {...baseProps} open={false} />);
const dialog = screen.getByRole('dialog', { hidden: true });
vi.spyOn(dialog, 'show');
rerender(<SidePanel {...baseProps} open />);
expect(dialog.show).toHaveBeenCalledOnce();
});
});

describe('when the panel is on mobile resolution', () => {
it('should describe the side panel as modal', () => {
const { rerender } = render(
Expand Down
81 changes: 39 additions & 42 deletions packages/circuit-ui/components/SidePanel/SidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import { clsx } from '../../styles/clsx.js';
import { useAnimation } from '../../hooks/useAnimation/index.js';
import { sharedClasses } from '../../styles/shared.js';
import { useI18n } from '../../hooks/useI18n/useI18n.js';
import { useLatest } from '../../hooks/useLatest/index.js';

import { Header } from './components/Header/index.js';
import type { ChildrenRenderProps, OnBack, OnClose } from './useSidePanel.js';
Expand Down Expand Up @@ -94,29 +93,33 @@ export const SidePanel = forwardRef<HTMLDialogElement, SidePanelProps>(

const [, setAnimating] = useAnimation();

const triggerAnimation = useCallback(
(isOpening: boolean, callBack?: OnBack | OnClose) => {
const onOpening = useCallback(() => {
setAnimating({
duration: animationDuration,
onStart: () => {
setAnimationClass(
animationDuration > 0
? sharedClasses.animationSlideRightIn
: undefined,
);
},
onEnd: () => {
setAnimationClass(undefined);
},
});
}, [animationDuration, setAnimating]);

const onClosing = useCallback(
(callBack?: OnBack | OnClose) => {
setAnimating({
duration: animationDuration,
onStart: () => {
if (isOpening) {
setAnimationClass(
animationDuration > 0
? sharedClasses.animationSlideRightIn
: undefined,
);
} else {
void callBack?.();
setAnimationClass(sharedClasses.animationSlideRightOut);
}
void callBack?.();
setAnimationClass(sharedClasses.animationSlideRightOut);
},
onEnd: () => {
if (isOpening) {
setAnimationClass(undefined);
} else {
setAnimationClass(classes.closed);
onCloseEnd?.();
}
setAnimationClass(classes.closed);
onCloseEnd?.();
},
});
},
Expand All @@ -132,12 +135,10 @@ export const SidePanel = forwardRef<HTMLDialogElement, SidePanelProps>(
setHeaderSticky(event.currentTarget.scrollTop > 0);
};

const triggerAnimationRef = useLatest(triggerAnimation);

useEffect(() => {
// trigger animation on opening
triggerAnimationRef.current(true);
}, [triggerAnimationRef]);
onOpening();
}, [onOpening]);

useEffect(
() => () => {
Expand All @@ -146,23 +147,6 @@ export const SidePanel = forwardRef<HTMLDialogElement, SidePanelProps>(
[onCloseEnd],
);

const content = (
<div onScroll={handleScroll}>
<Header
backButtonLabel={backButtonLabel}
closeButtonLabel={closeButtonLabel}
headline={headline}
id={headerAriaId}
onBack={onBack ? () => triggerAnimation(false, onBack) : undefined}
onClose={() => triggerAnimation(false, onClose)}
isSticky={isHeaderSticky}
/>
<div className={classes.content}>
{isFunction(children) ? children({ onBack, onClose }) : children}
</div>
</div>
);

return (
<Dialog
{...rest}
Expand All @@ -172,12 +156,25 @@ export const SidePanel = forwardRef<HTMLDialogElement, SidePanelProps>(
aria-labelledby={headerAriaId}
animationDuration={animationDuration}
className={clsx(classes.base, animationClass, className)}
onCloseStart={() => triggerAnimation(false, onBack || onClose)}
onCloseStart={() => onClosing(onBack || onClose)}
preventOutsideClickClose={true}
preventEscapeKeyClose={preventEscapeKeyClose}
hideCloseButton
>
{content}
<div className={classes.wrapper} onScroll={handleScroll}>
<Header
backButtonLabel={backButtonLabel}
closeButtonLabel={closeButtonLabel}
headline={headline}
id={headerAriaId}
onBack={onBack ? () => onClosing(onBack) : undefined}
onClose={() => onClosing(onClose)}
isSticky={isHeaderSticky}
/>
<div className={classes.content}>
{isFunction(children) ? children({ onBack, onClose }) : children}
</div>
</div>
</Dialog>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
vi,
type Mock,
} from 'vitest';
import { useContext, type ComponentType, useEffect } from 'react';
import { useContext, type ComponentType } from 'react';

import {
render,
Expand All @@ -48,26 +48,6 @@ import {

vi.mock('../../hooks/useMedia/index.js');

vi.mock('./SidePanel.js', () => ({
SidePanel: ({
onCloseEnd,
children,
}: Pick<SidePanelContextItem, 'onCloseEnd' | 'children'>) => {
useEffect(
() => () => {
onCloseEnd?.();
},
[onCloseEnd],
);

return (
<dialog open={true}>
{typeof children === 'function' ? children({}) : children}
</dialog>
);
},
}));

describe('SidePanelContext', () => {
beforeAll(() => {
vi.useFakeTimers();
Expand Down
4 changes: 4 additions & 0 deletions packages/circuit-ui/components/SidePanel/SidePanelContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ export function SidePanelProvider({
resolve();
}

dispatch({
type: 'update',
item: sidePanel,
});
dispatch({
type: 'remove',
id: sidePanel.id,
Expand Down

0 comments on commit 6b3c420

Please sign in to comment.