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

feat: add ability to passing data-attributes for some components #1484

Closed
wants to merge 2 commits into from
Closed
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
7 changes: 5 additions & 2 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import type {DOMProps, QAProps} from '../types';
import type {DOMProps, DataAttrProps, QAProps} from '../types';
import {block} from '../utils/cn';
import {isIcon} from '../utils/common';
import {eventBroker} from '../utils/event-broker';
Expand Down Expand Up @@ -53,7 +53,7 @@ export type ButtonPin =

export type ButtonWidth = 'auto' | 'max';

export interface ButtonProps extends DOMProps, QAProps {
export interface ButtonProps extends DOMProps, QAProps, DataAttrProps {
/** Button appearance */
view?: ButtonView;
size?: ButtonSize;
Expand Down Expand Up @@ -111,6 +111,7 @@ const ButtonWithHandlers = React.forwardRef<HTMLElement, ButtonProps>(function B
style,
className,
qa,
...dataAttrProps
},
ref,
) {
Expand Down Expand Up @@ -166,6 +167,7 @@ const ButtonWithHandlers = React.forwardRef<HTMLElement, ButtonProps>(function B
{
...extraProps,
...commonProps,
...dataAttrProps,
...(component ? {} : linkProps),
ref: ref as React.Ref<HTMLAnchorElement>,
'aria-disabled': disabled || loading,
Expand All @@ -177,6 +179,7 @@ const ButtonWithHandlers = React.forwardRef<HTMLElement, ButtonProps>(function B
<button
{...(extraProps as React.ButtonHTMLAttributes<HTMLButtonElement>)}
{...commonProps}
{...dataAttrProps}
ref={ref as React.Ref<HTMLButtonElement>}
type={type}
disabled={disabled || loading}
Expand Down
7 changes: 7 additions & 0 deletions src/components/Button/__tests__/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,11 @@ describe('Button', () => {
await user.click(button);
expect(handleOnClick).toHaveBeenCalledTimes(1);
});

test('passing data attribute', () => {
render(<Button data-action="create" />);
const button = screen.getByRole('button');

expect(button.dataset.action).toEqual('create');
});
});
11 changes: 8 additions & 3 deletions src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import {useCheckbox} from '../../hooks/private';
import {ControlLabel} from '../ControlLabel';
import type {ControlLabelSize} from '../ControlLabel';
import type {ControlProps, DOMProps, QAProps} from '../types';
import type {ControlProps, DOMProps, DataAttrProps, QAProps} from '../types';
import {block} from '../utils/cn';

import {CheckboxDashIcon} from './CheckboxDashIcon';
Expand All @@ -13,7 +13,7 @@ import './Checkbox.scss';

export type CheckboxSize = ControlLabelSize;

export interface CheckboxProps extends ControlProps, DOMProps, QAProps {
export interface CheckboxProps extends ControlProps, DOMProps, QAProps, DataAttrProps {
size?: CheckboxSize;
content?: React.ReactNode;
children?: React.ReactNode;
Expand All @@ -34,10 +34,15 @@ export const Checkbox = React.forwardRef<HTMLLabelElement, CheckboxProps>(
style,
className,
qa,
...otherProps
} = props;
const {checked, inputProps} = useCheckbox(props);
const text = content || children;

const dataAttrProps = Object.fromEntries(
Object.entries(otherProps).filter(([key]) => key.startsWith('data-')),
) as DataAttrProps;

const control = (
<span className={b('indicator')}>
<span className={b('icon')} aria-hidden>
Expand All @@ -47,7 +52,7 @@ export const Checkbox = React.forwardRef<HTMLLabelElement, CheckboxProps>(
<CheckboxTickIcon className={b('icon-svg', {type: 'tick'})} />
)}
</span>
<input {...inputProps} className={b('control')} />
<input {...inputProps} {...dataAttrProps} className={b('control')} />
<span className={b('outline')} />
</span>
);
Expand Down
13 changes: 13 additions & 0 deletions src/components/Checkbox/__tests__/Checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,17 @@ describe('Checkbox', () => {
checkbox.blur();
expect(handleOnBlur).toHaveBeenCalledTimes(1);
});

test('passing data attribute', async () => {
const onChangeFn = jest.fn((event: React.ChangeEvent<HTMLInputElement>) => {
event.persist();
});
const user = userEvent.setup();
render(<Checkbox onChange={onChangeFn} data-id="checkbox1" />);
const checkbox = screen.getByRole('checkbox');

await user.click(checkbox);

expect(onChangeFn.mock.calls[0][0].target.dataset['id']).toEqual('checkbox1');
});
});
6 changes: 4 additions & 2 deletions src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';

import type {DOMProps, QAProps} from '../types';
import type {DOMProps, DataAttrProps, QAProps} from '../types';
import {block} from '../utils/cn';
import {eventBroker} from '../utils/event-broker';

import './Link.scss';

export type LinkView = 'normal' | 'primary' | 'secondary';

export interface LinkProps extends DOMProps, QAProps {
export interface LinkProps extends DOMProps, QAProps, DataAttrProps {
view?: LinkView;
visitable?: boolean;
title?: string;
Expand Down Expand Up @@ -42,6 +42,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link
style,
className,
qa,
...dataAttrProps
},
ref,
) {
Expand All @@ -63,6 +64,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link
style,
className: b({view, visitable}, className),
'data-qa': qa,
...dataAttrProps,
};

const relProp = target === '_blank' && !rel ? 'noopener noreferrer' : rel;
Expand Down
8 changes: 8 additions & 0 deletions src/components/Link/__tests__/Link.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ describe('Link', () => {

expect(link).toHaveAttribute('rel', 'noopener noreferrer');
});

test('passing data attribute', () => {
render(<Link href="#" target="_blank" data-action="visit" />);

const link = screen.getByRole('link');

expect(link.dataset.action).toEqual('visit');
});
});
8 changes: 4 additions & 4 deletions src/components/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import {useRadio} from '../../hooks/private';
import {ControlLabel} from '../ControlLabel';
import type {ControlLabelSize} from '../ControlLabel';
import type {ControlProps, DOMProps, QAProps} from '../types';
import type {ControlProps, DOMProps, DataAttrProps, QAProps} from '../types';
import {block} from '../utils/cn';

import './Radio.scss';
Expand All @@ -12,7 +12,7 @@ const b = block('radio');

export type RadioSize = ControlLabelSize;

export interface RadioProps extends ControlProps, DOMProps, QAProps {
export interface RadioProps extends ControlProps, DOMProps, QAProps, DataAttrProps {
value: string;
size?: RadioSize;
content?: React.ReactNode;
Expand All @@ -22,13 +22,13 @@ export interface RadioProps extends ControlProps, DOMProps, QAProps {

export const Radio = React.forwardRef<HTMLLabelElement, RadioProps>(function Radio(props, ref) {
const {size = 'm', disabled = false, content, children, title, style, className, qa} = props;
const {checked, inputProps} = useRadio(props);
const {checked, inputProps, dataAttrProps} = useRadio(props);
const text = content || children;

const control = (
<span className={b('indicator')}>
<span className={b('disc')} />
<input {...inputProps} className={b('control')} />
<input {...inputProps} {...dataAttrProps} className={b('control')} />
<span className={b('outline')} />
</span>
);
Expand Down
14 changes: 14 additions & 0 deletions src/components/Radio/__tests__/Radio.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,18 @@ describe('Radio', () => {
radio.blur();
expect(handleOnBlur).toHaveBeenCalledTimes(1);
});

test('passing data attribute', async () => {
const onChangeFn = jest.fn((event: React.ChangeEvent<HTMLInputElement>) => {
event.persist();
});
const user = userEvent.setup();

render(<Radio value={value} onChange={onChangeFn} data-id="radio1" />);
const radio = screen.getByRole('radio');

await user.click(radio);

expect(onChangeFn.mock.calls[0][0].target.dataset['id']).toEqual('radio1');
});
});
18 changes: 15 additions & 3 deletions src/components/RadioButton/RadioButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';

import {useRadioGroup} from '../../hooks/private';
import type {ControlGroupOption, ControlGroupProps, DOMProps, QAProps} from '../types';
import type {
ControlGroupOption,
ControlGroupProps,
DOMProps,
DataAttrProps,
QAProps,
} from '../types';
import {block} from '../utils/cn';

import {RadioButtonOption as Option} from './RadioButtonOption';
Expand All @@ -17,7 +23,8 @@ export type RadioButtonWidth = 'auto' | 'max';
export interface RadioButtonProps<T extends string = string>
extends ControlGroupProps<T>,
DOMProps,
QAProps {
QAProps,
DataAttrProps {
size?: RadioButtonSize;
width?: RadioButtonWidth;
children?:
Expand All @@ -35,9 +42,13 @@ export const RadioButton = React.forwardRef(function RadioButton<T extends strin
props: RadioButtonProps<T>,
ref: React.ForwardedRef<HTMLDivElement>,
) {
const {size = 'm', width, style, className, qa, children} = props;
const {size = 'm', width, style, className, qa, children, ...otherProps} = props;
let options = props.options;

const dataAttrProps = Object.fromEntries(
Object.entries(otherProps).filter(([key]) => key.startsWith('data-')),
) as DataAttrProps;

if (!options) {
options = (
React.Children.toArray(children) as React.ReactElement<ControlGroupOption<T>>[]
Expand Down Expand Up @@ -98,6 +109,7 @@ export const RadioButton = React.forwardRef(function RadioButton<T extends strin
style={style}
className={b({size, width}, className)}
data-qa={qa}
{...dataAttrProps}
>
<div
ref={plateRef}
Expand Down
10 changes: 6 additions & 4 deletions src/components/RadioButton/RadioButtonOption.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from 'react';

import {useRadio} from '../../hooks/private';
import type {ControlProps} from '../types';
import type {ControlProps, DataAttrProps} from '../types';
import {block} from '../utils/cn';
import {isIcon} from '../utils/common';

const b = block('radio-button');

export interface RadioButtonOptionProps<ValueType extends string> extends ControlProps {
export interface RadioButtonOptionProps<ValueType extends string>
extends ControlProps,
DataAttrProps {
value: ValueType;
content?: React.ReactNode;
children?: React.ReactNode;
Expand All @@ -23,7 +25,7 @@ export const RadioButtonOption = React.forwardRef(function RadioButtonOption<T e
ref: React.ForwardedRef<HTMLLabelElement>,
) {
const {disabled = false, content, children, title} = props;
const {checked, inputProps} = useRadio(props);
const {checked, inputProps, dataAttrProps} = useRadio(props);
const inner = content || children;
const icon = isIcon(inner);

Expand All @@ -36,7 +38,7 @@ export const RadioButtonOption = React.forwardRef(function RadioButtonOption<T e
ref={ref}
title={title}
>
<input {...inputProps} className={b('option-control')} />
<input {...inputProps} {...dataAttrProps} className={b('option-control')} />
<span className={b('option-outline')} />
{inner && <span className={b('option-text', {icon})}>{inner}</span>}
</label>
Expand Down
19 changes: 17 additions & 2 deletions src/components/RadioButton/__tests__/RadioButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const qaId = 'radio-button-component';
const b = block('radio-button');

const options: RadioButtonOption[] = [
{value: 'Value 1', content: 'Value 1'},
{value: 'Value 2', content: 'Value 2'},
{value: 'Value 1', content: 'Value 1', 'data-id': 'option1'},
{value: 'Value 2', content: 'Value 2', 'data-id': 'option2'},
{value: 'Value 3', content: 'Value 3'},
];

Expand All @@ -29,6 +29,21 @@ describe('RadioButton', () => {
expect(ref.current).toBe(component);
});

test('passing data attribute', async () => {
const onChangeFn = jest.fn((event: React.ChangeEvent<HTMLInputElement>) => {
event.persist();
});
const user = userEvent.setup();

renderRadioButton({onChange: onChangeFn, 'data-id': 'radioButton1'});
const component = screen.getByTestId(qaId);
const radio1 = await within(component).findByText(options[1].content as string);

await user.click(radio1);

expect(onChangeFn.mock.calls[0][0].target.dataset['id']).toEqual('option2');
});

describe('visibility', () => {
test('render RadioButton by default', () => {
renderRadioButton();
Expand Down
8 changes: 4 additions & 4 deletions src/components/Switch/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import {useCheckbox} from '../../hooks/private';
import {ControlLabel} from '../ControlLabel';
import type {ControlLabelSize} from '../ControlLabel';
import type {ControlProps, DOMProps, QAProps} from '../types';
import type {ControlProps, DOMProps, DataAttrProps, QAProps} from '../types';
import {block} from '../utils/cn';

import './Switch.scss';
Expand All @@ -12,7 +12,7 @@ const b = block('switch');

export type SwitchSize = ControlLabelSize;

export interface SwitchProps extends ControlProps, DOMProps, QAProps {
export interface SwitchProps extends ControlProps, DOMProps, QAProps, DataAttrProps {
size?: SwitchSize;
content?: React.ReactNode;
children?: React.ReactNode;
Expand All @@ -21,15 +21,15 @@ export interface SwitchProps extends ControlProps, DOMProps, QAProps {

export const Switch = React.forwardRef<HTMLLabelElement, SwitchProps>(function Switch(props, ref) {
const {size = 'm', disabled = false, content, children, title, style, className, qa} = props;
const {checked, inputProps} = useCheckbox({
const {checked, inputProps, dataAttrProps} = useCheckbox({
...props,
controlProps: {...props.controlProps, role: 'switch'},
});
const text = content || children;

const control = (
<span className={b('indicator')}>
<input {...inputProps} className={b('control')} />
<input {...inputProps} {...dataAttrProps} className={b('control')} />
<span className={b('outline')} />
<span className={b('slider')} />
</span>
Expand Down
14 changes: 14 additions & 0 deletions src/components/Switch/__tests__/Switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,18 @@ describe('Switch', () => {

expect(component).not.toBeChecked();
});

test('passing data attribute', async () => {
const onChangeFn = jest.fn((event: React.ChangeEvent<HTMLInputElement>) => {
event.persist();
});
const user = userEvent.setup();

render(<Switch checked={false} onChange={onChangeFn} data-id="switch1" />);
const component = screen.getByRole('switch');

await user.click(component);

expect(onChangeFn.mock.calls[0][0].target.dataset['id']).toEqual('switch1');
});
});
Loading
Loading