Skip to content

Commit

Permalink
refactor(CollapseGroup): get rid of additional generics in component …
Browse files Browse the repository at this point in the history
…types
  • Loading branch information
nekitk committed Jul 26, 2021
1 parent edbb728 commit f2de9a2
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 111 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"ts-essentials": "^3.0.0",
"ts-jest": "^25.4.0",
"ts-node": "^8.8.2",
"typescript": "3.9.9",
"typescript": "4.3.5",
"typescript-eslint-parser": "^22.0.0"
},
"homepage": "https://consta-uikit.vercel.app"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ export function Playground() {
<CollapseGroup
style={{ maxWidth: 300 }}
items={items}
isAccordion={isAccordion}
{...(isAccordion
? {
isAccordion: true,
}
: {
isAccordion: false,
})}
size={size}
hoverEffect={hoverEffect}
view={view}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ const getItemLabel = (item: Item) => item.name;
const getItemContent = (item: Item) => item.text;
const getItemRightSide = () => defaultRightSide;

function renderComponent<ITEM, IS_ACCORDION extends boolean = false>(
props: CollapseGroupProps<ITEM, IS_ACCORDION>,
) {
function renderComponent<ITEM>(props: CollapseGroupProps<ITEM>) {
return render(<CollapseGroup data-testid={testId} {...props} />);
}

Expand Down
57 changes: 27 additions & 30 deletions src/components/CollapseGroup/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,66 +15,65 @@ export type DefaultItem = {
rightSide?: React.ReactNode | React.ReactNode[];
};

export type CollapseGroupPropOnOpen<IS_ACCORDION> = (params: {
e: React.MouseEvent<HTMLDivElement, MouseEvent>;
value: (IS_ACCORDION extends true ? number : number[]) | null;
}) => void;

export type CollapseGroupPropOpened<IS_ACCORDION> =
| (IS_ACCORDION extends true ? number : number[])
| null
| undefined;

type CollapseGroupPropGetItemLabel<ITEM> = (item: ITEM) => string;
type CollapseGroupPropGetItemContent<ITEM> = (item: ITEM) => React.ReactNode;
type CollapseGroupPropGetItemRightSide<ITEM> = (
item: ITEM,
) => React.ReactNode | React.ReactNode[] | undefined;

export type CollapseGroupProps<ITEM, IS_ACCORDION extends boolean> = PropsWithHTMLAttributesAndRef<
export type CollapseGroupProps<ITEM> = PropsWithHTMLAttributesAndRef<
{
items: ITEM[];
isAccordion?: IS_ACCORDION;
children?: never;
icon?: React.FC<IconProps>;
divider?: boolean;
size?: CollapsePropSize;
view?: CollapsePropView;
horizontalSpace?: CollapsePropHorizontalSpace;
hoverEffect?: boolean;
onOpen?: CollapseGroupPropOnOpen<IS_ACCORDION>;
opened?: CollapseGroupPropOpened<IS_ACCORDION>;
getItemLabel?: CollapseGroupPropGetItemLabel<ITEM>;
getItemContent?: CollapseGroupPropGetItemContent<ITEM>;
} & (
| {
closeIcon: React.FC<IconProps>;
directionIcon?: never;
closeDirectionIcon?: never;
isAccordion: true;
opened?: number | null;
onOpen?: (params: { value: number | null }) => void;
}
| {
closeIcon?: never;
directionIcon?: CollapseIconPropDirection;
closeDirectionIcon?: CollapseIconPropDirection;
isAccordion?: false;
opened?: number[] | null;
onOpen?: (params: { value: number[] | null }) => void;
}
) &
(
| {
closeIcon: React.FC<IconProps>;
directionIcon?: never;
closeDirectionIcon?: never;
}
| {
closeIcon?: never;
directionIcon?: CollapseIconPropDirection;
closeDirectionIcon?: CollapseIconPropDirection;
}
) &
(
| {
iconPosition?: 'left';
getItemRightSide?: CollapseGroupPropGetItemRightSide<ITEM>;
}
| {
iconPosition?: 'right';
iconPosition: 'right';
getItemRightSide?: never;
}
),
HTMLDivElement
> &
(ITEM extends { label: DefaultItem['label'] }
? {}
? { getItemLabel?: never }
: { getItemLabel: CollapseGroupPropGetItemLabel<ITEM> }) &
(ITEM extends { content: DefaultItem['content'] }
? {}
? { getItemContent?: never }
: { getItemContent: CollapseGroupPropGetItemContent<ITEM> });

export const defaultGetItemLabel: CollapseGroupPropGetItemLabel<DefaultItem> = (item) => item.label;
Expand All @@ -83,18 +82,16 @@ export const defaultGetItemContent: CollapseGroupPropGetItemContent<DefaultItem>
export const defaultGetItemRightSide: CollapseGroupPropGetItemContent<DefaultItem> = (item) =>
item.rightSide;

export type CollapseGroupComponent = <ITEM, IS_ACCORDION extends boolean = false>(
props: CollapseGroupProps<ITEM, IS_ACCORDION>,
export type CollapseGroupComponent = <ITEM>(
props: CollapseGroupProps<ITEM>,
) => React.ReactElement | null;

export type CollapseGroupRenderFunction = <ITEM, IS_ACCORDION extends boolean = false>(
props: CollapseGroupProps<ITEM, IS_ACCORDION>,
export type CollapseGroupRenderFunction = <ITEM>(
props: CollapseGroupProps<ITEM>,
ref: React.Ref<HTMLDivElement>,
) => React.ReactElement | null;

export function withDefaultGetters<ITEM, IS_ACCORDION extends boolean>(
props: CollapseGroupProps<ITEM, IS_ACCORDION>,
) {
export function withDefaultGetters<ITEM>(props: CollapseGroupProps<ITEM>) {
return {
...props,
getItemLabel: props.getItemLabel || defaultGetItemLabel,
Expand Down
48 changes: 23 additions & 25 deletions src/components/CollapseGroup/useChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,33 @@ import { useEffect, useState } from 'react';

import { useChoiceGroupIndexed } from '../../hooks/useChoiceGroupIndexed/useChoiceGroupIndexed';

import { CollapseGroupPropOnOpen, CollapseGroupPropOpened, CollapseGroupProps } from './helpers';
import { CollapseGroupProps } from './helpers';

type ChoiceGroupIndexedParams = {
value: CollapseGroupPropOpened<boolean>;
multiple: boolean;
callBack: CollapseGroupPropOnOpen<boolean>;
};

export const useChoice = <ITEM, IS_ACCORDION extends boolean>(
props: CollapseGroupProps<ITEM, IS_ACCORDION>,
) => {
const [openedKeys, setOpenedKeys] = useState<typeof props.opened>(props.opened);

const callBack: CollapseGroupPropOnOpen<Exclude<typeof props.isAccordion, undefined>> = (
params,
) => {
setOpenedKeys(params.value);
props.onOpen?.(params);
};

const choiceGroupIndexedParams: ChoiceGroupIndexedParams = {
value: openedKeys,
multiple: !props.isAccordion,
callBack,
};
export const useChoice = <ITEM>(props: CollapseGroupProps<ITEM>) => {
const [openedKeys, setOpenedKeys] = useState(props.opened);

useEffect(() => {
setOpenedKeys(props.opened);
}, [props.opened]);

return useChoiceGroupIndexed(choiceGroupIndexedParams);
return useChoiceGroupIndexed(
props.isAccordion
? {
value: (openedKeys as typeof props.opened) ?? null,
multiple: false,
callBack: (params) => {
setOpenedKeys(params.value);
props.onOpen?.(params);
},
isNullableValue: true,
}
: {
value: (openedKeys as typeof props.opened) ?? [],
multiple: true,
callBack: (params) => {
setOpenedKeys(params.value);
props.onOpen?.(params);
},
},
);
};
2 changes: 1 addition & 1 deletion src/components/Table/__stories__/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ export const withCustomFilters = createStory(
() => {
return (
<div className={cnTableStories()}>
<Table {...getKnobs({ filters: customFilters })} />
<Table {...getKnobs<typeof tableData.rows[number]>({ filters: customFilters })} />
</div>
);
},
Expand Down
76 changes: 30 additions & 46 deletions src/hooks/useChoiceGroupIndexed/useChoiceGroupIndexed.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
import React from 'react';

type ValueMultiple = number[] | null;
type ValueNotMultiple<IS_NULLABLE_VALUE> = IS_NULLABLE_VALUE extends true ? number | null : number;

type UseChoiceGroupIndexedParams<
MULTIPLE extends boolean = false,
IS_NULLABLE_VALUE extends boolean = false,
EVENT = React.MouseEvent<HTMLDivElement, MouseEvent>
> = {
value: (MULTIPLE extends true ? ValueMultiple : ValueNotMultiple<IS_NULLABLE_VALUE>) | undefined;
multiple: MULTIPLE;
callBack: (props: {
e: EVENT;
value: MULTIPLE extends true ? ValueMultiple : ValueNotMultiple<IS_NULLABLE_VALUE>;
}) => void;
isNullableValue?: IS_NULLABLE_VALUE;
};

function isMultiple<EVENT>(
params: UseChoiceGroupIndexedParams<boolean, boolean, EVENT>,
): params is UseChoiceGroupIndexedParams<true, boolean, EVENT> {
return !!params.multiple;
}

function isNotMultiple<EVENT>(
params: UseChoiceGroupIndexedParams<boolean, boolean, EVENT>,
): params is UseChoiceGroupIndexedParams<false, boolean, EVENT> {
return !params.multiple;
}
type ChangeEvent = React.MouseEvent<HTMLDivElement, MouseEvent>;
type CallBack<VALUE> = (props: { e: ChangeEvent; value: VALUE }) => void;

type UseChoiceGroupIndexedParams =
| {
multiple: true;
isNullableValue?: never;
value: number[] | null;
callBack: CallBack<number[] | null>;
}
| {
multiple: false;
isNullableValue: true;
value: number | null;
callBack: CallBack<number | null>;
}
| {
multiple: false;
isNullableValue: false;
value: number;
callBack: CallBack<number>;
};

function formatValue(value: number[] | number | null | undefined) {
if (Array.isArray(value)) {
Expand All @@ -39,17 +33,13 @@ function formatValue(value: number[] | number | null | undefined) {
return [];
}

export function useChoiceGroupIndexed<
MULTIPLE extends boolean,
IS_NULLABLE_VALUE extends boolean,
EVENT
>(props: UseChoiceGroupIndexedParams<MULTIPLE, IS_NULLABLE_VALUE, EVENT>) {
export function useChoiceGroupIndexed(props: UseChoiceGroupIndexedParams) {
const value = formatValue(props.value);

const getChecked = (index: number) => value.includes(index);

const getOnChange = (index: number) => (e: EVENT) => {
if (isMultiple(props)) {
const getOnChange = (index: number) => (e: ChangeEvent) => {
if (props.multiple) {
let newValue: number[] | null;
if (getChecked(index)) {
newValue = value.filter((item) => item !== index);
Expand All @@ -58,19 +48,13 @@ export function useChoiceGroupIndexed<
newValue = null;
}
} else {
newValue = [...value];
newValue.push(index);
newValue = [...value, index];
}
props.callBack({ e, value: newValue });
return;
}

if (isNotMultiple(props)) {
if (props.isNullableValue && getChecked(index)) {
props.callBack({ e, value: null });
} else {
props.callBack({ e, value: index });
}
} else if (props.isNullableValue && getChecked(index)) {
props.callBack({ e, value: null });
} else {
props.callBack({ e, value: index });
}
};

Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17828,10 +17828,10 @@ typescript-estree@18.0.0:
lodash.unescape "4.0.1"
semver "5.5.0"

typescript@3.9.9:
version "3.9.9"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674"
integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==
typescript@4.3.5:
version "4.3.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==

uglify-js@3.4.x:
version "3.4.10"
Expand Down

0 comments on commit f2de9a2

Please sign in to comment.