├── .eslintcache ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── Step-By-Step.md ├── dist ├── ToastContainer.tsx ├── ToastMessage.tsx └── ToastProvider.tsx ├── images ├── toast.PNG └── toast.gif ├── images_tutorial └── PrepareProjectStructure.png ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt ├── setup.js ├── src ├── App.css ├── App.tsx ├── CSS │ └── Tailwind.css ├── Components │ ├── Avatar │ │ └── Avatar.tsx │ ├── Buttons │ │ └── StayledButton.tsx │ ├── Select │ │ ├── Select.tsx │ │ └── SelectOption.tsx │ ├── Toast │ │ ├── ToastContainer.tsx │ │ ├── ToastMessage.tsx │ │ └── ToastProvider.tsx │ └── ToastDummyMessage.tsx ├── HomePage │ ├── Home.tsx │ └── Layout.tsx ├── Hooks │ └── useOutsideClick.tsx ├── Providers.tsx ├── index.tsx └── react-app-env.d.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\index.tsx":"1","C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\App.tsx":"2","C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\FileDragDrop\\FileDragDrop.tsx":"3","C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\constants.tsx":"4","C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\Progress\\ProgressBar.tsx":"5","C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\Buttons\\StayledButton.tsx":"6","C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\Buttons\\CancleButton.tsx":"7","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\index.tsx":"8","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\App.tsx":"9","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Providers.tsx":"10","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Toast\\ToastProvider.tsx":"11","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Toast\\ToastContainer.tsx":"12","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Toast\\ToastMessage.tsx":"13","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\HomePage\\Layout.tsx":"14","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\HomePage\\Home.tsx":"15","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Buttons\\StayledButton.tsx":"16","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Avatar\\Avatar.tsx":"17","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Select\\Select.tsx":"18","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Select\\SelectOption.tsx":"19","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Hooks\\useOutsideClick.tsx":"20","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\ToastDummyMessage.tsx":"21"},{"size":222,"mtime":1606942061793,"results":"22","hashOfConfig":"23"},{"size":1140,"mtime":1606951527245,"results":"24","hashOfConfig":"23"},{"size":10921,"mtime":1606995507533,"results":"25","hashOfConfig":"23"},{"size":1692,"mtime":1606941509511,"results":"26","hashOfConfig":"23"},{"size":2049,"mtime":1606943849676,"results":"27","hashOfConfig":"23"},{"size":2381,"mtime":1606942321522,"results":"28","hashOfConfig":"23"},{"size":590,"mtime":1606944214496,"results":"29","hashOfConfig":"23"},{"size":222,"mtime":1606942061793,"results":"30","hashOfConfig":"31"},{"size":378,"mtime":1607297617718,"results":"32","hashOfConfig":"31"},{"size":1065,"mtime":1607376150463,"results":"33","hashOfConfig":"31"},{"size":4609,"mtime":1607375607755,"results":"34","hashOfConfig":"31"},{"size":2090,"mtime":1607376259386,"results":"35","hashOfConfig":"31"},{"size":3366,"mtime":1607283079145,"results":"36","hashOfConfig":"31"},{"size":230,"mtime":1607376097993,"results":"37","hashOfConfig":"31"},{"size":6922,"mtime":1607376067931,"results":"38","hashOfConfig":"31"},{"size":2569,"mtime":1607283076620,"results":"39","hashOfConfig":"31"},{"size":1811,"mtime":1607283077366,"results":"40","hashOfConfig":"31"},{"size":2294,"mtime":1607372826373,"results":"41","hashOfConfig":"31"},{"size":891,"mtime":1607293554585,"results":"42","hashOfConfig":"31"},{"size":457,"mtime":1607280588831,"results":"43","hashOfConfig":"31"},{"size":1481,"mtime":1607371512628,"results":"44","hashOfConfig":"31"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"47"},"102f6c5",{"filePath":"48","messages":"49","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"50","messages":"51","errorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"52","messages":"53","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"47"},{"filePath":"54","messages":"55","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"47"},{"filePath":"56","messages":"57","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"47"},{"filePath":"58","messages":"59","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"47"},{"filePath":"60","messages":"61","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},"h9qkjs",{"filePath":"63","messages":"64","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"65","messages":"66","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"67","messages":"68","errorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"69","usedDeprecatedRules":"62"},{"filePath":"70","messages":"71","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"72","messages":"73","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"74","usedDeprecatedRules":"75"},{"filePath":"76","messages":"77","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"78","messages":"79","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"80","usedDeprecatedRules":"62"},{"filePath":"81","messages":"82","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"83","messages":"84","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"85","messages":"86","errorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"87","usedDeprecatedRules":"62"},{"filePath":"88","messages":"89","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"90","messages":"91","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"92","usedDeprecatedRules":"62"},{"filePath":"93","messages":"94","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\index.tsx",[],["95","96"],"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\App.tsx",["97"],"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\FileDragDrop\\FileDragDrop.tsx",["98","99","100"],"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\constants.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\Progress\\ProgressBar.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\Buttons\\StayledButton.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\Drag-Drop-File\\File-DragDropper\\src\\Components\\Buttons\\CancleButton.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\index.tsx",[],["101","102"],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\App.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Providers.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Toast\\ToastProvider.tsx",["103","104","105","106"],"/* Author: Dalibor Kundrat https://github.com/damikun */\r\n\r\nimport { IconProp } from \"@fortawesome/fontawesome-svg-core\";\r\nimport React, { useCallback, useContext, useState } from \"react\";\r\nimport ToastContainer, { ToastContainerProps } from \"./ToastContainer\";\r\nimport { Truncate } from \"./ToastMessage\";\r\n\r\n/////////////////////////////////////\r\n/// Types\r\n/////////////////////////////////////\r\n\r\nexport type ToastProviderProps = {\r\n children: React.ReactNode;\r\n} & ToastContainerProps;\r\n\r\ntype TostMessageType = \"Info\" | \"Success\" | \"Warning\" | \"Error\";\r\n\r\nexport type Toast = {\r\n id: string;\r\n lifetime: number;\r\n message: string | React.ReactNode;\r\n type?: TostMessageType;\r\n truncate?: Truncate;\r\n icon?: IconProp;\r\n header?: string;\r\n};\r\n\r\nexport type ToastContextType = {\r\n data: Array;\r\n pushError(message: string, lifetime?: number, truncate?: Truncate): void;\r\n pushWarning(message: string, lifetime?: number, truncate?: Truncate): void;\r\n pushSuccess(message: string, lifetime?: number, truncate?: Truncate): void;\r\n pushInfo(message: string, lifetime?: number, truncate?: Truncate): void;\r\n push(\r\n message: string,\r\n type: TostMessageType,\r\n lifetime?: number,\r\n truncate?: Truncate\r\n ): void;\r\n pushCustom(\r\n message: string | React.ReactNode,\r\n lifetime: number,\r\n truncate?: Truncate,\r\n icon?: IconProp | React.ReactNode\r\n ): void;\r\n remove(id: string): void;\r\n};\r\n\r\n/////////////////////////////////////\r\n/// Global and Helpers\r\n/////////////////////////////////////\r\n\r\nexport const ToastContext = React.createContext(\r\n undefined\r\n);\r\n\r\nfunction uuidv4() {\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\r\n var r = (Math.random() * 16) | 0,\r\n v = c == \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n}\r\n\r\nexport const useToast = () => useContext(ToastContext);\r\n\r\nconst DEFAULT_INTERVAL = 2500;\r\n\r\n/////////////////////////////////////\r\n/// Implementation\r\n/////////////////////////////////////\r\n\r\nexport default function ToastProvider({\r\n children,\r\n variant,\r\n}: ToastProviderProps) {\r\n const [data, setData] = useState>([]);\r\n\r\n const Push = useCallback(\r\n (\r\n message: string,\r\n type: TostMessageType,\r\n lifetime?: number,\r\n truncate?: Truncate\r\n ) => {\r\n if (message) {\r\n const new_item: Toast = {\r\n id: uuidv4(),\r\n message: message,\r\n type: type,\r\n lifetime: lifetime ? lifetime : DEFAULT_INTERVAL,\r\n truncate: truncate,\r\n };\r\n\r\n setData((prevState) => [...prevState, new_item]);\r\n }\r\n },\r\n [setData, data]\r\n );\r\n\r\n const PushCustom = useCallback(\r\n (\r\n message: string | React.ReactNode,\r\n lifetime?: number,\r\n truncate?: Truncate,\r\n icon?: IconProp\r\n ) => {\r\n if (message) {\r\n const new_item: Toast = {\r\n id: uuidv4(),\r\n message: message,\r\n lifetime: lifetime ? lifetime : DEFAULT_INTERVAL,\r\n truncate: truncate,\r\n icon: icon,\r\n type: undefined,\r\n };\r\n\r\n setData((prevState) => [...prevState, new_item]);\r\n }\r\n },\r\n [setData, data]\r\n );\r\n\r\n const PushError = useCallback(\r\n (message: string, lifetime?: number, truncate?: Truncate) =>\r\n Push(message, \"Error\", lifetime, truncate),\r\n [Push]\r\n );\r\n const PushWarning = useCallback(\r\n (message: string, lifetime?: number, truncate?: Truncate) =>\r\n Push(message, \"Warning\", lifetime, truncate),\r\n [Push]\r\n );\r\n const PushSuccess = useCallback(\r\n (message: string, lifetime?: number, truncate?: Truncate) =>\r\n Push(message, \"Success\", lifetime, truncate),\r\n [Push]\r\n );\r\n const PushInfo = useCallback(\r\n (message: string, lifetime?: number, truncate?: Truncate) =>\r\n Push(message, \"Info\", lifetime, truncate),\r\n [Push]\r\n );\r\n\r\n const ToastContexd = useCallback(() => {\r\n return {\r\n data: data,\r\n pushError: PushError,\r\n pushWarning: PushWarning,\r\n pushSuccess: PushSuccess,\r\n pushInfo: PushInfo,\r\n push: Push,\r\n pushCustom: PushCustom,\r\n\r\n async remove(id: string) {\r\n setData((prevState) => prevState.filter((e) => e.id != id));\r\n },\r\n };\r\n }, [\r\n data,\r\n setData,\r\n PushError,\r\n PushWarning,\r\n PushSuccess,\r\n PushInfo,\r\n Push,\r\n PushCustom,\r\n ]);\r\n\r\n return (\r\n \r\n \r\n {children}\r\n \r\n );\r\n}\r\n","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Toast\\ToastContainer.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Toast\\ToastMessage.tsx",["107","108"],"/* Author: Dalibor Kundrat https://github.com/damikun */\r\n\r\nimport React, { useEffect } from \"react\";\r\nimport { Toast } from \"./ToastProvider\";\r\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\r\nimport {\r\n faTimes,\r\n faExclamationCircle,\r\n faCheck,\r\n faInfoCircle,\r\n} from \"@fortawesome/free-solid-svg-icons\";\r\nimport clsx from \"clsx\";\r\n\r\nconst VARIANTS = {\r\n Info: {\r\n base: \"bg-white border-blue-500\",\r\n iconstyle: \"text-blue-500 \",\r\n icon: faInfoCircle,\r\n name: \"Info\",\r\n },\r\n\r\n Error: {\r\n base: \"bg-white border-red-500 \",\r\n iconstyle: \"text-red-500 \",\r\n icon: faExclamationCircle,\r\n name: \"Error\",\r\n },\r\n\r\n Warning: {\r\n base: \"bg-white border-yellow-500\",\r\n iconstyle: \"text-yellow-500 \",\r\n icon: faExclamationCircle,\r\n name: \"Warning\",\r\n },\r\n\r\n Success: {\r\n base: \"bg-white border-green-500\",\r\n iconstyle: \"text-green-500 \",\r\n icon: faCheck,\r\n name: \"Success\",\r\n },\r\n};\r\n\r\nexport type Truncate =\r\n | \"truncate-1-lines\"\r\n | \"truncate-2-lines\"\r\n | \"truncate-3-lines\";\r\n\r\nexport type ToastMessage = {\r\n id: string;\r\n lifetime?: number;\r\n variant?: keyof typeof VARIANTS | undefined;\r\n onRemove?: (id: string) => void;\r\n truncate?: Truncate;\r\n} & Toast;\r\n\r\nexport default function ToastMessage({\r\n id,\r\n header,\r\n message,\r\n lifetime,\r\n onRemove,\r\n truncate = \"truncate-1-lines\",\r\n icon,\r\n type,\r\n}: ToastMessage) {\r\n const Var = type\r\n ? VARIANTS[type]\r\n : {\r\n base: \"bg-white border-gray-600 \",\r\n iconstyle: \"\",\r\n icon: icon,\r\n name: header,\r\n };\r\n\r\n useEffect(() => {\r\n if (lifetime && onRemove) {\r\n setTimeout(() => {\r\n onRemove(id);\r\n }, lifetime);\r\n }\r\n }, [lifetime]);\r\n\r\n return (\r\n \r\n
\r\n {Var.icon && (\r\n \r\n \r\n
\r\n )}\r\n\r\n
\r\n
{Var.name}
\r\n \r\n {message}\r\n

\r\n
\r\n onRemove && onRemove(id)}\r\n className={clsx(\r\n \"w-10 h-12 mr-2 items-center mx-auto\",\r\n \"text-center leading-none text-lg\"\r\n )}\r\n >\r\n \r\n \r\n \r\n \r\n );\r\n}\r\n",["109","110"],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\HomePage\\Layout.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\HomePage\\Home.tsx",["111"],"import React, { useEffect, useState } from \"react\";\r\nimport clsx from \"clsx\";\r\nimport StayledButton from \"../Components/Buttons/StayledButton\";\r\nimport { useToast } from \"../Components/Toast/ToastProvider\";\r\nimport Select from \"../Components/Select/Select\";\r\nimport SelectOptions from \"../Components/Select/SelectOption\";\r\nimport ToastDummyMessage from \"../Components/ToastDummyMessage\";\r\nimport { useGlobalState } from \"../Providers\";\r\n\r\nconst text = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum`;\r\n\r\nexport default function HomePage() {\r\n const toast = useToast();\r\n const [interval, setInterval] = useState(1500);\r\n const [position, setPosition] = useState(\"top_right\");\r\n const globalState = useGlobalState();\r\n\r\n useEffect(() => {\r\n //@ts-ignore\r\n globalState?.setPosition({ variant: position });\r\n }, [position]);\r\n\r\n return (\r\n \r\n
\r\n
\r\n
\r\n \r\n\r\n \r\n setInterval(value as number)}\r\n >\r\n 1000 ms\r\n 2500 ms\r\n 5000 ms\r\n \r\n
\r\n
\r\n
\r\n \r\n \r\n setPosition(value as string)}\r\n >\r\n Top-Right\r\n Top-Middle\r\n Top-Left\r\n Bottom-Right\r\n \r\n Bottom-Middle\r\n \r\n Bottom-Left\r\n \r\n
\r\n
\r\n \r\n
\r\n \r\n \r\n toast?.pushError(\"Oppps Error\", interval)}\r\n >\r\n Error\r\n \r\n \r\n toast?.pushWarning(\r\n \"Warning appear\",\r\n interval,\r\n \"truncate-2-lines\"\r\n )\r\n }\r\n >\r\n Warning\r\n \r\n toast?.pushSuccess(\"Action success\", interval)}\r\n >\r\n Success\r\n \r\n toast?.pushInfo(\"Info message\", interval)}\r\n >\r\n Info\r\n \r\n toast?.pushCustom(, interval)}\r\n >\r\n Custom\r\n \r\n
\r\n \r\n
\r\n \r\n \r\n \r\n toast?.pushInfo(text, interval, \"truncate-1-lines\")\r\n }\r\n >\r\n 1 line\r\n \r\n \r\n toast?.pushInfo(text, interval, \"truncate-2-lines\")\r\n }\r\n >\r\n 2 lines\r\n \r\n \r\n toast?.pushInfo(text, interval, \"truncate-3-lines\")\r\n }\r\n >\r\n 3 lines\r\n \r\n
\r\n \r\n \r\n \r\n );\r\n}\r\n\r\n/////////////////////////////////\r\n/////////////////////////////////\r\n\r\ntype SectionHeaderProps = {\r\n name: string;\r\n justify_end?: boolean;\r\n};\r\nfunction SectionHeader({ justify_end, name }: SectionHeaderProps) {\r\n return (\r\n \r\n {name}\r\n \r\n );\r\n}\r\nfunction GetPositionText(value: string) {\r\n switch (value) {\r\n case \"top_right\":\r\n return \"Top-Right\";\r\n case \"top_left\":\r\n return \"Top-Left\";\r\n case \"top_middle\":\r\n return \"Top-Middle\";\r\n case \"bottom_right\":\r\n return \"Bottom-Right\";\r\n case \"bottom_left\":\r\n return \"Bottom-Left\";\r\n case \"bottom_middle\":\r\n return \"Bottom-Middle\";\r\n }\r\n}\r\n","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Buttons\\StayledButton.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Avatar\\Avatar.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Select\\Select.tsx",["112","113","114"],"/* Author: Dalibor Kundrat https://github.com/damikun */\r\n\r\nimport clsx from \"clsx\";\r\nimport React, { useCallback, useEffect, useRef } from \"react\";\r\nimport { useState } from \"react\";\r\nimport { useOutsideClick } from \"../../Hooks/useOutsideClick\";\r\n\r\ntype SelectProps = {\r\n initinal: number | string;\r\n value?: string;\r\n children: React.ReactNode;\r\n onChange?: (value: string | number) => void;\r\n justify_end?: boolean;\r\n};\r\n\r\nexport type SelectContext = {\r\n onSelect(value: string | number): void;\r\n};\r\n\r\nexport const SelectContext = React.createContext(\r\n undefined\r\n);\r\n\r\nexport default function Select({\r\n children,\r\n onChange,\r\n initinal,\r\n justify_end = false,\r\n value,\r\n}: SelectProps) {\r\n const [open, setIsOpen] = useState(false);\r\n\r\n const [selected, setSelected] = useState(initinal);\r\n\r\n useEffect(() => {\r\n onChange && onChange(selected);\r\n }, [selected, onChange]);\r\n\r\n const ref = useRef(null);\r\n\r\n function onClickOutside() {\r\n setIsOpen(false);\r\n }\r\n\r\n useOutsideClick(ref, onClickOutside);\r\n\r\n const handleSlect = useCallback((value: string | number) => {\r\n setSelected(value);\r\n setIsOpen(false);\r\n }, []);\r\n\r\n const Context = useCallback(() => {\r\n return {\r\n onSelect: handleSlect,\r\n };\r\n }, []);\r\n\r\n return (\r\n \r\n {\r\n e.preventDefault();\r\n setIsOpen((e) => !e);\r\n }}\r\n className={clsx(\r\n \"flex flex-col w-full border-2 border-transparent\",\r\n \"hover:border-gray-300 transition duration-150\",\r\n \"focus:border-blue-500 rounded-md cursor-pointer\"\r\n )}\r\n >\r\n
\r\n {value ? value : selected}\r\n
\r\n
\r\n {open == true && (\r\n \r\n {children}\r\n
\r\n )}\r\n \r\n \r\n
\r\n );\r\n}\r\n","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\Select\\SelectOption.tsx",[],"C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Hooks\\useOutsideClick.tsx",["115"],"import { useEffect } from \"react\";\r\n\r\nexport function useOutsideClick(ref: any, onEvent: () => void) {\r\n useEffect(() => {\r\n function handleClickOutside(event: any) {\r\n if (ref.current && !ref.current.contains(event.target)) {\r\n onEvent();\r\n }\r\n }\r\n\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n\r\n return () => {\r\n document.removeEventListener(\"mousedown\", handleClickOutside);\r\n };\r\n }, [ref]);\r\n}\r\n","C:\\Users\\dakupc\\Documents\\.NetDev\\React Toast Component\\src\\Components\\ToastDummyMessage.tsx",[],{"ruleId":"116","replacedBy":"117"},{"ruleId":"118","replacedBy":"119"},{"ruleId":"120","severity":1,"message":"121","line":9,"column":10,"nodeType":"122","messageId":"123","endLine":9,"endColumn":15},{"ruleId":"124","severity":1,"message":"125","line":59,"column":3,"nodeType":"126","endLine":80,"endColumn":4},{"ruleId":"124","severity":1,"message":"127","line":246,"column":28,"nodeType":"122","endLine":246,"endColumn":35},{"ruleId":"124","severity":1,"message":"128","line":255,"column":6,"nodeType":"129","endLine":255,"endColumn":44,"suggestions":"130"},{"ruleId":"116","replacedBy":"131"},{"ruleId":"118","replacedBy":"132"},{"ruleId":"133","severity":1,"message":"134","line":60,"column":13,"nodeType":"135","messageId":"136","endLine":60,"endColumn":15},{"ruleId":"124","severity":1,"message":"137","line":98,"column":5,"nodeType":"129","endLine":98,"endColumn":20,"suggestions":"138"},{"ruleId":"124","severity":1,"message":"137","line":121,"column":5,"nodeType":"129","endLine":121,"endColumn":20,"suggestions":"139"},{"ruleId":"133","severity":1,"message":"140","line":156,"column":61,"nodeType":"135","messageId":"136","endLine":156,"endColumn":63},{"ruleId":"141","severity":1,"message":"142","line":57,"column":25,"nodeType":"122","messageId":"143","endLine":57,"endColumn":37},{"ruleId":"124","severity":1,"message":"144","line":82,"column":6,"nodeType":"129","endLine":82,"endColumn":16,"suggestions":"145"},{"ruleId":"116","replacedBy":"146"},{"ruleId":"118","replacedBy":"147"},{"ruleId":"124","severity":1,"message":"148","line":21,"column":6,"nodeType":"129","endLine":21,"endColumn":16,"suggestions":"149"},{"ruleId":"141","severity":1,"message":"150","line":20,"column":14,"nodeType":"122","messageId":"143","endLine":20,"endColumn":27},{"ruleId":"124","severity":1,"message":"151","line":56,"column":6,"nodeType":"129","endLine":56,"endColumn":8,"suggestions":"152"},{"ruleId":"133","severity":1,"message":"134","line":76,"column":17,"nodeType":"135","messageId":"136","endLine":76,"endColumn":19},{"ruleId":"124","severity":1,"message":"153","line":16,"column":6,"nodeType":"129","endLine":16,"endColumn":11,"suggestions":"154"},"no-native-reassign",["155"],"no-negated-in-lhs",["156"],"@typescript-eslint/no-unused-vars","'state' is assigned a value but never used.","Identifier","unusedVar","react-hooks/exhaustive-deps","The 'upload' function makes the dependencies of useCallback Hook (at line 218) change on every render. Move it inside the useCallback callback. Alternatively, wrap the definition of 'upload' in its own useCallback() Hook.","FunctionDeclaration","The ref value 'divRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'divRef.current' to a variable inside the effect, and use that variable in the cleanup function.","React Hook useEffect has a missing dependency: 'onDrop'. Either include it or remove the dependency array.","ArrayExpression",["157"],["155"],["156"],"eqeqeq","Expected '===' and instead saw '=='.","BinaryExpression","unexpected","React Hook useCallback has an unnecessary dependency: 'data'. Either exclude it or remove the dependency array.",["158"],["159"],"Expected '!==' and instead saw '!='.","@typescript-eslint/no-redeclare","'ToastMessage' is already defined.","redeclared","React Hook useEffect has missing dependencies: 'id' and 'onRemove'. Either include them or remove the dependency array. If 'onRemove' changes too often, find the parent component that defines it and wrap that definition in useCallback.",["160"],["155"],["156"],"React Hook useEffect has a missing dependency: 'globalState'. Either include it or remove the dependency array.",["161"],"'SelectContext' is already defined.","React Hook useCallback has a missing dependency: 'handleSlect'. Either include it or remove the dependency array.",["162"],"React Hook useEffect has a missing dependency: 'onEvent'. Either include it or remove the dependency array. If 'onEvent' changes too often, find the parent component that defines it and wrap that definition in useCallback.",["163"],"no-global-assign","no-unsafe-negation",{"desc":"164","fix":"165"},{"desc":"166","fix":"167"},{"desc":"166","fix":"168"},{"desc":"169","fix":"170"},{"desc":"171","fix":"172"},{"desc":"173","fix":"174"},{"desc":"175","fix":"176"},"Update the dependencies array to be: [onDragEnter, onDragLeave, onDragOver, onDrop]",{"range":"177","text":"178"},"Update the dependencies array to be: [setData]",{"range":"179","text":"180"},{"range":"181","text":"180"},"Update the dependencies array to be: [id, lifetime, onRemove]",{"range":"182","text":"183"},"Update the dependencies array to be: [globalState, position]",{"range":"184","text":"185"},"Update the dependencies array to be: [handleSlect]",{"range":"186","text":"187"},"Update the dependencies array to be: [onEvent, ref]",{"range":"188","text":"189"},[7063,7101],"[onDragEnter, onDragLeave, onDragOver, onDrop]",[2679,2694],"[setData]",[3225,3240],[1724,1734],"[id, lifetime, onRemove]",[1349,1359],"[globalState, position]",[1287,1289],"[handleSlect]",[445,450],"[onEvent, ref]"] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | ################### 5 | # compiled source # 6 | ################### 7 | *.com 8 | *.class 9 | *.dll 10 | *.exe 11 | *.pdb 12 | *.dll.config 13 | *.cache 14 | *.suo 15 | # Include dlls if they’re in the NuGet packages directory 16 | !/packages/*/lib/*.dll 17 | # Include dlls if they're in the CommonReferences directory 18 | !*CommonReferences/*.dll 19 | #################### 20 | # VS Upgrade stuff # 21 | #################### 22 | _UpgradeReport_Files/ 23 | ############### 24 | # Directories # 25 | ############### 26 | bin/ 27 | obj/ 28 | TestResults/ 29 | ################### 30 | # Web publish log # 31 | ################### 32 | *.Publish.xml 33 | ############# 34 | # Resharper # 35 | ############# 36 | /_ReSharper.* 37 | *.ReSharper.* 38 | ############ 39 | # Packages # 40 | ############ 41 | # it’s better to unpack these files and commit the raw source 42 | # git has its own built in compression methods 43 | *.7z 44 | *.dmg 45 | *.gz 46 | *.iso 47 | *.jar 48 | *.rar 49 | *.tar 50 | *.zip 51 | ###################### 52 | # Logs and databases # 53 | ###################### 54 | *.log 55 | *.sqlite 56 | # OS generated files # 57 | ###################### 58 | .DS_Store? 59 | ehthumbs.db 60 | Icon? 61 | Thumbs.db 62 | 63 | 64 | # User-specific files 65 | *.user 66 | *.userosscache 67 | *.sln.docstates 68 | 69 | # User-specific files (MonoDevelop/Xamarin Studio) 70 | *.userprefs 71 | 72 | # Build results 73 | [Dd]ebug/ 74 | [Dd]ebugPublic/ 75 | [Rr]elease/ 76 | [Rr]eleases/ 77 | x64/ 78 | x86/ 79 | build/ 80 | bld/ 81 | [Bb]in/ 82 | [Oo]bj/ 83 | 84 | # Visual Studo 2015 cache/options directory 85 | .vs/ 86 | 87 | # MSTest test Results 88 | [Tt]est[Rr]esult*/ 89 | [Bb]uild[Ll]og.* 90 | 91 | # NUNIT 92 | *.VisualState.xml 93 | TestResult.xml 94 | 95 | # Build Results of an ATL Project 96 | [Dd]ebugPS/ 97 | [Rr]eleasePS/ 98 | dlldata.c 99 | 100 | # DNX 101 | project.lock.json 102 | artifacts/ 103 | 104 | *_i.c 105 | *_p.c 106 | *_i.h 107 | *.ilk 108 | *.meta 109 | *.obj 110 | *.pch 111 | *.pgc 112 | *.pgd 113 | *.rsp 114 | *.sbr 115 | *.tlb 116 | *.tli 117 | *.tlh 118 | *.tmp 119 | *.tmp_proj 120 | *.vspscc 121 | *.vssscc 122 | .builds 123 | *.pidb 124 | *.svclog 125 | *.scc 126 | 127 | # Chutzpah Test files 128 | _Chutzpah* 129 | 130 | # Visual C++ cache files 131 | ipch/ 132 | *.aps 133 | *.ncb 134 | *.opensdf 135 | *.sdf 136 | *.cachefile 137 | 138 | # Visual Studio profiler 139 | *.psess 140 | *.vsp 141 | *.vspx 142 | 143 | # TFS 2012 Local Workspace 144 | $tf/ 145 | 146 | # Guidance Automation Toolkit 147 | *.gpState 148 | 149 | # ReSharper is a .NET coding add-in 150 | _ReSharper*/ 151 | *.[Rr]e[Ss]harper 152 | *.DotSettings.user 153 | 154 | # JustCode is a .NET coding add-in 155 | .JustCode 156 | 157 | # TeamCity is a build add-in 158 | _TeamCity* 159 | 160 | # DotCover is a Code Coverage Tool 161 | *.dotCover 162 | 163 | # NCrunch 164 | _NCrunch_* 165 | .*crunch*.local.xml 166 | 167 | # MightyMoose 168 | *.mm.* 169 | AutoTest.Net/ 170 | 171 | # Web workbench (sass) 172 | .sass-cache/ 173 | 174 | # Installshield output folder 175 | [Ee]xpress/ 176 | 177 | # DocProject is a documentation generator add-in 178 | DocProject/buildhelp/ 179 | DocProject/Help/*.HxT 180 | DocProject/Help/*.HxC 181 | DocProject/Help/*.hhc 182 | DocProject/Help/*.hhk 183 | DocProject/Help/*.hhp 184 | DocProject/Help/Html2 185 | DocProject/Help/html 186 | 187 | # Click-Once directory 188 | publish/ 189 | 190 | # Publish Web Output 191 | *.[Pp]ublish.xml 192 | *.azurePubxml 193 | # TODO: Comment the next line if you want to checkin your web deploy settings 194 | # but database connection strings (with potential passwords) will be unencrypted 195 | *.pubxml 196 | *.publishproj 197 | 198 | # NuGet Packages 199 | *.nupkg 200 | # The packages folder can be ignored because of Package Restore 201 | **/packages/* 202 | # except build/, which is used as an MSBuild target. 203 | !**/packages/build/ 204 | # Uncomment if necessary however generally it will be regenerated when needed 205 | #!**/packages/repositories.config 206 | 207 | # Windows Azure Build Output 208 | csx/ 209 | *.build.csdef 210 | 211 | # Windows Store app package directory 212 | AppPackages/ 213 | 214 | # Visual Studio cache files 215 | # files ending in .cache can be ignored 216 | *.[Cc]ache 217 | # but keep track of directories ending in .cache 218 | !*.[Cc]ache/ 219 | 220 | # Others 221 | ClientBin/ 222 | [Ss]tyle[Cc]op.* 223 | ~$* 224 | *~ 225 | *.dbmdl 226 | *.dbproj.schemaview 227 | *.pfx 228 | *.publishsettings 229 | node_modules/ 230 | bower_components/ 231 | orleans.codegen.cs 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | 244 | # SQL Server files 245 | *.mdf 246 | *.ldf 247 | 248 | # Business Intelligence projects 249 | *.rdl.data 250 | *.bim.layout 251 | *.bim_*.settings 252 | 253 | # Microsoft Fakes 254 | FakesAssemblies/ 255 | 256 | # Node.js Tools for Visual Studio 257 | .ntvs_analysis.dat 258 | 259 | # Visual Studio 6 build log 260 | *.plg 261 | 262 | # Visual Studio 6 workspace options file 263 | *.opt -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/WebApi/bin/Debug/netcoreapp3.1/WebApi.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}/WebApi", 15 | "stopAtEntry": false, 16 | "serverReadyAction": { 17 | "action": "openExternally", 18 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)" 19 | }, 20 | "env": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | }, 23 | "sourceFileMap": { 24 | "/Views": "${workspaceFolder}/Views" 25 | } 26 | }, 27 | { 28 | "name": ".NET Core Attach", 29 | "type": "coreclr", 30 | "request": "attach", 31 | "processId": "${command:pickProcess}" 32 | }, 33 | { 34 | "name": "Docker .NET Core Launch", 35 | "type": "docker", 36 | "request": "launch", 37 | "preLaunchTask": "docker-run: debug", 38 | "netCore": { 39 | "appProject": "${workspaceFolder}/WebApi/WebApi.csproj" 40 | } 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "markdown-todo.sortByCount": false, 3 | 4 | // "eslint.validate": [ 5 | // "javascript", 6 | // "javascriptreact", 7 | // { "language": "typescript", "autoFix": true }, 8 | // { "language": "typescriptreact", "autoFix": true } 9 | // ], 10 | 11 | "eslint.workingDirectories": [ "./" ], 12 | "extensions.autoCheckUpdates": false, 13 | "extensions.autoUpdate": false 14 | "omnisharp.enableEditorConfigSupport": true, 15 | 16 | "editor.formatOnSave": true, 17 | 18 | "[javascript]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | }, 21 | 22 | "editor.semanticHighlighting.enabled": true, 23 | "csharp.semanticHighlighting.enabled": true, 24 | "omnisharp.enableEditorConfigSupport": true, 25 | "editor.formatOnType": true, 26 | "editor.formatOnPaste": true, 27 | "editor.formatOnSave": true, 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/WebApi/WebApi.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/WebApi/WebApi.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/WebApi/WebApi.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | }, 41 | { 42 | "type": "docker-build", 43 | "label": "docker-build: debug", 44 | "dependsOn": [ 45 | "build" 46 | ], 47 | "dockerBuild": { 48 | "tag": "akl:dev", 49 | "target": "base", 50 | "dockerfile": "${workspaceFolder}/WebApi/Dockerfile", 51 | "context": "${workspaceFolder}", 52 | "pull": true 53 | }, 54 | "netCore": { 55 | "appProject": "${workspaceFolder}/WebApi/WebApi.csproj" 56 | } 57 | }, 58 | { 59 | "type": "docker-build", 60 | "label": "docker-build: release", 61 | "dependsOn": [ 62 | "build" 63 | ], 64 | "dockerBuild": { 65 | "tag": "akl:latest", 66 | "dockerfile": "${workspaceFolder}/WebApi/Dockerfile", 67 | "context": "${workspaceFolder}", 68 | "pull": true 69 | }, 70 | "netCore": { 71 | "appProject": "${workspaceFolder}/WebApi/WebApi.csproj" 72 | } 73 | }, 74 | { 75 | "type": "docker-run", 76 | "label": "docker-run: debug", 77 | "dependsOn": [ 78 | "docker-build: debug" 79 | ], 80 | "dockerRun": { 81 | "os": "Windows" 82 | }, 83 | "netCore": { 84 | "appProject": "${workspaceFolder}/WebApi/WebApi.csproj", 85 | "enableDebugging": true 86 | } 87 | }, 88 | { 89 | "type": "docker-run", 90 | "label": "docker-run: release", 91 | "dependsOn": [ 92 | "docker-build: release" 93 | ], 94 | "dockerRun": { 95 | "os": "Windows" 96 | }, 97 | "netCore": { 98 | "appProject": "${workspaceFolder}/WebApi/WebApi.csproj" 99 | } 100 | } 101 | ] 102 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dalibor Kundrat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | React-Toast-Notify 5 | 6 | 7 |

React Toast Component

8 |

9 | Online Demo 10 |

11 |

12 | 13 | ### Description 14 | 15 | This is custom toast component implemented by react hooks + React Context API and stayled using tailwindCSS framework. Feel free to inspirate by implementation :) This is trim of my UI lib that i use for my projects. 16 | 17 | This componnent allow to push notifications to user screen and auto remove it after set or default time.. 18 | 19 | Example implements this push-Events: 20 | 21 | - Info 22 | - Error 23 | - Warning 24 | - Success 25 | - Custom body 26 | 27 | #### Concepts 28 | 29 | - Responsive 30 | - Using Hooks and Context API 31 | - Using Tailwind and Fontawesome 32 | (All can be adjusted by endpoint user) 33 | 34 | ### Installation 35 | 36 | (!!! Package is not available on npm !!!) 37 | 38 | 1. Clone the repo 39 | ```sh 40 | git clone https://github.com/damikun/React-Toast.git 41 | ``` 42 | 2. Restore packages 43 | ``` 44 | yarn install 45 | ``` 46 | 3. Build and run demo 47 | ``` 48 | yarn run start 49 | ``` 50 | 51 | 52 | 53 | ### Configuration API 54 | 55 | Toast Provider 56 | 57 | - Usually placed in Providers.tsx or on top of App.tsx 58 | - Give you access to toast 59 | - In this example toast are send from "HomePage" 60 | 61 | ```tsx 62 | 63 | 64 | 65 | 66 | 67 | ``` 68 | 69 | Use hook to access toast actions 70 | 71 | ```tsx 72 | // Custom hook to access default context 73 | const toast = useToast(); 74 | // OR 75 | // Use of concrete conetxt 76 | const toast = useContext(ToastContext); 77 | ``` 78 | 79 | Example: 80 | 81 | ```tsx 82 | export default function HomePage() { 83 | const toast = useToast(); 84 | 85 | return ( 86 |
87 | toast?.pushError("Oppps Error", 5000)} 90 | > 91 | Error 92 | 93 |
94 | ); 95 | } 96 | ``` 97 | 98 | Various types to push 99 | 100 | ```tsx 101 | toast?.pushError("Error messgae", 5000); 102 | toast?.pushWarning("Warning message"); // Default timeValue 103 | toast?.pushSuccess("Success message"); 104 | toast?.pushInfo("Info Message"); 105 | toast?.push("Message", "Info", 2000); 106 | toast?.pushCustom(, 2000); 107 | toast?.pushError("Error messgae", 5000, "truncate-2-lines"); 108 | ``` 109 | 110 | Predefined types (can be extended) 111 | 112 | ```tsx 113 | type TostMessageType = "Info" | "Success" | "Warning" | "Error"; 114 | ``` 115 | 116 | Support message truncate trim 117 | 118 | ```tsx 119 | type Truncate = "truncate-1-lines" | "truncate-2-lines" | "truncate-3-lines"; 120 | ``` 121 | 122 | Pass any custom React.ReactNode component to body 123 | 124 | ```tsx 125 | toast?.pushCustom(, 2000); 126 | toast?.pushCustom(
My custom body
, 2000); 127 | ``` 128 | 129 | Various toast position 130 | 131 | ```tsx 132 | 133 | 134 | // ... 135 | 136 | 137 | type Position "top_right" | "top_middle" | "top_left" | "bottom_right" | "bottom_middle" | "bottom_left" 138 | ``` 139 | 140 | ### Other 141 | 142 | For valid display check that your React _"root"_ is flexible and fulscreen to support all browsers behaviour... 143 | 144 | ``` 145 | // public/index.html 146 |
147 | ``` 148 | 149 | ### Doc 150 | 151 | You can read [setp-by-step](./Step-By-Step.md) tutorial if you wanna start from scratch. (Currently writting / Not finished). 152 | 153 | ## License 154 | 155 | This project is licensed with the [MIT license](LICENSE). 156 | -------------------------------------------------------------------------------- /Step-By-Step.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | Currently many web apps use some kind of notification to let user now about the events. There are various types of notification but this article is focused only on Web App Toasts. They are not fixed to OS / Framework and do not required any aditional provider, **they are simple React functions()** and they leave only under it.. 4 | 5 | ##### Instrumentation: 6 | 7 | This step-by-step example will use 8 | 9 | - React 10 | - React Context API 11 | - Styling using TailwindCSS 12 | 13 | ##### Expected result: 14 | 15 | Toast component from scratch as **one Hook** all what you need to push events like: 16 | 17 | - Push-Info 18 | - Push-Error 19 | - Push-Warning 20 | - Push-Success 21 | - Push-CustomBody 22 | 23 | ##### Agenda: 24 | 25 | 1. Project initialization (will use create-react-app scripts) and init TailwindCSS 26 | 2. Describe parts and implementation of ToastProvider, ToastContainer, TastMessage 27 | 3. Explain how Toast state is handled 28 | 4. Do whatever more you want by yourself :) 29 | 30 | #### Project Init 31 | 32 | Lets use _create-react-app_ script with typescript template. If you wanna learn more about template [click hire](https://create-react-app.dev/docs/adding-typescript/). 33 | 34 | ``` 35 | npx create-react-app my-app --template typescript 36 | // or 37 | yarn create react-app toast-step-by-step --template typescript 38 | ``` 39 | 40 | Now wait after all gets downloaded and initialised by package-manager. 41 | 42 | Delete unnecesary files generated by templeate (tests and stuff around are not needed) 43 | 44 | ![ProjectStructure](./images_tutorial/PrepareProjectStructure.png) 45 | 46 | Fill App.tsx and index.tsx by content below: 47 | 48 | **App.tsx** 49 | 50 | ``` 51 | import React from 'react'; 52 | 53 | export default function App() { 54 | return ( 55 | <>Hello World 56 | ); 57 | } 58 | ``` 59 | 60 | **index.tsx** 61 | 62 | ``` 63 | import React from 'react'; 64 | import ReactDOM from 'react-dom'; 65 | import App from './App'; 66 | 67 | ReactDOM.render( 68 | 69 | 70 | , 71 | document.getElementById('root') 72 | ); 73 | ``` 74 | 75 | Now try to run App using: 76 | 77 | ``` 78 | yarn run start 79 | ``` 80 | 81 | If all go well you shoud get default server info in terminal 82 | 83 | ``` 84 | Compiled successfully! 85 | 86 | You can now view my-app in the browser. 87 | 88 | Local: http://localhost:3000 89 | On Your Network: http://172.20.10.9:3000 90 | ``` 91 | 92 | And in browser you can see _Hello World_ message. 93 | 94 | #### Tailwind Init 95 | 96 | Use folowing command to install tailwind and connected packages. To read more about Tailwind instalation options [click hire](https://tailwindcss.com/docs/installation). 97 | 98 | ``` 99 | yarn add tailwindcss@latest postcss@latest autoprefixer@latest 100 | // Or 101 | npm install tailwindcss@latest postcss@latest autoprefixer@latest 102 | ``` 103 | 104 | Create _postcss.config.js_ under root folder. 105 | 106 | ``` 107 | // In case auto init 108 | module.exports = { 109 | plugins: { 110 | tailwindcss: {}, 111 | autoprefixer: {}, 112 | } 113 | } 114 | 115 | // Or specify plugin path 116 | // module.exports = { 117 | // plugins: [ 118 | // require("tailwindcss")("./tailwind.config.js"), 119 | // require("autoprefixer"), 120 | // ], 121 | // }; 122 | ``` 123 | 124 | Run Tailwind Init command 125 | 126 | ``` 127 | npx tailwindcss init 128 | ``` 129 | 130 | The result in console 131 | 132 | ``` 133 | tailwindcss 2.0.2 134 | ✅ Created Tailwind config file: tailwind.config.js 135 | ``` 136 | 137 | The Tailwind config was created and will be edited later... For now it can have this info: (This can look different between versions). 138 | 139 | _tailwind.config.js_ 140 | 141 | ``` 142 | module.exports = { 143 | purge: [], 144 | darkMode: false, // or 'media' or 'class' 145 | theme: { 146 | extend: {}, 147 | }, 148 | variants: { 149 | extend: {}, 150 | }, 151 | plugins: [], 152 | } 153 | ``` 154 | 155 | Lets create empty CSS file _App.css_ and fill it with tailwind anotations... 156 | 157 | - You can put any custom CSS on top of @tailwind anotations 158 | 159 | ``` 160 | // Any other custom CSS must be in this place !!! 161 | @tailwind base; 162 | @tailwind components; 163 | @tailwind utilities; 164 | ``` 165 | 166 | Lets instal postcss-cli we need it to run postcss commands.. 167 | 168 | - Install it as dev dependecy 169 | 170 | ``` 171 | yarn add postcss-cli 172 | ``` 173 | 174 | Lets create new script under _package.json_ called build:css 175 | 176 | - It will take our CSS from _App.css_ and Purge output of Tailwind classes to output file index.css 177 | 178 | - Purge is proces of removing unused css classes to make result file smaller. 179 | 180 | - Lets edit _start_ script to run _build:css_ bofore _react-script_ 181 | 182 | ``` 183 | "scripts": { 184 | "start": "yarn run build:css && react-scripts start", 185 | "build": "react-scripts build", 186 | "test": "react-scripts test", 187 | "eject": "react-scripts eject", 188 | "build:css": "postcss src/App.css -o src/index.css" 189 | }, 190 | ``` 191 | 192 | Lets edit our tailwind.config.js to setup purge params. 193 | 194 | ``` 195 | module.exports = { 196 | purge: { 197 | preserveHtmlElements: true, 198 | enabled: true, 199 | content: ["./src/**/*.{ts,tsx}"], 200 | }, 201 | darkMode: false, // or 'media' or 'class' 202 | theme: { 203 | extend: {}, 204 | }, 205 | variants: { 206 | extend: {}, 207 | }, 208 | plugins: [], 209 | } 210 | ``` 211 | 212 | Now you can run build:css command 213 | 214 | ``` 215 | yarn run build:css 216 | ``` 217 | 218 | - The resul is that index.css was generated 219 | - Contains minimum CSS to run the app 220 | - Unused CSS was removed 221 | - Full Tailwind CSS has more that 46k of lines... Purge 450 lines.. 222 | 223 | Lets import new _index.css_ to our _index.tsx_ 224 | 225 | ``` 226 | import React from 'react'; 227 | import ReactDOM from 'react-dom'; 228 | import App from './App'; 229 | import "./index.css" // Added + 230 | 231 | ReactDOM.render( 232 | 233 | 234 | , 235 | document.getElementById('root') 236 | ); 237 | ``` 238 | 239 | From now you can use tailwind classes. 240 | 241 | #### VS Code 242 | 243 | If you are user of VSCode you can install folowing extensions to make your life better :P 244 | 245 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 246 | - [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 247 | 248 | /// Todo... 249 | -------------------------------------------------------------------------------- /dist/ToastContainer.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import clsx from "clsx"; 4 | import React, { useContext } from "react"; 5 | import ToastMessage from "./ToastMessage"; 6 | import { ToastContext } from "./ToastProvider"; 7 | 8 | export type ToastContainerProps = { 9 | variant?: keyof typeof VARIANTS; 10 | }; 11 | 12 | const VARIANTS = { 13 | top_left: { 14 | style: "top-0 left-0", 15 | }, 16 | top_right: { 17 | style: "top-0 right-0", 18 | }, 19 | bottom_right: { 20 | style: "bottom-0 right-0", 21 | }, 22 | bottom_left: { 23 | style: "bottom-0 left-0", 24 | }, 25 | top_middle: { 26 | style: "top-0 left-1/2 -translate-x-1/2 transform", 27 | }, 28 | bottom_middle: { 29 | style: "bottom-0 left-1/2 -translate-x-1/2 transform", 30 | }, 31 | undefined: { 32 | style: "top-0 right-0", 33 | }, 34 | }; 35 | 36 | export default function ToastContainer({ 37 | variant = "top_right", 38 | }: ToastContainerProps) { 39 | const context = useContext(ToastContext); 40 | 41 | const Var = VARIANTS[variant] || VARIANTS.top_right; 42 | 43 | function handleRemove(id: string) { 44 | context?.remove(id); 45 | } 46 | 47 | return ( 48 |
55 |
60 | {context?.data.map((toast) => { 61 | return ( 62 |
69 | 79 |
80 | ); 81 | })} 82 |
83 |
84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /dist/ToastMessage.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import React, { useEffect } from "react"; 4 | import { Toast } from "./ToastProvider"; 5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 6 | import { 7 | faTimes, 8 | faExclamationCircle, 9 | faCheck, 10 | faInfoCircle, 11 | } from "@fortawesome/free-solid-svg-icons"; 12 | import clsx from "clsx"; 13 | 14 | const VARIANTS = { 15 | Info: { 16 | base: "bg-white border-blue-500", 17 | iconstyle: "text-blue-500 ", 18 | icon: faInfoCircle, 19 | name: "Info", 20 | }, 21 | 22 | Error: { 23 | base: "bg-white border-red-500 ", 24 | iconstyle: "text-red-500 ", 25 | icon: faExclamationCircle, 26 | name: "Error", 27 | }, 28 | 29 | Warning: { 30 | base: "bg-white border-yellow-500", 31 | iconstyle: "text-yellow-500 ", 32 | icon: faExclamationCircle, 33 | name: "Warning", 34 | }, 35 | 36 | Success: { 37 | base: "bg-white border-green-500", 38 | iconstyle: "text-green-500 ", 39 | icon: faCheck, 40 | name: "Success", 41 | }, 42 | }; 43 | 44 | export type Truncate = 45 | | "truncate-1-lines" 46 | | "truncate-2-lines" 47 | | "truncate-3-lines"; 48 | 49 | export type ToastMessage = { 50 | id: string; 51 | lifetime?: number; 52 | variant?: keyof typeof VARIANTS | undefined; 53 | onRemove?: (id: string) => void; 54 | truncate?: Truncate; 55 | } & Toast; 56 | 57 | export default function ToastMessage({ 58 | id, 59 | header, 60 | message, 61 | lifetime, 62 | onRemove, 63 | truncate = "truncate-1-lines", 64 | icon, 65 | type, 66 | }: ToastMessage) { 67 | const Var = type 68 | ? VARIANTS[type] 69 | : { 70 | base: "bg-white border-gray-600 ", 71 | iconstyle: "", 72 | icon: icon, 73 | name: header, 74 | }; 75 | 76 | useEffect(() => { 77 | if (lifetime && onRemove) { 78 | setTimeout(() => { 79 | onRemove(id); 80 | }, lifetime); 81 | } 82 | }, [lifetime]); 83 | 84 | return ( 85 |
94 |
95 | {Var.icon && ( 96 |
102 | 106 |
107 | )} 108 | 109 |
110 |
{Var.name}
111 |

118 | {message} 119 |

120 |
121 |
onRemove && onRemove(id)} 123 | className={clsx( 124 | "w-10 h-12 mr-2 items-center mx-auto", 125 | "text-center leading-none text-lg" 126 | )} 127 | > 128 | 135 |
136 |
137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /dist/ToastProvider.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import { IconProp } from "@fortawesome/fontawesome-svg-core"; 4 | import React, { useCallback, useContext, useState } from "react"; 5 | import ToastContainer, { ToastContainerProps } from "./ToastContainer"; 6 | import { Truncate } from "./ToastMessage"; 7 | 8 | ///////////////////////////////////// 9 | /// Types 10 | ///////////////////////////////////// 11 | 12 | export type ToastProviderProps = { 13 | children: React.ReactNode; 14 | } & ToastContainerProps; 15 | 16 | type TostMessageType = "Info" | "Success" | "Warning" | "Error"; 17 | 18 | export type Toast = { 19 | id: string; 20 | lifetime: number; 21 | message: string | React.ReactNode; 22 | type?: TostMessageType; 23 | truncate?: Truncate; 24 | icon?: IconProp; 25 | header?: string; 26 | }; 27 | 28 | export type ToastContextType = { 29 | data: Array; 30 | pushError(message: string, lifetime?: number, truncate?: Truncate): void; 31 | pushWarning(message: string, lifetime?: number, truncate?: Truncate): void; 32 | pushSuccess(message: string, lifetime?: number, truncate?: Truncate): void; 33 | pushInfo(message: string, lifetime?: number, truncate?: Truncate): void; 34 | push( 35 | message: string, 36 | type: TostMessageType, 37 | lifetime?: number, 38 | truncate?: Truncate 39 | ): void; 40 | pushCustom( 41 | message: string | React.ReactNode, 42 | lifetime: number, 43 | truncate?: Truncate, 44 | icon?: IconProp | React.ReactNode 45 | ): void; 46 | remove(id: string): void; 47 | }; 48 | 49 | ///////////////////////////////////// 50 | /// Global and Helpers 51 | ///////////////////////////////////// 52 | 53 | export const ToastContext = React.createContext( 54 | undefined 55 | ); 56 | 57 | function uuidv4() { 58 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { 59 | var r = (Math.random() * 16) | 0, 60 | v = c == "x" ? r : (r & 0x3) | 0x8; 61 | return v.toString(16); 62 | }); 63 | } 64 | 65 | export const useToast = () => useContext(ToastContext); 66 | 67 | const DEFAULT_INTERVAL = 2500; 68 | 69 | ///////////////////////////////////// 70 | /// Implementation 71 | ///////////////////////////////////// 72 | 73 | export default function ToastProvider({ 74 | children, 75 | variant, 76 | }: ToastProviderProps) { 77 | const [data, setData] = useState>([]); 78 | 79 | const Push = useCallback( 80 | ( 81 | message: string, 82 | type: TostMessageType, 83 | lifetime?: number, 84 | truncate?: Truncate 85 | ) => { 86 | if (message) { 87 | const new_item: Toast = { 88 | id: uuidv4(), 89 | message: message, 90 | type: type, 91 | lifetime: lifetime ? lifetime : DEFAULT_INTERVAL, 92 | truncate: truncate, 93 | }; 94 | 95 | setData((prevState) => [...prevState, new_item]); 96 | } 97 | }, 98 | [setData, data] 99 | ); 100 | 101 | const PushCustom = useCallback( 102 | ( 103 | message: string | React.ReactNode, 104 | lifetime?: number, 105 | truncate?: Truncate, 106 | icon?: IconProp 107 | ) => { 108 | if (message) { 109 | const new_item: Toast = { 110 | id: uuidv4(), 111 | message: message, 112 | lifetime: lifetime ? lifetime : DEFAULT_INTERVAL, 113 | truncate: truncate, 114 | icon: icon, 115 | type: undefined, 116 | }; 117 | 118 | setData((prevState) => [...prevState, new_item]); 119 | } 120 | }, 121 | [setData, data] 122 | ); 123 | 124 | const PushError = useCallback( 125 | (message: string, lifetime?: number, truncate?: Truncate) => 126 | Push(message, "Error", lifetime, truncate), 127 | [Push] 128 | ); 129 | const PushWarning = useCallback( 130 | (message: string, lifetime?: number, truncate?: Truncate) => 131 | Push(message, "Warning", lifetime, truncate), 132 | [Push] 133 | ); 134 | const PushSuccess = useCallback( 135 | (message: string, lifetime?: number, truncate?: Truncate) => 136 | Push(message, "Success", lifetime, truncate), 137 | [Push] 138 | ); 139 | const PushInfo = useCallback( 140 | (message: string, lifetime?: number, truncate?: Truncate) => 141 | Push(message, "Info", lifetime, truncate), 142 | [Push] 143 | ); 144 | 145 | const ToastContexd = useCallback(() => { 146 | return { 147 | data: data, 148 | pushError: PushError, 149 | pushWarning: PushWarning, 150 | pushSuccess: PushSuccess, 151 | pushInfo: PushInfo, 152 | push: Push, 153 | pushCustom: PushCustom, 154 | 155 | async remove(id: string) { 156 | setData((prevState) => prevState.filter((e) => e.id != id)); 157 | }, 158 | }; 159 | }, [ 160 | data, 161 | setData, 162 | PushError, 163 | PushWarning, 164 | PushSuccess, 165 | PushInfo, 166 | Push, 167 | PushCustom, 168 | ]); 169 | 170 | return ( 171 | 172 | 173 | {children} 174 | 175 | ); 176 | } 177 | -------------------------------------------------------------------------------- /images/toast.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damikun/React-Toast/82c457cd656b66beaf416331ea3ba250d0220079/images/toast.PNG -------------------------------------------------------------------------------- /images/toast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damikun/React-Toast/82c457cd656b66beaf416331ea3ba250d0220079/images/toast.gif -------------------------------------------------------------------------------- /images_tutorial/PrepareProjectStructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damikun/React-Toast/82c457cd656b66beaf416331ea3ba250d0220079/images_tutorial/PrepareProjectStructure.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://damikun.github.io/React-Toast", 3 | "name": "react-toast-notify", 4 | "version": "0.0.2", 5 | "license": "MIT", 6 | "author": "Dalibor Kundrat", 7 | "private": false, 8 | "keywords": [ 9 | "react", 10 | "react-toast", 11 | "react-notify", 12 | "react-toast-component", 13 | "react-push" 14 | ], 15 | "babel": { 16 | "presets": [ 17 | "@babel/preset-react" 18 | ] 19 | }, 20 | "peerDependencies": { 21 | "react": "^17.0.1" 22 | }, 23 | "dependencies": { 24 | "@babel/cli": "^7.12.8", 25 | "@babel/preset-react": "^7.12.7", 26 | "@types/node": "^12.19.8", 27 | "@types/react": "^16.14.2", 28 | "@types/react-dom": "^16.9.10", 29 | "autoprefixer": "^10.1.0", 30 | "react": "^17.0.1", 31 | "react-dom": "^17.0.1", 32 | "react-scripts": "4.0.1", 33 | "typescript": "^4.1.2" 34 | }, 35 | "scripts": { 36 | "start": "yarn run build:css && react-scripts start", 37 | "build": " react-scripts build", 38 | "build:css": "postcss src/CSS/Tailwind.css -o src/CSS/App.css ", 39 | "predeploy": "react-scripts build", 40 | "deploy": "gh-pages -d build", 41 | "publish:npm": " mkdir dist && babel src/Components/Toast -d dist --copy-files" 42 | }, 43 | "eslintConfig": { 44 | "extends": [ 45 | "react-app", 46 | "react-app/jest" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "@fortawesome/fontawesome-free": "^5.13.0", 51 | "@fortawesome/fontawesome-svg-core": "^1.2.28", 52 | "@fortawesome/free-solid-svg-icons": "^5.13.0", 53 | "@fortawesome/react-fontawesome": "^0.1.9", 54 | "@tailwindcss/typography": "^0.2.0", 55 | "clsx": "^1.1.1", 56 | "gh-pages": "^3.1.0", 57 | "postcss": "^8.2.1", 58 | "postcss-cli": "^8.3.1", 59 | "tailwindcss": "^2.0.2", 60 | "tailwindcss-truncate-multiline": "^1.0.3" 61 | }, 62 | "browserslist": { 63 | "production": [ 64 | ">0.2%", 65 | "not dead", 66 | "not op_mini all" 67 | ], 68 | "development": [ 69 | "last 1 chrome version", 70 | "last 1 firefox version", 71 | "last 1 safari version" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require("tailwindcss")("./tailwind.config.js"), 4 | require("autoprefixer"), 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damikun/React-Toast/82c457cd656b66beaf416331ea3ba250d0220079/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | React Toast component 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /setup.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const file = path.resolve("./node_modules/babel-preset-react-app/index.js"); 5 | const text = fs.readFileSync(file, "utf8"); 6 | 7 | if (!text.includes("babel-plugin-relay")) { 8 | if (text.includes("const plugins = [")) { 9 | text = text.replace( 10 | "const plugins = [", 11 | "const plugins = [\n require.resolve('babel-plugin-relay')," 12 | ); 13 | fs.writeFileSync(file, text, "utf8"); 14 | } else { 15 | throw new Error(`Failed to inject babel-plugin-relay.`); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | //@ts-nocheck 3 | import React from "react"; 4 | import "./App.css"; 5 | import HomePage from "./HomePage/Home"; 6 | import Layout from "./HomePage/Layout"; 7 | import Providers from "./Providers"; 8 | 9 | function App() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /src/CSS/Tailwind.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable at-rule-no-unknown */ 2 | html, 3 | body { 4 | width: 100%; 5 | height: 100%; 6 | } 7 | input::-ms-clear, 8 | input::-ms-reveal { 9 | display: none; 10 | } 11 | *, 12 | *::before, 13 | *::after { 14 | -webkit-box-sizing: border-box; 15 | box-sizing: border-box; 16 | } 17 | html { 18 | font-family: sans-serif; 19 | line-height: 1.15; 20 | -webkit-text-size-adjust: 100%; 21 | -ms-text-size-adjust: 100%; 22 | -ms-overflow-style: scrollbar; 23 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 24 | } 25 | @-ms-viewport { 26 | width: device-width; 27 | } 28 | body { 29 | margin: 0; 30 | color: rgba(0, 0, 0, 0.65); 31 | font-size: 14px; 32 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 33 | "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", 34 | "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 35 | font-variant: tabular-nums; 36 | line-height: 1.5715; 37 | background-color: #fff; 38 | -webkit-font-feature-settings: "tnum"; 39 | font-feature-settings: "tnum"; 40 | } 41 | [tabindex="-1"]:focus { 42 | outline: none !important; 43 | } 44 | 45 | @import "tailwindcss/base"; 46 | @import "tailwindcss/components"; 47 | 48 | @import "tailwindcss/utilities"; 49 | -------------------------------------------------------------------------------- /src/Components/Avatar/Avatar.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import React, { useEffect, useRef, useState } from "react"; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | import { IconProp } from "@fortawesome/fontawesome-svg-core"; 6 | import clsx from "clsx"; 7 | 8 | export type AvatarProps = { 9 | src?: string; 10 | lable?: string; 11 | icon?: IconProp; 12 | }; 13 | 14 | export default function Avatar({ src, lable, icon }: AvatarProps) { 15 | const [loading, setLoading] = useState(true); 16 | const [error, setLoadError] = useState(false); 17 | 18 | const image = useRef(null); 19 | 20 | useEffect(() => { 21 | if (image?.current?.complete) setLoading(false); 22 | }, []); 23 | 24 | return ( 25 |
33 |
37 | {icon ? ( 38 | 42 | ) : ( 43 |
{lable}
44 | )} 45 |
46 |
50 | {src !== undefined && src !== null && !error && ( 51 | User avatar setLoading(false)} 57 | onError={(e) => setLoadError(true)} 58 | /> 59 | )} 60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/Components/Buttons/StayledButton.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import React from "react"; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | import { IconProp } from "@fortawesome/fontawesome-svg-core"; 6 | import clsx from "clsx"; 7 | 8 | export type StayledButtonPorps = { 9 | children?: React.ReactNode; 10 | icon?: IconProp; 11 | isloading?: boolean; 12 | loadingPlaceholder?: React.ReactNode; 13 | variant?: keyof typeof VARIANTS; 14 | } & React.DetailedHTMLProps< 15 | React.ButtonHTMLAttributes, 16 | HTMLButtonElement 17 | >; 18 | 19 | const VARIANTS = { 20 | primarygray: { 21 | base: "text-white hover:bg-gray-600 bg-gray-500", 22 | }, 23 | 24 | primaryred: { 25 | base: "text-white hover:bg-red-600 bg-red-500", 26 | }, 27 | 28 | primaryorange: { 29 | base: "text-white hover:bg-oragne-600 bg-oragne-500", 30 | }, 31 | 32 | primarygreen: { 33 | base: "text-white hover:bg-green-600 bg-green-500", 34 | }, 35 | 36 | primaryblue: { 37 | base: "text-white hover:bg-blue-600 bg-blue-500", 38 | }, 39 | 40 | secondaryblue: { 41 | base: "text-blue-500 hover:bg-gray-200 bg-gray-100", 42 | }, 43 | 44 | invisible: { 45 | base: "", 46 | }, 47 | }; 48 | 49 | export default function StayledButton({ 50 | children, 51 | variant = "primarygray", 52 | isloading, 53 | loadingPlaceholder, 54 | icon, 55 | disabled, 56 | onClick, 57 | ...rest 58 | }: StayledButtonPorps) { 59 | const Var = VARIANTS[variant] || VARIANTS.primarygray; 60 | 61 | function HandleClick(event: React.MouseEvent) { 62 | onClick && onClick(event); 63 | } 64 | 65 | return ( 66 | 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /src/Components/Select/Select.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import clsx from "clsx"; 4 | import React, { useCallback, useEffect, useRef } from "react"; 5 | import { useState } from "react"; 6 | import { useOutsideClick } from "../../Hooks/useOutsideClick"; 7 | 8 | type SelectProps = { 9 | initinal: number | string; 10 | value?: string; 11 | children: React.ReactNode; 12 | onChange?: (value: string | number) => void; 13 | justify_end?: boolean; 14 | }; 15 | 16 | export type SelectContext = { 17 | onSelect(value: string | number): void; 18 | }; 19 | 20 | export const SelectContext = React.createContext( 21 | undefined 22 | ); 23 | 24 | export default function Select({ 25 | children, 26 | onChange, 27 | initinal, 28 | justify_end = false, 29 | value, 30 | }: SelectProps) { 31 | const [open, setIsOpen] = useState(false); 32 | 33 | const [selected, setSelected] = useState(initinal); 34 | 35 | useEffect(() => { 36 | onChange && onChange(selected); 37 | }, [selected, onChange]); 38 | 39 | const ref = useRef(null); 40 | 41 | function onClickOutside() { 42 | setIsOpen(false); 43 | } 44 | 45 | useOutsideClick(ref, onClickOutside); 46 | 47 | const handleSlect = useCallback((value: string | number) => { 48 | setSelected(value); 49 | setIsOpen(false); 50 | }, []); 51 | 52 | const Context = useCallback(() => { 53 | return { 54 | onSelect: handleSlect, 55 | }; 56 | }, []); 57 | 58 | return ( 59 | 60 |
{ 63 | e.preventDefault(); 64 | setIsOpen((e) => !e); 65 | }} 66 | className={clsx( 67 | "flex flex-col w-full border-2 border-transparent", 68 | "hover:border-gray-300 transition duration-150", 69 | "focus:border-blue-500 rounded-md cursor-pointer" 70 | )} 71 | > 72 |
73 | {value ? value : selected} 74 |
75 |
76 | {open == true && ( 77 |
84 | {children} 85 |
86 | )} 87 |
88 |
89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/Components/Select/SelectOption.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import React, { useContext } from "react"; 4 | import { SelectContext } from "./Select"; 5 | 6 | type SelectOptionsProps = { 7 | children: React.ReactNode; 8 | value: number | string; 9 | }; 10 | 11 | export type CustomDivProps = { 12 | myval: string | number; 13 | } & React.HTMLProps; 14 | 15 | class CustomDiv extends React.Component { 16 | render() { 17 | return
; 18 | } 19 | } 20 | 21 | export default function SelectOptions({ children, value }: SelectOptionsProps) { 22 | const context = useContext(SelectContext); 23 | return ( 24 | { 26 | e.preventDefault(); 27 | e.stopPropagation(); 28 | context?.onSelect(value); 29 | }} 30 | myval={value} 31 | className="hover:bg-gray-100 py-0.5 px-2 cursor-pointer" 32 | > 33 | {children} 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/Components/Toast/ToastContainer.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import clsx from "clsx"; 4 | import React, { useContext } from "react"; 5 | import ToastMessage from "./ToastMessage"; 6 | import { ToastContext } from "./ToastProvider"; 7 | 8 | export type ToastContainerProps = { 9 | variant?: keyof typeof VARIANTS; 10 | }; 11 | 12 | const VARIANTS = { 13 | top_left: { 14 | style: "top-0 left-0", 15 | }, 16 | top_right: { 17 | style: "top-0 right-0", 18 | }, 19 | bottom_right: { 20 | style: "bottom-0 right-0", 21 | }, 22 | bottom_left: { 23 | style: "bottom-0 left-0", 24 | }, 25 | top_middle: { 26 | style: "top-0 left-1/2 -translate-x-1/2 transform", 27 | }, 28 | bottom_middle: { 29 | style: "bottom-0 left-1/2 -translate-x-1/2 transform", 30 | }, 31 | undefined: { 32 | style: "top-0 right-0", 33 | }, 34 | }; 35 | 36 | export default function ToastContainer({ 37 | variant = "top_right", 38 | }: ToastContainerProps) { 39 | const context = useContext(ToastContext); 40 | 41 | const Var = VARIANTS[variant] || VARIANTS.top_right; 42 | 43 | function handleRemove(id: string) { 44 | context?.remove(id); 45 | } 46 | 47 | return ( 48 |
55 |
60 | {context?.data.map((toast) => { 61 | return ( 62 |
69 | 79 |
80 | ); 81 | })} 82 |
83 |
84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/Components/Toast/ToastMessage.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import React, { useEffect } from "react"; 4 | import { Toast } from "./ToastProvider"; 5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 6 | import { 7 | faTimes, 8 | faExclamationCircle, 9 | faCheck, 10 | faInfoCircle, 11 | } from "@fortawesome/free-solid-svg-icons"; 12 | import clsx from "clsx"; 13 | 14 | const VARIANTS = { 15 | Info: { 16 | base: "bg-white border-blue-500", 17 | iconstyle: "text-blue-500 ", 18 | icon: faInfoCircle, 19 | name: "Info", 20 | }, 21 | 22 | Error: { 23 | base: "bg-white border-red-500 ", 24 | iconstyle: "text-red-500 ", 25 | icon: faExclamationCircle, 26 | name: "Error", 27 | }, 28 | 29 | Warning: { 30 | base: "bg-white border-yellow-500", 31 | iconstyle: "text-yellow-500 ", 32 | icon: faExclamationCircle, 33 | name: "Warning", 34 | }, 35 | 36 | Success: { 37 | base: "bg-white border-green-500", 38 | iconstyle: "text-green-500 ", 39 | icon: faCheck, 40 | name: "Success", 41 | }, 42 | }; 43 | 44 | export type Truncate = 45 | | "truncate-1-lines" 46 | | "truncate-2-lines" 47 | | "truncate-3-lines"; 48 | 49 | export type ToastMessage = { 50 | id: string; 51 | lifetime?: number; 52 | variant?: keyof typeof VARIANTS | undefined; 53 | onRemove?: (id: string) => void; 54 | truncate?: Truncate; 55 | } & Toast; 56 | 57 | export default function ToastMessage({ 58 | id, 59 | header, 60 | message, 61 | lifetime, 62 | onRemove, 63 | truncate = "truncate-1-lines", 64 | icon, 65 | type, 66 | }: ToastMessage) { 67 | const Var = type 68 | ? VARIANTS[type] 69 | : { 70 | base: "bg-white border-gray-600 ", 71 | iconstyle: "", 72 | icon: icon, 73 | name: header, 74 | }; 75 | 76 | useEffect(() => { 77 | if (lifetime && onRemove) { 78 | setTimeout(() => { 79 | onRemove(id); 80 | }, lifetime); 81 | } 82 | }, [lifetime]); 83 | 84 | return ( 85 |
94 |
95 | {Var.icon && ( 96 |
102 | 106 |
107 | )} 108 | 109 |
110 |
{Var.name}
111 |

118 | {message} 119 |

120 |
121 |
onRemove && onRemove(id)} 123 | className={clsx( 124 | "w-10 h-12 mr-2 items-center mx-auto", 125 | "text-center leading-none text-lg" 126 | )} 127 | > 128 | 135 |
136 |
137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /src/Components/Toast/ToastProvider.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import { IconProp } from "@fortawesome/fontawesome-svg-core"; 4 | import React, { useCallback, useContext, useState } from "react"; 5 | import ToastContainer, { ToastContainerProps } from "./ToastContainer"; 6 | import { Truncate } from "./ToastMessage"; 7 | 8 | ///////////////////////////////////// 9 | /// Types 10 | ///////////////////////////////////// 11 | 12 | export type ToastProviderProps = { 13 | children: React.ReactNode; 14 | } & ToastContainerProps; 15 | 16 | type TostMessageType = "Info" | "Success" | "Warning" | "Error"; 17 | 18 | export type Toast = { 19 | id: string; 20 | lifetime: number; 21 | message: string | React.ReactNode; 22 | type?: TostMessageType; 23 | truncate?: Truncate; 24 | icon?: IconProp; 25 | header?: string; 26 | }; 27 | 28 | export type ToastContextType = { 29 | data: Array; 30 | pushError(message: string, lifetime?: number, truncate?: Truncate): void; 31 | pushWarning(message: string, lifetime?: number, truncate?: Truncate): void; 32 | pushSuccess(message: string, lifetime?: number, truncate?: Truncate): void; 33 | pushInfo(message: string, lifetime?: number, truncate?: Truncate): void; 34 | push( 35 | message: string, 36 | type: TostMessageType, 37 | lifetime?: number, 38 | truncate?: Truncate 39 | ): void; 40 | pushCustom( 41 | message: string | React.ReactNode, 42 | lifetime: number, 43 | truncate?: Truncate, 44 | icon?: IconProp | React.ReactNode 45 | ): void; 46 | remove(id: string): void; 47 | }; 48 | 49 | ///////////////////////////////////// 50 | /// Global and Helpers 51 | ///////////////////////////////////// 52 | 53 | export const ToastContext = React.createContext( 54 | undefined 55 | ); 56 | 57 | function uuidv4() { 58 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { 59 | var r = (Math.random() * 16) | 0, 60 | v = c == "x" ? r : (r & 0x3) | 0x8; 61 | return v.toString(16); 62 | }); 63 | } 64 | 65 | export const useToast = () => useContext(ToastContext); 66 | 67 | const DEFAULT_INTERVAL = 2500; 68 | 69 | ///////////////////////////////////// 70 | /// Implementation 71 | ///////////////////////////////////// 72 | 73 | export default function ToastProvider({ 74 | children, 75 | variant, 76 | }: ToastProviderProps) { 77 | const [data, setData] = useState>([]); 78 | 79 | const Push = useCallback( 80 | ( 81 | message: string, 82 | type: TostMessageType, 83 | lifetime?: number, 84 | truncate?: Truncate 85 | ) => { 86 | if (message) { 87 | const new_item: Toast = { 88 | id: uuidv4(), 89 | message: message, 90 | type: type, 91 | lifetime: lifetime ? lifetime : DEFAULT_INTERVAL, 92 | truncate: truncate, 93 | }; 94 | 95 | setData((prevState) => [...prevState, new_item]); 96 | } 97 | }, 98 | [setData, data] 99 | ); 100 | 101 | const PushCustom = useCallback( 102 | ( 103 | message: string | React.ReactNode, 104 | lifetime?: number, 105 | truncate?: Truncate, 106 | icon?: IconProp 107 | ) => { 108 | if (message) { 109 | const new_item: Toast = { 110 | id: uuidv4(), 111 | message: message, 112 | lifetime: lifetime ? lifetime : DEFAULT_INTERVAL, 113 | truncate: truncate, 114 | icon: icon, 115 | type: undefined, 116 | }; 117 | 118 | setData((prevState) => [...prevState, new_item]); 119 | } 120 | }, 121 | [setData, data] 122 | ); 123 | 124 | const PushError = useCallback( 125 | (message: string, lifetime?: number, truncate?: Truncate) => 126 | Push(message, "Error", lifetime, truncate), 127 | [Push] 128 | ); 129 | const PushWarning = useCallback( 130 | (message: string, lifetime?: number, truncate?: Truncate) => 131 | Push(message, "Warning", lifetime, truncate), 132 | [Push] 133 | ); 134 | const PushSuccess = useCallback( 135 | (message: string, lifetime?: number, truncate?: Truncate) => 136 | Push(message, "Success", lifetime, truncate), 137 | [Push] 138 | ); 139 | const PushInfo = useCallback( 140 | (message: string, lifetime?: number, truncate?: Truncate) => 141 | Push(message, "Info", lifetime, truncate), 142 | [Push] 143 | ); 144 | 145 | const ToastContexd = useCallback(() => { 146 | return { 147 | data: data, 148 | pushError: PushError, 149 | pushWarning: PushWarning, 150 | pushSuccess: PushSuccess, 151 | pushInfo: PushInfo, 152 | push: Push, 153 | pushCustom: PushCustom, 154 | 155 | async remove(id: string) { 156 | setData((prevState) => prevState.filter((e) => e.id != id)); 157 | }, 158 | }; 159 | }, [ 160 | data, 161 | setData, 162 | PushError, 163 | PushWarning, 164 | PushSuccess, 165 | PushInfo, 166 | Push, 167 | PushCustom, 168 | ]); 169 | 170 | return ( 171 | 172 | 173 | {children} 174 | 175 | ); 176 | } 177 | -------------------------------------------------------------------------------- /src/Components/ToastDummyMessage.tsx: -------------------------------------------------------------------------------- 1 | /* Author: Dalibor Kundrat https://github.com/damikun */ 2 | 3 | import clsx from "clsx"; 4 | import React from "react"; 5 | import Avatar from "./Avatar/Avatar"; 6 | 7 | export default function ToastDummyMessage() { 8 | return ( 9 |
10 |
11 |
17 |
23 | 27 |
28 | 29 |
37 | Dalibor 38 |
39 |
40 |
41 | This is custom toast 42 |

Builded by using React hooks and context API

43 |
44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/HomePage/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import clsx from "clsx"; 3 | import StayledButton from "../Components/Buttons/StayledButton"; 4 | import { useToast } from "../Components/Toast/ToastProvider"; 5 | import Select from "../Components/Select/Select"; 6 | import SelectOptions from "../Components/Select/SelectOption"; 7 | import ToastDummyMessage from "../Components/ToastDummyMessage"; 8 | import { useGlobalState } from "../Providers"; 9 | 10 | const text = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum`; 11 | 12 | export default function HomePage() { 13 | const toast = useToast(); 14 | const [interval, setInterval] = useState(1500); 15 | const [position, setPosition] = useState("top_right"); 16 | const globalState = useGlobalState(); 17 | 18 | useEffect(() => { 19 | //@ts-ignore 20 | globalState?.setPosition({ variant: position }); 21 | }, [position]); 22 | 23 | return ( 24 |
30 |
31 |
32 |
33 | 34 | 35 |
41 | 49 |
50 |
51 |
52 | 53 |
59 | 74 |
75 |
76 |
77 |
78 | 79 |
85 | toast?.pushError("Oppps Error", interval)} 88 | > 89 | Error 90 | 91 | 94 | toast?.pushWarning( 95 | "Warning appear", 96 | interval, 97 | "truncate-2-lines" 98 | ) 99 | } 100 | > 101 | Warning 102 | 103 | toast?.pushSuccess("Action success", interval)} 106 | > 107 | Success 108 | 109 | toast?.pushInfo("Info message", interval)} 112 | > 113 | Info 114 | 115 | toast?.pushCustom(, interval)} 118 | > 119 | Custom 120 | 121 |
122 |
123 |
124 | 125 |
131 | 134 | toast?.pushInfo(text, interval, "truncate-1-lines") 135 | } 136 | > 137 | 1 line 138 | 139 | 142 | toast?.pushInfo(text, interval, "truncate-2-lines") 143 | } 144 | > 145 | 2 lines 146 | 147 | 150 | toast?.pushInfo(text, interval, "truncate-3-lines") 151 | } 152 | > 153 | 3 lines 154 | 155 |
156 |
157 |
158 |
159 | ); 160 | } 161 | 162 | ///////////////////////////////// 163 | ///////////////////////////////// 164 | 165 | type SectionHeaderProps = { 166 | name: string; 167 | justify_end?: boolean; 168 | }; 169 | function SectionHeader({ justify_end, name }: SectionHeaderProps) { 170 | return ( 171 |
177 | {name} 178 |
179 | ); 180 | } 181 | function GetPositionText(value: string) { 182 | switch (value) { 183 | case "top_right": 184 | return "Top-Right"; 185 | case "top_left": 186 | return "Top-Left"; 187 | case "top_middle": 188 | return "Top-Middle"; 189 | case "bottom_right": 190 | return "Bottom-Right"; 191 | case "bottom_left": 192 | return "Bottom-Left"; 193 | case "bottom_middle": 194 | return "Bottom-Middle"; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/HomePage/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type LayoutProps = { 4 | children: React.ReactNode; 5 | }; 6 | export default function Layout({ children }: LayoutProps) { 7 | return
{children}
; 8 | } 9 | -------------------------------------------------------------------------------- /src/Hooks/useOutsideClick.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export function useOutsideClick(ref: any, onEvent: () => void) { 4 | useEffect(() => { 5 | function handleClickOutside(event: any) { 6 | if (ref.current && !ref.current.contains(event.target)) { 7 | onEvent(); 8 | } 9 | } 10 | 11 | document.addEventListener("mousedown", handleClickOutside); 12 | 13 | return () => { 14 | document.removeEventListener("mousedown", handleClickOutside); 15 | }; 16 | }, [ref]); 17 | } 18 | -------------------------------------------------------------------------------- /src/Providers.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { ToastContainerProps } from "./Components/Toast/ToastContainer"; 3 | import ToastProvider from "./Components/Toast/ToastProvider"; 4 | 5 | export type GlobalContextType = { 6 | position: ToastContainerProps; 7 | // will believe that user pass correct string:) 8 | setPosition: React.Dispatch>; 9 | }; 10 | 11 | export const GlobalContext = React.createContext( 12 | undefined 13 | ); 14 | 15 | export const useGlobalState = () => useContext(GlobalContext); 16 | 17 | type ProvidersProps = { 18 | children: React.ReactNode; 19 | }; 20 | 21 | const Providers = ({ children }: ProvidersProps) => { 22 | const [position, setPosition] = useState({ 23 | variant: "top_right", 24 | }); 25 | 26 | return ( 27 | 33 | {children} 34 | 35 | ); 36 | }; 37 | 38 | export default Providers; 39 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import "./CSS/App.css"; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require("tailwindcss/defaultTheme"); 2 | 3 | const colors = require("tailwindcss/colors"); 4 | 5 | module.exports = { 6 | purge: { 7 | preserveHtmlElements: true, 8 | enabled: true, 9 | content: ["./src/**/*.{ts,tsx}"], 10 | }, 11 | theme: { 12 | colors: { 13 | transparent: "transparent", 14 | current: "currentColor", 15 | black: "#000", 16 | white: "#fff", 17 | gray: colors.coolGray, 18 | red: colors.red, 19 | blue: colors.blue, 20 | yellow: colors.amber, 21 | indigo: colors.indigo, 22 | oragne: colors.orange, 23 | purple: colors.purple, 24 | pink: colors.pink, 25 | green: colors.green, 26 | white: colors.white, 27 | black: colors.black, 28 | }, 29 | 30 | extend: { 31 | scale: { 32 | 102: "1.02", 33 | }, 34 | 35 | truncate: { 36 | lines: { 37 | 1: "1", 38 | 2: "2", 39 | 3: "3", 40 | 5: "5", 41 | 8: "8", 42 | }, 43 | }, 44 | }, 45 | }, 46 | plugins: [ 47 | require("@tailwindcss/typography"), 48 | require("tailwindcss-truncate-multiline")(), 49 | ], 50 | corePlugins: { 51 | alignContent: true, 52 | gradientColorStops: true, 53 | }, 54 | variants: { 55 | gradientColorStops: [ 56 | "responsive", 57 | "hover", 58 | "focus", 59 | "active", 60 | "group-hover", 61 | ], 62 | boxShadow: ["responsive", "hover", "focus"], 63 | outline: ["responsive", "focus"], 64 | backgroundColor: ["responsive", "hover", "focus"], 65 | borderColor: [ 66 | "responsive", 67 | "hover", 68 | "focus", 69 | "focus-within", 70 | "active", 71 | "group-hover", 72 | ], 73 | borderWidth: ["responsive", "last", "hover", "focus", "focus-within"], 74 | margin: ["responsive", "last"], 75 | alignContent: ["responsive", "hover", "focus"], 76 | }, 77 | }; 78 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext", 8 | "es2019" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "strictNullChecks": true, 23 | "suppressImplicitAnyIndexErrors": true, 24 | "noUnusedLocals": true, 25 | "noFallthroughCasesInSwitch": true 26 | }, 27 | "include": [ 28 | "src", 29 | "src/Components/Groups/GroupListView.tsx", 30 | "babel.config.js" 31 | ], 32 | "exclude": [ 33 | "node_modules", 34 | "build", 35 | "scripts", 36 | "acceptance-tests", 37 | "webpack", 38 | "jest", 39 | "src/setupTests.ts" 40 | ] 41 | } 42 | --------------------------------------------------------------------------------