Skip to content

Commit eee29a3

Browse files
author
Kubit
committed
Add PillSelectorV2 and new PillV2 Styles
1 parent 400ca99 commit eee29a3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1873
-343
lines changed

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export * from './message';
3636
export * from './modal';
3737
export * from './oliveMenu';
3838
export * from './pillSelector';
39+
export * from './pillSelectorV2';
3940
export * from './tabs';
4041
export * from './snackbar';
4142
export * from './stepperNumber';

src/components/pillSelector/pillSelectorControlled.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,7 @@ const PillSelectorControlled = React.forwardRef(PillSelectorBoundary) as <
9292
}
9393
) => ReturnType<typeof PillSelectorBoundary>;
9494

95+
/**
96+
* @deprecated Try the new PillSelectorV2 component
97+
*/
9598
export { PillSelectorControlled };

src/components/pillSelector/pillSelectorStandAlone.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ const PillSelectorStandAloneComponent = (
3333

3434
const isPillSelected = props.pillSelected?.length !== 0;
3535

36-
React.useImperativeHandle(ref, () => {
37-
return listEl.current as HTMLDivElement;
38-
}, []);
36+
React.useImperativeHandle(
37+
ref,
38+
() => {
39+
return listEl.current as HTMLDivElement;
40+
},
41+
[]
42+
);
3943

4044
return (
4145
<PillSelectorWrapper

src/components/pillSelector/pillSelectorUnControlled.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ const PillSelectorUnControlled = React.forwardRef(PillSelectorUnControlledCompon
4545
}
4646
) => ReturnType<typeof PillSelectorUnControlledComponent>;
4747

48+
/**
49+
* @deprecated Try the new PillSelectorV2 component
50+
*/
4851
export { PillSelectorUnControlled };
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { screen } from '@testing-library/react';
2+
import * as React from 'react';
3+
4+
import { axe } from 'jest-axe';
5+
6+
import { ICONS } from '@/assets';
7+
import {
8+
PillSelectorSizeTypeV2,
9+
PillSelectorVariantTypeV2,
10+
} from '@/designSystem/kubit/components/variants';
11+
import { renderProvider } from '@/tests/renderProvider/renderProvider.utility';
12+
import { ROLES } from '@/types';
13+
14+
import { PillSelectorControlled } from '../pillSelectorControlled';
15+
import { PillSelectorUnControlled } from '../pillSelectorUnControlled';
16+
import { IPillSelectorControlled, IPillSelectorUnControlled, PillSelectorType } from '../types';
17+
18+
const mockProps: IPillSelectorUnControlled = {
19+
variant: PillSelectorVariantTypeV2.DEFAULT,
20+
size: PillSelectorSizeTypeV2.LARGE,
21+
pills: [
22+
{ label: { content: 'Pill 1' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 1' },
23+
{ label: { content: 'Pill 2 ' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 2' },
24+
{ label: { content: 'Pill 3' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 3' },
25+
{ label: { content: 'Pill 4' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 4' },
26+
],
27+
};
28+
29+
describe('PillSelectorUncontrolled', () => {
30+
it('Should render a set of pills, by default pill type is input checkbox (PillSelectorType.SELECTOR_MULTIPLE)', async () => {
31+
const { container } = renderProvider(<PillSelectorUnControlled {...mockProps} />);
32+
33+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
34+
expect(pills).toHaveLength(mockProps.pills?.length as number);
35+
36+
const results = await axe(container);
37+
expect(container).toHTMLValidate();
38+
expect(results).toHaveNoViolations();
39+
});
40+
41+
it('When SELECTOR_SIMPLE, pills are render as checkbox', async () => {
42+
const { container } = renderProvider(
43+
<PillSelectorUnControlled {...mockProps} type={PillSelectorType.SELECTOR_SIMPLE} />
44+
);
45+
46+
const pills = screen.getAllByRole(ROLES.RADIO);
47+
expect(pills).toHaveLength(mockProps.pills?.length as number);
48+
49+
const results = await axe(container);
50+
expect(container).toHTMLValidate();
51+
expect(results).toHaveNoViolations();
52+
});
53+
54+
it('Size prop is optional', () => {
55+
const { size, ...restMockProps } = mockProps;
56+
renderProvider(<PillSelectorUnControlled {...restMockProps} />);
57+
58+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
59+
expect(pills).toHaveLength(mockProps.pills?.length as number);
60+
});
61+
62+
it('When SELECTOR_MULTIPLE, value should be an array of the value of the selected options', () => {
63+
const value = [mockProps.pills?.[0].value as string];
64+
renderProvider(<PillSelectorUnControlled {...mockProps} defaultValue={value} />);
65+
66+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
67+
expect(pills[0]).toBeChecked();
68+
});
69+
70+
it('When SELECTOR_SIMPLE, value should be a value from the values of the selected options', () => {
71+
const value = mockProps.pills?.[0].value as string;
72+
renderProvider(
73+
<PillSelectorUnControlled
74+
{...mockProps}
75+
defaultValue={value}
76+
type={PillSelectorType.SELECTOR_SIMPLE}
77+
/>
78+
);
79+
80+
const pills = screen.getAllByRole(ROLES.RADIO);
81+
expect(pills[0]).toBeChecked();
82+
});
83+
84+
it('When SELECTOR_MULTIPLE, onChange should return an array of the value of the selected options', () => {
85+
const value = [mockProps.pills?.[0].value as string];
86+
const handleChange = jest.fn();
87+
renderProvider(<PillSelectorUnControlled {...mockProps} onChange={handleChange} />);
88+
89+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
90+
pills[0].click();
91+
expect(handleChange).toHaveBeenCalledWith(value);
92+
});
93+
94+
it('When SELECTOR_MULTIPLE, onChange should return an array of the value of the selected options, if it is already selected it should be removed', () => {
95+
const value = [mockProps.pills?.[0].value as string];
96+
const handleChange = jest.fn();
97+
renderProvider(
98+
<PillSelectorUnControlled {...mockProps} defaultValue={value} onChange={handleChange} />
99+
);
100+
101+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
102+
pills[0].click();
103+
expect(handleChange).toHaveBeenCalledWith([]);
104+
});
105+
106+
it('When SELECTOR_MULTIPLE, onChange should return an array of the value of the selected options, if it not is already selected it should be added', () => {
107+
const value = [mockProps.pills?.[0].value as string];
108+
const handleChange = jest.fn();
109+
renderProvider(
110+
<PillSelectorUnControlled {...mockProps} defaultValue={value} onChange={handleChange} />
111+
);
112+
113+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
114+
pills[1].click();
115+
expect(handleChange).toHaveBeenCalledWith([
116+
mockProps.pills?.[0].value as string,
117+
mockProps.pills?.[1].value as string,
118+
]);
119+
});
120+
121+
it('When SELECTOR_SIMPLE, onChange should return a value from the values of the selected options', () => {
122+
const value = mockProps.pills?.[0].value as string;
123+
const handleChange = jest.fn();
124+
renderProvider(
125+
<PillSelectorUnControlled
126+
{...mockProps}
127+
type={PillSelectorType.SELECTOR_SIMPLE}
128+
onChange={handleChange}
129+
/>
130+
);
131+
132+
const pills = screen.getAllByRole(ROLES.RADIO);
133+
pills[0].click();
134+
expect(handleChange).toHaveBeenCalledWith(value);
135+
});
136+
});
137+
138+
const mockPropsControlled: IPillSelectorControlled = {
139+
variant: PillSelectorVariantTypeV2.DEFAULT,
140+
size: PillSelectorSizeTypeV2.LARGE,
141+
pills: [
142+
{ label: { content: 'Pill 1' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 1' },
143+
{ label: { content: 'Pill 2 ' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 2' },
144+
{ label: { content: 'Pill 3' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 3' },
145+
{ label: { content: 'Pill 4' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 4' },
146+
],
147+
};
148+
149+
describe('PillSelectorcontrolled', () => {
150+
it('Should render a set of pills, by default pill type is input checkbox (PillSelectorType.SELECTOR_MULTIPLE)', () => {
151+
renderProvider(<PillSelectorControlled {...mockPropsControlled} />);
152+
153+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
154+
expect(pills).toHaveLength(mockProps.pills?.length as number);
155+
});
156+
157+
it('If onChange is not passed, when pressing over a pill it will not trigger any action', () => {
158+
renderProvider(<PillSelectorControlled {...mockPropsControlled} />);
159+
160+
const pills = screen.getAllByRole(ROLES.CHECKBOX);
161+
pills[0].click();
162+
163+
expect(pills[0]).not.toBeChecked();
164+
});
165+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export type {
2+
PillSelectorPillType as PillSelectorPillTypeV2,
3+
PillSelectorValueType as PillSelectorValueTypeV2,
4+
IPillSelectorUnControlled as IPillSelectorV2,
5+
IPillSelectorControlled as IPillSelectorControlledV2,
6+
PillSelectorPropsStylesType as PillSelectorPropsStylesTypeV2,
7+
PillSelectorStylesType as PillSelectorStylesTypeV2,
8+
} from './types';
9+
10+
export { PillSelectorType as PillSelectorTypeV2 } from './types/pillSelectorType';
11+
12+
export { PillSelectorUnControlled as PillSelectorV2 } from './pillSelectorUnControlled';
13+
export { PillSelectorControlled as PillSelectorControlledV2 } from './pillSelectorControlled';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import styled from 'styled-components';
2+
3+
import { getStyles } from '@/utils';
4+
5+
import { PillSelectorPropsStylesType } from './types';
6+
7+
export const RootContainerStyled = styled.div<{
8+
styles?: PillSelectorPropsStylesType;
9+
}>`
10+
${({ styles }) => getStyles(styles?.rootContainer)};
11+
`;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as React from 'react';
2+
3+
import { STYLES_NAME } from '@/constants';
4+
import { useStylesV2 } from '@/hooks';
5+
6+
import { PillSelectorStandAlone } from './pillSelectorStandAlone';
7+
import {
8+
IPillSelectorControlled,
9+
PillSelectorType,
10+
PillSelectorVariantPropsStylesType,
11+
} from './types';
12+
13+
const PillSelectorControlledComponent = (
14+
{
15+
variant,
16+
size,
17+
ctv,
18+
type = PillSelectorType.SELECTOR_MULTIPLE,
19+
...props
20+
}: IPillSelectorControlled,
21+
ref: React.ForwardedRef<HTMLDivElement>
22+
) => {
23+
const variantStyles = useStylesV2<PillSelectorVariantPropsStylesType>({
24+
styleName: STYLES_NAME.PILL_SELECTOR_V2,
25+
variantName: variant,
26+
customTokens: ctv,
27+
isOptional: true,
28+
});
29+
30+
// Size prop is optional, else select the first size from the variantStyles
31+
const styles = size ? variantStyles?.[size] : variantStyles?.[Object.keys(variantStyles)[0]];
32+
33+
const handlePillChange = (event: React.ChangeEvent<HTMLInputElement>) => {
34+
if (!props.onChange) {
35+
return;
36+
}
37+
if (type === PillSelectorType.SELECTOR_SIMPLE) {
38+
props.onChange(event.target.value);
39+
return;
40+
}
41+
// SELECTOR MULTIPLE
42+
if (Array.isArray(props.value)) {
43+
const valueIncluded = props.value.includes(event.target.value);
44+
const newValue = valueIncluded
45+
? props.value.filter(v => v !== event.target.value)
46+
: [...props.value, event.target.value];
47+
props.onChange(newValue);
48+
return;
49+
}
50+
// When value === undefined or value is not array
51+
props.onChange([event.target.value]);
52+
};
53+
54+
return (
55+
<PillSelectorStandAlone
56+
ref={ref}
57+
styles={styles}
58+
type={type}
59+
onPillChange={handlePillChange}
60+
{...props}
61+
/>
62+
);
63+
};
64+
65+
export const PillSelectorControlled = React.forwardRef(PillSelectorControlledComponent);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as React from 'react';
2+
3+
import { PillTypeV2, PillV2 } from '@/components/pillV2';
4+
5+
import { RootContainerStyled } from './pillSelector.styled';
6+
import { IPillSelectorStandAlone, PillSelectorType } from './types';
7+
8+
const PillSelectorStandAloneComponent = (
9+
{ dataTestId = 'pillSelector', ...props }: IPillSelectorStandAlone,
10+
ref: React.ForwardedRef<HTMLDivElement> | undefined | null
11+
): JSX.Element => {
12+
return (
13+
<RootContainerStyled ref={ref} styles={props.styles}>
14+
{props.pills?.map((pill, index) => {
15+
const selected =
16+
props.type === PillSelectorType.SELECTOR_MULTIPLE
17+
? pill.value !== undefined &&
18+
Array.isArray(props.value) &&
19+
props.value.includes(pill.value)
20+
: pill.value === props.value;
21+
return (
22+
<PillV2
23+
key={index}
24+
dataTestId={pill.dataTestId}
25+
disabled={pill.disabled ?? props.disabled}
26+
label={pill.label}
27+
leftIcon={pill.icon}
28+
name={props.name}
29+
rightIcon={selected ? props.selectedIcon : undefined}
30+
selected={selected}
31+
size={pill.size ?? props.styles?.pill?.size}
32+
type={
33+
props.type === PillSelectorType.SELECTOR_MULTIPLE
34+
? PillTypeV2.SELECTOR_MULTIPLE
35+
: PillTypeV2.SELECTOR_SIMPLE
36+
}
37+
value={pill.value}
38+
variant={pill.variant ?? props.styles?.pill?.variant}
39+
onChange={props.onPillChange}
40+
/>
41+
);
42+
})}
43+
</RootContainerStyled>
44+
);
45+
};
46+
47+
export const PillSelectorStandAlone = React.forwardRef(PillSelectorStandAloneComponent);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as React from 'react';
2+
3+
import { PillSelectorControlled } from './pillSelectorControlled';
4+
import { IPillSelectorUnControlled, PillSelectorType } from './types';
5+
6+
const PillSelectorUnControlledComponent = (
7+
{
8+
type = PillSelectorType.SELECTOR_MULTIPLE,
9+
defaultValue,
10+
onChange,
11+
...props
12+
}: IPillSelectorUnControlled,
13+
ref: React.ForwardedRef<HTMLDivElement>
14+
) => {
15+
const [value, setValue] = React.useState(defaultValue);
16+
17+
const handleChange = (newValue: string | number | Array<string | number>) => {
18+
setValue(newValue);
19+
onChange?.(newValue);
20+
};
21+
22+
return (
23+
<PillSelectorControlled
24+
ref={ref}
25+
type={type}
26+
value={value}
27+
onChange={handleChange}
28+
{...props}
29+
/>
30+
);
31+
};
32+
33+
export const PillSelectorUnControlled = React.forwardRef(PillSelectorUnControlledComponent);

0 commit comments

Comments
 (0)