Skip to content

Commit

Permalink
Merge pull request #25 from kubit-ui/feature/improvements-and-fix-bugs
Browse files Browse the repository at this point in the history
Feature/improvements and fix bugs
  • Loading branch information
kubit-ui authored Aug 7, 2024
2 parents 400ca99 + ab8a3c5 commit 5f61563
Show file tree
Hide file tree
Showing 50 changed files with 2,035 additions and 344 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kubit-ui-web/react-components",
"version": "1.9.0",
"version": "1.10.0",
"description": "Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience",
"author": {
"name": "Kubit",
Expand Down Expand Up @@ -111,8 +111,8 @@
"@types/react-dom": "^18.3.0",
"@types/styled-components": "^5.1.34",
"@types/ungap__structured-clone": "^1.2.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"@ungap/structured-clone": "^1.2.0",
"@vitejs/plugin-react": "^4.3.1",
"babel-jest": "^29.7.0",
Expand All @@ -127,7 +127,7 @@
"eslint-plugin-n": "^17.10.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-promise": "^7.0.0",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.9",
Expand Down
3 changes: 2 additions & 1 deletion src/components/accordion/accordion.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ export const AccordionHeaderMainContainerStyled = styled.div<IAccordionStyles>`
${props => getStyles(props.styles)}
`;

export const AccordionHeaderTitleHeadlineStyled = styled.span`
export const AccordionHeaderTitleHeadlineStyled = styled.span<IAccordionStyles>`
display: flex;
width: 100%;
${props => getStyles(props.styles)}
`;

export const AccordionTitleStyled = styled.span<IAccordionStyles>`
Expand Down
5 changes: 4 additions & 1 deletion src/components/accordion/accordionStandAlone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ const AccordionStandAloneComponent = (
styles={props.styles.headerInternalContainer}
>
<AccordionHeaderMainContainerStyled styles={props.styles.headerMainContainer}>
<AccordionHeaderTitleHeadlineStyled as={props.triggerComponent}>
<AccordionHeaderTitleHeadlineStyled
as={props.triggerComponent}
styles={props.styles.titleHeaderMainContainer}
>
<AccordionTriggerStyled
aria-controls={PANEL_ID}
aria-expanded={open}
Expand Down
1 change: 1 addition & 0 deletions src/components/accordion/types/accordionTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type AccordionPropsStylesType = {
headerExternalContainer?: CommonStyleType;
headerInternalContainer?: CommonStyleType;
headerMainContainer?: CommonStyleType;
titleHeaderMainContainer?: CommonStyleType;
trigger?: CommonStyleType;
/**
* @deprecated currently link styles is used instead of trigger when tittle is not type of string. In the next major only trigger styles will be used
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export * from './message';
export * from './modal';
export * from './oliveMenu';
export * from './pillSelector';
export * from './pillSelectorV2';
export * from './tabs';
export * from './snackbar';
export * from './stepperNumber';
Expand Down
3 changes: 3 additions & 0 deletions src/components/pillSelector/pillSelectorControlled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,7 @@ const PillSelectorControlled = React.forwardRef(PillSelectorBoundary) as <
}
) => ReturnType<typeof PillSelectorBoundary>;

/**
* @deprecated Try the new PillSelectorV2 component
*/
export { PillSelectorControlled };
3 changes: 3 additions & 0 deletions src/components/pillSelector/pillSelectorUnControlled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ const PillSelectorUnControlled = React.forwardRef(PillSelectorUnControlledCompon
}
) => ReturnType<typeof PillSelectorUnControlledComponent>;

/**
* @deprecated Try the new PillSelectorV2 component
*/
export { PillSelectorUnControlled };
165 changes: 165 additions & 0 deletions src/components/pillSelectorV2/__tests__/pillSelector.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { screen } from '@testing-library/react';
import * as React from 'react';

import { axe } from 'jest-axe';

import { ICONS } from '@/assets';
import {
PillSelectorSizeTypeV2,
PillSelectorVariantTypeV2,
} from '@/designSystem/kubit/components/variants';
import { renderProvider } from '@/tests/renderProvider/renderProvider.utility';
import { ROLES } from '@/types';

import { PillSelectorControlled } from '../pillSelectorControlled';
import { PillSelectorUnControlled } from '../pillSelectorUnControlled';
import { IPillSelectorControlled, IPillSelectorUnControlled, PillSelectorType } from '../types';

const mockProps: IPillSelectorUnControlled = {
variant: PillSelectorVariantTypeV2.DEFAULT,
size: PillSelectorSizeTypeV2.LARGE,
pills: [
{ label: { content: 'Pill 1' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 1' },
{ label: { content: 'Pill 2 ' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 2' },
{ label: { content: 'Pill 3' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 3' },
{ label: { content: 'Pill 4' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 4' },
],
};

describe('PillSelectorUncontrolled', () => {
it('Should render a set of pills, by default pill type is input checkbox (PillSelectorType.SELECTOR_MULTIPLE)', async () => {
const { container } = renderProvider(<PillSelectorUnControlled {...mockProps} />);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
expect(pills).toHaveLength(mockProps.pills?.length as number);

const results = await axe(container);
expect(container).toHTMLValidate();
expect(results).toHaveNoViolations();
});

it('When SELECTOR_SIMPLE, pills are render as checkbox', async () => {
const { container } = renderProvider(
<PillSelectorUnControlled {...mockProps} type={PillSelectorType.SELECTOR_SIMPLE} />
);

const pills = screen.getAllByRole(ROLES.RADIO);
expect(pills).toHaveLength(mockProps.pills?.length as number);

const results = await axe(container);
expect(container).toHTMLValidate();
expect(results).toHaveNoViolations();
});

it('Size prop is optional', () => {
const { size, ...restMockProps } = mockProps;
renderProvider(<PillSelectorUnControlled {...restMockProps} />);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
expect(pills).toHaveLength(mockProps.pills?.length as number);
});

it('When SELECTOR_MULTIPLE, value should be an array of the value of the selected options', () => {
const value = [mockProps.pills?.[0].value as string];
renderProvider(<PillSelectorUnControlled {...mockProps} defaultValue={value} />);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
expect(pills[0]).toBeChecked();
});

it('When SELECTOR_SIMPLE, value should be a value from the values of the selected options', () => {
const value = mockProps.pills?.[0].value as string;
renderProvider(
<PillSelectorUnControlled
{...mockProps}
defaultValue={value}
type={PillSelectorType.SELECTOR_SIMPLE}
/>
);

const pills = screen.getAllByRole(ROLES.RADIO);
expect(pills[0]).toBeChecked();
});

it('When SELECTOR_MULTIPLE, onChange should return an array of the value of the selected options', () => {
const value = [mockProps.pills?.[0].value as string];
const handleChange = jest.fn();
renderProvider(<PillSelectorUnControlled {...mockProps} onChange={handleChange} />);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
pills[0].click();
expect(handleChange).toHaveBeenCalledWith(value);
});

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', () => {
const value = [mockProps.pills?.[0].value as string];
const handleChange = jest.fn();
renderProvider(
<PillSelectorUnControlled {...mockProps} defaultValue={value} onChange={handleChange} />
);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
pills[0].click();
expect(handleChange).toHaveBeenCalledWith([]);
});

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', () => {
const value = [mockProps.pills?.[0].value as string];
const handleChange = jest.fn();
renderProvider(
<PillSelectorUnControlled {...mockProps} defaultValue={value} onChange={handleChange} />
);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
pills[1].click();
expect(handleChange).toHaveBeenCalledWith([
mockProps.pills?.[0].value as string,
mockProps.pills?.[1].value as string,
]);
});

it('When SELECTOR_SIMPLE, onChange should return a value from the values of the selected options', () => {
const value = mockProps.pills?.[0].value as string;
const handleChange = jest.fn();
renderProvider(
<PillSelectorUnControlled
{...mockProps}
type={PillSelectorType.SELECTOR_SIMPLE}
onChange={handleChange}
/>
);

const pills = screen.getAllByRole(ROLES.RADIO);
pills[0].click();
expect(handleChange).toHaveBeenCalledWith(value);
});
});

const mockPropsControlled: IPillSelectorControlled = {
variant: PillSelectorVariantTypeV2.DEFAULT,
size: PillSelectorSizeTypeV2.LARGE,
pills: [
{ label: { content: 'Pill 1' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 1' },
{ label: { content: 'Pill 2 ' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 2' },
{ label: { content: 'Pill 3' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 3' },
{ label: { content: 'Pill 4' }, icon: { icon: ICONS.ICON_PLACEHOLDER }, value: 'value 4' },
],
};

describe('PillSelectorcontrolled', () => {
it('Should render a set of pills, by default pill type is input checkbox (PillSelectorType.SELECTOR_MULTIPLE)', () => {
renderProvider(<PillSelectorControlled {...mockPropsControlled} />);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
expect(pills).toHaveLength(mockProps.pills?.length as number);
});

it('If onChange is not passed, when pressing over a pill it will not trigger any action', () => {
renderProvider(<PillSelectorControlled {...mockPropsControlled} />);

const pills = screen.getAllByRole(ROLES.CHECKBOX);
pills[0].click();

expect(pills[0]).not.toBeChecked();
});
});
13 changes: 13 additions & 0 deletions src/components/pillSelectorV2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type {
PillSelectorPillType as PillSelectorPillTypeV2,
PillSelectorValueType as PillSelectorValueTypeV2,
IPillSelectorUnControlled as IPillSelectorV2,
IPillSelectorControlled as IPillSelectorControlledV2,
PillSelectorPropsStylesType as PillSelectorPropsStylesTypeV2,
PillSelectorStylesType as PillSelectorStylesTypeV2,
} from './types';

export { PillSelectorType as PillSelectorTypeV2 } from './types/pillSelectorType';

export { PillSelectorUnControlled as PillSelectorV2 } from './pillSelectorUnControlled';
export { PillSelectorControlled as PillSelectorControlledV2 } from './pillSelectorControlled';
11 changes: 11 additions & 0 deletions src/components/pillSelectorV2/pillSelector.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from 'styled-components';

import { getStyles } from '@/utils';

import { PillSelectorPropsStylesType } from './types';

export const RootContainerStyled = styled.div<{
styles?: PillSelectorPropsStylesType;
}>`
${({ styles }) => getStyles(styles?.rootContainer)};
`;
65 changes: 65 additions & 0 deletions src/components/pillSelectorV2/pillSelectorControlled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as React from 'react';

import { STYLES_NAME } from '@/constants';
import { useStylesV2 } from '@/hooks';

import { PillSelectorStandAlone } from './pillSelectorStandAlone';
import {
IPillSelectorControlled,
PillSelectorType,
PillSelectorVariantPropsStylesType,
} from './types';

const PillSelectorControlledComponent = (
{
variant,
size,
ctv,
type = PillSelectorType.SELECTOR_MULTIPLE,
...props
}: IPillSelectorControlled,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const variantStyles = useStylesV2<PillSelectorVariantPropsStylesType>({
styleName: STYLES_NAME.PILL_SELECTOR_V2,
variantName: variant,
customTokens: ctv,
isOptional: true,
});

// Size prop is optional, else select the first size from the variantStyles
const styles = size ? variantStyles?.[size] : variantStyles?.[Object.keys(variantStyles)[0]];

const handlePillChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (!props.onChange) {
return;
}
if (type === PillSelectorType.SELECTOR_SIMPLE) {
props.onChange(event.target.value);
return;
}
// SELECTOR MULTIPLE
if (Array.isArray(props.value)) {
const valueIncluded = props.value.includes(event.target.value);
const newValue = valueIncluded
? props.value.filter(v => v !== event.target.value)
: [...props.value, event.target.value];
props.onChange(newValue);
return;
}
// When value === undefined or value is not array
props.onChange([event.target.value]);
};

return (
<PillSelectorStandAlone
ref={ref}
styles={styles}
type={type}
onPillChange={handlePillChange}
{...props}
/>
);
};

export const PillSelectorControlled = React.forwardRef(PillSelectorControlledComponent);
47 changes: 47 additions & 0 deletions src/components/pillSelectorV2/pillSelectorStandAlone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react';

import { PillTypeV2, PillV2 } from '@/components/pillV2';

import { RootContainerStyled } from './pillSelector.styled';
import { IPillSelectorStandAlone, PillSelectorType } from './types';

const PillSelectorStandAloneComponent = (
{ dataTestId = 'pillSelector', ...props }: IPillSelectorStandAlone,
ref: React.ForwardedRef<HTMLDivElement> | undefined | null
): JSX.Element => {
return (
<RootContainerStyled ref={ref} styles={props.styles}>
{props.pills?.map((pill, index) => {
const selected =
props.type === PillSelectorType.SELECTOR_MULTIPLE
? pill.value !== undefined &&
Array.isArray(props.value) &&
props.value.includes(pill.value)
: pill.value === props.value;
return (
<PillV2
key={index}
dataTestId={pill.dataTestId}
disabled={pill.disabled ?? props.disabled}
label={pill.label}
leftIcon={pill.icon}
name={props.name}
rightIcon={selected ? props.selectedIcon : undefined}
selected={selected}
size={pill.size ?? props.styles?.pill?.size}
type={
props.type === PillSelectorType.SELECTOR_MULTIPLE
? PillTypeV2.SELECTOR_MULTIPLE
: PillTypeV2.SELECTOR_SIMPLE
}
value={pill.value}
variant={pill.variant ?? props.styles?.pill?.variant}
onChange={props.onPillChange}
/>
);
})}
</RootContainerStyled>
);
};

export const PillSelectorStandAlone = React.forwardRef(PillSelectorStandAloneComponent);
Loading

0 comments on commit 5f61563

Please sign in to comment.