Skip to content

Commit cd078be

Browse files
author
Hector Arce De Las Heras
committed
Enhanced Accessibility with New Props
This update introduces new properties aimed at bolstering accessibility across various components. These newly added props are designed to meet accessibility standards, ensuring that the components are more inclusive and user-friendly for individuals with disabilities. By incorporating these accessibility-focused properties, the components now offer improved semantic structure, keyboard navigability, and screen reader support, thereby enhancing the overall user experience for a wider audience.
1 parent bca9b1f commit cd078be

File tree

10 files changed

+258
-27
lines changed

10 files changed

+258
-27
lines changed

src/components/message/__tests__/message.test.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as React from 'react';
33

44
import { axe } from 'jest-axe';
55

6+
import { LinkTargetType } from '@/components/link';
67
import { renderProvider } from '@/tests/renderProvider/renderProvider.utility';
78
import { ROLES } from '@/types';
89

@@ -25,6 +26,7 @@ const MOCK = {
2526
open: true,
2627
closeIcon: { onClick: jest.fn() },
2728
link: { content: 'Link', variant: 'SECONDARY', url: '#' },
29+
dataTestId: 'dataTestId',
2830
};
2931

3032
const mockPropsWithTagAndExtraAction = {
@@ -142,4 +144,82 @@ describe('Message component', () => {
142144
expect(container).toHTMLValidate();
143145
expect(results).toHaveNoViolations();
144146
});
147+
148+
it('Click on Message container when it is a link', async () => {
149+
const messageContainerProps = {
150+
url: 'https://www.google.com',
151+
target: LinkTargetType.BLANK,
152+
onClick: jest.fn(),
153+
};
154+
155+
const { container } = renderProvider(
156+
<Message
157+
{...MOCK}
158+
actionButton={undefined}
159+
link={undefined}
160+
messageContainerProps={messageContainerProps}
161+
/>
162+
);
163+
164+
const parentContainer = screen.getAllByRole(ROLES.LINK)[0];
165+
fireEvent.click(parentContainer);
166+
expect(messageContainerProps.onClick).toHaveBeenCalled();
167+
168+
const results = await axe(container);
169+
expect(container).toHTMLValidate();
170+
expect(results).toHaveNoViolations();
171+
});
172+
it('Click on titleAndContentContainer container when it is a link', async () => {
173+
const titleAndContentContainerProps = {
174+
url: 'https://www.google.com',
175+
target: LinkTargetType.BLANK,
176+
onClick: jest.fn(),
177+
};
178+
179+
const { container } = renderProvider(
180+
<Message
181+
{...MOCK}
182+
actionButton={undefined}
183+
link={undefined}
184+
titleAndContentContainerProps={titleAndContentContainerProps}
185+
/>
186+
);
187+
188+
const parentContainer = screen.getAllByRole(ROLES.LINK)[0];
189+
fireEvent.click(parentContainer);
190+
expect(titleAndContentContainerProps.onClick).toHaveBeenCalled();
191+
192+
const results = await axe(container);
193+
expect(container).toHTMLValidate();
194+
expect(results).toHaveNoViolations();
195+
});
196+
it('Show links and inlineLink', async () => {
197+
const links = [
198+
{ content: 'Link 1 Array', variant: 'SECONDARY', url: '#' },
199+
{ content: 'Link 2 Array', variant: 'SECONDARY', url: '#' },
200+
];
201+
const inlineLink = { content: 'Inline Link', variant: 'SECONDARY', url: '#' };
202+
203+
const { container } = renderProvider(
204+
<Message
205+
{...MOCK}
206+
actionButton={undefined}
207+
inlineLink={inlineLink}
208+
link={undefined}
209+
links={links}
210+
/>
211+
);
212+
213+
const linkElement1 = screen.getByRole('link', { name: /Link 1 Array/i });
214+
const linkElement2 = screen.getByRole('link', { name: /Link 2 Array/i });
215+
const inlineLinkElement = screen.getByRole('link', { name: /Inline Link/i });
216+
217+
expect(linkElement1).toBeInTheDocument();
218+
expect(linkElement2).toBeInTheDocument();
219+
expect(inlineLinkElement).toBeInTheDocument();
220+
221+
const results = await axe(container);
222+
expect(container).toHTMLValidate();
223+
expect(results).toHaveNoViolations();
224+
});
145225
});

src/components/message/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type {
1010
MessageLinkType,
1111
MessagePropsThemeType,
1212
MessageStylesType,
13+
MessageContainerAsLinkType,
1314
} from './types';
1415

1516
export { MessageControlled } from './messageControlled';

src/components/message/message.styled.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,9 @@ export const LinkContainerStyled = styled.div<{
7171
}>`
7272
${({ styles }) => getStyles(styles.linkContainer)};
7373
`;
74+
75+
export const LinksContainerStyled = styled.div<{
76+
styles: MessagePropsThemeType;
77+
}>`
78+
${({ styles }) => getStyles(styles.linksContainer)};
79+
`;

src/components/message/messageControlled.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react';
22

33
import { STYLES_NAME } from '@/constants';
44
import { useStyles } from '@/hooks/useStyles/useStyles';
5+
import { useGenericComponents } from '@/provider';
56
import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary';
67

78
// styles
@@ -15,7 +16,9 @@ const MessageControlledComponent = React.forwardRef(
1516
): JSX.Element => {
1617
const styles = useStyles<MessagePropsThemeType, V>(STYLES_NAME.MESSAGE, props.variant, ctv);
1718

18-
return <MessageStandAlone ref={ref} styles={styles} {...props} />;
19+
const { LINK } = useGenericComponents();
20+
21+
return <MessageStandAlone ref={ref} linkComponent={LINK} styles={styles} {...props} />;
1922
}
2023
);
2124
MessageControlledComponent.displayName = 'MessageControlledComponent';

src/components/message/messageStandAlone.tsx

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable complexity */
12
import * as React from 'react';
23

34
import { Button } from '@/components/button';
@@ -15,6 +16,7 @@ import {
1516
CloseButtonSectionStyled,
1617
ExtraActioButtonWrapperStyled,
1718
LinkContainerStyled,
19+
LinksContainerStyled,
1820
MessageContentStyled,
1921
MessageHeaderStyled,
2022
MessageStyled,
@@ -92,16 +94,30 @@ const MessageStandAloneComponent = (
9294
<MessageStyled
9395
ref={ref}
9496
aria-live={ariaLive}
97+
as={props.messageContainerProps?.url ? props.linkComponent : 'div'}
9598
data-testid={`${props.dataTestId}Message`}
9699
id={props.id}
97100
role={props.role}
98101
styles={props.styles}
102+
target={props.messageContainerProps?.target}
103+
url={(props.messageContainerProps?.url || undefined) as string}
104+
onClick={props.messageContainerProps?.onClick}
99105
>
100106
{buildIconOrIllustration()}
107+
{props.closeIcon?.onClick && (
108+
<CloseButtonSectionStyled styles={props.styles}>
109+
<ElementOrIcon customIconStyles={props.styles.closeIcon} {...props.closeIcon} />
110+
</CloseButtonSectionStyled>
111+
)}
101112
<MessageHeaderStyled
113+
as={props.titleAndContentContainerProps?.url ? props.linkComponent : 'div'}
102114
isLargeMessage={isLargeMessage}
115+
role={props.titleAndContentRole}
103116
styles={props.styles}
117+
target={props.titleAndContentContainerProps?.target}
118+
url={(props.titleAndContentContainerProps?.url || undefined) as string}
104119
withIcon={!!props.closeIcon}
120+
onClick={props.titleAndContentContainerProps?.onClick}
105121
>
106122
{props.title && (
107123
<MessageTextStyled
@@ -122,16 +138,23 @@ const MessageStandAloneComponent = (
122138
{props.tag?.content && buildTag()}
123139
<MessageContentStyled isLargeMessage={isLargeMessage} styles={props.styles}>
124140
{buildContent()}
125-
</MessageContentStyled>
126-
<ButtonSectionStyled styles={props.styles}>
127-
{buildExtraActionButton()}
128-
{buildActionButton()}
129-
{props.closeIcon?.onClick && (
130-
<CloseButtonSectionStyled styles={props.styles}>
131-
<ElementOrIcon customIconStyles={props.styles.closeIcon} {...props.closeIcon} />
132-
</CloseButtonSectionStyled>
141+
{props.inlineLink?.content && (
142+
<Link
143+
dataTestId={`${props.dataTestId}Link`}
144+
decoration={TextDecorationType.UNDERLINE}
145+
{...props.inlineLink}
146+
>
147+
{props.inlineLink.content}
148+
</Link>
133149
)}
134-
</ButtonSectionStyled>
150+
</MessageContentStyled>
151+
{(props.extraActionButton || props.actionButton) && (
152+
<ButtonSectionStyled styles={props.styles}>
153+
{buildExtraActionButton()}
154+
{buildActionButton()}
155+
</ButtonSectionStyled>
156+
)}
157+
135158
{props.link?.content && (
136159
<LinkContainerStyled styles={props.styles}>
137160
<Link
@@ -143,6 +166,23 @@ const MessageStandAloneComponent = (
143166
</Link>
144167
</LinkContainerStyled>
145168
)}
169+
{props.links && props.links.length > 0 && (
170+
<LinksContainerStyled styles={props.styles}>
171+
{props.links.map((link, index) =>
172+
link.content ? (
173+
<LinkContainerStyled key={index} styles={props.styles}>
174+
<Link
175+
dataTestId={`${props.dataTestId}Link${index}`}
176+
decoration={TextDecorationType.UNDERLINE}
177+
{...link}
178+
>
179+
{link.content}
180+
</Link>
181+
</LinkContainerStyled>
182+
) : null
183+
)}
184+
</LinksContainerStyled>
185+
)}
146186
</MessageHeaderStyled>
147187
</MessageStyled>
148188
)}

src/components/message/stories/argtypes.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,28 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string):
3636
category: CATEGORY_CONTROL.MODIFIERS,
3737
},
3838
},
39+
messageContainerProps: {
40+
description: 'Object with properties for message container',
41+
control: { type: 'object' },
42+
type: { name: 'object' },
43+
table: {
44+
type: {
45+
summary: 'MessageAsLinkType',
46+
},
47+
category: CATEGORY_CONTROL.CONTENT,
48+
},
49+
},
50+
titleAndContentContainerProps: {
51+
description: 'Object with properties for title and content container',
52+
control: { type: 'object' },
53+
type: { name: 'object' },
54+
table: {
55+
type: {
56+
summary: 'MessageAsLinkType',
57+
},
58+
category: CATEGORY_CONTROL.CONTENT,
59+
},
60+
},
3961
actionButton: {
4062
description: 'Object with properties for action button',
4163
control: { type: 'object' },
@@ -102,6 +124,28 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string):
102124
category: CATEGORY_CONTROL.CONTENT,
103125
},
104126
},
127+
inlineLink: {
128+
description: 'Object with inlinelink properties',
129+
type: { name: 'object', required: false },
130+
control: { type: 'object' },
131+
table: {
132+
type: {
133+
summary: 'MessageLinkType',
134+
},
135+
category: CATEGORY_CONTROL.CONTENT,
136+
},
137+
},
138+
links: {
139+
description: 'Object with links',
140+
type: { name: 'object', required: false },
141+
control: { type: 'object' },
142+
table: {
143+
type: {
144+
summary: 'MessageLinkType[]',
145+
},
146+
category: CATEGORY_CONTROL.CONTENT,
147+
},
148+
},
105149
infoIcon: {
106150
description: 'Object with properties of the icon to show into title message',
107151
type: { name: 'object' },
@@ -154,7 +198,17 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string):
154198
table: {
155199
type: {
156200
summary: 'ROLES',
157-
detail: 'alert, status',
201+
},
202+
category: CATEGORY_CONTROL.MODIFIERS,
203+
},
204+
},
205+
titleAndContentRole: {
206+
description: 'Prop used for accesibility to asign a rol',
207+
control: { type: 'select' },
208+
type: { name: 'string' },
209+
table: {
210+
type: {
211+
summary: 'ROLES',
158212
},
159213
category: CATEGORY_CONTROL.MODIFIERS,
160214
},

src/components/message/stories/message.stories.tsx

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
22
import React from 'react';
33

44
import { ICONS } from '@/assets';
5+
import { LinkTargetType } from '@/components/link';
56
import { ReplaceContent } from '@/components/storybook/replaceContent/replaceContent';
67
import { STYLES_NAME } from '@/constants';
78
import { themesObject, variantsObject } from '@/designSystem/themesObject';
@@ -33,30 +34,58 @@ export const Message: Story = {
3334
args: {
3435
variant: Object.values(variantsObject[themeSelected].MessageVariantType || {})[0] as string,
3536
content: {
36-
content: (
37-
<ReplaceContent width="100%">
38-
<p>lorem ipsum</p>
39-
<p>
40-
lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum
41-
lorem ipsum
42-
</p>
43-
</ReplaceContent>
44-
),
37+
content: 'This is a text content large message',
38+
},
39+
inlineLink: {
40+
content: ' Inline Link',
41+
variant: 'SECONDARY',
42+
url: 'https://www.google.com',
43+
target: LinkTargetType.BLANK,
4544
},
4645
title: { content: 'Title' },
4746
infoIcon: { icon: ICONS.ICON_GHOST, altText: 'info' },
4847
closeIcon: { icon: ICONS.ICON_CLOSE, altText: 'close' },
49-
actionButton: {
50-
content: 'Action',
51-
variant: Object.values(variantsObject[themeSelected].ButtonVariantType || {})[0] as string,
52-
size: Object.values(variantsObject[themeSelected].ButtonSizeType || {})[2] as string,
48+
link: {
49+
content: 'Link',
50+
variant: 'SECONDARY',
51+
url: 'https://www.google.com',
52+
target: LinkTargetType.BLANK,
5353
},
54+
links: [
55+
{
56+
content: 'Link 1',
57+
variant: 'SECONDARY',
58+
url: 'https://www.google.com',
59+
target: LinkTargetType.BLANK,
60+
},
61+
{
62+
content: 'Link 2',
63+
variant: 'SECONDARY',
64+
url: 'https://www.google.com',
65+
target: LinkTargetType.BLANK,
66+
},
67+
],
68+
// actionButton: {
69+
// content: 'Action',
70+
// variant: Object.values(variantsObject[themeSelected].ButtonVariantType || {})[0] as string,
71+
// size: Object.values(variantsObject[themeSelected].ButtonSizeType || {})[2] as string,
72+
// },
5473
tag: {
5574
variant: Object.values(variantsObject[themeSelected].TagVariantType || {})[0] as string,
5675
option: Object.values(variantsObject[themeSelected].TagOptionType || {})[0] as string,
5776
status: Object.values(variantsObject[themeSelected].TagStatusType || {})[0] as string,
5877
content: 'Tag content',
5978
},
79+
dataTestId: 'dataTestId',
80+
// messageContainerProps: {
81+
// url: 'https://www.google.com',
82+
// target: LinkTargetType.BLANK,
83+
// },
84+
// titleAndContentContainerProps: {
85+
// url: 'https://www.google.com',
86+
// target: LinkTargetType.BLANK,
87+
// onClick: () => console.log('Card clicked'),
88+
// },
6089
themeArgs: themesObject[themeSelected][STYLES_NAME.MESSAGE],
6190
},
6291
};

0 commit comments

Comments
 (0)