Skip to content

Commit 939bf21

Browse files
author
Hector Arce De Las Heras
committed
Include new QuickButton Component
1 parent 9bb73e3 commit 939bf21

File tree

19 files changed

+717
-0
lines changed

19 files changed

+717
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { screen } from '@testing-library/react';
2+
import * as React from 'react';
3+
4+
import { axe } from 'jest-axe';
5+
6+
import { ButtonType } from '@/components/button';
7+
import { renderProvider } from '@/tests/renderProvider/renderProvider.utility';
8+
9+
import { QuickButton } from '../quickButton';
10+
11+
const mockProps = {
12+
variant: 'PRIMARY',
13+
onClick: jest.fn,
14+
dataTestId: 'button-component',
15+
buttonId: 'labelId',
16+
icon: { icon: 'ARROW_RIGHT', altText: 'altText' },
17+
['aria-label']: 'quickButton',
18+
type: ButtonType.BUTTON,
19+
};
20+
21+
describe('QuickButton component', () => {
22+
it('Should render a label when provided', async () => {
23+
const { container } = renderProvider(
24+
<QuickButton {...mockProps} label={{ content: 'label' }} />
25+
);
26+
27+
const quickButton = screen.getByRole('button');
28+
const label = screen.getByText('label');
29+
30+
expect(quickButton).toBeDefined();
31+
expect(label).toBeDefined();
32+
33+
const results = await axe(container);
34+
expect(container).toHTMLValidate();
35+
expect(results).toHaveNoViolations();
36+
});
37+
38+
it('Variant is optional', async () => {
39+
const { container } = renderProvider(<QuickButton {...mockProps} variant={undefined} />);
40+
41+
const quickButton = screen.getByRole('button', { name: 'quickButton' });
42+
43+
expect(quickButton).toBeDefined();
44+
45+
const results = await axe(container);
46+
expect(container).toHTMLValidate();
47+
expect(results).toHaveNoViolations();
48+
});
49+
50+
it('Type is optional', async () => {
51+
const { container } = renderProvider(<QuickButton {...mockProps} type={undefined} />);
52+
53+
const button = screen.getByRole('button', { name: 'quickButton' });
54+
55+
expect(button).toBeDefined();
56+
57+
const results = await axe(container);
58+
expect(container).toHTMLValidate();
59+
expect(results).toHaveNoViolations();
60+
});
61+
});

src/components/quickButton/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { QuickButtonStandAlone } from './quickButtonStandAlone';
2+
export { QuickButton } from './quickButton';
3+
4+
export * from './types';
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import styled, { css } from 'styled-components';
2+
3+
import { getStyles, getTypographyStyles } from '@/utils/getStyles/getStyles';
4+
5+
import { QuickButtonState, QuickButtonVariantStylesType } from './types';
6+
7+
export const QuickButtonContainerStyled = styled.div<{
8+
maxWidth?: string;
9+
styles?: QuickButtonVariantStylesType;
10+
}>`
11+
${({ styles }) => getStyles(styles?.[QuickButtonState.DEFAULT]?.container)}
12+
${({ styles }) => getTypographyStyles(styles?.[QuickButtonState.DEFAULT]?.container)}
13+
${({ maxWidth }) =>
14+
maxWidth &&
15+
css`
16+
max-width: ${maxWidth};
17+
`};
18+
19+
&:disabled {
20+
${({ styles }) => getStyles(styles?.[QuickButtonState.DISABLED]?.container)}
21+
${({ styles }) => getTypographyStyles(styles?.[QuickButtonState.DISABLED]?.container)}
22+
}
23+
24+
&:hover:not(:disabled) {
25+
${({ styles }) => getStyles(styles?.[QuickButtonState.HOVER]?.container)}
26+
${({ styles }) => getTypographyStyles(styles?.[QuickButtonState.HOVER]?.container)}
27+
}
28+
29+
&:active:not(:disabled) {
30+
${({ styles }) => getStyles(styles?.[QuickButtonState.PRESSED]?.container)}
31+
${({ styles }) => getTypographyStyles(styles?.[QuickButtonState.PRESSED]?.container)}
32+
}
33+
`;
34+
35+
export const QuickButtonStyled = styled.button<{
36+
styles?: QuickButtonVariantStylesType;
37+
}>`
38+
${({ styles }) => getStyles(styles?.[QuickButtonState.DEFAULT]?.button)}
39+
40+
&:disabled {
41+
${({ styles }) => getStyles(styles?.[QuickButtonState.DISABLED]?.button)}
42+
}
43+
44+
&:hover:not(:disabled) {
45+
${({ styles }) => getStyles(styles?.[QuickButtonState.HOVER]?.button)}
46+
}
47+
48+
&:active:not(:disabled) {
49+
${({ styles }) => getStyles(styles?.[QuickButtonState.PRESSED]?.button)}
50+
}
51+
`;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//vendors
2+
import * as React from 'react';
3+
4+
import { States, useManageState } from '@/hooks';
5+
//hooks
6+
import { useStyles } from '@/hooks/useStyles/useStyles';
7+
import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary';
8+
9+
import { ButtonType } from '../button';
10+
//components
11+
import { QuickButtonStandAlone } from './quickButtonStandAlone';
12+
import {
13+
IQuickButton,
14+
IQuickButtonStandAlone,
15+
QuickButtonState,
16+
QuickButtonVariantStylesType,
17+
} from './types';
18+
19+
//types
20+
21+
const QUICK_BUTTON_STYLES = 'QUICK_BUTTON_STYLES';
22+
23+
const QuickButtonComponent = React.forwardRef(
24+
<V extends string | unknown>(
25+
{ type = ButtonType.BUTTON, disabled = false, ctv, ...props }: IQuickButton<V>,
26+
ref: React.ForwardedRef<HTMLButtonElement> | undefined | null
27+
): JSX.Element => {
28+
const styles = useStyles<QuickButtonVariantStylesType, V>(
29+
QUICK_BUTTON_STYLES,
30+
props.variant,
31+
ctv
32+
);
33+
34+
const { state, setRef } = useManageState({
35+
states: Object.values(QuickButtonState) as States,
36+
ref: ref as React.ForwardedRef<HTMLElement> | undefined | null,
37+
disabled,
38+
});
39+
40+
return (
41+
<QuickButtonStandAlone
42+
{...props}
43+
ref={setRef as React.ForwardedRef<HTMLButtonElement>}
44+
state={state as unknown as QuickButtonState}
45+
styles={styles}
46+
type={type}
47+
/>
48+
);
49+
}
50+
);
51+
QuickButtonComponent.displayName = 'QuickButtonComponent';
52+
53+
const QuickButtonBoundary = <V extends string | unknown>(
54+
props: IQuickButton<V>,
55+
ref: React.ForwardedRef<HTMLButtonElement> | undefined | null
56+
): JSX.Element => (
57+
<ErrorBoundary
58+
fallBackComponent={
59+
<FallbackComponent>
60+
<QuickButtonStandAlone {...(props as unknown as IQuickButtonStandAlone)} ref={ref} />
61+
</FallbackComponent>
62+
}
63+
>
64+
<QuickButtonComponent {...props} ref={ref} />
65+
</ErrorBoundary>
66+
);
67+
68+
/**
69+
* @description
70+
* QuickButton component is a button component that can be used to create a button.
71+
* @param {React.PropsWithChildren<IQuickButton<V>>} props
72+
* @returns {JSX.Element}
73+
*/
74+
const QuickButton = React.forwardRef(QuickButtonBoundary) as <V>(
75+
props: React.PropsWithChildren<IQuickButton<V>> & {
76+
ref?: React.ForwardedRef<HTMLButtonElement> | undefined | null;
77+
}
78+
) => ReturnType<typeof QuickButtonBoundary>;
79+
80+
export { QuickButton };
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as React from 'react';
2+
3+
import { ElementOrIcon } from '@/components/elementOrIcon';
4+
5+
import { Label } from '../label';
6+
import { QuickButtonContainerStyled, QuickButtonStyled } from './quickButton.styled';
7+
import { IQuickButtonStandAlone, QuickButtonState } from './types';
8+
9+
const QuickButtonStandAloneComponent = (
10+
props: IQuickButtonStandAlone,
11+
ref: React.ForwardedRef<HTMLButtonElement> | undefined | null
12+
): JSX.Element => {
13+
const button = (
14+
<QuickButtonStyled
15+
ref={ref}
16+
aria-label={props.label ? '' : props['aria-label']}
17+
data-testid={props.dataTestId}
18+
disabled={props.state === QuickButtonState.DISABLED}
19+
id={props.buttonId}
20+
styles={props.styles}
21+
type={props.type}
22+
onClick={props.onClick}
23+
>
24+
<ElementOrIcon
25+
color={props.styles?.[props.state]?.icon?.color}
26+
height={props.styles?.[props.state]?.icon?.height}
27+
width={props.styles?.[props.state]?.icon?.width}
28+
{...props.icon}
29+
/>
30+
</QuickButtonStyled>
31+
);
32+
33+
return props.label ? (
34+
<QuickButtonContainerStyled maxWidth={props.maxWidth} styles={props.styles}>
35+
{button}
36+
<Label
37+
color={props.styles?.[props.state]?.label?.color}
38+
inputId={props.buttonId}
39+
textVariant={props.styles?.[props.state]?.label?.font_family}
40+
weight={props.styles?.[props.state]?.label?.font_weight}
41+
{...props.label}
42+
>
43+
{props.label.content}
44+
</Label>
45+
</QuickButtonContainerStyled>
46+
) : (
47+
button
48+
);
49+
};
50+
51+
/**
52+
* @description
53+
* QuickButton component is a button component that can be used to create a button.
54+
* @param {React.PropsWithChildren<IQuickButtonStandAlone>} props
55+
* @returns {JSX.Element}
56+
* @constructor
57+
*/
58+
export const QuickButtonStandAlone = React.forwardRef(QuickButtonStandAloneComponent);
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { ICONS } from '@/assets';
2+
import { ButtonType } from '@/components/button';
3+
import { CATEGORY_CONTROL } from '@/constants/categoryControl';
4+
import { IThemeObjectVariants } from '@/designSystem/themesObject';
5+
import { objectFlip } from '@/storybook/utils/utils';
6+
import { ArgTypesReturn } from '@/types';
7+
8+
export const argtypes = (variants: IThemeObjectVariants, themeSelected: string): ArgTypesReturn => {
9+
return {
10+
theme: {
11+
table: {
12+
disable: true,
13+
},
14+
},
15+
variant: {
16+
control: { type: 'select' },
17+
type: { name: 'string', required: true },
18+
description: 'Quick button variant',
19+
options: Object.keys(variants[themeSelected].QuickButtonVariantType || {}),
20+
table: {
21+
type: {
22+
summary: 'string',
23+
},
24+
category: CATEGORY_CONTROL.MODIFIERS,
25+
},
26+
},
27+
disabled: {
28+
description: 'Specifies if the quickButton element is disabled or not',
29+
type: { name: 'boolean' },
30+
control: { type: 'boolean' },
31+
table: {
32+
type: {
33+
summary: 'boolean',
34+
},
35+
defaultValue: { summary: false },
36+
category: CATEGORY_CONTROL.MODIFIERS,
37+
},
38+
},
39+
buttonId: {
40+
description: 'String used for join button with label',
41+
control: { type: 'text' },
42+
type: { name: 'string' },
43+
table: {
44+
type: {
45+
summary: 'string',
46+
},
47+
category: CATEGORY_CONTROL.MODIFIERS,
48+
},
49+
},
50+
label: {
51+
description: 'Object with label properties',
52+
control: { type: 'object' },
53+
type: { name: 'object' },
54+
table: {
55+
type: {
56+
summary: 'QuickButtonLabelType',
57+
},
58+
category: CATEGORY_CONTROL.CONTENT,
59+
},
60+
},
61+
icon: {
62+
description: 'Object with icon properties',
63+
control: { type: 'object', labels: objectFlip(ICONS) },
64+
type: { name: 'object' },
65+
table: {
66+
type: {
67+
summary: 'IElementOrIcon',
68+
},
69+
category: CATEGORY_CONTROL.CONTENT,
70+
},
71+
},
72+
maxWidth: {
73+
description: 'Max width container box',
74+
type: { name: 'string' },
75+
control: { type: 'text' },
76+
table: {
77+
type: {
78+
summary: 'string',
79+
},
80+
category: CATEGORY_CONTROL.MODIFIERS,
81+
},
82+
},
83+
onClick: {
84+
description: 'The event occurs when the user clicks on the element',
85+
control: false,
86+
table: {
87+
type: {
88+
summary: 'MouseEventHandler<HTMLButtonElement>',
89+
},
90+
category: CATEGORY_CONTROL.FUNCTIONS,
91+
},
92+
},
93+
type: {
94+
options: Object.keys(ButtonType),
95+
control: { type: 'select' },
96+
type: { name: 'string' },
97+
description: 'Define buttons type',
98+
table: {
99+
type: {
100+
summary: 'string',
101+
},
102+
defaultValue: { summary: ButtonType.BUTTON },
103+
category: CATEGORY_CONTROL.MODIFIERS,
104+
},
105+
},
106+
['aria-label']: {
107+
description: 'Aria label of the button',
108+
control: { type: 'text' },
109+
type: { name: 'string' },
110+
table: {
111+
type: {
112+
summary: 'string',
113+
},
114+
category: CATEGORY_CONTROL.ACCESIBILITY,
115+
},
116+
},
117+
dataTestId: {
118+
description: 'String used for testing',
119+
control: { type: 'text' },
120+
type: { name: 'string' },
121+
table: {
122+
type: {
123+
summary: 'string',
124+
},
125+
category: CATEGORY_CONTROL.TESTING,
126+
},
127+
},
128+
ctv: {
129+
description: 'Object used for update variant styles',
130+
type: { name: 'object' },
131+
control: { type: 'object' },
132+
table: {
133+
type: {
134+
summary: 'object',
135+
},
136+
category: CATEGORY_CONTROL.CUSTOMIZATION,
137+
},
138+
},
139+
};
140+
};

0 commit comments

Comments
 (0)