diff --git a/packages/components/tag/index.tsx b/packages/components/tag/index.tsx deleted file mode 100644 index 5b3e22cf86..0000000000 --- a/packages/components/tag/index.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { ReactSVG } from "react-svg"; -// @ts-expect-error TS(2307): Cannot find module 'PUBLIC_DIR/images/cross.react.... Remove this comment to see the full error message -import CrossIconReactSvgUrl from "PUBLIC_DIR/images/cross.react.svg?url"; -// @ts-expect-error TS(2307): Cannot find module 'PUBLIC_DIR/images/tag.react.sv... Remove this comment to see the full error message -import TagIconReactSvgUrl from "PUBLIC_DIR/images/tag.react.svg?url"; -import DropDown from "../drop-down"; -import DropDownItem from "../drop-down-item"; -import IconButton from "../icon-button"; -import Text from "../text"; - -import { - StyledTag, - StyledDropdownIcon, - StyledDropdownText, -} from "./styled-tag"; - -const Tag = ({ - tag, - label, - isNewTag, - isDisabled, - isDefault, - isLast, - onDelete, - onClick, - advancedOptions, - tagMaxWidth, - id, - className, - style, - icon -}: any) => { - const [openDropdown, setOpenDropdown] = React.useState(false); - - const tagRef = React.useRef(null); - const isMountedRef = React.useRef(true); - const onClickOutside = React.useCallback((e: any) => { - if (e?.target?.className?.includes("advanced-tag") || !isMountedRef.current) - return; - - setOpenDropdown(false); - }, []); - - React.useEffect(() => { - if (openDropdown) { - return document.addEventListener("click", onClickOutside); - } - - document.removeEventListener("click", onClickOutside); - return () => { - document.removeEventListener("click", onClickOutside); - }; - }, [openDropdown, onClickOutside]); - - React.useEffect(() => { - return () => { - isMountedRef.current = false; - }; - }, []); - - const openDropdownAction = (e: any) => { - if (e?.target?.className?.includes("backdrop-active")) return; - - setOpenDropdown(true); - }; - - const onClickAction = React.useCallback( - (e: any) => { - if (onClick && !isDisabled) { - onClick(e.target.dataset.tag); - } - }, - [onClick, isDisabled] - ); - - const onDeleteAction = React.useCallback( - (e: any) => { - if (e.target != tagRef.current && onDelete) { - onDelete && onDelete(tag); - } - }, - [onDelete, tag, tagRef] - ); - - return <> - {!!advancedOptions ? ( - <> - - // @ts-expect-error TS(2322): Type '{ children: string; className: string; "font... Remove this comment to see the full error message - - ... - - - // @ts-expect-error TS(2769): No overload matches this call. - - {advancedOptions.map((tag: any, index: any) => ( - - - - {tag} - - - ))} - - - ) : ( - - {icon ? ( - - ) : ( - <> - // @ts-expect-error TS(2322): Type '{ children: any; className: string; title: a... Remove this comment to see the full error message - - {label} - - {isNewTag && ( - - )} - - )} - - )} - ; -}; - -Tag.propTypes = { - /** Accepts the tag id */ - tag: PropTypes.string, - /** Accepts the tag label */ - label: PropTypes.string, - /** Accepts class */ - className: PropTypes.string, - /** Accepts id */ - id: PropTypes.string, - /** Accepts css style */ - style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), - /** Accepts the tag styles as new and adds the delete button */ - isNewTag: PropTypes.bool, - /** Accepts the tag styles as disabled and disables clicking */ - isDisabled: PropTypes.bool, - /** Accepts the function that is called when the tag is clicked */ - onClick: PropTypes.func, - /** Accepts the function that ist called when the tag delete button is clicked */ - onDelete: PropTypes.func, - /** Accepts the max width of the tag */ - tagMaxWidth: PropTypes.string, - /** Accepts the dropdown options */ - advancedOptions: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), -}; - -export default React.memo(Tag); diff --git a/packages/components/tag/tag.stories.tsx b/packages/components/tag/tag.stories.tsx deleted file mode 100644 index 11e584968c..0000000000 --- a/packages/components/tag/tag.stories.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from "react"; - -import Tag from "."; - -export default { - title: "Components/Tag", - component: Tag, - 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) => ; - -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 = { - tag: "script", - label: "Script", - isNewTag: false, - isDisabled: false, - onDelete: (tag: any) => console.log(tag), - onClick: (tag: any) => console.log(tag), - advancedOptions: null, - tagMaxWidth: "160px", - id: "", - className: "", - style: { color: "red" }, -}; - -export const WithDropDown = 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 -WithDropDown.args = { - tag: "script", - label: "Script", - isNewTag: false, - isDisabled: false, - onDelete: (tag: any) => console.log(tag), - onClick: (tag: any) => console.log(tag), - advancedOptions: ["Option 1", "Option 2"], -}; - -export const NewTag = 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 -NewTag.args = { - tag: "script", - label: "Script", - isNewTag: true, - isDisabled: false, - onDelete: (tag: any) => console.log(tag), - onClick: (tag: any) => console.log(tag), - advancedOptions: null, -}; - -export const DisabledTag = 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 -DisabledTag.args = { - tag: "script", - label: "No tag", - isNewTag: false, - isDisabled: true, - onDelete: (tag: any) => console.log(tag), - onClick: (tag: any) => console.log(tag), - advancedOptions: null, -}; diff --git a/packages/components/tag/tag.test.tsx b/packages/components/tag/tag.test.tsx deleted file mode 100644 index 54eb4927d1..0000000000 --- a/packages/components/tag/tag.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -// @ts-expect-error TS(7016): Could not find a declaration file for module 'enzy... Remove this comment to see the full error message -import { mount } from "enzyme"; -import Tag from "."; - -const baseProps = { - tag: "script", - label: "Script", - isNewTag: false, - isDisabled: false, - onDelete: (tag: any) => console.log(tag), - onClick: (tag: any) => console.log(tag), - advancedOptions: null, - tagMaxWidth: "160px", -}; - -// @ts-expect-error TS(2582): Cannot find name 'describe'. Do you need to instal... Remove this comment to see the full error message -describe("", () => { - // @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message - it("renders without error", () => { - const wrapper = mount(); - - // @ts-expect-error TS(2304): Cannot find name 'expect'. - expect(wrapper).toExist(); - }); - - // @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message - it("accepts id", () => { - const wrapper = mount(); - - // @ts-expect-error TS(2304): Cannot find name 'expect'. - expect(wrapper.prop("id")).toEqual("testId"); - }); - - // @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message - it("accepts className", () => { - const wrapper = mount(); - - // @ts-expect-error TS(2304): Cannot find name 'expect'. - expect(wrapper.prop("className")).toEqual("test"); - }); -}); diff --git a/packages/shared/components/index.ts b/packages/shared/components/index.ts index 5572ffd53d..0f8392cbcf 100644 --- a/packages/shared/components/index.ts +++ b/packages/shared/components/index.ts @@ -83,6 +83,7 @@ import { SaveCancelButtons } from "./save-cancel-buttons"; import { TimePicker } from "./time-picker"; import { ArticleItem } from "./article-item"; import { ToggleContent } from "./toggle-content"; +import { Tag } from "./tag"; export type { TFallbackAxisSideDirection, @@ -95,6 +96,7 @@ export type { }; export { + Tag, ToggleContent, ArticleItem, TimePicker, diff --git a/packages/components/tag/README.md b/packages/shared/components/tag/README.md similarity index 100% rename from packages/components/tag/README.md rename to packages/shared/components/tag/README.md diff --git a/packages/components/tag/styled-tag.ts b/packages/shared/components/tag/Tag.styled.ts similarity index 60% rename from packages/components/tag/styled-tag.ts rename to packages/shared/components/tag/Tag.styled.ts index 131a55cde7..0960f6c937 100644 --- a/packages/components/tag/styled-tag.ts +++ b/packages/shared/components/tag/Tag.styled.ts @@ -1,13 +1,20 @@ import styled, { css } from "styled-components"; import { ReactSVG } from "react-svg"; -import Text from "../text"; -import Base from "../themes/base"; +import { Text } from "../text"; -const StyledTag = styled.div` +import { Base } from "../../themes"; + +const StyledTag = styled.div<{ + tagMaxWidth?: string; + isLast?: boolean; + isDisabled?: boolean; + isNewTag?: boolean; + isDefault?: boolean; + isClickable?: boolean; +}>` width: fit-content; - // @ts-expect-error TS(2339): Property 'tagMaxWidth' does not exist on type 'The... Remove this comment to see the full error message max-width: ${(props) => (props.tagMaxWidth ? props.tagMaxWidth : "100%")}; display: flex; @@ -20,28 +27,23 @@ const StyledTag = styled.div` ${(props) => props.theme.interfaceDirection === "rtl" ? css` - // @ts-expect-error TS(2339): Property 'isLast' does not exist on type 'ThemedSt... Remove this comment to see the full error message margin-left: ${props.isLast ? "0" : "4px"}; ` : css` - // @ts-expect-error TS(2339): Property 'isLast' does not exist on type 'ThemedSt... Remove this comment to see the full error message margin-right: ${props.isLast ? "0" : "4px"}; `} background: ${(props) => - // @ts-expect-error TS(2339): Property 'isDisabled' does not exist on type 'Them... Remove this comment to see the full error message props.isDisabled ? props.theme.tag.disabledBackground - // @ts-expect-error TS(2339): Property 'isNewTag' does not exist on type 'Themed... Remove this comment to see the full error message : props.isNewTag - ? props.theme.tag.newTagBackground - : props.theme.tag.background}; + ? props.theme.tag.newTagBackground + : props.theme.tag.background}; border-radius: 6px; .tag-text { color: ${(props) => - // @ts-expect-error TS(2339): Property 'isDefault' does not exist on type 'Theme... Remove this comment to see the full error message props.isDefault ? props.theme.tag.defaultTagColor : props.theme.tag.color}; @@ -68,14 +70,12 @@ const StyledTag = styled.div` } ${(props) => - // @ts-expect-error TS(2339): Property 'isClickable' does not exist on type 'The... Remove this comment to see the full error message props.isClickable && - // @ts-expect-error TS(2339): Property 'isDisabled' does not exist on type 'Them... Remove this comment to see the full error message !props.isDisabled && css` cursor: pointer; &:hover { - background: ${(props) => props.theme.tag.hoverBackground}; + background: ${props.theme.tag.hoverBackground}; } `} `; diff --git a/packages/shared/components/tag/Tag.tsx b/packages/shared/components/tag/Tag.tsx new file mode 100644 index 0000000000..338f3023e5 --- /dev/null +++ b/packages/shared/components/tag/Tag.tsx @@ -0,0 +1,185 @@ +import React from "react"; +import { ReactSVG } from "react-svg"; + +import CrossIconReactSvgUrl from "PUBLIC_DIR/images/cross.react.svg?url"; +import TagIconReactSvgUrl from "PUBLIC_DIR/images/tag.react.svg?url"; + +import { DropDown } from "../drop-down"; +import { DropDownItem } from "../drop-down-item"; +import { IconButton } from "../icon-button"; +import { Text } from "../text"; + +import { + StyledTag, + StyledDropdownIcon, + StyledDropdownText, +} from "./Tag.styled"; +import { TagProps } from "./Tag.types"; + +export const TagPure = ({ + tag, + label, + isNewTag, + isDisabled, + isDefault, + isLast, + onDelete, + onClick, + advancedOptions, + tagMaxWidth, + id, + className, + style, + icon, +}: TagProps) => { + const [openDropdown, setOpenDropdown] = React.useState(false); + + const tagRef = React.useRef(null); + const isMountedRef = React.useRef(true); + + const onClickOutside = React.useCallback((e: Event) => { + const target = e.target as HTMLElement; + if (target.className?.includes("advanced-tag") || !isMountedRef.current) + return; + + setOpenDropdown(false); + }, []); + + React.useEffect(() => { + if (openDropdown) { + return document.addEventListener("click", onClickOutside); + } + + document.removeEventListener("click", onClickOutside); + return () => { + document.removeEventListener("click", onClickOutside); + }; + }, [openDropdown, onClickOutside]); + + React.useEffect(() => { + return () => { + isMountedRef.current = false; + }; + }, []); + + const openDropdownAction = (e: React.MouseEvent) => { + const target = e.target as HTMLDivElement; + if (target?.className?.includes("backdrop-active")) return; + + setOpenDropdown(true); + }; + + const onClickAction = React.useCallback( + (e: React.MouseEvent | React.ChangeEvent) => { + if (onClick && !isDisabled) { + const target = e.target as HTMLDivElement; + onClick(target.dataset.tag); + } + }, + [onClick, isDisabled], + ); + + const onDeleteAction = React.useCallback( + (e: React.MouseEvent) => { + if (e.target !== tagRef.current) { + onDelete?.(tag); + } + }, + [onDelete, tag, tagRef], + ); + + return advancedOptions ? ( + <> + + + ... + + + + {advancedOptions.map((t, index) => ( + + + + {tag} + + + ))} + + + ) : ( + + {icon ? ( + + ) : ( + <> + + {label} + + {isNewTag && ( + + )} + + )} + + ); +}; + +const Tag = React.memo(TagPure); + +export { Tag }; diff --git a/packages/shared/components/tag/Tag.types.tsx b/packages/shared/components/tag/Tag.types.tsx new file mode 100644 index 0000000000..9a0112b993 --- /dev/null +++ b/packages/shared/components/tag/Tag.types.tsx @@ -0,0 +1,27 @@ +export interface TagProps { + /** Accepts the tag id */ + tag: string; + /** Accepts the tag label */ + label?: string; + /** Accepts class */ + className?: string; + /** Accepts id */ + id?: string; + /** Accepts css style */ + style?: React.CSSProperties; + /** Accepts the tag styles as new and adds the delete button */ + isNewTag?: boolean; + /** Accepts the tag styles as disabled and disables clicking */ + isDisabled?: boolean; + /** Accepts the function that is called when the tag is clicked */ + onClick: (tag?: string) => void; + /** Accepts the function that ist called when the tag delete button is clicked */ + onDelete?: (tag?: string) => void; + /** Accepts the max width of the tag */ + tagMaxWidth?: string; + /** Accepts the dropdown options */ + advancedOptions?: React.ReactNode[]; + icon?: string; + isDefault?: boolean; + isLast?: boolean; +} diff --git a/packages/shared/components/tag/index.tsx b/packages/shared/components/tag/index.tsx new file mode 100644 index 0000000000..736cac7bef --- /dev/null +++ b/packages/shared/components/tag/index.tsx @@ -0,0 +1 @@ +export { Tag } from "./Tag"; diff --git a/packages/shared/components/tag/tag.stories.tsx b/packages/shared/components/tag/tag.stories.tsx new file mode 100644 index 0000000000..3fab998820 --- /dev/null +++ b/packages/shared/components/tag/tag.stories.tsx @@ -0,0 +1,67 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import { TagPure } from "./Tag"; + +const meta = { + title: "Components/Tag", + component: TagPure, + 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", + }, + }, +} satisfies Meta; +type Story = StoryObj; + +export default meta; + +export const Default: Story = { + args: { + tag: "script", + label: "Script", + isNewTag: false, + isDisabled: false, + onDelete: () => {}, + onClick: () => {}, + + tagMaxWidth: "160px", + id: "", + className: "", + style: { color: "red" }, + }, +}; + +export const WithDropDown: Story = { + args: { + tag: "script", + label: "Script", + isNewTag: false, + isDisabled: false, + onDelete: () => {}, + onClick: () => {}, + advancedOptions: ["Option 1", "Option 2"], + }, +}; + +export const NewTag: Story = { + args: { + tag: "script", + label: "Script", + isNewTag: true, + isDisabled: false, + onDelete: () => {}, + onClick: () => {}, + }, +}; + +export const DisabledTag: Story = { + args: { + tag: "script", + label: "No tag", + isNewTag: false, + isDisabled: true, + onDelete: () => {}, + onClick: () => {}, + }, +}; diff --git a/packages/shared/components/tag/tag.test.tsx b/packages/shared/components/tag/tag.test.tsx new file mode 100644 index 0000000000..dd4cfe84b1 --- /dev/null +++ b/packages/shared/components/tag/tag.test.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; + +import { Tag } from "./Tag"; + +const baseProps = { + tag: "script", + label: "Script", + isNewTag: false, + isDisabled: false, + onDelete: () => {}, + onClick: () => {}, + + tagMaxWidth: "160px", +}; + +describe("", () => { + it("renders without error", () => { + render(); + + expect(screen.getByTestId("tag")).toBeInTheDocument(); + }); + + // it("accepts id", () => { + // const wrapper = mount(); + + // // @ts-expect-error TS(2304): Cannot find name 'expect'. + // expect(wrapper.prop("id")).toEqual("testId"); + // }); + + // it("accepts className", () => { + // const wrapper = mount(); + + // // @ts-expect-error TS(2304): Cannot find name 'expect'. + // expect(wrapper.prop("className")).toEqual("test"); + // }); +});