Skip to content

Commit 57667b6

Browse files
author
Kubit
committed
Improve modalv2 with arias and accesibility props
1 parent b150eb9 commit 57667b6

File tree

15 files changed

+197
-252
lines changed

15 files changed

+197
-252
lines changed

src/components/modal/hooks/__tests__/modal.useChangeDimension.test.tsx

Lines changed: 0 additions & 97 deletions
This file was deleted.

src/components/modal/hooks/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/components/modal/hooks/useChangeDimension.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/components/modal/modalControlled.tsx

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import * as React from 'react';
22

33
import { STYLES_NAME } from '@/constants';
4-
import { useMediaDevice, useScrollEffect, useStyles, useSwipeDown, useZoomEffect } from '@/hooks';
4+
import {
5+
useMediaDevice,
6+
useScrollDetectionWithAutoFocus,
7+
useScrollEffect,
8+
useStyles,
9+
useSwipeDown,
10+
useZoomEffect,
11+
} from '@/hooks';
512
import { ErrorBoundary } from '@/provider/errorBoundary/errorBoundary';
613
import { FallbackComponent } from '@/provider/errorBoundary/fallbackComponent';
714
import { DeviceBreakpointsType } from '@/types';
@@ -26,23 +33,64 @@ const ModalControlledComponent = React.forwardRef(
2633
): JSX.Element => {
2734
const styles = useStyles<ModalBaseStylesType, V>(STYLES_NAME.MODAL, variant, ctv);
2835
const device = useMediaDevice();
36+
const innerRef = React.useRef<HTMLDivElement | null>(null);
2937

30-
const conditional = React.useMemo(
38+
const handleInnerRef = React.useCallback(node => {
39+
innerRef.current = node;
40+
const modalHeader = innerRef.current?.querySelector('[data-modal-header]') as
41+
| HTMLElement
42+
| null
43+
| undefined;
44+
const modalContent = innerRef.current?.querySelector('[data-modal-content]') as
45+
| HTMLElement
46+
| null
47+
| undefined;
48+
const modalDraggableIcon = innerRef.current?.querySelector('[data-modal-draggable-icon]') as
49+
| HTMLElement
50+
| null
51+
| undefined;
52+
const modalIllustration = innerRef.current?.querySelector(
53+
'[data-modal-ilustration-container]'
54+
)?.firstElementChild as HTMLElement | null | undefined;
55+
56+
handleModalZoomEffect(innerRef.current);
57+
handleHeaderShadowEffect(modalHeader);
58+
handleContentScrollEffect(modalContent);
59+
handleIllustrationResizeEffect(modalIllustration);
60+
handleContentZoomEffect(modalContent);
61+
handleContentScrollDetection(modalContent);
62+
handleDraggableIconSwipeDown(modalDraggableIcon);
63+
}, []);
64+
65+
React.useImperativeHandle(ref, () => {
66+
return innerRef?.current as HTMLDivElement;
67+
}, []);
68+
69+
const isTabletOrMobile = React.useMemo(
3170
() =>
3271
device !== DeviceBreakpointsType.DESKTOP && device !== DeviceBreakpointsType.LARGE_DESKTOP,
3372
[device]
3473
);
35-
const { scrollableRef, resizeRef, shadowRef } = useScrollEffect({
36-
conditional,
74+
75+
const {
76+
scrollableRef: handleContentScrollEffect,
77+
resizeRef: handleIllustrationResizeEffect,
78+
shadowRef: handleHeaderShadowEffect,
79+
} = useScrollEffect({
80+
conditional: isTabletOrMobile,
3781
shadowStyles: styles.headerContainer?.box_shadow,
3882
});
3983

40-
const zoomRef = useZoomEffect(CONTAINER_STYLES_EDIT, MAX_ZOOM);
41-
const zoomRefChild = useZoomEffect(CONTENT_STYLES_EDIT, MAX_ZOOM);
84+
const handleModalZoomEffect = useZoomEffect(CONTAINER_STYLES_EDIT, MAX_ZOOM);
85+
const handleContentZoomEffect = useZoomEffect(CONTENT_STYLES_EDIT, MAX_ZOOM);
4286

43-
const { setPopoverRef, setDragIconRef } = useSwipeDown(props.popover?.animationOptions, () =>
44-
props.onClose?.()
45-
);
87+
const { setPopoverRef: handlePopoverSwipeDown, setDragIconRef: handleDraggableIconSwipeDown } =
88+
useSwipeDown(props.popover?.animationOptions, () => props.onClose?.());
89+
90+
const { hasScroll: contentHasScroll, handleScrollDetection: handleContentScrollDetection } =
91+
useScrollDetectionWithAutoFocus({
92+
parentElementRef: innerRef,
93+
});
4694

4795
const handlePopoverCloseInternally = () => {
4896
props.popover?.onCloseInternally?.();
@@ -52,16 +100,11 @@ const ModalControlledComponent = React.forwardRef(
52100
const modalStructure = (
53101
<ModalStandAlone
54102
{...props}
55-
ref={ref}
103+
ref={handleInnerRef}
104+
contentHasScroll={contentHasScroll}
56105
device={device}
57-
dragIconRef={setDragIconRef}
58-
popover={{ ...props.popover, forwardedRef: setPopoverRef }}
59-
resizeRef={resizeRef}
60-
scrollableRef={scrollableRef}
61-
shadowRef={shadowRef}
106+
popover={{ ...props.popover, forwardedRef: handlePopoverSwipeDown }}
62107
styles={styles}
63-
zoomRef={zoomRef}
64-
zoomRefChild={zoomRefChild}
65108
onPopoverCloseInternally={handlePopoverCloseInternally}
66109
/>
67110
);

src/components/modal/modalStandAlone.tsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { PopoverComponentType, PopoverPositionVariantType } from '@/components/p
99
import { Text } from '@/components/text/text';
1010
import { TextComponentType } from '@/components/text/types/component';
1111
import { useId } from '@/hooks';
12-
import { DeviceBreakpointsType } from '@/types';
12+
import { DeviceBreakpointsType, ROLES } from '@/types';
1313

1414
import {
1515
DraggableIcon,
@@ -26,11 +26,6 @@ import { IModalStandAlone } from './types';
2626
const ModalStandAloneComponent = (
2727
{
2828
dataTestId = 'modalDataTestId',
29-
scrollableRef,
30-
resizeRef,
31-
shadowRef,
32-
zoomRef,
33-
zoomRefChild,
3429
customHeightAllDevices = false,
3530
customWidthAllDevices = false,
3631
...props
@@ -41,19 +36,16 @@ const ModalStandAloneComponent = (
4136
const modalId = props.id ?? uniqueModalId;
4237
const uniqueTitleId = useId('modal-title');
4338
const titleIdFinal = props.title?.id ?? uniqueTitleId;
44-
const modalRef = React.useRef<HTMLDivElement | null>(null);
45-
46-
React.useImperativeHandle(ref, () => {
47-
zoomRef(modalRef.current);
48-
return modalRef.current as HTMLDivElement;
49-
});
5039

5140
const buildIconOrIllustration = () => {
5241
if (props.imageIllustrationHeader?.illustration) {
5342
return (
54-
<ModalImageStyled $styles={props.styles} data-testid={`${dataTestId}ImageHeader`}>
43+
<ModalImageStyled
44+
data-modal-ilustration-container
45+
$styles={props.styles}
46+
data-testid={`${dataTestId}ImageHeader`}
47+
>
5548
<ElementOrIllustration
56-
ref={resizeRef}
5749
customIllustrationStyles={props.styles.imageIllustrationHeader}
5850
data-testid={`${dataTestId}ImageHeader`}
5951
{...props.imageIllustrationHeader}
@@ -88,19 +80,20 @@ const ModalStandAloneComponent = (
8880
aria-labelledby={titleIdFinal}
8981
aria-modal={props.open}
9082
clickOverlayClose={!props.blocked}
91-
component={PopoverComponentType.DIALOG}
83+
component={PopoverComponentType.DIV}
9284
dataTestId={`${dataTestId}Popover`}
9385
hasBackDrop={true}
9486
id={modalId}
9587
open={props.open}
9688
positionVariant={PopoverPositionVariantType.FIXED}
89+
role={ROLES.DIALOG}
9790
trapFocusInsideModal={true}
9891
variant={props.styles.popoverVariant}
9992
onCloseInternally={props.onPopoverCloseInternally}
10093
{...props.popover}
10194
>
10295
<ModalStyled
103-
ref={modalRef}
96+
ref={ref}
10497
$maxHeight={props.maxHeight}
10598
$maxWidth={props.maxWidth}
10699
$minHeight={customHeightAllDevices ? props.minHeight : onlyDesktopSize(props.minHeight)}
@@ -110,9 +103,9 @@ const ModalStandAloneComponent = (
110103
hasFooter={!!props.footer?.content}
111104
onKeyDown={event => props.onKeyDown?.(event)}
112105
>
113-
<ModalHeaderStyled ref={shadowRef} $styles={props.styles}>
106+
<ModalHeaderStyled data-modal-header $styles={props.styles}>
114107
{!props.blocked && props.dragIcon && (
115-
<DraggableIcon ref={props.dragIconRef} $styles={props.styles}>
108+
<DraggableIcon data-modal-draggable-icon $styles={props.styles}>
116109
<ElementOrIcon customIconStyles={props.styles?.dragIcon} {...props.dragIcon} />
117110
</DraggableIcon>
118111
)}
@@ -148,13 +141,16 @@ const ModalStandAloneComponent = (
148141
)}
149142
</ModalHeaderStyled>
150143
<ModalContentStyled
151-
ref={ref => {
152-
scrollableRef(ref);
153-
zoomRefChild(ref);
154-
}}
144+
data-modal-content
155145
$minContentHeight={props.minContentHeight}
156146
$styles={props.styles}
147+
aria-label={props.contentHasScroll ? props.contentScrollArias?.['aria-label'] : undefined}
148+
aria-labelledby={
149+
props.contentHasScroll ? props.contentScrollArias?.['aria-labelledby'] : undefined
150+
}
157151
data-testid={`${dataTestId}Content`}
152+
role={props.contentHasScroll ? ROLES.REGION : undefined}
153+
tabIndex={props.contentHasScroll ? 0 : undefined}
158154
>
159155
{props.content}
160156
</ModalContentStyled>

src/components/modal/stories/argtypes.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,19 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string):
219219
category: CATEGORY_CONTROL.CONTENT,
220220
},
221221
},
222+
contentScrollArias: {
223+
description: 'Aria label for content when scroll',
224+
type: { name: 'object' },
225+
control: { type: 'object' },
226+
table: {
227+
type: {
228+
summary: 'ModalContentScrollAriasType',
229+
detail:
230+
'ModalContentScrollAriasType: { ["aria-label"]?: string; ["aria-labelledby"]?: string; }',
231+
},
232+
category: CATEGORY_CONTROL.ACCESIBILITY,
233+
},
234+
},
222235
footer: {
223236
description: 'Footer of the modal',
224237
type: { name: 'object' },

0 commit comments

Comments
 (0)