Shared:Components:Tags Rewrite to typescript

This commit is contained in:
Akmal Isomadinov 2023-12-22 17:39:44 +05:00
parent a509bbd0d6
commit 2e6ab2d06a
9 changed files with 227 additions and 179 deletions

View File

@ -85,6 +85,7 @@ import { TimePicker } from "./time-picker";
import { ArticleItem } from "./article-item";
import { ToggleContent } from "./toggle-content";
import { Tag } from "./tag";
import { Tags } from "./tags";
export type {
TFallbackAxisSideDirection,
@ -98,6 +99,7 @@ export type {
export {
Tag,
Tags,
ToggleContent,
ArticleItem,
TimePicker,

View File

@ -0,0 +1,27 @@
import { Meta, StoryObj } from "@storybook/react";
import { Tags } from "./Tags";
type TagsType = typeof Tags;
type Story = StoryObj<TagsType>;
const meta: Meta<TagsType> = {
title: "Components/Tags",
component: Tags,
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/ZiW5KSwb4t7Tj6Nz5TducC/UI-Kit-DocSpace-1.0.0?type=design&node-id=62-2597&mode=design&t=TBNCKMQKQMxr44IZ-0",
},
},
};
export default meta;
export const Default: Story = {
args: {
tags: ["tag1", "tag2"],
columnCount: 2,
onSelectTag: () => {},
},
};

View File

@ -1,4 +1,4 @@
import styled, { css } from "styled-components";
import styled from "styled-components";
const StyledTags = styled.div`
width: 100%;

View File

@ -0,0 +1,163 @@
import React, { FC } from "react";
import { Tag } from "../tag";
import StyledTags from "./Tags.styled";
import { isTagType } from "./Tags.utils";
import type { TagType, TagsProps } from "./Tags.types";
export const Tags: FC<TagsProps> = ({
id,
tags,
style,
className,
columnCount,
onSelectTag,
}) => {
const [renderedTags, setRenderedTags] = React.useState<TagType[]>([]);
const tagsRef = React.useRef<HTMLDivElement>(null);
const updateRenderedTags = React.useCallback(() => {
if (tags && tagsRef) {
if (!columnCount) return;
const newTags: TagType[] = [];
const containerWidth = tagsRef.current?.offsetWidth ?? 0;
if (tags.length === 1) {
const firstTag = tags[0];
if (isTagType(firstTag) && firstTag?.isDefault) {
const tag = { ...firstTag, maxWidth: `100%` };
newTags.push(tag);
} else if (isTagType(firstTag) && firstTag?.isThirdParty) {
const tag = { ...firstTag, maxWidth: `36px` };
newTags.push(tag);
} else {
const label = isTagType(firstTag) ? firstTag.label : firstTag;
const tag = { label, maxWidth: `100%` };
newTags.push(tag);
}
return setRenderedTags(newTags);
}
if (
columnCount >= tags.length ||
(tags.length === 2 &&
isTagType(tags[0]) &&
tags[0]?.isThirdParty &&
isTagType(tags[1]) &&
tags[1]?.isDefault)
) {
const thirdPartyTagCount =
isTagType(tags[0]) && tags[0]?.isThirdParty ? 1 : 0;
const currentTagMaxWidth =
(containerWidth -
thirdPartyTagCount * 40 -
(tags.length - thirdPartyTagCount) * 4) /
(tags.length - thirdPartyTagCount);
const maxWidthPercent = Math.floor(
(currentTagMaxWidth / containerWidth) * 100,
);
for (let i = 0; i < tags.length; i += 1) {
const tag = tags[i];
const isTag = isTagType(tag);
if (isTag && tag?.isThirdParty) {
const tagNew = { ...tag, maxWidth: `36px` };
newTags.push(tagNew);
} else if (isTag && tag?.isDefault) {
const tagNew = { ...tag, maxWidth: `${maxWidthPercent}%` };
newTags.push(tagNew);
} else {
const tagNew = {
label: isTag ? tag.label : tag,
maxWidth: `${maxWidthPercent}%`,
};
newTags.push(tagNew);
}
}
} else {
const tagWithDropdown = {
label: "",
key: "selector",
advancedOptions: tags.slice(
columnCount,
tags.length,
) as React.ReactNode[],
};
const currentTagMaxWidth =
(containerWidth - columnCount * 4 - 35) / columnCount;
const maxWidthPercent = Math.floor(
(currentTagMaxWidth / containerWidth) * 100,
);
if (columnCount !== 0) {
for (let i = 0; i < columnCount; i += 1) {
const tag = tags[i];
const isTag = isTagType(tag);
if (isTag && tag?.isThirdParty) {
const tagNew = { ...tag, maxWidth: `36px` };
newTags.push(tagNew);
} else if (isTag && tag?.isDefault) {
const tagNew = { ...tag, maxWidth: `${maxWidthPercent}%` };
newTags.push(tagNew);
} else {
const tagNew = {
label: isTag ? tag.label : tag,
maxWidth: `${maxWidthPercent}%`,
};
newTags.push(tagNew);
}
}
}
newTags.push(tagWithDropdown);
newTags[newTags.length - 1].maxWidth = `35px`;
}
setRenderedTags(newTags);
}
}, [tags, tagsRef, columnCount]);
React.useEffect(() => {
updateRenderedTags();
}, [updateRenderedTags]);
return (
<StyledTags
data-testid="tags"
id={id}
className={className}
style={style}
ref={tagsRef}
>
{renderedTags?.length > 0 &&
renderedTags.map((tag, idx) => {
return (
<Tag
{...tags}
key={tag.label}
tag={tag.label}
advancedOptions={tag.advancedOptions}
tagMaxWidth={tag.maxWidth}
isNewTag={false}
label={tag.label}
onClick={onSelectTag}
isLast={idx === renderedTags.length - 1}
/>
);
})}
</StyledTags>
);
};

View File

@ -0,0 +1,28 @@
export type TagType = {
key?: string;
isDefault?: boolean;
isThirdParty?: boolean;
/** Accepts the tag label */
label: string;
/** Accepts the max width of the tag */
maxWidth?: string;
/** Accepts the dropdown options */
advancedOptions?: React.ReactNode[];
/** Accepts the tag styles as disabled and disables clicking */
isDisabled?: boolean;
};
export interface TagsProps {
/** Accepts id */
id?: string;
/** Accepts the tags */
tags: Array<TagType | string>;
/** Accepts class */
className?: string;
/** Accepts the tag column count */
columnCount: number;
/** Accepts css style */
style?: React.CSSProperties;
/** Accepts the function that is called when the tag is selected */
onSelectTag: (tag?: string) => void;
}

View File

@ -0,0 +1,5 @@
import type { TagType } from "./Tags.types";
export const isTagType = (tag: TagType | string): tag is TagType => {
return typeof tag === "object";
};

View File

@ -0,0 +1 @@
export { Tags } from "./Tags";

View File

@ -1,151 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import Tag from "../tag";
import StyledTags from "./StyledTags";
const Tags = ({
id,
className,
style,
tags,
columnCount,
onSelectTag
}: any) => {
const [renderedTags, setRenderedTags] = React.useState(null);
const tagsRef = React.useRef(null);
const updateRenderedTags = React.useCallback(() => {
if (tags && tagsRef) {
if (!columnCount) return;
const newTags = [];
// @ts-expect-error TS(2531): Object is possibly 'null'.
const containerWidth = tagsRef.current.offsetWidth;
if (tags.length === 1) {
if (tags[0]?.isDefault) {
const tag = { ...tags[0], maxWidth: `100%` };
newTags.push(tag);
} else if (tags[0]?.isThirdParty) {
const tag = { ...tags[0], maxWidth: `36px` };
newTags.push(tag);
} else {
const tag = { label: tags[0].label || tags[0], maxWidth: `100%` };
newTags.push(tag);
}
// @ts-expect-error TS(2345): Argument of type 'any[]' is not assignable to para... Remove this comment to see the full error message
return setRenderedTags(newTags);
}
if (
columnCount >= tags.length ||
(tags.length === 2 && tags[0]?.isThirdParty && tags[1]?.isDefault)
) {
const thirdPartyTagCount = tags[0]?.isThirdParty ? 1 : 0;
const currentTagMaxWidth =
(containerWidth -
thirdPartyTagCount * 40 -
(tags.length - thirdPartyTagCount) * 4) /
(tags.length - thirdPartyTagCount);
const maxWidthPercent = Math.floor(
(currentTagMaxWidth / containerWidth) * 100
);
for (let i = 0; i < tags.length; i++) {
if (tags[i]?.isThirdParty) {
const tag = { ...tags[i], maxWidth: `36px` };
newTags.push(tag);
} else if (tags[i]?.isDefault) {
const tag = { ...tags[i], maxWidth: `${maxWidthPercent}%` };
newTags.push(tag);
} else {
const tag = { label: tags[i], maxWidth: `${maxWidthPercent}%` };
newTags.push(tag);
}
}
} else {
const tagWithDropdown = {
key: "selector",
advancedOptions: tags.slice(columnCount, tags.length),
};
const currentTagMaxWidth =
(containerWidth - columnCount * 4 - 35) / columnCount;
const maxWidthPercent = Math.floor(
(currentTagMaxWidth / containerWidth) * 100
);
if (columnCount !== 0) {
for (let i = 0; i < columnCount; i++) {
if (tags[i]?.isThirdParty) {
const tag = { ...tags[i], maxWidth: `36px` };
newTags.push(tag);
} else if (tags[i]?.isDefault) {
const tag = { ...tags[i], maxWidth: `${maxWidthPercent}%` };
newTags.push(tag);
} else {
const tag = { label: tags[i], maxWidth: `${maxWidthPercent}%` };
newTags.push(tag);
}
}
}
newTags.push(tagWithDropdown);
newTags[newTags.length - 1].maxWidth = `35px`;
}
// @ts-expect-error TS(2345): Argument of type 'any[]' is not assignable to para... Remove this comment to see the full error message
setRenderedTags(newTags);
}
}, [tags, tagsRef, columnCount]);
React.useEffect(() => {
updateRenderedTags();
}, [tags, tagsRef, columnCount]);
return (
<StyledTags id={id} className={className} style={style} ref={tagsRef}>
// @ts-expect-error TS(2339): Property 'length' does not exist on type 'never'.
{renderedTags?.length > 0 &&
// @ts-expect-error TS(2531): Object is possibly 'null'.
renderedTags.map((tag: any, index: any) => (
<Tag
key={`${tag.label}_${index}`}
label={tag.label}
advancedOptions={tag.advancedOptions}
tagMaxWidth={tag.maxWidth}
tagMinWidth={tag.minWidth}
isNewTag={false}
onClick={onSelectTag}
// @ts-expect-error TS(2531): Object is possibly 'null'.
isLast={index === renderedTags.length - 1}
{...tag}
/>
))}
</StyledTags>
);
};
Tags.propTypes = {
/** Accepts the tags */
tags: PropTypes.array,
/** Accepts the tag column count */
columnCount: PropTypes.number,
/** Accepts class */
className: PropTypes.string,
/** Accepts id */
id: PropTypes.string,
/** Accepts css style */
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
/** Accepts the function that is called when the tag is selected */
onSelectTag: PropTypes.func,
};
export default Tags;

View File

@ -1,27 +0,0 @@
import React from "react";
import Tags from ".";
export default {
title: "Components/Tags",
component: Tags,
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/ZiW5KSwb4t7Tj6Nz5TducC/UI-Kit-DocSpace-1.0.0?type=design&node-id=62-2597&mode=design&t=TBNCKMQKQMxr44IZ-0",
},
},
};
const Template = (args: any) => <Tags {...args} />;
export const Default = Template.bind({});
// @ts-expect-error TS(2339): Property 'args' does not exist on type '(args: any... Remove this comment to see the full error message
Default.args = {
tags: ["tag1", "tag2"],
id: "",
className: "",
columnCount: 2,
style: {},
onSelectTag: (tags: any) => {},
};