├── .gitignore ├── README.md ├── docs ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── babel.config.js ├── blog │ ├── 2019-05-28-first-blog-post.md │ ├── 2019-05-29-long-blog-post.md │ ├── 2021-08-01-mdx-blog-post.mdx │ ├── 2021-08-26-welcome │ │ ├── docusaurus-plushie-banner.jpeg │ │ └── index.md │ ├── authors.yml │ └── tags.yml ├── docs │ ├── core-concepts │ │ ├── _category_.json │ │ ├── congratulations.md │ │ └── markdown-features.mdx │ ├── getting-started │ │ ├── _category_.json │ │ ├── installation.mdx │ │ └── quick-start.md │ ├── hooks │ │ ├── _category_.json │ │ ├── useClipboard.mdx │ │ ├── usePrevious.mdx │ │ └── useToggle.mdx │ └── utils │ │ ├── _category_.json │ │ └── hasKey.md ├── docusaurus.config.ts ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── sidebars.ts ├── src │ ├── components │ │ ├── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ └── Tabs │ │ │ └── Tabs.tsx │ ├── css │ │ └── custom.css │ ├── pages │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── markdown-page.md │ └── utils │ │ └── routes.ts ├── static │ ├── .nojekyll │ └── img │ │ ├── docusaurus-social-card.jpg │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── rsu.jpg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg ├── tsconfig.json └── yarn.lock ├── package-lock.json ├── package.json ├── package ├── .github │ └── workflows │ │ └── ci.yml ├── .gitignore ├── .watchmanconfig ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── jest.config.js ├── package-prod.json ├── package.json ├── src │ ├── hooks │ │ ├── index.ts │ │ ├── useClipboard │ │ │ ├── useClipboard.test.ts │ │ │ └── useClipboard.ts │ │ ├── useCountdown │ │ │ ├── useCountdown.test.ts │ │ │ └── useCountdown.ts │ │ ├── useLocalStorage │ │ │ ├── useLocalStorage.test.ts │ │ │ └── useLocalStorage.ts │ │ ├── usePrevious │ │ │ ├── usePrevious.test.ts │ │ │ └── usePrevious.ts │ │ ├── useToggle │ │ │ ├── useToggle.test.ts │ │ │ └── useToggle.ts │ │ ├── useUnmount │ │ │ ├── useUnmount.test.ts │ │ │ └── useUnmount.ts │ │ └── useWindowSize │ │ │ ├── useWindowSize.test.ts │ │ │ └── useWindowSize.ts │ ├── index.ts │ ├── services │ │ ├── common-error.ts │ │ ├── common-services.ts │ │ └── error-handler.service.ts │ ├── types │ │ ├── index.d.ts │ │ ├── useClipboard.d.ts │ │ ├── useCountdown.d.ts │ │ ├── usePrevious.d.ts │ │ ├── useToggle.d.ts │ │ ├── useUnmount.d.ts │ │ └── utils │ │ │ ├── function │ │ │ ├── clamp.d.ts │ │ │ ├── hasKey.d.ts │ │ │ ├── isNull.d.ts │ │ │ ├── isUndefined.d.ts │ │ │ ├── sample.d.ts │ │ │ ├── throttle.d.ts │ │ │ └── uniqueArray.d.ts │ │ │ ├── sleep.d.ts │ │ │ ├── string │ │ │ └── toUpperCase.d.ts │ │ │ └── toLower.d.ts │ └── utils │ │ ├── function │ │ ├── add.ts │ │ ├── clamp │ │ │ ├── clamp.test.ts │ │ │ └── clamp.ts │ │ ├── hasKey │ │ │ ├── hasKey.test.ts │ │ │ └── hasKey.ts │ │ ├── index.ts │ │ ├── isNull │ │ │ ├── isNull.test.ts │ │ │ └── isNull.ts │ │ ├── isUndefined │ │ │ ├── isUndefined.test.ts │ │ │ └── isUndefined.ts │ │ ├── sample │ │ │ ├── sample.test.ts │ │ │ └── sample.ts │ │ ├── sleep │ │ │ ├── sleep.test.ts │ │ │ └── sleep.ts │ │ ├── throttle │ │ │ ├── throttle.test.ts │ │ │ └── throttle.ts │ │ └── uniqueArray │ │ │ ├── uniqueArray.test.ts │ │ │ └── uniqueArray.ts │ │ ├── index.ts │ │ └── string │ │ ├── index.ts │ │ ├── toLower │ │ ├── toLower.test.ts │ │ └── toLower.ts │ │ └── toUpperCase │ │ ├── toUpperCase.test.ts │ │ └── toUpperCase.ts ├── tsconfig.json └── tsup.config.ts ├── smart-app ├── .gitignore ├── README.md ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public │ └── vite.svg ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── components │ │ ├── common │ │ │ └── section-wrapper.tsx │ │ └── hooks │ │ │ ├── UseClipboard.tsx │ │ │ ├── UseCountdown.tsx │ │ │ ├── UseLocalStorage.tsx │ │ │ └── UsePrevious.tsx │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | ⚠️ This library is in beta version might be all hooks are not yet ready! we will soon dispatch hooks with bulletproof tests 🚀 4 |

5 | 6 |
7 | 8 | 9 |

10 | Logo 11 |

12 | 13 | --- 14 | 15 | 16 | ![npm](https://img.shields.io/npm/v/react-smart-utils) 17 | [![Discord](https://img.shields.io/discord/123456789012345678)](https://discord.gg/DTJV27BYwA) 18 | 19 | Discord: https://discord.gg/DTJV27BYwA 20 | 21 | 22 | 23 | 24 | 25 | 26 | # React Smart Utils Library 27 | 28 | A comprehensive set of utility functions and components designed to streamline development with React and TypeScript. This library helps improve your workflow with reusable components and functions, customizable and extensible to meet the needs of various React projects. 29 | 30 | --- 31 | 32 | - [Docs](https://react-smart-utils.netlify.app/) 33 | 34 | 35 | ## Table of Contents 36 | 37 | - [Installation](#installation) 38 | - [Features](#features) 39 | - [Usage](#usage) 40 | - [Example Hooks](#example-hooks) 41 | - [Available Hooks](#available-hooks) 42 | - [Contributing](#contributing) 43 | - [Code Style Guidelines](#code-style-guidelines) 44 | - [Roadmap](#roadmap) 45 | - [License](#license) 46 | 47 | --- 48 | 49 | ## Installation 50 | 51 | To install the React Smart Utils library, use npm or yarn: 52 | 53 | 54 | ```bash 55 | npm install react-smart-utils 56 | ``` 57 | or 58 | 59 | ```bash 60 | yarn add react-smart-utils 61 | ``` 62 | --- 63 | 64 | ## Features 65 | 66 | - **Custom Hooks**: Reusable React hooks to solve common use cases. 67 | - **Utility Functions**: Handy utilities to simplify everyday coding tasks. 68 | - **TypeScript Support**: Fully typed with TypeScript for a smooth developer experience. 69 | - **Tree-shakable**: Only include what you need, minimizing bundle size. 70 | 71 | --- 72 | 73 | 74 | 75 | 76 | ## Contributing 77 | 78 | We welcome contributions! Please follow the instructions below to set up the project for development and make your contributions. 79 | 80 | 1. **Fork the repository**on GitHub. 81 | 2. **Clone your forked repository**: 82 | 83 | ```bash 84 | git clone https://github.com/your-username/react-smart-utils.git 85 | cd package 86 | yarn install 87 | ``` 88 | 89 | 3.**Test your functions or hooks in smart-app:** 90 | 91 | ```bash 92 | cd smart-app 93 | yarn install 94 | yarn dev 95 | ``` 96 | 97 | Note: You can push example code as it helps in preparing documents (When Raise PR you must paste the path in PR description) 98 | 99 | 4.**Create a new branch for your feature:** 100 | 101 | ```bash 102 | git checkout -b feature/my-new-feature 103 | ``` 104 | 105 | 106 | 5. **Make your changes, then commit and push:** 107 | 108 | ```bash 109 | git commit -m "Add new feature" 110 | git push origin feature/my-new-feature 111 | ``` 112 | 113 | **Submit a Pull Request with a detailed explanation of your changes.** 114 | -------------------------------------------------------------------------------- /docs/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | build/ 4 | *.min.js 5 | .eslintrc.js 6 | jest.config.js 7 | .prettierrc 8 | .prettierignore 9 | *.config.ts 10 | *.json 11 | *.lock 12 | *-lock.json -------------------------------------------------------------------------------- /docs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // docs/.eslintrc.js 3 | module.exports = { 4 | env: { 5 | browser: true, 6 | es2021: true, 7 | }, 8 | extends: [ 9 | 'eslint:recommended', 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 12, 13 | sourceType: 'module', 14 | }, 15 | rules: { 16 | // add your custom rules 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | build/ 4 | *.min.js 5 | .eslintrc.js 6 | jest.config.js 7 | .prettierrc 8 | .prettierignore 9 | *.config.ts 10 | *.json 11 | *.lock 12 | *-lock.json -------------------------------------------------------------------------------- /docs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 4 | }; 5 | -------------------------------------------------------------------------------- /docs/blog/2019-05-28-first-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: first-blog-post 3 | title: First Blog Post 4 | authors: [slorber, yangshun] 5 | tags: [hola, docusaurus] 6 | --- 7 | 8 | Lorem ipsum dolor sit amet... 9 | 10 | 11 | 12 | ...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | -------------------------------------------------------------------------------- /docs/blog/2019-05-29-long-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: long-blog-post 3 | title: Long Blog Post 4 | authors: yangshun 5 | tags: [hello, docusaurus] 6 | --- 7 | 8 | This is the summary of a very long blog post, 9 | 10 | Use a `` comment to limit blog post size in the list view. 11 | 12 | 13 | 14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 15 | 16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 17 | 18 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 19 | 20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 21 | 22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 23 | 24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 25 | 26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 27 | 28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 29 | 30 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 31 | 32 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 33 | 34 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 35 | 36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 37 | 38 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 39 | 40 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 41 | 42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 43 | 44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 45 | -------------------------------------------------------------------------------- /docs/blog/2021-08-01-mdx-blog-post.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: mdx-blog-post 3 | title: MDX Blog Post 4 | authors: [slorber] 5 | tags: [docusaurus] 6 | --- 7 | 8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). 9 | 10 | :::tip 11 | 12 | Use the power of React to create interactive blog posts. 13 | 14 | ::: 15 | 16 | {/* truncate */} 17 | 18 | For example, use JSX to create an interactive button: 19 | 20 | ```js 21 | 22 | ``` 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vituarvom/react-smart-utils/246ab65609f58ab27e71adcceaefa756ddf9f2b2/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg -------------------------------------------------------------------------------- /docs/blog/2021-08-26-welcome/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | authors: [slorber, yangshun] 5 | tags: [facebook, hello, docusaurus] 6 | --- 7 | 8 | [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). 9 | 10 | Here are a few tips you might find useful. 11 | 12 | 13 | 14 | Simply add Markdown files (or folders) to the `blog` directory. 15 | 16 | Regular blog authors can be added to `authors.yml`. 17 | 18 | The blog post date can be extracted from filenames, such as: 19 | 20 | - `2019-05-30-welcome.md` 21 | - `2019-05-30-welcome/index.md` 22 | 23 | A blog post folder can be convenient to co-locate blog post images: 24 | 25 | ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) 26 | 27 | The blog supports tags as well! 28 | 29 | **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. 30 | -------------------------------------------------------------------------------- /docs/blog/authors.yml: -------------------------------------------------------------------------------- 1 | yangshun: 2 | name: Yangshun Tay 3 | title: Front End Engineer @ Facebook 4 | url: https://github.com/yangshun 5 | image_url: https://github.com/yangshun.png 6 | page: true 7 | socials: 8 | x: yangshunz 9 | github: yangshun 10 | 11 | slorber: 12 | name: Sébastien Lorber 13 | title: Docusaurus maintainer 14 | url: https://sebastienlorber.com 15 | image_url: https://github.com/slorber.png 16 | page: 17 | # customize the url of the author page at /blog/authors/ 18 | permalink: '/all-sebastien-lorber-articles' 19 | socials: 20 | x: sebastienlorber 21 | linkedin: sebastienlorber 22 | github: slorber 23 | newsletter: https://thisweekinreact.com 24 | -------------------------------------------------------------------------------- /docs/blog/tags.yml: -------------------------------------------------------------------------------- 1 | facebook: 2 | label: Facebook 3 | permalink: /facebook 4 | description: Facebook tag description 5 | 6 | hello: 7 | label: Hello 8 | permalink: /hello 9 | description: Hello tag description 10 | 11 | docusaurus: 12 | label: Docusaurus 13 | permalink: /docusaurus 14 | description: Docusaurus tag description 15 | 16 | hola: 17 | label: Hola 18 | permalink: /hola 19 | description: Hola tag description 20 | -------------------------------------------------------------------------------- /docs/docs/core-concepts/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Core Concepts", 3 | "position": 1, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "5 minutes to learn the most important Docusaurus concepts." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/core-concepts/congratulations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Main 6 | 7 | This document provides an overview of the core concepts that underpin the React Utils Library. Understanding these concepts will help you make the most of the library and leverage its full potential in your React applications. 8 | 9 | ## Table of Contents 10 | 11 | - [Introduction to React Utils](#introduction-to-react-utils) 12 | - [Utilities vs. Hooks](#utilities-vs-hooks) 13 | 14 | ## Introduction to React Utils 15 | 16 | The React Utils Library is designed to streamline your development process by providing a collection of reusable utility functions and custom hooks. This library focuses on enhancing code readability, maintainability, and overall productivity while working with React and TypeScript. 17 | 18 | ## Utilities vs. Hooks 19 | 20 | ### Utilities 21 | 22 | Utilities are simple, reusable functions that perform a specific task. They help simplify code and reduce duplication. Examples include string manipulation functions, array methods, and more. 23 | 24 | **Example Utility Function: `kebabCase`** 25 | 26 | ```javascript 27 | import { kebabCase } from 'react-utils'; 28 | 29 | const str = kebabCase('React Utils Library'); // 'react-utils-library' 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/docs/core-concepts/markdown-features.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Markdown Features 6 | 7 | Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**. 8 | 9 | ## Front Matter 10 | 11 | Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/): 12 | 13 | ```text title="my-doc.md" 14 | // highlight-start 15 | --- 16 | id: my-doc-id 17 | title: My document title 18 | description: My document description 19 | slug: /my-custom-url 20 | --- 21 | // highlight-end 22 | 23 | ## Markdown heading 24 | 25 | Markdown text with [links](./hello.md) 26 | ``` 27 | 28 | ## Links 29 | 30 | Regular Markdown links are supported, using url paths or relative file paths. 31 | 32 | ```md 33 | Let's see how to [Create a page](/create-a-page). 34 | ``` 35 | 36 | ```md 37 | Let's see how to [Create a page](./create-a-page.md). 38 | ``` 39 | 40 | **Result:** Let's see how to [Create a page](./create-a-page.md). 41 | 42 | ## Images 43 | 44 | Regular Markdown images are supported. 45 | 46 | You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`): 47 | 48 | ```md 49 | ![Docusaurus logo](/img/docusaurus.png) 50 | ``` 51 | 52 | ![Docusaurus logo](/img/docusaurus.png) 53 | 54 | You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them: 55 | 56 | ```md 57 | ![Docusaurus logo](./img/docusaurus.png) 58 | ``` 59 | 60 | ## Code Blocks 61 | 62 | Markdown code blocks are supported with Syntax highlighting. 63 | 64 | ````md 65 | ```jsx title="src/components/HelloDocusaurus.js" 66 | function HelloDocusaurus() { 67 | return

Hello, Docusaurus!

; 68 | } 69 | ``` 70 | ```` 71 | 72 | ```jsx title="src/components/HelloDocusaurus.js" 73 | function HelloDocusaurus() { 74 | return

Hello, Docusaurus!

; 75 | } 76 | ``` 77 | 78 | ## Admonitions 79 | 80 | Docusaurus has a special syntax to create admonitions and callouts: 81 | 82 | ```md 83 | :::tip My tip 84 | 85 | Use this awesome feature option 86 | 87 | ::: 88 | 89 | :::danger Take care 90 | 91 | This action is dangerous 92 | 93 | ::: 94 | ``` 95 | 96 | :::tip My tip 97 | 98 | Use this awesome feature option 99 | 100 | ::: 101 | 102 | :::danger Take care 103 | 104 | This action is dangerous 105 | 106 | ::: 107 | 108 | ## MDX and React Components 109 | 110 | [MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**: 111 | 112 | ```jsx 113 | export const Highlight = ({children, color}) => ( 114 | { 123 | alert(`You clicked the color ${color} with label ${children}`) 124 | }}> 125 | {children} 126 | 127 | ); 128 | 129 | This is Docusaurus green ! 130 | 131 | This is Facebook blue ! 132 | ``` 133 | 134 | export const Highlight = ({ children, color }) => ( 135 | { 144 | alert(`You clicked the color ${color} with label ${children}`); 145 | }} 146 | > 147 | {children} 148 | 149 | ); 150 | 151 | This is Docusaurus green ! 152 | 153 | This is Facebook blue ! 154 | -------------------------------------------------------------------------------- /docs/docs/getting-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting Started", 3 | "position": 0, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "A comprehensive set of utility functions and components designed to streamline development with React and TypeScript. This library helps improve your workflow with reusable components and functions, customizable and extensible to meet the needs of various React projects." 7 | } 8 | } -------------------------------------------------------------------------------- /docs/docs/getting-started/installation.mdx: -------------------------------------------------------------------------------- 1 | import RSUTabs from '../../src/components/Tabs/Tabs.tsx'; 2 | 3 | # Installation 4 | 5 | The React Utils Library provides a set of utility functions and components designed to streamline development with React and TypeScript. Follow the instructions below to install the library in your project. 6 | 7 | ## Prerequisites 8 | 9 | Before installing the React Utils Library, ensure that you have the following prerequisites: 10 | 11 | - **Node.js**: Make sure you have Node.js installed. You can download it from [Node.js official website](https://nodejs.org/). 12 | 13 | - **Package Manager**: Choose a package manager that you prefer to use: 14 | - **npm**: Comes pre-installed with Node.js. 15 | - **yarn**: You can install it by following the instructions on [Yarn's official website](https://classic.yarnpkg.com/en/docs/install). 16 | 17 | ## Installation Steps 18 | 19 | To install the React Utils Library using 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/docs/getting-started/quick-start.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | Welcome to the React Utils Library! This guide will help you get started quickly with the essential features of the library. 4 | 5 | ## Table of Contents 6 | 7 | - [Prerequisites](/docs/getting-started/installation#prerequisites) 8 | - [Installation](/docs/getting-started/installation) 9 | - [Basic Usage](#basic-usage) 10 | - [Common Utilities](#common-utilities) 11 | - [Custom Hooks](#custom-hooks) 12 | - [Example Project](#example-project) 13 | -------------------------------------------------------------------------------- /docs/docs/hooks/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Hooks", 3 | "position": 4, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "A comprehensive set of utility functions and components designed to streamline development with React and TypeScript. This library helps improve your workflow with reusable components and functions, customizable and extensible to meet the needs of various React projects." 7 | } 8 | } -------------------------------------------------------------------------------- /docs/docs/hooks/useClipboard.mdx: -------------------------------------------------------------------------------- 1 | import Tabs from '@theme/Tabs'; 2 | import TabItem from '@theme/TabItem'; 3 | import RSUTabs, { ExampleCodeBlocks } from '../../src/components/Tabs/Tabs.tsx'; 4 | 5 | ## useClipboard 6 | 7 | The useClipboard hook is a custom React hook that provides an easy way to copy text to the clipboard. This hook enhances user experience by simplifying clipboard operations and handling success and error states effectively. 8 | 9 | ## Installation 10 | 11 | Make sure to have the React Utils Library installed: 12 | 13 | mdx-code-block 14 | 15 | 16 | { 22 | const { isCopied, copy, copiedText } = useClipboard(); 23 | 24 | const handleCopy = () => { 25 | copy("Hello"); 26 | }; 27 | 28 | return ( 29 |
30 | 33 | {isCopied &&

Copied: {copiedText}

} 34 |
35 | ); 36 | }; 37 | 38 | export default ClipboardComponent; 39 | ` 40 | } 41 | 42 | 43 | jsCode={` 44 | import { useClipboard } from 'react-smart-utils'; 45 | 46 | const ClipboardComponent = () => { 47 | const { isCopied, copy, copiedText } = useClipboard(); 48 | 49 | const handleCopy = () => { 50 | copy("Hello"); 51 | }; 52 | 53 | return ( 54 |
55 | 58 | {isCopied &&

Copied: {copiedText}

} 59 |
60 | ); 61 | }; 62 | 63 | export default ClipboardComponent; 64 | `} 65 | 66 | api={ 67 | ` 68 | const { isCopied, copy, copiedText } = useClipboard(); 69 | ` 70 | } 71 | /> 72 | -------------------------------------------------------------------------------- /docs/docs/hooks/usePrevious.mdx: -------------------------------------------------------------------------------- 1 | import Tabs from '@theme/Tabs'; 2 | import TabItem from '@theme/TabItem'; 3 | import RSUTabs, { ExampleCodeBlocks } from '../../src/components/Tabs/Tabs.tsx'; 4 | 5 | ## usePrevious 6 | 7 | The `usePrevious` hook stores and retrieves the previous value of a given variable in a React functional component. This can be helpful for comparing changes between the current and previous values of state or props. 8 | 9 | ## Installation 10 | 11 | Make sure to have the React Utils Library installed: 12 | 13 | ```mdx-code-block 14 | 15 | ``` 16 | 17 | { 25 | const [count, setCount] = useState(0); 26 | const prevCount = usePrevious(count); // Track the previous value of count 27 | 28 | useEffect(() => { 29 | console.log(\`Previous count: \${prevCount}, Current count: \${count}\`); 30 | }, [count, prevCount]); 31 | 32 | return ( 33 | 34 |

Current count: {count}

35 |

Previous count: {prevCount}

36 | 37 |
38 | ); 39 | } 40 | 41 | export default UsePrevious; 42 | ` 43 | 44 | } 45 | 46 | jsCode={` 47 | import { useState, useEffect } from "react"; 48 | import { usePrevious } from "react-smart-utils"; 49 | import { SectionWrapper } from "../common/section-wrapper"; 50 | 51 | 52 | const UsePrevious = () => { 53 | const [count, setCount] = useState(0); 54 | const prevCount = usePrevious(count); // Track the previous value of count 55 | 56 | useEffect(() => { 57 | console.log(\`Previous count: \${prevCount}, Current count: \${count}\`); 58 | }, [count, prevCount]); 59 | 60 | return ( 61 | 62 |

Current count: {count}

63 |

Previous count: {prevCount}

64 | 65 |
66 | ); 67 | }; 68 | 69 | export default UsePrevious; 70 | 71 | ` 72 | } 73 | 74 | api={`const prevValue = usePrevious(value: T); 75 | `} 76 | /> 77 | -------------------------------------------------------------------------------- /docs/docs/hooks/useToggle.mdx: -------------------------------------------------------------------------------- 1 | import Tabs from '@theme/Tabs'; 2 | import TabItem from '@theme/TabItem'; 3 | import RSUTabs, { ExampleCodeBlocks } from '../../src/components/Tabs/Tabs.tsx'; 4 | 5 | ## useToggle 6 | 7 | The `useToggle` hook is a custom React hook that allows you to manage a boolean state with an easy-to-use toggle functionality. This hook is especially useful for managing the visibility of components, switch states, and any other binary conditions. 8 | 9 | ## Installation 10 | 11 | Make sure to have the React Utils Library installed: 12 | 13 | ```mdx-code-block 14 | 15 | ``` 16 | 17 | { 24 | const [isToggled, toggle] = useToggle(); 25 | 26 | return ( 27 |
28 |

{isToggled ? "On" : "Off"}

29 | 30 |
31 | ); 32 | }; 33 | 34 | export default ToggleComponent; 35 | ` 36 | 37 | } 38 | 39 | jsCode={` 40 | import React from "react"; 41 | import { useToggle } from "react-smart-utils"; 42 | 43 | const ToggleComponent = () => { 44 | const [isToggled, toggle] = useToggle(); 45 | 46 | return ( 47 |
48 |

{isToggled ? "On" : "Off"}

49 | 50 |
51 | ); 52 | }; 53 | 54 | export default ToggleComponent;`} 55 | 56 | api={`const [value, toggleValue] = useToggle(initialState?: boolean); 57 | `} 58 | /> 59 | -------------------------------------------------------------------------------- /docs/docs/utils/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Utils", 3 | "position": 4, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "A comprehensive set of utility functions and components designed to streamline development with React and TypeScript. This library helps improve your workflow with reusable components and functions, customizable and extensible to meet the needs of various React projects." 7 | } 8 | } -------------------------------------------------------------------------------- /docs/docs/utils/hasKey.md: -------------------------------------------------------------------------------- 1 | ## hasKey 2 | -------------------------------------------------------------------------------- /docs/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import { themes as prismThemes } from "prism-react-renderer"; 2 | import type { Config } from "@docusaurus/types"; 3 | import type * as Preset from "@docusaurus/preset-classic"; 4 | 5 | const config: Config = { 6 | title: "React Smart Utils", 7 | tagline: "Smart Hooks", 8 | favicon: "img/favicon.ico", 9 | 10 | // Set the production url of your site here 11 | url: "https://github.com", 12 | // Set the // pathname under which your site is served 13 | // For GitHub pages deployment, it is often '//' 14 | baseUrl: "/", 15 | 16 | // GitHub pages deployment config. 17 | // If you aren't using GitHub pages, you don't need these. 18 | organizationName: "vituarvom", // Usually your GitHub org/user name. 19 | projectName: "react-smart-utils", // Usually your repo name. 20 | 21 | onBrokenLinks: "warn", 22 | onBrokenMarkdownLinks: "warn", 23 | 24 | // Even if you don't use internationalization, you can use this field to set 25 | // useful metadata like html lang. For example, if your site is Chinese, you 26 | // may want to replace "en" with "zh-Hans". 27 | i18n: { 28 | defaultLocale: "en", 29 | locales: ["en"], 30 | }, 31 | 32 | presets: [ 33 | [ 34 | "classic", 35 | { 36 | docs: { 37 | sidebarPath: "./sidebars.ts", 38 | // Please change this to your repo. 39 | // Remove this to remove the "edit this page" links. 40 | editUrl: "https://github.com/vituarvom/react-smart-utils", 41 | }, 42 | blog: { 43 | showReadingTime: true, 44 | feedOptions: { 45 | type: ["rss", "atom"], 46 | xslt: true, 47 | }, 48 | // Please change this to your repo. 49 | // Remove this to remove the "edit this page" links. 50 | editUrl: "https://github.com/vituarvom/react-smart-utils", 51 | // Useful options to enforce blogging best practices 52 | onInlineTags: "warn", 53 | onInlineAuthors: "warn", 54 | onUntruncatedBlogPosts: "warn", 55 | }, 56 | theme: { 57 | customCss: "./src/css/custom.css", 58 | }, 59 | } satisfies Preset.Options, 60 | ], 61 | ], 62 | 63 | themeConfig: { 64 | // Replace with your project's social card 65 | image: "img/docusaurus-social-card.jpg", 66 | navbar: { 67 | title: "React Smart Utils", 68 | logo: { 69 | alt: "My Site Logo", 70 | src: "img/rsu.jpg", 71 | }, 72 | items: [ 73 | { 74 | type: "docSidebar", 75 | sidebarId: "tutorialSidebar", 76 | position: "left", 77 | label: "Tutorial", 78 | }, 79 | { to: "/blog", label: "Blog", position: "left" }, 80 | { 81 | href: "https://github.com/vituarvom/react-smart-utils", 82 | label: "GitHub", 83 | position: "right", 84 | }, 85 | ], 86 | }, 87 | footer: { 88 | style: "dark", 89 | links: [ 90 | { 91 | title: "Docs", 92 | items: [ 93 | { 94 | label: "Tutorial", 95 | to: "/docs/category/getting-started", 96 | }, 97 | ], 98 | }, 99 | { 100 | title: "Community", 101 | items: [ 102 | { 103 | label: "Discord", 104 | href: "https://discord.gg/DTJV27BYwA", 105 | }, 106 | ], 107 | }, 108 | { 109 | title: "More", 110 | items: [ 111 | { 112 | label: "Blog", 113 | to: "/blog", 114 | }, 115 | { 116 | label: "GitHub", 117 | href: "https://github.com/vituarvom/react-smart-utils", 118 | }, 119 | ], 120 | }, 121 | ], 122 | copyright: `Copyright © ${new Date().getFullYear()} Vituarvom, Inc. Built with Docusaurus.`, 123 | }, 124 | prism: { 125 | theme: prismThemes.github, 126 | darkTheme: prismThemes.dracula, 127 | }, 128 | } satisfies Preset.ThemeConfig, 129 | }; 130 | 131 | export default config; 132 | -------------------------------------------------------------------------------- /docs/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import pluginJs from '@eslint/js'; 3 | import tseslint from 'typescript-eslint'; 4 | import pluginReact from 'eslint-plugin-react'; 5 | 6 | export default [ 7 | { files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] }, 8 | { languageOptions: { globals: globals.browser } }, 9 | pluginJs.configs.recommended, 10 | ...tseslint.configs.recommended, 11 | pluginReact.configs.flat.recommended, 12 | ]; 13 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc", 16 | "prettier:fix": "prettier --write .", 17 | "eslint:fix": "eslint --fix .", 18 | "test": "jest", 19 | "test:coverage": "jest --coverage" 20 | }, 21 | "dependencies": { 22 | "@docusaurus/core": "3.5.2", 23 | "@docusaurus/preset-classic": "3.5.2", 24 | "@mdx-js/react": "^3.0.0", 25 | "clsx": "^2.0.0", 26 | "prism-react-renderer": "^2.3.0", 27 | "react": "^18.0.0", 28 | "react-dom": "^18.0.0" 29 | }, 30 | "devDependencies": { 31 | "@docusaurus/module-type-aliases": "3.5.2", 32 | "@docusaurus/tsconfig": "3.5.2", 33 | "@docusaurus/types": "3.5.2", 34 | "@eslint/js": "^9.12.0", 35 | "eslint": "^9.12.0", 36 | "eslint-config-prettier": "^9.1.0", 37 | "eslint-plugin-prettier": "^5.2.1", 38 | "eslint-plugin-react": "^7.37.1", 39 | "globals": "^15.11.0", 40 | "jest": "^29.7.0", 41 | "prettier": "^3.3.3", 42 | "typescript": "~5.5.2", 43 | "typescript-eslint": "^8.8.1" 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.5%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 3 chrome version", 53 | "last 3 firefox version", 54 | "last 5 safari version" 55 | ] 56 | }, 57 | "engines": { 58 | "node": ">=18.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | // By default, Docusaurus generates a sidebar from the docs folder structure 15 | tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], 16 | 17 | // But you can create a sidebar manually 18 | /* 19 | tutorialSidebar: [ 20 | 'intro', 21 | 'hello', 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['tutorial-basics/create-a-document'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | export default sidebars; 32 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | import React from 'react'; 3 | import clsx from 'clsx'; 4 | import Heading from '@theme/Heading'; 5 | import styles from './styles.module.css'; 6 | 7 | type FeatureItem = { 8 | title: string; 9 | Svg: React.ComponentType>; 10 | description: JSX.Element; 11 | }; 12 | 13 | const FeatureList: FeatureItem[] = [ 14 | { 15 | title: 'Easy to Use', 16 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 17 | description: ( 18 | <> 19 | Docusaurus was designed from the ground up to be easily installed and 20 | used to get your website up and running quickly. 21 | 22 | ), 23 | }, 24 | { 25 | title: 'Focus on What Matters', 26 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 27 | description: ( 28 | <> 29 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 30 | ahead and move your docs into the docs directory. 31 | 32 | ), 33 | }, 34 | { 35 | title: 'Powered by React', 36 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 37 | description: ( 38 | <> 39 | Extend or customize your website layout by reusing React. Docusaurus can 40 | be extended while reusing the same header and footer. 41 | 42 | ), 43 | }, 44 | ]; 45 | 46 | function Feature({ title, Svg, description }: FeatureItem) { 47 | return ( 48 |
49 |
50 | 51 |
52 |
53 | {title} 54 |

{description}

55 |
56 |
57 | ); 58 | } 59 | 60 | export default function HomepageFeatures(): JSX.Element { 61 | return ( 62 |
63 |
64 |
65 | {FeatureList.map((props, idx) => ( 66 | 67 | ))} 68 |
69 |
70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/components/Tabs/Tabs.tsx: -------------------------------------------------------------------------------- 1 | // src/components/Tabs.js 2 | import React from 'react'; 3 | import Tabs from '@theme/Tabs'; 4 | import TabItem from '@theme/TabItem'; 5 | import CodeBlock from '@theme/CodeBlock'; 6 | import Heading from '@theme/MDXComponents/Heading'; 7 | 8 | const RSUTabs = () => { 9 | return ( 10 | 11 | 12 | npm install react-smart-utils 13 | 14 | 15 | yarn add react-smart-utils 16 | 17 | 18 | 19 | pnpm install react-smart-utils 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default RSUTabs; 26 | 27 | export const ExampleCodeBlocks = ({ 28 | jsCode, 29 | tsCode, 30 | api, 31 | }: { 32 | jsCode: string; 33 | tsCode: string; 34 | api: string; 35 | }) => { 36 | return ( 37 | <> 38 | Example code 39 | 40 | 41 | {tsCode} 42 | 43 | 44 | {jsCode} 45 | 46 | 47 | {api} 48 | 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from '@docusaurus/Link'; 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 4 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 5 | import Heading from '@theme/Heading'; 6 | import Layout from '@theme/Layout'; 7 | import clsx from 'clsx'; 8 | 9 | import { appRoutes } from '../utils/routes'; 10 | import styles from './index.module.css'; 11 | 12 | function HomepageHeader() { 13 | const { siteConfig } = useDocusaurusContext(); 14 | return ( 15 |
16 |
17 | 18 | {siteConfig.title} 19 | 20 |

{siteConfig.tagline}

21 |
22 | 26 | Get Started 27 | 28 |
29 |
30 |
31 | ); 32 | } 33 | 34 | export default function Home(): JSX.Element { 35 | const { siteConfig } = useDocusaurusContext(); 36 | return ( 37 | 41 | 42 |
43 | 44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/src/utils/routes.ts: -------------------------------------------------------------------------------- 1 | export const appRoutes = { 2 | 'getting-started': 'docs/category/getting-started', 3 | }; 4 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vituarvom/react-smart-utils/246ab65609f58ab27e71adcceaefa756ddf9f2b2/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vituarvom/react-smart-utils/246ab65609f58ab27e71adcceaefa756ddf9f2b2/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vituarvom/react-smart-utils/246ab65609f58ab27e71adcceaefa756ddf9f2b2/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vituarvom/react-smart-utils/246ab65609f58ab27e71adcceaefa756ddf9f2b2/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/rsu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vituarvom/react-smart-utils/246ab65609f58ab27e71adcceaefa756ddf9f2b2/docs/static/img/rsu.jpg -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_tree.svg: -------------------------------------------------------------------------------- 1 | 2 | Focus on What Matters 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-smart-utils-mono-repo", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "react-smart-utils-mono-repo", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "workspaces": [ 12 | "package/*", 13 | "smart-app/*" 14 | ] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-smart-utils-mono-repo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/vituarvom/react-smart-utils.git", 6 | "author": "bharatdussa ", 7 | "license": "MIT", 8 | "private": true, 9 | "workspaces": [ 10 | "package/*", 11 | "smart-app/*" 12 | ], 13 | "scripts": { 14 | "setup": "cd package && yarn install && yarn link && cd .. && cd smart-app && yarn link react-smart-utils && yarn install" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/ci.yml 2 | 3 | name: Run Tests on Pull Request 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | 19 | 20 | - name: Set up Node.js 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '20.17.0' 24 | 25 | - name: Install dependencies 26 | run: yarn install 27 | 28 | - name: Run Tests 29 | run: yarn test 30 | 31 | - name: Upload coverage report 32 | if: success() 33 | uses: actions/upload-artifact@v3 34 | with: 35 | name: coverage-report 36 | path: coverage/ 37 | -------------------------------------------------------------------------------- /package/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | # Ignore test-related files 4 | /coverage.data 5 | /coverage/ 6 | 7 | # Build files 8 | /dist 9 | 10 | yarn.lock 11 | package-lock.json -------------------------------------------------------------------------------- /package/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /package/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to React Utils Library 2 | 3 | Thank you for considering contributing to this project! By contributing, you help make this project better and improve the React development community. This guide will walk you through the steps for contributing to the project. 4 | 5 | --- 6 | 7 | ## How to Contribute 8 | 9 | There are several ways you can contribute: 10 | 11 | 1. *Reporting Bugs*: If you find a bug, please open an issue. 12 | 2. *Suggesting Features*: Have an idea for an enhancement? Let us know by creating an issue! 13 | 3. *Submitting Pull Requests*: Fix bugs, add features, or improve documentation. 14 | 4. *Improving Documentation*: Help by making the documentation clearer and more complete. 15 | 5. *Participating in Discussions*: Join the discussions in the issues to help steer the project's direction. 16 | 17 | --- 18 | 19 | ## Getting Started 20 | 21 | To get started, follow the steps below: 22 | 23 | ### 1. Fork the Repository 24 | 25 | Start by forking the repository to your GitHub account: 26 | 27 | - Navigate to the main repository and click the *Fork* button. 28 | - This will create a copy of the repository in your GitHub account. 29 | 30 | ### 2. Clone the Repository 31 | 32 | After forking the repository, clone it to your local machine: 33 | 34 | ```bash 35 | git clone https://github.com/YOUR_USERNAME/react-utils-library.git 36 | ``` 37 | 38 | 39 | Navigate into the repository directory: 40 | 41 | 42 | ```bash 43 | cd react-utils 44 | ``` 45 | 46 | 47 | ### 3. Install Dependencies 48 | 49 | We use yarn for managing dependencies. You can install them by running: 50 | 51 | ```bash 52 | yarn install 53 | ``` 54 | 55 | 56 | ## Features 57 | 58 | The React Utils Library includes essential features to enhance your React applications: 59 | 60 | - *Custom Hooks*: Easily create and manage reusable custom hooks tailored to your application needs. 61 | 62 | - *Documentation*: Comprehensive documentation is provided to guide developers in understanding and utilizing the library effectively. 63 | 64 | 65 | 66 | 67 | ## Code Style Guidelines 68 | 69 | To maintain code consistency across the project, we follow the following guidelines: 70 | 71 | - *Use TypeScript*: Type annotations are required for functions, interfaces, and components. 72 | 73 | - *Prettier*: We use [Prettier](https://prettier.io/) for formatting the code. Please run Prettier before committing changes: 74 | 75 | ```bash 76 | yarn run format 77 | ``` 78 | 79 | Testing: Ensure your code is covered by tests. We use Jest for unit tests. Run tests with: 80 | 81 | ```bash 82 | yarn run test 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /package/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Vituarvom 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 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | ⚠️ This library is in beta version might be all hooks are not yet ready! we will soon dispatch hooks with bulletproof tests 🚀 4 |

5 | 6 |
7 | 8 | 9 |

10 | Logo 11 |

12 | 13 | --- 14 | 15 | 16 | ![npm](https://img.shields.io/npm/v/react-smart-utils) 17 | [![Discord](https://img.shields.io/discord/123456789012345678)](https://discord.gg/DTJV27BYwA) 18 | 19 | Discord: https://discord.gg/DTJV27BYwA 20 | 21 | 22 | 23 | 24 | 25 | 26 | # React Smart Utils Library 27 | 28 | A comprehensive set of utility functions and components designed to streamline development with React and TypeScript. This library helps improve your workflow with reusable components and functions, customizable and extensible to meet the needs of various React projects. 29 | 30 | --- 31 | 32 | - [Docs](https://react-smart-utils.netlify.app/) 33 | 34 | 35 | ## Table of Contents 36 | 37 | - [Installation](#installation) 38 | - [Features](#features) 39 | - [Usage](#usage) 40 | - [Example Hooks](#example-hooks) 41 | - [Available Hooks](#available-hooks) 42 | - [Contributing](#contributing) 43 | - [Code Style Guidelines](#code-style-guidelines) 44 | - [Roadmap](#roadmap) 45 | - [License](#license) 46 | 47 | --- 48 | 49 | ## Installation 50 | 51 | To install the React Smart Utils library, use npm or yarn: 52 | 53 | 54 | ```bash 55 | npm install react-smart-utils 56 | ``` 57 | or 58 | 59 | ```bash 60 | yarn add react-smart-utils 61 | ``` 62 | --- 63 | 64 | ## Features 65 | 66 | - **Custom Hooks**: Reusable React hooks to solve common use cases. 67 | - **Utility Functions**: Handy utilities to simplify everyday coding tasks. 68 | - **TypeScript Support**: Fully typed with TypeScript for a smooth developer experience. 69 | - **Tree-shakable**: Only include what you need, minimizing bundle size. 70 | 71 | --- 72 | 73 | 74 | 75 | 76 | ## Contributing 77 | 78 | We welcome contributions! Please follow the instructions below to set up the project for development and make your contributions. 79 | 80 | 1. **Fork the repository**on GitHub. 81 | 2. **Clone your forked repository**: 82 | 83 | ```bash 84 | git clone https://github.com/your-username/react-smart-utils.git 85 | cd react-smart-utils 86 | ``` 87 | 88 | 89 | 3.**Create a new branch for your feature:** 90 | 91 | ```bash 92 | git checkout -b feature/my-new-feature 93 | ``` 94 | 95 | 96 | 4. **Make your changes, then commit and push:** 97 | 98 | ```bash 99 | git commit -m "Add new feature" 100 | git push origin feature/my-new-feature 101 | ``` 102 | 103 | **Submit a Pull Request with a detailed explanation of your changes.** 104 | -------------------------------------------------------------------------------- /package/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "jsdom" 5 | }; 6 | -------------------------------------------------------------------------------- /package/package-prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-smart-utils", 3 | "version": "0.0.1", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.mjs", 6 | "types": "./dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/vituarvom/react-smart-utils" 13 | }, 14 | "author": "bharatdussa ", 15 | "license": "MIT", 16 | "keywords": [ 17 | "react", 18 | "react hooks", 19 | "react utils", 20 | "react-smart-utils", 21 | "utils", 22 | "hooks", 23 | "react-hooks" 24 | ], 25 | "devDependencies": { 26 | "@types/jest": "^29.5.13", 27 | "jest": "^29.7.0", 28 | "react-test-renderer": "^18.3.1", 29 | "ts-jest": "^29.2.5", 30 | "ts-node": "^10.9.2", 31 | "tsup": "^8.3.0", 32 | "typescript": "^5.6.2" 33 | }, 34 | "scripts": { 35 | "build": "tsup", 36 | "test": "jest" 37 | }, 38 | "dependencies": { 39 | "@testing-library/react-hooks": "^8.0.1", 40 | "@types/react": "^18.3.11", 41 | "clinic": "^13.0.0", 42 | "jest-environment-jsdom": "^29.7.0", 43 | "react": "^18.3.1", 44 | "react-dom": "^18.3.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-smart-utils", 3 | "version": "0.0.1", 4 | "main": "./src/index.ts", 5 | "files": [ 6 | "src" 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/vituarvom/react-smart-utils" 11 | }, 12 | "author": "bharatdussa ", 13 | "license": "MIT", 14 | "keywords": [ 15 | "react", 16 | "react hooks", 17 | "react utils", 18 | "react-smart-utils", 19 | "utils", 20 | "hooks", 21 | "react-hooks" 22 | ], 23 | "devDependencies": { 24 | "@types/jest": "^29.5.13", 25 | "jest": "^29.7.0", 26 | "react-test-renderer": "^18.3.1", 27 | "ts-jest": "^29.2.5", 28 | "ts-node": "^10.9.2", 29 | "tsup": "^8.3.0", 30 | "typescript": "^5.6.2" 31 | }, 32 | "scripts": { 33 | "build": "tsup", 34 | "test": "jest" 35 | }, 36 | "dependencies": { 37 | "@testing-library/react-hooks": "^8.0.1", 38 | "@types/react": "^18.3.11", 39 | "clinic": "^13.0.0", 40 | "jest-environment-jsdom": "^29.7.0", 41 | "react": "^18.3.1", 42 | "react-dom": "^18.3.1" 43 | }, 44 | "peerDependencies": { 45 | "react": "^18.3.1", 46 | "react-dom": "^18.3.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /package/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useToggle } from "./useToggle/useToggle"; 2 | export { usePrevious } from "./usePrevious/usePrevious"; 3 | export { useWindowSize } from "./useWindowSize/useWindowSize"; 4 | export { useClipboard } from "./useClipboard/useClipboard"; 5 | export { useCountdown } from "./useCountdown/useCountdown" 6 | export { useLocalStorage } from "./useLocalStorage/useLocalStorage"; 7 | -------------------------------------------------------------------------------- /package/src/hooks/useClipboard/useClipboard.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import { useClipboard } from './useClipboard'; 3 | 4 | describe('useClipboard Hook', () => { 5 | beforeEach(() => { 6 | jest.clearAllMocks(); 7 | jest.restoreAllMocks(); 8 | }); 9 | 10 | it('should initialize with default values', () => { 11 | const { result } = renderHook(() => useClipboard('default text')); 12 | expect(result.current.isCopied).toBe(false); 13 | expect(result.current.copiedText).toBe('default text'); 14 | }); 15 | 16 | it('should copy text to the clipboard and set isCopied to true', async () => { 17 | const { result } = renderHook(() => useClipboard()); 18 | 19 | // Mock navigator.clipboard.writeText 20 | const writeTextMock = jest.fn().mockResolvedValue(Promise.resolve()); 21 | Object.assign(navigator, { 22 | clipboard: { 23 | writeText: writeTextMock, 24 | }, 25 | }); 26 | 27 | await act(async () => { 28 | await result.current.copy('Hello, World!'); 29 | }); 30 | 31 | expect(writeTextMock).toHaveBeenCalledWith('Hello, World!'); 32 | expect(result.current.isCopied).toBe(true); 33 | expect(result.current.copiedText).toBe('Hello, World!'); 34 | }); 35 | 36 | it('should copy initial text if no text is provided in copy', async () => { 37 | const { result } = renderHook(() => useClipboard('Initial Text')); 38 | 39 | // Mock navigator.clipboard.writeText 40 | const writeTextMock = jest.fn().mockResolvedValue(Promise.resolve()); 41 | Object.assign(navigator, { 42 | clipboard: { 43 | writeText: writeTextMock, 44 | }, 45 | }); 46 | 47 | await act(async () => { 48 | await result.current.copy(''); 49 | }); 50 | 51 | expect(writeTextMock).toHaveBeenCalledWith('Initial Text'); 52 | expect(result.current.isCopied).toBe(true); 53 | expect(result.current.copiedText).toBe('Initial Text'); 54 | }); 55 | 56 | it('should throw an error if non-string value is passed to copy', async () => { 57 | const { result } = renderHook(() => useClipboard()); 58 | 59 | await expect(async () => { 60 | await result.current.copy(123 as unknown as string); 61 | }).rejects.toThrow('copy function only accepts strings'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /package/src/hooks/useClipboard/useClipboard.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export function useClipboard(initialText = '') { 4 | const [isCopied, setIsCopied] = useState(false); 5 | const [copiedText, setCopiedText] = useState(initialText); 6 | 7 | if (typeof initialText !== 'string') { 8 | throw new Error('useClipboard only accepts strings'); 9 | } 10 | 11 | const copy = async (text: string) => { 12 | if (typeof text !== 'string') { 13 | throw new Error('copy function only accepts strings'); 14 | } 15 | 16 | try { 17 | const textToCopy = text || initialText; 18 | await navigator.clipboard.writeText(textToCopy); 19 | setCopiedText(textToCopy); 20 | setIsCopied(true); 21 | } catch (err) { 22 | console.error('Failed to copy text: ', err); 23 | setIsCopied(false); 24 | } 25 | }; 26 | 27 | return { isCopied, copy, copiedText } as const; 28 | } 29 | -------------------------------------------------------------------------------- /package/src/hooks/useCountdown/useCountdown.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react-hooks"; 2 | import { useCountdown } from "./useCountdown"; // Adjust the import path as necessary 3 | import { formatTime } from "../../services/common-services"; // Adjust the import path as necessary 4 | 5 | jest.mock("../../services/common-services"); 6 | 7 | describe("useCountdown", () => { 8 | beforeEach(() => { 9 | jest.useFakeTimers(); // Enable fake timers for all tests 10 | }); 11 | 12 | afterEach(() => { 13 | jest.runOnlyPendingTimers(); // Clean up pending timers after each test 14 | jest.useRealTimers(); // Restore real timers after each test 15 | }); 16 | 17 | test("should initialize with the correct starting time", () => { 18 | const initialTime = 60; // 60 seconds 19 | const { result } = renderHook(() => useCountdown(initialTime)); 20 | 21 | expect(result.current[0]).toBe(initialTime); 22 | }); 23 | 24 | test("should start counting down correctly", () => { 25 | const initialTime = 3; // 3 seconds 26 | const { result } = renderHook(() => useCountdown(initialTime)); 27 | 28 | act(() => { 29 | result.current[1].start(); 30 | }); 31 | 32 | act(() => { 33 | jest.advanceTimersByTime(1000); // Fast-forward 1 second 34 | }); 35 | 36 | expect(result.current[0]).toBe(2); // Should decrement to 2 seconds 37 | 38 | act(() => { 39 | jest.advanceTimersByTime(2000); // Fast-forward 2 more seconds 40 | }); 41 | 42 | expect(result.current[0]).toBe(0); // Should reach 0 and stop 43 | }); 44 | 45 | test.skip('should call onComplete when countdown reaches 0', () => { 46 | const onComplete = jest.fn().mockReturnValue(() => "completed"); 47 | const onTick = jest.fn(); 48 | 49 | // Render the hook with initial time and the onComplete callback 50 | const { result } = renderHook(() => 51 | useCountdown(1, { onTick, onComplete }) // Set initial time to 1 second 52 | ); 53 | 54 | // Start the countdown 55 | act(() => { 56 | result.current[1].start(); 57 | }); 58 | 59 | // Fast-forward until the countdown completes 60 | act(() => { 61 | jest.advanceTimersByTime(1000); // Advance 1 second 62 | }); 63 | 64 | // Expect the onComplete callback to have been called 65 | 66 | }); 67 | 68 | 69 | test("should call onTick at each tick", () => { 70 | const onTick = jest.fn(); 71 | const { result } = renderHook(() => useCountdown(5, { onTick })); 72 | 73 | act(() => { 74 | result.current[1].start(); 75 | }); 76 | 77 | act(() => { 78 | jest.advanceTimersByTime(1000); // 1 second 79 | }); 80 | 81 | expect(onTick).toHaveBeenCalledTimes(1); 82 | 83 | act(() => { 84 | jest.advanceTimersByTime(2000); // 2 more seconds 85 | }); 86 | 87 | expect(onTick).toHaveBeenCalledTimes(3); // Total of 3 ticks 88 | }); 89 | 90 | test("should pause the countdown", () => { 91 | const { result } = renderHook(() => useCountdown(5)); 92 | 93 | act(() => { 94 | result.current[1].start(); 95 | }); 96 | 97 | act(() => { 98 | jest.advanceTimersByTime(2000); // Fast-forward 2 seconds 99 | }); 100 | 101 | expect(result.current[0]).toBe(3); // Should be at 3 seconds 102 | 103 | act(() => { 104 | result.current[1].pause(); 105 | }); 106 | 107 | act(() => { 108 | jest.advanceTimersByTime(2000); // Fast-forward 2 seconds 109 | }); 110 | 111 | expect(result.current[0]).toBe(3); // Should still be at 3 seconds due to pause 112 | }); 113 | 114 | test("should resume countdown from paused state", () => { 115 | const { result } = renderHook(() => useCountdown(5)); 116 | 117 | act(() => { 118 | result.current[1].start(); 119 | }); 120 | 121 | act(() => { 122 | jest.advanceTimersByTime(2000); // Fast-forward 2 seconds 123 | }); 124 | 125 | expect(result.current[0]).toBe(3); // Should be at 3 seconds 126 | 127 | act(() => { 128 | result.current[1].pause(); 129 | }); 130 | 131 | act(() => { 132 | jest.advanceTimersByTime(2000); // Fast-forward 2 seconds 133 | }); 134 | 135 | expect(result.current[0]).toBe(3); // Still paused 136 | 137 | act(() => { 138 | result.current[1].start(); // Resume 139 | }); 140 | 141 | act(() => { 142 | jest.advanceTimersByTime(2000); // Fast-forward 2 seconds 143 | }); 144 | 145 | expect(result.current[0]).toBe(1); // Should be at 1 second 146 | }); 147 | 148 | test("should reset the countdown to the initial time", () => { 149 | const initialTime = 5; // 5 seconds 150 | const { result } = renderHook(() => useCountdown(initialTime)); 151 | 152 | act(() => { 153 | result.current[1].start(); 154 | }); 155 | 156 | act(() => { 157 | jest.advanceTimersByTime(2000); // Fast-forward 2 seconds 158 | }); 159 | 160 | expect(result.current[0]).toBe(3); // Should be at 3 seconds 161 | 162 | act(() => { 163 | result.current[1].reset(); 164 | }); 165 | 166 | expect(result.current[0]).toBe(initialTime); // Should reset to initial time 167 | }); 168 | 169 | test("should increase the countdown time", () => { 170 | const initialTime = 5; // 5 seconds 171 | const { result } = renderHook(() => useCountdown(initialTime)); 172 | 173 | act(() => { 174 | result.current[1].increaseTime(3); // Increase by 3 seconds 175 | }); 176 | 177 | expect(result.current[0]).toBe(8); // Should be at 8 seconds 178 | }); 179 | 180 | test("should decrease the countdown time without going negative", () => { 181 | const initialTime = 5; // 5 seconds 182 | const { result } = renderHook(() => useCountdown(initialTime)); 183 | 184 | act(() => { 185 | result.current[1].decreaseTime(3); // Decrease by 3 seconds 186 | }); 187 | 188 | expect(result.current[0]).toBe(2); // Should be at 2 seconds 189 | 190 | act(() => { 191 | result.current[1].decreaseTime(3); // Try to decrease more than available 192 | }); 193 | 194 | expect(result.current[0]).toBe(0); // Should not go below 0 195 | }); 196 | 197 | test("should format the time correctly", () => { 198 | const initialTime = 70; // 70 seconds 199 | const formatMock = jest.fn((seconds) => `${seconds} seconds`); 200 | (formatTime as jest.Mock).mockImplementation(formatMock); 201 | 202 | const { result } = renderHook(() => 203 | useCountdown(initialTime, { format: "hh:mm:ss" }) 204 | ); 205 | 206 | expect(result.current[0]).toBe("hh:mm:ss seconds"); // Should show initial time 207 | 208 | act(() => { 209 | result.current[1].start(); 210 | }); 211 | 212 | act(() => { 213 | jest.advanceTimersByTime(1000); // Fast-forward 1 second 214 | }); 215 | 216 | expect(result.current[0]).toBe("69 seconds"); // Should show decremented time 217 | expect(formatMock).toHaveBeenCalled(); 218 | }); 219 | }); 220 | -------------------------------------------------------------------------------- /package/src/hooks/useCountdown/useCountdown.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useRef } from "react"; 2 | import { formatTime } from "../../services/common-services"; 3 | import type { CountdownControls, UseCountdownOptions } from "hooks"; 4 | 5 | export const useCountdown = ( 6 | initialTimeInSeconds: number, 7 | { interval = 1000, onTick, onComplete, format }: UseCountdownOptions = {} 8 | ): [string | number | null, CountdownControls] => { 9 | const [count, setCount] = useState(initialTimeInSeconds); 10 | const [paused, setPaused] = useState(true); 11 | const timerId = useRef(null); 12 | 13 | useEffect(() => { 14 | if (paused) return; 15 | 16 | const updateCountdown = () => { 17 | setCount((prevCount) => { 18 | if (prevCount <= 0) { 19 | clearInterval(timerId.current as NodeJS.Timeout); 20 | if (onComplete) onComplete(); 21 | return 0; // Stop at 0 22 | } 23 | if (onTick) onTick(); 24 | return prevCount - 1; // Decrement the count 25 | }); 26 | }; 27 | 28 | timerId.current = setInterval(updateCountdown, interval); 29 | return () => { 30 | clearInterval(timerId.current as NodeJS.Timeout); 31 | }; 32 | }, [paused, interval, onTick, onComplete]); 33 | 34 | // Start the countdown 35 | const start = () => { 36 | if (count > 0) { 37 | setPaused(false); 38 | } 39 | }; 40 | 41 | // Pause the countdown 42 | const pause = () => { 43 | setPaused(true); 44 | clearInterval(timerId.current as NodeJS.Timeout); 45 | }; 46 | 47 | // Reset the countdown to the initial state 48 | const reset = () => { 49 | clearInterval(timerId.current as NodeJS.Timeout); 50 | setCount(initialTimeInSeconds); // Reset to initial time 51 | setPaused(true); // Ensure it's paused 52 | }; 53 | 54 | // Increase time by X seconds 55 | const increaseTime = (seconds: number) => { 56 | setCount((prevCount) => Math.max(0, prevCount + seconds)); // Ensure non-negative 57 | }; 58 | 59 | // Decrease time by X seconds (without going below 0) 60 | const decreaseTime = (seconds: number) => { 61 | setCount((prevCount) => Math.max(0, prevCount - seconds)); // Ensure non-negative 62 | }; 63 | 64 | // Format and return the countdown value and control functions 65 | const formattedTime = 66 | typeof format === "function" 67 | ? format(count) 68 | : typeof format === "string" 69 | ? formatTime(format, count) 70 | : count; 71 | 72 | return [ 73 | formattedTime, 74 | { start, pause, resume: start, reset, increaseTime, decreaseTime }, 75 | ]; 76 | }; 77 | -------------------------------------------------------------------------------- /package/src/hooks/useLocalStorage/useLocalStorage.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react-hooks"; 2 | import { useLocalStorage } from "./useLocalStorage"; // Replace with the actual path to your hook 3 | 4 | // Mock localStorage for testing 5 | beforeEach(() => { 6 | localStorage.clear(); 7 | jest.clearAllMocks(); 8 | }); 9 | 10 | // Use fake timers to handle debounce 11 | jest.useFakeTimers(); 12 | 13 | describe("useLocalStorage Hook", () => { 14 | it("should return initial value if localStorage is empty", () => { 15 | const { result } = renderHook(() => useLocalStorage("testKey", "defaultValue")); 16 | 17 | expect(result.current[0]).toBe("defaultValue"); 18 | }); 19 | 20 | it("should retrieve and parse value from localStorage", () => { 21 | localStorage.setItem("testKey", JSON.stringify("storedValue")); 22 | const { result } = renderHook(() => useLocalStorage("testKey", "defaultValue")); 23 | 24 | expect(result.current[0]).toBe("storedValue"); 25 | }); 26 | 27 | it("should store a value in localStorage", () => { 28 | const { result } = renderHook(() => useLocalStorage("testKey", "defaultValue")); 29 | 30 | act(() => { 31 | result.current[1]("newValue"); 32 | }); 33 | 34 | // Fast-forward all timers (handle debounce delay) 35 | jest.runAllTimers(); 36 | 37 | expect(localStorage.getItem("testKey")).toBe(JSON.stringify("newValue")); 38 | expect(result.current[0]).toBe("newValue"); 39 | }); 40 | 41 | it("should remove the key from localStorage", () => { 42 | const { result } = renderHook(() => useLocalStorage("testKey", "defaultValue")); 43 | 44 | act(() => { 45 | result.current[1]("newValue"); 46 | }); 47 | 48 | // Fast-forward timers to apply the first setValue 49 | jest.runAllTimers(); 50 | 51 | // Now remove the key 52 | act(() => { 53 | result.current[2]("testKey"); // removeKey 54 | }); 55 | 56 | // Fast-forward timers after the remove call 57 | jest.runAllTimers(); 58 | 59 | expect(localStorage.getItem("testKey")).toBeNull(); 60 | expect(result.current[0]).toBe("defaultValue"); 61 | }); 62 | 63 | it("should remove key and reset to default value", () => { 64 | const { result } = renderHook(() => 65 | useLocalStorage("testKey", "defaultValue") 66 | ); 67 | 68 | // Set a new value in localStorage 69 | act(() => { 70 | result.current[1]("newValue"); 71 | }); 72 | 73 | // Fast-forward timers to apply the first setValue 74 | jest.runAllTimers(); 75 | 76 | // Clear the key from localStorage 77 | act(() => { 78 | result.current[2]("testKey"); // Calling removeKey 79 | }); 80 | 81 | // Fast-forward timers after clearing the key 82 | jest.runAllTimers(); 83 | 84 | // Check if the localStorage key is removed 85 | expect(localStorage.getItem("testKey")).toBeNull(); // Assert that the key is cleared 86 | 87 | // Check if the state is reset to the initial value 88 | expect(result.current[0]).toBe("defaultValue"); // The value should revert to the initial/default value 89 | }); 90 | }) -------------------------------------------------------------------------------- /package/src/hooks/useLocalStorage/useLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from "react"; 2 | 3 | function debounce void>( 4 | fn: T, 5 | delay: number 6 | ) { 7 | let timeoutId: ReturnType | null = null; 8 | 9 | // The debounced function 10 | const debouncedFunction = (...args: Parameters) => { 11 | // Clear the previous timeout 12 | if (timeoutId) { 13 | clearTimeout(timeoutId); 14 | } 15 | 16 | // Set a new timeout 17 | timeoutId = setTimeout(() => { 18 | fn(...args); 19 | }, delay); 20 | }; 21 | 22 | // Attach a cancel method to the debounced function 23 | debouncedFunction.cancel = () => { 24 | if (timeoutId) { 25 | clearTimeout(timeoutId); 26 | timeoutId = null; 27 | } 28 | }; 29 | 30 | return debouncedFunction; 31 | } 32 | 33 | export function useLocalStorage(key: string, initialValue?: T) { 34 | const [storedValue, setStoredValue] = useState(() => { 35 | try { 36 | if ( 37 | typeof window === "undefined" || 38 | typeof localStorage === "undefined" 39 | ) { 40 | return initialValue; 41 | } 42 | const item = localStorage.getItem(key as string); 43 | return item ? (JSON.parse(item) as T) : initialValue; 44 | } catch (error) { 45 | console.error( 46 | `Error reading localStorage key "${key as string}":`, 47 | error 48 | ); 49 | return initialValue; // Default to initialValue on error 50 | } 51 | }); 52 | // Debounced localStorage update 53 | const debouncedSetItem = useCallback( 54 | debounce((value: T | unknown) => { 55 | try { 56 | if (value === null || value === undefined) { 57 | localStorage.removeItem(key as string); 58 | } else { 59 | localStorage.setItem(key as string, JSON.stringify(value)); 60 | } 61 | } catch (error) { 62 | console.error( 63 | ` Error setting localStorage key "${key as string}":`, 64 | error 65 | ); 66 | } 67 | }, 300), 68 | [key] 69 | ); 70 | 71 | // Handle updates from other tabs 72 | useEffect(() => { 73 | const handleStorageChange = (e: StorageEvent) => { 74 | if (e.key === key) { 75 | if (e.newValue !== null) { 76 | try { 77 | setStoredValue(JSON.parse(e.newValue) as T); 78 | } catch (error) { 79 | console.error( 80 | `Error parsing storage event for "${key as string}":`, 81 | error 82 | ); 83 | } 84 | } else { 85 | setStoredValue(undefined); 86 | } 87 | } 88 | }; 89 | 90 | window.addEventListener("storage", handleStorageChange); 91 | return () => { 92 | window.removeEventListener("storage", handleStorageChange); 93 | debouncedSetItem.cancel(); 94 | }; 95 | }, [key, debouncedSetItem]); 96 | 97 | const setValue = (value: T | ((val: T | undefined) => T)) => { 98 | try { 99 | const valueToStore = 100 | value instanceof Function ? value(storedValue) : value; 101 | setStoredValue(valueToStore); 102 | debouncedSetItem(valueToStore); 103 | } catch (error) { 104 | console.error( 105 | `Error setting localStorage key "${key as string}":`, 106 | error 107 | ); 108 | } 109 | }; 110 | 111 | const removeKey = (keyToRemove: T) => { 112 | try { 113 | localStorage.removeItem(keyToRemove as string); 114 | setStoredValue(initialValue); 115 | } catch (error) { 116 | console.error( 117 | `Error clearing localStorage key "${keyToRemove as string}":`, 118 | error 119 | ); 120 | } 121 | }; 122 | 123 | return [storedValue, setValue, removeKey] as const; 124 | } 125 | -------------------------------------------------------------------------------- /package/src/hooks/usePrevious/usePrevious.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from "@testing-library/react-hooks"; 2 | import { usePrevious } from "./usePrevious"; 3 | 4 | describe("usePrevious", () => { 5 | it("should return the correct previous value when changing from a number to another number", () => { 6 | const { result, rerender } = renderHook(({ value }) => usePrevious(value), { 7 | initialProps: { value: 5 }, 8 | }); 9 | 10 | rerender({ value: 10 }); 11 | expect(result.current).toBe(5); 12 | 13 | rerender({ value: 20 }); 14 | expect(result.current).toBe(10); 15 | }); 16 | 17 | it("should return null on the first render", () => { 18 | const { result } = renderHook(() => usePrevious(1)); 19 | 20 | expect(result.current).toBeNull(); 21 | }); 22 | 23 | it("should return the previous value on subsequent renders", () => { 24 | const { result, rerender } = renderHook(({ value }) => usePrevious(value), { 25 | initialProps: { value: 1 }, 26 | }); 27 | 28 | // First render 29 | expect(result.current).toBeNull(); 30 | 31 | rerender({ value: 2 }); 32 | expect(result.current).toBe(1); 33 | 34 | rerender({ value: 3 }); 35 | expect(result.current).toBe(2); 36 | }); 37 | 38 | it("should throw an error if the value is undefined", () => { 39 | const { result } = renderHook(() => usePrevious(undefined)); 40 | 41 | expect(result.error).toEqual( 42 | new Error( 43 | 'rsc: error in hook "usePrevious": Expected type defined value, but received undefined.' 44 | ) 45 | ); 46 | }); 47 | 48 | test("should throw a hook error when value is a function", () => { 49 | const functionError = new Error( 50 | 'rsc: error in hook "usePrevious": Expected type non-function value (avoid passing functions directly to usePrevious), but received function.' 51 | ); 52 | 53 | const mockFunction = () => {}; 54 | 55 | const { result } = renderHook(() => usePrevious(mockFunction)); 56 | 57 | expect(result.error).toEqual(functionError); 58 | }); 59 | 60 | test("should work with non-primitive values (arrays and objects)", () => { 61 | const initialObject = { key: "value" }; 62 | const newObject = { key: "new value" }; 63 | 64 | const { result, rerender } = renderHook((props) => usePrevious(props), { 65 | initialProps: initialObject, 66 | }); 67 | 68 | expect(result.current).toBe(null); 69 | rerender(newObject); 70 | expect(result.current).toBe(initialObject); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /package/src/hooks/usePrevious/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import ErrorHandler from "../../services/error-handler.service"; 3 | 4 | /** 5 | * The `usePrevious` function in TypeScript allows you to store and retrieve the previous value of a 6 | * variable in a React functional component. 7 | * @param {T} value - The `value` parameter in the `usePrevious` function is the value for which you 8 | * want to keep track of the previous value. This function is designed to be used in React functional 9 | * components to store and retrieve the previous value of a given input value. 10 | * @returns The `usePrevious` function returns the previous value of the input value passed to it. 11 | */ 12 | 13 | export function usePrevious(value: T): T | null { 14 | const errorHandler = new ErrorHandler("usePrevious"); 15 | 16 | if (value === undefined) { 17 | errorHandler.throwHookError("usePrevious", "defined value", value); 18 | } 19 | 20 | if (typeof value === "function") { 21 | errorHandler.throwHookError( 22 | "usePrevious", 23 | "non-function value (avoid passing functions directly to usePrevious)", 24 | value 25 | ); 26 | } 27 | 28 | const ref = useRef(null); 29 | 30 | useEffect(() => { 31 | ref.current = value; 32 | }, [value]); 33 | 34 | return ref.current; 35 | } 36 | -------------------------------------------------------------------------------- /package/src/hooks/useToggle/useToggle.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from "@testing-library/react-hooks"; 2 | import { useToggle } from "./useToggle"; 3 | 4 | describe("useToggle Hook", () => { 5 | it("should toggle between true and false", () => { 6 | const { result } = renderHook(() => useToggle(false)); 7 | 8 | expect(result.current[0]).toBe(false); 9 | 10 | act(() => result.current[1]()); 11 | expect(result.current[0]).toBe(true); 12 | 13 | act(() => result.current[1]()); 14 | expect(result.current[0]).toBe(false); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /package/src/hooks/useToggle/useToggle.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | /** 4 | * The `useToggle` function in TypeScript returns a boolean state value and a function to toggle that 5 | * value. 6 | * @param {boolean} [initialValue=false] - The `initialValue` parameter in the `useToggle` function is 7 | * a boolean value that determines the initial state of the toggle. If no value is provided when 8 | * calling the `useToggle` function, the default initial value will be `false`. 9 | * @returns An array containing the current state value and a function to toggle the state value is 10 | * being returned. 11 | */ 12 | export const useToggle = (initialValue: boolean = false) => { 13 | const [state, setState] = useState(initialValue); 14 | const toggle = () => setState(!state); 15 | return [state, toggle] as const; 16 | }; 17 | -------------------------------------------------------------------------------- /package/src/hooks/useUnmount/useUnmount.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import { useUnmount } from './useUnmount'; // Adjust the import based on your project structure 3 | 4 | describe('useUnmount', () => { 5 | it('should call the callback when the component unmounts', () => { 6 | const mockCallback = jest.fn(); 7 | 8 | const { unmount } = renderHook(() => useUnmount(mockCallback)); 9 | 10 | // Unmount the hook 11 | unmount(); 12 | 13 | expect(mockCallback).toHaveBeenCalledTimes(1); 14 | }); 15 | 16 | it('should not throw an error when the callback executes successfully', () => { 17 | const mockCallback = jest.fn(); 18 | 19 | const { unmount } = renderHook(() => useUnmount(mockCallback)); 20 | 21 | expect(() => unmount()).not.toThrow(); 22 | expect(mockCallback).toHaveBeenCalledTimes(1); 23 | }); 24 | 25 | it('should catch errors thrown by the callback', () => { 26 | const mockCallback = jest.fn(() => { 27 | throw new Error('Test error'); 28 | }); 29 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); 30 | 31 | const { unmount } = renderHook(() => useUnmount(mockCallback)); 32 | 33 | expect(() => unmount()).not.toThrow(); 34 | 35 | // Check if console.error was called with the correct message 36 | expect(consoleErrorSpy).toHaveBeenCalledWith('Error in unmount callback', expect.any(Error)); 37 | 38 | consoleErrorSpy.mockRestore(); // Restore the original console.error 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /package/src/hooks/useUnmount/useUnmount.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export const useUnmount = (callback: () => void) => { 4 | useEffect(() => { 5 | return () => { 6 | try { 7 | callback(); 8 | } catch (error) { 9 | console.error("Error in unmount callback", error); 10 | } 11 | }; 12 | }, []); 13 | }; 14 | -------------------------------------------------------------------------------- /package/src/hooks/useWindowSize/useWindowSize.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import { renderHook } from "@testing-library/react-hooks"; 5 | import { useWindowSize } from "./useWindowSize"; 6 | 7 | const originalWindow = { ...window }; 8 | 9 | beforeAll(() => { 10 | Object.defineProperty(window, "innerWidth", { 11 | configurable: true, 12 | value: 800, 13 | }); 14 | Object.defineProperty(window, "innerHeight", { 15 | configurable: true, 16 | value: 600, 17 | }); 18 | }); 19 | 20 | afterEach(() => { 21 | Object.defineProperty(window, "innerWidth", { 22 | configurable: true, 23 | value: originalWindow.innerWidth, 24 | }); 25 | Object.defineProperty(window, "innerHeight", { 26 | configurable: true, 27 | value: originalWindow.innerHeight, 28 | }); 29 | }); 30 | 31 | describe("useWindowSize", () => { 32 | it("should initialize with correct window size", () => { 33 | const { result } = renderHook(() => useWindowSize()); 34 | expect(result.current.width).toBe(800); 35 | expect(result.current.height).toBe(600); 36 | }); 37 | 38 | it("should update window size on resize", () => { 39 | const { result } = renderHook(() => useWindowSize()); 40 | 41 | window.innerWidth = 1024; 42 | window.innerHeight = 768; 43 | window.dispatchEvent(new Event("resize")); 44 | 45 | expect(result.current.width).toBe(1024); 46 | expect(result.current.height).toBe(768); 47 | }); 48 | 49 | it("should not update state excessively due to throttling", () => { 50 | jest.useFakeTimers(); 51 | const { result } = renderHook(() => useWindowSize()); 52 | 53 | window.innerWidth = 1024; 54 | window.innerHeight = 768; 55 | window.dispatchEvent(new Event("resize")); 56 | 57 | window.innerWidth = 1200; 58 | window.innerHeight = 900; 59 | window.dispatchEvent(new Event("resize")); 60 | 61 | jest.advanceTimersByTime(100); 62 | 63 | expect(result.current.width).toBe(1200); 64 | expect(result.current.height).toBe(900); 65 | 66 | jest.useRealTimers(); 67 | }); 68 | 69 | it("should handle non-finite window dimensions gracefully", () => { 70 | const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(); 71 | const { result } = renderHook(() => useWindowSize()); 72 | 73 | Object.defineProperty(window, "innerWidth", { 74 | configurable: true, 75 | value: Number.NaN, 76 | }); 77 | Object.defineProperty(window, "innerHeight", { 78 | configurable: true, 79 | value: Number.POSITIVE_INFINITY, 80 | }); 81 | window.dispatchEvent(new Event("resize")); 82 | 83 | expect(consoleWarnSpy).toHaveBeenCalledWith( 84 | "Received non-finite dimensions for window size:", 85 | { 86 | newWidth: Number.NaN, 87 | newHeight: Number.POSITIVE_INFINITY, 88 | } 89 | ); 90 | 91 | expect(result.current.width).toBe(originalWindow.innerWidth); 92 | expect(result.current.height).toBe(originalWindow.innerHeight); 93 | 94 | consoleWarnSpy.mockRestore(); 95 | }); 96 | 97 | it("should handle server-side rendering gracefully", () => { 98 | jest 99 | .spyOn(global, "window", "get") 100 | .mockReturnValue(undefined as unknown as Window & typeof globalThis); 101 | 102 | const { result } = renderHook(() => useWindowSize()); 103 | expect(result.current.width).toBe(0); 104 | expect(result.current.height).toBe(0); 105 | 106 | jest.spyOn(global, "window", "get").mockRestore(); 107 | }); 108 | 109 | it("should clean up the event listener on unmount", () => { 110 | const addEventListenerSpy = jest.spyOn(window, "addEventListener"); 111 | const removeEventListenerSpy = jest.spyOn(window, "removeEventListener"); 112 | 113 | const { unmount } = renderHook(() => useWindowSize()); 114 | 115 | expect(addEventListenerSpy).toHaveBeenCalledWith( 116 | "resize", 117 | expect.any(Function) 118 | ); 119 | 120 | unmount(); 121 | 122 | expect(removeEventListenerSpy).toHaveBeenCalledWith( 123 | "resize", 124 | expect.any(Function) 125 | ); 126 | 127 | addEventListenerSpy.mockRestore(); 128 | removeEventListenerSpy.mockRestore(); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /package/src/hooks/useWindowSize/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useCallback } from "react"; 2 | import { throttle } from "../../utils"; 3 | 4 | interface WindowSize { 5 | width: number; 6 | height: number; 7 | } 8 | 9 | export const useWindowSize = (): WindowSize => { 10 | const [windowSize, setWindowSize] = useState({ 11 | width: typeof window !== "undefined" ? window.innerWidth : 0, 12 | height: typeof window !== "undefined" ? window.innerHeight : 0, 13 | }); 14 | 15 | const handleWindowSize = useCallback(() => { 16 | if (typeof window !== "undefined") { 17 | const newWidth = window.innerWidth; 18 | const newHeight = window.innerHeight; 19 | 20 | if (Number.isFinite(newWidth) && Number.isFinite(newHeight)) { 21 | setWindowSize({ 22 | width: newWidth, 23 | height: newHeight, 24 | }); 25 | } else { 26 | console.warn("Received non-finite dimensions for window size:", { 27 | newWidth, 28 | newHeight, 29 | }); 30 | } 31 | } 32 | }, []); 33 | 34 | useEffect(() => { 35 | if (typeof window === "undefined") { 36 | console.warn( 37 | "Window object is not defined. This may be running on the server side." 38 | ); 39 | return; 40 | } 41 | 42 | const throttledHandleWindowSize = throttle(handleWindowSize, 100); 43 | 44 | window.addEventListener("resize", throttledHandleWindowSize); 45 | 46 | handleWindowSize(); 47 | 48 | return () => { 49 | window.removeEventListener("resize", throttledHandleWindowSize); 50 | }; 51 | }, [handleWindowSize]); 52 | 53 | return windowSize; 54 | }; 55 | -------------------------------------------------------------------------------- /package/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./hooks"; 2 | export * from "./utils"; 3 | -------------------------------------------------------------------------------- /package/src/services/common-error.ts: -------------------------------------------------------------------------------- 1 | export const errorMessages = { 2 | TypeError: "Invalid parameter: Expected a specific type.", 3 | RangeError: "Value out of range.", 4 | ReferenceError: "Invalid reference: Variable is not defined.", 5 | SyntaxError: "Invalid syntax: Code contains a syntax error.", 6 | EvalError: "Invalid use of eval(): Unsafe or prohibited use of eval().", 7 | URIError: "Invalid URI: URI handling error occurred.", 8 | Error: "An error occurred: Unable to process the request.", 9 | }; 10 | -------------------------------------------------------------------------------- /package/src/services/common-services.ts: -------------------------------------------------------------------------------- 1 | export const formatTime = ( 2 | format: "hh:mm" | "hh:mm:ss" | "mm:ss" | "ss" | undefined, 3 | seconds: number 4 | ): string => { 5 | const hours = Math.floor(seconds / 3600); 6 | const minutes = Math.floor((seconds % 3600) / 60); 7 | const secs = seconds % 60; 8 | 9 | switch (format) { 10 | case "hh:mm": 11 | return `${hours}:${minutes < 10 ? "0" : ""}${minutes}`; 12 | case "hh:mm:ss": 13 | return `${hours}:${minutes < 10 ? "0" : ""}${minutes}:${ 14 | secs < 10 ? "0" : "" 15 | }${secs}`; 16 | case "mm:ss": 17 | return `${minutes}:${secs < 10 ? "0" : ""}${secs}`; 18 | case "ss": 19 | return `${seconds}s`; 20 | default: 21 | return seconds.toString(); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /package/src/services/error-handler.service.ts: -------------------------------------------------------------------------------- 1 | type ErrorType = 2 | | "TypeError" 3 | | "RangeError" 4 | | "ReferenceError" 5 | | "SyntaxError" 6 | | "Error"; 7 | 8 | type PrimitiveType = 9 | | "string" 10 | | "number" 11 | | "boolean" 12 | | "null" 13 | | "undefined" 14 | | "symbol" 15 | | "bigint"; 16 | 17 | type NonPrimitiveType = 18 | | "object" 19 | | "Function" 20 | | "Array" 21 | | "Date" 22 | | "RegExp" 23 | | "Map" 24 | | "Set" 25 | | "Promise" 26 | | "Error"; 27 | 28 | type Type = NonPrimitiveType | PrimitiveType; 29 | 30 | /** 31 | * A class for handling errors in a structured and reusable way. 32 | * It supports different types of errors and generates dynamic messages. 33 | */ 34 | class ErrorHandler { 35 | private readonly functionName: string; 36 | 37 | constructor(functionName: string) { 38 | this.functionName = functionName; 39 | } 40 | 41 | /** 42 | * Generates and throws an error with a dynamic message. 43 | * 44 | * @param {ErrorType} errorType - The type of the error to throw. 45 | * @param {string} expectedType - The expected data type or condition. 46 | * @param {any} receivedValue - The actual value that caused the error. 47 | */ 48 | public throwError( 49 | errorType: ErrorType, 50 | expectedType: string, 51 | receivedValue: any 52 | ): never { 53 | const actualType = typeof receivedValue; 54 | const errorMessage = `rsc: error from ${this.functionName}: Expected type ${expectedType} but received ${actualType}.`; 55 | 56 | const errorClasses: { [key in ErrorType]: new (message: string) => Error } = 57 | { 58 | TypeError, 59 | RangeError, 60 | ReferenceError, 61 | SyntaxError, 62 | Error, 63 | }; 64 | const ErrorConstructor = errorClasses[errorType] || Error; 65 | throw new ErrorConstructor(errorMessage); 66 | } 67 | 68 | /** 69 | * Throws a TypeError with a dynamic message. 70 | * @param {string} expectedType - The expected data type or condition. 71 | * @param {any} receivedValue - The actual value that caused the error. 72 | */ 73 | public throwTypeError( 74 | expectedType: Type | Type[], 75 | receivedValue: any 76 | ): never { 77 | this.throwError( 78 | "TypeError", 79 | Array.isArray(expectedType) 80 | ? expectedType.join(" or ").toString() 81 | : expectedType.toString(), 82 | receivedValue 83 | ); 84 | } 85 | 86 | /** 87 | * Throws a RangeError with a dynamic message. 88 | * 89 | * @param {string} expectedRange - The expected value range or condition. 90 | * @param {any} receivedValue - The actual value that caused the error. 91 | */ 92 | public throwRangeError(expectedRange: string, receivedValue: any): never { 93 | this.throwError("RangeError", expectedRange, receivedValue); 94 | } 95 | 96 | /** 97 | * Throws a ReferenceError with a dynamic message. 98 | * 99 | * @param {string} expectedReference - The expected reference or condition. 100 | * @param {any} receivedValue - The actual value that caused the error. 101 | */ 102 | public throwReferenceError( 103 | expectedReference: string, 104 | receivedValue: any 105 | ): never { 106 | this.throwError("ReferenceError", expectedReference, receivedValue); 107 | } 108 | 109 | /** 110 | * Throws a custom error message for hooks. 111 | * @param {string} hookName - The name of the hook. 112 | * @param {string} expectedType - The expected data type or condition. 113 | * @param {any} receivedValue - The actual value that caused the error. 114 | */ 115 | public throwHookError( 116 | hookName: string, 117 | expectedType: string, 118 | receivedValue?: any, 119 | options?: ErrorOptions 120 | ): never { 121 | const errorMessage = `rsc: error in hook "${hookName}": Expected type ${expectedType}, but received ${typeof receivedValue}.`; 122 | throw new Error(errorMessage, options); 123 | } 124 | 125 | /** 126 | * The function `throwInvalidHookUsage` throws an error message indicating that a hook must be called 127 | * within a function component or another hook. 128 | * @param {string} hookName - The `hookName` parameter in the `throwInvalidHookUsage` function 129 | * represents the name of the hook that was called incorrectly. 130 | */ 131 | public throwInvalidHookUsage(hookName: string): never { 132 | throw new Error( 133 | `Invalid hook call: ${hookName} must be called within a function component or another hook.` 134 | ); 135 | } 136 | } 137 | 138 | export default ErrorHandler; 139 | -------------------------------------------------------------------------------- /package/src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "hooks" { 2 | export * from "./usePrevious"; 3 | export * from "./useToggle"; 4 | } 5 | 6 | declare module "utils/string" { 7 | export * from "./utils/string/toUpperCase"; 8 | } 9 | 10 | declare module "utils/function" { 11 | export * from "./utils/function/clamp"; 12 | export * from "./utils/function/hasKey"; 13 | export * from "./utils/function/throttle"; 14 | export * from "./utils/function/isUndefined"; 15 | export * from "./utils/function/sample"; 16 | export * from "./utils/function/throttle"; 17 | export * from "./utils/function/uniqueArray"; 18 | export * from "./utils/function/isNull"; 19 | } 20 | -------------------------------------------------------------------------------- /package/src/types/useClipboard.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The useClipboard function in TypeScript allows for copying text to the clipboard and provides 3 | * feedback on whether the operation was successful. 4 | * @param [initialText] - The `initialText` parameter in the `useClipboard` function is used to set the 5 | * initial text that will be copied to the clipboard if no text is provided when calling the `copy` 6 | * function. 7 | * @returns The function `useClipboard` returns an object with three properties: `isCopied`, `copy`, 8 | * and `copiedText`. These properties are returned as a constant object using TypeScript's `as const` 9 | * syntax. 10 | */ 11 | export declare function useClipboard(initialText?: string): Readonly<{ 12 | isCopied: boolean; 13 | copy: (text: string) => Promise; 14 | copiedText: string; 15 | }>; 16 | -------------------------------------------------------------------------------- /package/src/types/useCountdown.d.ts: -------------------------------------------------------------------------------- 1 | declare module "hooks" { 2 | export type FormatOptions = 3 | | "hh:mm" 4 | | "hh:mm:ss" 5 | | "mm:ss" 6 | | "ss" 7 | | ((timeInSeconds: number) => string); 8 | 9 | export interface UseCountdownOptions { 10 | interval?: number; // Interval in milliseconds for countdown updates (default 1000ms) 11 | onTick?: () => void; // Callback invoked on each tick 12 | onComplete?: () => void; // Callback invoked when countdown reaches zero 13 | format?: FormatOptions; // Format string or function for formatting countdown time 14 | } 15 | 16 | export interface CountdownControls { 17 | start: () => void; // Start the countdown 18 | pause: () => void; // Pause the countdown 19 | resume: () => void; // Resume the countdown 20 | reset: () => void; // Reset the countdown to the initial state 21 | increaseTime: (seconds: number) => void; // Increase the countdown by x seconds 22 | decreaseTime: (seconds: number) => void; // Decrease the countdown by x seconds 23 | } 24 | 25 | /** 26 | * useCountdown hook to create a countdown timer with extra functionalities like pause, reset, and more. 27 | * 28 | * @param {number} initialTimeInSeconds - The initial countdown time in seconds. 29 | * @param {UseCountdownOptions} options - Optional settings like interval, onTick, onComplete, and format. 30 | * - `interval` (number): The interval in milliseconds for countdown updates (default is 1000ms). 31 | * - `onTick` (function): A callback function invoked on each tick. 32 | * - `onComplete` (function): A callback function invoked when the countdown reaches zero. 33 | * - `format` (string or function): Predefined format options ('hh:mm', 'hh:mm:ss', 'mm:ss', 'ss') or a custom formatting function. 34 | * 35 | * @returns [formattedTime, countdownControls] - Countdown time and control functions. 36 | - `formattedTime` (string | number | null): The current countdown time, formatted according to the specified format. 37 | - `countdownControls` (CountdownControls): An object containing methods to control the countdown: 38 | - `start`: Starts the countdown. 39 | - `pause`: Pauses the countdown. 40 | - `resume`: Resumes the countdown. 41 | - `reset`: Resets the countdown to the initial time. 42 | - `increaseTime(seconds: number)`: Increases the countdown time by the specified number of seconds. 43 | - `decreaseTime(seconds: number)`: Decreases the countdown time by the specified number of seconds. 44 | */ 45 | declare function useCountdown( 46 | initialTimeInSeconds: number, // Initial countdown time in seconds 47 | options?: UseCountdownOptions // Optional countdown settings 48 | ): [string | number | null, CountdownControls]; 49 | } 50 | -------------------------------------------------------------------------------- /package/src/types/usePrevious.d.ts: -------------------------------------------------------------------------------- 1 | // types/usePrevious.d.ts 2 | 3 | declare module 'hooks' { 4 | /** 5 | * The `usePrevious` hook allows you to store and retrieve the previous value of a variable 6 | * in a React functional component. 7 | * 8 | * @param {T} value - The value for which you want to keep track of the previous value. 9 | * This function is designed to be used in React functional components to store and retrieve 10 | * the previous value of a given input value. 11 | * 12 | * @returns {T | null} - The previous value of the input value passed to it. 13 | * If no previous value exists (i.e., the hook is used for the first time), it returns null. 14 | * 15 | * @template T - The type of the value being tracked. 16 | */ 17 | export function usePrevious(value: T): T | null; 18 | } 19 | -------------------------------------------------------------------------------- /package/src/types/useToggle.d.ts: -------------------------------------------------------------------------------- 1 | // types/useToggle.d.ts 2 | 3 | declare module 'hooks' { 4 | /** 5 | * The `useToggle` hook returns a boolean state value and a function to toggle that value. 6 | * 7 | * @param {boolean} [initialValue=false] - The initial state of the toggle. If no value is provided, 8 | * the default initial value will be `false`. 9 | * 10 | * @returns {[boolean, () => void]} - An array containing the current state value and a function to toggle 11 | * the state value. 12 | */ 13 | export function useToggle(initialValue?: boolean): [boolean, () => void]; 14 | } 15 | -------------------------------------------------------------------------------- /package/src/types/useUnmount.d.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | declare module "hooks" { 4 | /** 5 | * The `useUnmount` hook returns a boolean state value and a function to toggle that value. 6 | * 7 | * @param callback - The `callback` parameter in the `useUnmount` function is a function that will be 8 | * executed when the component using this hook is unmounted. It is a function that you pass to 9 | * `useUnmount` to perform cleanup or any necessary actions before the component is removed from the DOM. 10 | */ 11 | export function useUnmount(callback: () => void): void; 12 | } 13 | -------------------------------------------------------------------------------- /package/src/types/utils/function/clamp.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Clamps a number between a specified minimum and maximum range, with an option to allow Infinity values. 3 | * 4 | * @param {number} value - The number to be clamped. 5 | * @param {number} min - The minimum allowable value. 6 | * @param {number} max - The maximum allowable value. 7 | * @param {boolean} [allowInfinity=false] - Whether to allow Infinity as a result or throw an error for large numbers. 8 | * @returns {number} The clamped value, ensuring it falls between the min and max. 9 | */ 10 | export declare function clamp(value: number, min: number, max: number, allowInfinity?: boolean): number; 11 | -------------------------------------------------------------------------------- /package/src/types/utils/function/hasKey.d.ts: -------------------------------------------------------------------------------- 1 | // types/utils/string/toUpperCase.d.ts 2 | 3 | declare module "utils/function" { 4 | /** 5 | * The `hasKey` function checks if an object has a specific key and returns a boolean value. 6 | * @param {object} obj - The `obj` parameter is the object that you want to check for the presence of a 7 | * specific key. 8 | * @param {string | symbol} key - The `key` parameter in the `hasKey` function is the property key 9 | * (string or symbol) that you want to check for existence in the given object. 10 | * @returns The `hasKey` function returns a boolean value indicating whether the provided object `obj` 11 | * has the specified key `key`. 12 | */ 13 | export function hasKey(obj: object, key: string | symbol): boolean; 14 | } 15 | -------------------------------------------------------------------------------- /package/src/types/utils/function/isNull.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The `isNull` function in TypeScript checks if a value is null using strict equality and `Object.is` 3 | * comparison. 4 | * @param {unknown} value - The `value` parameter in the `isNull` function is of type `unknown`, which 5 | * means it can be any type. The function checks if the value is strictly equal to `null` or if it is 6 | * `null` using `Object.is` method. 7 | * @returns The function `isNull` returns a boolean value indicating whether the input `value` is 8 | * `null` or not. 9 | */ 10 | 11 | declare function isNull(value: unknown): boolean; 12 | -------------------------------------------------------------------------------- /package/src/types/utils/function/isUndefined.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The function `isUndefined` in TypeScript checks if a value is undefined and returns a boolean 3 | * result. 4 | * @param {unknown} value - The `value` parameter in the `isUndefined` function is of type `unknown`, 5 | * which means it can be any type. The function checks if the type of the `value` is "undefined" and 6 | * returns a boolean value accordingly. 7 | * @returns The function `isUndefined` is returning a boolean value indicating whether the input 8 | * `value` is of type "undefined". 9 | */ 10 | declare function isUndefined(value: unknown): boolean; 11 | -------------------------------------------------------------------------------- /package/src/types/utils/function/sample.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The `sample` function selects a random element from an input array. 3 | * @param {T[]} arr - An array of elements of type `T`, from which to select a random element. 4 | * @returns A randomly selected element of type `T` from the input array. 5 | * @throws {TypeError} Will throw an error if the input is not an array. 6 | * @throws {Error} Will throw an error if the input array is empty. 7 | */ 8 | export declare function sample(arr: T[]): T; 9 | -------------------------------------------------------------------------------- /package/src/types/utils/function/throttle.d.ts: -------------------------------------------------------------------------------- 1 | type throttleCancel = { 2 | cancel: () => void; 3 | }; 4 | /** 5 | * The `throttle` function in TypeScript allows you to limit the rate at which a function can be 6 | * called. 7 | * @param {T} func - The `func` parameter in the `throttle` function is the function that you want to 8 | * throttle. This function will be called at most once within the specified `wait` time interval. 9 | * @param {number} wait - The `wait` parameter in the `throttle` function represents the time interval 10 | * in milliseconds that must elapse before the original function `func` can be called again. This 11 | * interval helps in limiting the frequency of function calls to prevent overwhelming the system with 12 | * rapid invocations. 13 | * @returns The `throttle` function returns a new function that wraps the original function provided as 14 | * an argument. This new function has additional functionality to throttle the execution of the 15 | * original function based on a specified time interval (`wait` parameter). The returned function also 16 | * has a `cancel` method that can be used to cancel the throttling and allow immediate execution of the 17 | * original function on the next call. 18 | */ 19 | export declare function throttle unknown>( 20 | func: T, 21 | wait: number 22 | ): T & throttleCancel; 23 | -------------------------------------------------------------------------------- /package/src/types/utils/function/uniqueArray.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes duplicate elements from an array, handling both primitive and non-primitive values. 3 | * @template T - The type of elements in the array. 4 | * @param {T[]} arr - An array of elements of any type to be made unique. 5 | * @returns {T[]} A new array containing unique elements from the input array `arr`. 6 | * Duplicates are removed, and uniqueness is determined based on the value of the elements. 7 | */ 8 | export declare const uniqueArray: (arr: T[]) => T[]; 9 | -------------------------------------------------------------------------------- /package/src/types/utils/sleep.d.ts: -------------------------------------------------------------------------------- 1 | export declare const sleep: (ms: number) => Promise; 2 | -------------------------------------------------------------------------------- /package/src/types/utils/string/toUpperCase.d.ts: -------------------------------------------------------------------------------- 1 | // types/utils/string/toUpperCase.d.ts 2 | 3 | declare module 'utils/string' { 4 | /** 5 | * The function `toUpperCase` takes a string input and returns the input string converted to uppercase. 6 | * It includes error handling for non-string inputs. 7 | * 8 | * @param {string} str - The string to be converted to uppercase. 9 | * @returns {string} - The input string `str` converted to uppercase using the `toUpperCase` method. 10 | * @throws {TypeError} - Throws an error if the input is not a string. 11 | */ 12 | export function toUpperCase(str: string): string; 13 | } 14 | -------------------------------------------------------------------------------- /package/src/types/utils/toLower.d.ts: -------------------------------------------------------------------------------- 1 | declare function toLower(str:string):string; -------------------------------------------------------------------------------- /package/src/utils/function/add.ts: -------------------------------------------------------------------------------- 1 | export const add = () => 4 + 50; -------------------------------------------------------------------------------- /package/src/utils/function/clamp/clamp.test.ts: -------------------------------------------------------------------------------- 1 | import { act } from "@testing-library/react-hooks"; 2 | import ErrorHandler from "../../../services/error-handler.service"; 3 | import { clamp } from "./clamp"; 4 | 5 | describe("clamp", () => { 6 | 7 | 8 | test("should return the value if it's within the min and max range", () => { 9 | const result = clamp(5, 0, 10); 10 | expect(result).toBe(5); 11 | }); 12 | 13 | test("should return the minimum value if the value is less than min", () => { 14 | const result = clamp(-5, 0, 10); 15 | expect(result).toBe(0); 16 | }); 17 | 18 | test("should return the maximum value if the value is greater than max", () => { 19 | const result = clamp(15, 0, 10); 20 | expect(result).toBe(10); 21 | }); 22 | 23 | test("should throw a type error when value, min, or max is not a number", () => { 24 | expect(() => clamp("5" as any, 1, 10)).toThrow(TypeError); 25 | }); 26 | 27 | test("should throw a range error when value, min or max are passed greater than MAX_VALUE", () => { 28 | expect(() => clamp(1e1000, 10, 1e20000)).toThrow(RangeError); 29 | }); 30 | 31 | test("should return Infinity when value, min or max are passed greater than MAX_VALUE with allowInfinity to true", () => { 32 | const result = clamp(1e1000, 10, 1e20000, true); 33 | expect(result).toEqual(Infinity); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /package/src/utils/function/clamp/clamp.ts: -------------------------------------------------------------------------------- 1 | import ErrorHandler from "../../../services/error-handler.service"; 2 | 3 | /** 4 | * Clamps a number between a specified minimum and maximum range, with an option to allow Infinity values. 5 | * 6 | * @param {number} value - The number to be clamped. 7 | * @param {number} min - The minimum allowable value. 8 | * @param {number} max - The maximum allowable value. 9 | * @param {boolean} [allowInfinity=false] - Whether to allow Infinity as a result or throw an error for large numbers. 10 | * @returns {number} The clamped value, ensuring it falls between the min and max. 11 | */ 12 | export function clamp( 13 | value: number, 14 | min: number, 15 | max: number, 16 | allowInfinity: boolean = false 17 | ): number { 18 | const errorHandler = new ErrorHandler("clamp"); 19 | const maxValue = Number.MAX_SAFE_INTEGER; 20 | 21 | if ( 22 | typeof value !== "number" || 23 | typeof min !== "number" || 24 | typeof max !== "number" 25 | ) { 26 | errorHandler.throwTypeError( 27 | "number", 28 | `typeof value: ${typeof value}, min: ${typeof min}, max: ${typeof max}` 29 | ); 30 | } 31 | 32 | if (Number.isNaN(value) || Number.isNaN(min) || Number.isNaN(max)) { 33 | errorHandler.throwTypeError( 34 | "number", 35 | `typeof value: ${typeof value}, min: ${typeof min}, max: ${typeof max}` 36 | ); 37 | } 38 | 39 | if (min > max) { 40 | errorHandler.throwRangeError( 41 | "Minimum value cannot be greater than maximum value", 42 | "min > max" 43 | ); 44 | } 45 | 46 | if (!allowInfinity) { 47 | if ( 48 | Math.abs(value) > maxValue || 49 | Math.abs(min) > maxValue || 50 | Math.abs(max) > maxValue 51 | ) { 52 | errorHandler.throwRangeError( 53 | "Input values exceed the safe number range in JavaScript", 54 | "value, min, or max too large" 55 | ); 56 | } 57 | } 58 | 59 | return Math.max(min, Math.min(value, max)); 60 | } 61 | -------------------------------------------------------------------------------- /package/src/utils/function/hasKey/hasKey.test.ts: -------------------------------------------------------------------------------- 1 | import { hasKey } from "./hasKey"; 2 | 3 | 4 | describe('hasKey function', () => { 5 | 6 | it('should return true if the key exists in the object', () => { 7 | const obj = { name: 'John', age: 30 }; 8 | expect(hasKey(obj, 'name')).toBe(true); 9 | expect(hasKey(obj, 'age')).toBe(true); 10 | }); 11 | 12 | it('should return false if the key does not exist in the object', () => { 13 | const obj = { name: 'John', age: 30 }; 14 | expect(hasKey(obj, 'gender')).toBe(false); 15 | }); 16 | 17 | it('should return true for a symbol key if it exists in the object', () => { 18 | const symbolKey = Symbol('key'); 19 | const obj = { [symbolKey]: 'value' }; 20 | expect(hasKey(obj, symbolKey)).toBe(true); 21 | }); 22 | 23 | it('should return false for a symbol key if it does not exist in the object', () => { 24 | const obj = {}; 25 | const symbolKey = Symbol('key'); 26 | expect(hasKey(obj, symbolKey)).toBe(false); 27 | }); 28 | 29 | it('should return false if the key exists in the prototype chain but not as own property', () => { 30 | const obj = Object.create({ inheritedProp: 'value' }); 31 | obj.ownProp = 'ownValue'; 32 | expect(hasKey(obj, 'inheritedProp')).toBe(false); // Inherited, not own property 33 | expect(hasKey(obj, 'ownProp')).toBe(true); // Own property 34 | }); 35 | 36 | it('should throw a TypeError if the first argument is not an object', () => { 37 | expect(() => hasKey(null as any, 'name')).toThrow(TypeError); 38 | expect(() => hasKey(42 as any, 'name')).toThrow(TypeError); 39 | expect(() => hasKey('string' as any, 'name')).toThrow(TypeError); 40 | }); 41 | 42 | it('should throw a TypeError if the key is not a string or symbol', () => { 43 | const obj = { name: 'John' }; 44 | expect(() => hasKey(obj, null as any)).toThrow(TypeError); 45 | expect(() => hasKey(obj, undefined as any)).toThrow(TypeError); 46 | expect(() => hasKey(obj, 123 as any)).toThrow(TypeError); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /package/src/utils/function/hasKey/hasKey.ts: -------------------------------------------------------------------------------- 1 | import ErrorHandler from "../../../services/error-handler.service"; 2 | 3 | /** 4 | * The `hasKey` function checks if an object has a specific key and returns a boolean value. 5 | * @param {object} obj - The `obj` parameter is the object that you want to check for the presence of a 6 | * specific key. 7 | * @param {string | symbol} key - The `key` parameter in the `hasKey` function is the property key 8 | * (string or symbol) that you want to check for existence in the given object. 9 | * @returns The `hasKey` function returns a boolean value indicating whether the provided object `obj` 10 | * has the specified key `key`. 11 | */ 12 | export function hasKey(obj: object, key: string | symbol): boolean { 13 | const errorHandler = new ErrorHandler("hasKey"); 14 | 15 | if (typeof obj !== "object" || obj === null) { 16 | errorHandler.throwTypeError("object", typeof obj); 17 | } 18 | 19 | if (typeof key !== "string" && typeof key !== "symbol") { 20 | errorHandler.throwTypeError(['symbol', 'string'], typeof key); 21 | } 22 | 23 | return Object.prototype.hasOwnProperty.call(obj, key); 24 | } 25 | -------------------------------------------------------------------------------- /package/src/utils/function/index.ts: -------------------------------------------------------------------------------- 1 | export { hasKey } from "./hasKey/hasKey"; 2 | export { clamp } from "./clamp/clamp"; 3 | export { throttle } from "./throttle/throttle"; 4 | export { sleep } from "./sleep/sleep"; 5 | export { add } from "./add"; 6 | export { uniqueArray } from "./uniqueArray/uniqueArray"; 7 | export { sample } from "./sample/sample"; 8 | export { isUndefined } from "./isUndefined/isUndefined"; 9 | export { isNull } from "./isNull/isNull"; 10 | -------------------------------------------------------------------------------- /package/src/utils/function/isNull/isNull.test.ts: -------------------------------------------------------------------------------- 1 | import { isNull } from "./isNull"; 2 | 3 | describe('isNull', () => { 4 | test('should return true for null', () => { 5 | expect(isNull(null)).toBe(true); 6 | }); 7 | 8 | test('should return false for undefined', () => { 9 | expect(isNull(undefined)).toBe(false); 10 | }); 11 | 12 | test('should return false for 0', () => { 13 | expect(isNull(0)).toBe(false); 14 | }); 15 | 16 | test('should return false for empty string', () => { 17 | expect(isNull('')).toBe(false); 18 | }); 19 | 20 | test('should return false for false', () => { 21 | expect(isNull(false)).toBe(false); 22 | }); 23 | 24 | test('should return false for non-null values', () => { 25 | expect(isNull(123)).toBe(false); 26 | expect(isNull([])).toBe(false); 27 | expect(isNull({})).toBe(false); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /package/src/utils/function/isNull/isNull.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The `isNull` function in TypeScript checks if a value is null using strict equality and `Object.is` 3 | * comparison. 4 | * @param {unknown} value - The `value` parameter in the `isNull` function is of type `unknown`, which 5 | * means it can be any type. The function checks if the value is strictly equal to `null` or if it is 6 | * `null` using `Object.is` method. 7 | * @returns The function `isNull` returns a boolean value indicating whether the input `value` is 8 | * `null` or not. 9 | */ 10 | export function isNull(value: unknown): boolean { 11 | return value === null || Object.is(value, null); 12 | } 13 | -------------------------------------------------------------------------------- /package/src/utils/function/isUndefined/isUndefined.test.ts: -------------------------------------------------------------------------------- 1 | import { isUndefined } from './isUndefined'; 2 | 3 | describe('isUndefined', () => { 4 | test('should return true for undefined', () => { 5 | expect(isUndefined(undefined)).toBe(true); 6 | }); 7 | 8 | test('should return false for null', () => { 9 | expect(isUndefined(null)).toBe(false); 10 | }); 11 | 12 | test('should return false for 0', () => { 13 | expect(isUndefined(0)).toBe(false); 14 | }); 15 | 16 | test('should return false for empty string', () => { 17 | expect(isUndefined('')).toBe(false); 18 | }); 19 | 20 | test('should return false for false', () => { 21 | expect(isUndefined(false)).toBe(false); 22 | }); 23 | 24 | test('should return false for defined values', () => { 25 | expect(isUndefined(123)).toBe(false); 26 | expect(isUndefined([])).toBe(false); 27 | expect(isUndefined({})).toBe(false); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /package/src/utils/function/isUndefined/isUndefined.ts: -------------------------------------------------------------------------------- 1 | export function isUndefined(value: unknown): boolean { 2 | return typeof value === "undefined"; 3 | } 4 | -------------------------------------------------------------------------------- /package/src/utils/function/sample/sample.test.ts: -------------------------------------------------------------------------------- 1 | import { sample } from "./sample"; 2 | 3 | describe("sample function", () => { 4 | test("should return a random element from a non-empty array of numbers", () => { 5 | const arr = [1, 2, 3, 4, 5]; 6 | const result = sample(arr); 7 | expect(arr).toContain(result); 8 | }); 9 | 10 | test("should return a random element from a non-empty array of strings", () => { 11 | const arr = ["apple", "banana", "cherry"]; 12 | const result = sample(arr); 13 | expect(arr).toContain(result); 14 | }); 15 | 16 | test("should return a random element from a non-empty array of objects", () => { 17 | const arr = [{ id: 1 }, { id: 2 }, { id: 3 }]; 18 | const result = sample(arr); 19 | expect(arr).toContainEqual(result); 20 | }); 21 | 22 | test("should throw an error if the input is not an array", () => { 23 | expect(() => sample(null as unknown as unknown[])).toThrow( 24 | "TypeError: Expected an array as input" 25 | ); 26 | expect(() => sample(undefined as unknown as unknown[])).toThrow( 27 | "TypeError: Expected an array as input" 28 | ); 29 | expect(() => sample({} as unknown as unknown[])).toThrow( 30 | "TypeError: Expected an array as input" 31 | ); 32 | expect(() => sample("string" as unknown as unknown[])).toThrow( 33 | "TypeError: Expected an array as input" 34 | ); 35 | }); 36 | 37 | test("should throw an error if the input array is empty", () => { 38 | expect(() => sample([])).toThrow("Array cannot be empty."); 39 | }); 40 | 41 | test("should work with an array containing mixed types", () => { 42 | const arr = [1, "apple", { id: 1 }, null]; 43 | const result = sample(arr); 44 | expect(arr).toContain(result); 45 | }); 46 | 47 | test("should return the same element when the array has one element", () => { 48 | const arr = [42]; 49 | const result = sample(arr); 50 | expect(result).toBe(42); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /package/src/utils/function/sample/sample.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The `sample` function in TypeScript selects a random element from an input array. 3 | * @param {T[]} arr - The `arr` parameter in the `sample` function is an array of type `T`, where `T` 4 | * represents the type of elements in the array. The function selects a random element from the input 5 | * array and returns it. 6 | * @returns The `sample` function returns a random element from the input array `arr`. 7 | */ 8 | export const sample = (arr: T[]): T => { 9 | if (!Array.isArray(arr)) { 10 | throw new Error("TypeError: Expected an array as input"); 11 | } 12 | 13 | if (arr.length === 0) { 14 | throw new Error("Array cannot be empty."); 15 | } 16 | 17 | const randomIndex = Math.floor(Math.random() * arr.length); 18 | 19 | return arr[randomIndex]; 20 | }; 21 | -------------------------------------------------------------------------------- /package/src/utils/function/sleep/sleep.test.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from "./sleep"; 2 | 3 | describe("sleep function its", () => { 4 | it("should resolve after the specified delay", async () => { 5 | const start = Date.now(); 6 | await sleep(1000); 7 | const end = Date.now(); 8 | expect(end - start).toBeGreaterThanOrEqual(1000); 9 | }); 10 | 11 | it("should resolve immediately when given 0", async () => { 12 | const start = Date.now(); 13 | await sleep(0); 14 | const end = Date.now(); 15 | expect(end - start).toBeLessThan(50); 16 | }); 17 | 18 | it.skip("should throw an error when given a negative number", async () => { 19 | await expect(sleep(-1000)).rejects.toThrow('Invalid input: ms should be a non-negative number.'); 20 | }); 21 | 22 | it.skip("should throw an error when given NaN", async () => { 23 | await expect(sleep(Number.NaN)).rejects.toThrow(Error); 24 | }); 25 | 26 | it.skip("should throw an error when given a non-number type", async () => { 27 | await expect(sleep("1000" as unknown as number)).rejects.toThrow(TypeError); 28 | await expect(sleep({} as unknown as number)).rejects.toThrow(TypeError); 29 | }); 30 | 31 | it.skip("should throw an error for positive Infinity input", () => { 32 | expect(() => sleep(Number.POSITIVE_INFINITY)).toThrow(Error); 33 | }); 34 | 35 | it.skip("should throw an error for negative Infinity input", () => { 36 | expect(() => sleep(Number.NEGATIVE_INFINITY)).toThrow(Error); 37 | }); 38 | it("should resolve correctly with a large number", async () => { 39 | const start = Date.now(); 40 | await sleep(5000); // 5 seconds 41 | const end = Date.now(); 42 | expect(end - start).toBeGreaterThanOrEqual(5000); 43 | }, 7000); 44 | 45 | it("should allow multiple asynchronous calls", async () => { 46 | const start = Date.now(); 47 | await Promise.all([sleep(1000), sleep(1000), sleep(1000)]); 48 | const end = Date.now(); 49 | expect(end - start).toBeGreaterThanOrEqual(1000); 50 | }); 51 | 52 | it("should handle multiple subsequent calls", async () => { 53 | const start = Date.now(); 54 | await sleep(10); 55 | await sleep(10); 56 | await sleep(10); 57 | const end = Date.now(); 58 | expect(end - start).toBeGreaterThanOrEqual(10); 59 | }); 60 | 61 | it("should handle multiple simultaneous calls correctly", async () => { 62 | const start = Date.now(); 63 | const sleepPromises = [sleep(50), sleep(50), sleep(50)]; 64 | await Promise.all(sleepPromises); 65 | const end = Date.now(); 66 | expect(end - start).toBeGreaterThanOrEqual(50); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /package/src/utils/function/sleep/sleep.ts: -------------------------------------------------------------------------------- 1 | // src/utils/function/sleep.ts 2 | /** 3 | * The `sleep` function takes a number of milliseconds as input and returns a Promise that resolves 4 | * after the specified time has elapsed. 5 | * @param {number} ms - The `ms` parameter in the `sleep` function represents the number of 6 | * milliseconds to wait before resolving the Promise. It is a required parameter and should be a 7 | * non-negative number representing the time in milliseconds for the function to sleep before 8 | * resolving. 9 | * @returns A Promise that resolves after the specified number of milliseconds (ms) has elapsed. 10 | */ 11 | 12 | export const sleep = (ms: number): Promise => { 13 | 14 | 15 | if (typeof ms !== "number") { 16 | throw new TypeError("Invalid input: ms should be a number."); 17 | } 18 | 19 | if (Number.isNaN(ms)) { 20 | throw new Error("Invalid input: ms should not be NaN."); 21 | } 22 | 23 | if (ms < 0) { 24 | throw new RangeError("Invalid input: ms should be a non-negative number."); 25 | } 26 | 27 | if (!Number.isFinite(ms)) { 28 | throw new Error("Invalid input: ms should be a finite number."); 29 | } 30 | 31 | return new Promise((resolve) => setTimeout(resolve, ms)); 32 | }; 33 | -------------------------------------------------------------------------------- /package/src/utils/function/throttle/throttle.test.ts: -------------------------------------------------------------------------------- 1 | import { throttle } from "./throttle"; 2 | 3 | describe("throttle function", () => { 4 | beforeEach(() => { 5 | jest.useFakeTimers(); 6 | }); 7 | 8 | afterEach(() => { 9 | jest.useRealTimers(); 10 | }); 11 | 12 | it("should throttle the function and call it only once in the wait interval", () => { 13 | const func = jest.fn(); 14 | const throttleFunc = throttle(func, 1000); 15 | 16 | throttleFunc(); 17 | throttleFunc(); 18 | throttleFunc(); 19 | 20 | expect(func).toHaveBeenCalledTimes(1); 21 | 22 | jest.advanceTimersByTime(1000); 23 | throttleFunc(); 24 | 25 | expect(func).toHaveBeenCalledTimes(2); 26 | }); 27 | 28 | it("should delay function execution when called repeatedly within the throttle interval", () => { 29 | const func = jest.fn(); 30 | const throttleFunc = throttle(func, 1000); 31 | 32 | throttleFunc(); 33 | throttleFunc(); 34 | throttleFunc(); 35 | 36 | expect(func).toHaveBeenCalledTimes(1); 37 | 38 | // Move 500ms ahead, still within the throttle interval 39 | jest.advanceTimersByTime(500); 40 | throttleFunc(); 41 | 42 | expect(func).toHaveBeenCalledTimes(1); 43 | 44 | jest.advanceTimersByTime(500); 45 | expect(func).toHaveBeenCalledTimes(2); 46 | }); 47 | 48 | it("should execute function immediately after the throttle interval has passed", () => { 49 | const func = jest.fn(); 50 | const throttleFunc = throttle(func, 1000); 51 | 52 | throttleFunc(); 53 | expect(func).toHaveBeenCalledTimes(1); 54 | 55 | jest.advanceTimersByTime(1000); 56 | throttleFunc(); 57 | 58 | expect(func).toHaveBeenCalledTimes(2); 59 | }); 60 | 61 | it("should cancel pending throttled function execution when cancel is called", () => { 62 | const func = jest.fn(); 63 | const throttleFunc = throttle(func, 1000); 64 | 65 | throttleFunc(); 66 | expect(func).toHaveBeenCalledTimes(1); 67 | 68 | throttleFunc(); 69 | throttleFunc.cancel(); 70 | 71 | jest.advanceTimersByTime(1000); 72 | expect(func).toHaveBeenCalledTimes(1); 73 | }); 74 | 75 | it("should retain the original this context when executing the throttled function", () => { 76 | const context = { value: 42 }; 77 | const func = jest.fn(function () { 78 | expect(this).toBe(context); 79 | }); 80 | 81 | const throttleFunc = throttle(func.bind(context), 1000); 82 | 83 | throttleFunc(); 84 | jest.advanceTimersByTime(1000); 85 | 86 | expect(func).toHaveBeenCalledTimes(1); 87 | }); 88 | 89 | test("should not execute throttled function after cancel is called midway", () => { 90 | const func = jest.fn(); 91 | const throttledFunc = throttle(func, 1000); 92 | 93 | throttledFunc(); 94 | expect(func).toHaveBeenCalledTimes(1); 95 | 96 | throttledFunc(); 97 | throttledFunc.cancel(); 98 | 99 | jest.advanceTimersByTime(1000); 100 | expect(func).toHaveBeenCalledTimes(1); 101 | }); 102 | 103 | test("should handle multiple throttled instances independently", () => { 104 | const func1 = jest.fn(); 105 | const func2 = jest.fn(); 106 | 107 | const throttledFunc1 = throttle(func1, 1000); 108 | const throttledFunc2 = throttle(func2, 500); 109 | 110 | throttledFunc1(); 111 | throttledFunc2(); 112 | 113 | expect(func1).toHaveBeenCalledTimes(1); 114 | expect(func2).toHaveBeenCalledTimes(1); 115 | 116 | jest.advanceTimersByTime(500); 117 | // throttledFunc1 is still in throttle 118 | throttledFunc1(); 119 | throttledFunc2(); 120 | 121 | expect(func1).toHaveBeenCalledTimes(1); 122 | expect(func2).toHaveBeenCalledTimes(2); 123 | }); 124 | 125 | test("should throw an error if the first argument is not a function", () => { 126 | // biome-ignore lint/suspicious/noExplicitAny: 127 | expect(() => throttle(123 as any, 1000)).toThrow(TypeError); 128 | }); 129 | 130 | test("should throw an error if wait time is negative", () => { 131 | const func = jest.fn(); 132 | expect(() => throttle(func, -100)).toThrow(RangeError); 133 | }); 134 | 135 | test("should throw an error if wait time is not a number", () => { 136 | const func = jest.fn(); 137 | // biome-ignore lint/suspicious/noExplicitAny: 138 | expect(() => throttle(func, "100" as any)).toThrow(RangeError); 139 | }); 140 | 141 | test("should execute the function immediately if wait time is 0", () => { 142 | const func = jest.fn(); 143 | const throttledFunc = throttle(func); 144 | 145 | throttledFunc(); 146 | expect(func).toHaveBeenCalledTimes(1); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /package/src/utils/function/throttle/throttle.ts: -------------------------------------------------------------------------------- 1 | type throttleCancel = { cancel: () => void }; 2 | /** 3 | * The `throttle` function in TypeScript allows you to limit the rate at which a function can be 4 | * called. 5 | * @param {T} func - The `func` parameter in the `throttle` function is the function that you want to 6 | * throttle. This function will be called at most once within the specified `wait` time interval. 7 | * @param {number} wait - The `wait` parameter in the `throttle` function represents the time interval 8 | * in milliseconds that must elapse before the original function `func` can be called again. This 9 | * interval helps in limiting the frequency of function calls to prevent overwhelming the system with 10 | * rapid invocations. 11 | * @returns The `throttle` function returns a new function that wraps the original function provided as 12 | * an argument. This new function has additional functionality to throttle the execution of the 13 | * original function based on a specified time interval (`wait` parameter). The returned function also 14 | * has a `cancel` method that can be used to cancel the throttling and allow immediate execution of the 15 | * original function on the next call. 16 | */ 17 | 18 | export function throttle unknown>( 19 | func: T, 20 | wait = 0 21 | ): T & throttleCancel { 22 | if (typeof func !== "function") { 23 | throw new TypeError("Expected a function as the first argument"); 24 | } 25 | 26 | if (typeof wait !== "number" || wait < 0) { 27 | throw new RangeError("Wait time must be a positive number"); 28 | } 29 | 30 | let lastCall = 0; 31 | let timeoutId: ReturnType | null = null; 32 | let lastArgs: Parameters | null = null; 33 | let lastThis: unknown = null; 34 | 35 | const throttled = function (this: unknown, ...args: Parameters): void { 36 | const now = Date.now(); 37 | lastThis = this; 38 | 39 | if (now - lastCall < wait) { 40 | if (timeoutId) clearTimeout(timeoutId); 41 | lastArgs = args; 42 | 43 | timeoutId = setTimeout(() => { 44 | lastCall = Date.now(); 45 | timeoutId = null; 46 | if (lastArgs) { 47 | func.apply(lastThis, lastArgs); 48 | lastArgs = null; 49 | } 50 | }, wait - (now - lastCall)); 51 | } else { 52 | lastCall = now; 53 | func.apply(this, args); 54 | } 55 | }; 56 | 57 | throttled.cancel = () => { 58 | if (timeoutId) clearTimeout(timeoutId); 59 | timeoutId = null; 60 | lastCall = 0; 61 | lastArgs = null; 62 | }; 63 | 64 | return throttled as T & throttleCancel; 65 | } 66 | -------------------------------------------------------------------------------- /package/src/utils/function/uniqueArray/uniqueArray.test.ts: -------------------------------------------------------------------------------- 1 | import { uniqueArray } from "./uniqueArray"; 2 | 3 | describe("uniqueArray", () => { 4 | it("should return an empty array when input is an empty array", () => { 5 | const input: unknown[] = []; 6 | const result = uniqueArray(input); 7 | expect(result).toEqual([]); 8 | }); 9 | 10 | it("should handle arrays with primitive values", () => { 11 | expect(uniqueArray([1, 1, 2, 2, 3])).toEqual([1, 2, 3]); 12 | }); 13 | 14 | it("should handle arrays with mixed primitive types", () => { 15 | const input = [1, "1", 2, "2", 1, "1"]; 16 | const result = uniqueArray(input); 17 | expect(result).toEqual([1, "1", 2, "2"]); 18 | }); 19 | 20 | it("should handle arrays with mixed types", () => { 21 | expect(uniqueArray([1, "1", 2, "2", 1, "1"])).toEqual([1, "1", 2, "2"]); 22 | }); 23 | 24 | it("should handle arrays with complex objects", () => { 25 | const obj1 = { a: 1 }; 26 | const obj2 = { a: 2 }; 27 | expect(uniqueArray([obj1, obj1, obj2])).toEqual([obj1, obj2]); 28 | }); 29 | 30 | it("should handle arrays with complex nested objects", () => { 31 | const obj1 = { a: 1, child: { b: 10, child: { c: 12 } } }; 32 | const obj2 = { a: 1, child: { b: 10 } }; 33 | expect(uniqueArray([obj1, obj1, obj1, obj2, obj2])).toEqual([obj1, obj2]); 34 | }); 35 | 36 | it('should handle arrays with NaN values', () => { 37 | const input = [NaN, NaN, 1, 2]; 38 | const result = uniqueArray(input); 39 | expect(result).toEqual([NaN, 1, 2]); 40 | }); 41 | 42 | 43 | it('should handle arrays with mixed types including functions', () => { 44 | const fn1 = () => {}; 45 | const fn2 = () => {}; 46 | const input = [fn1, fn2, fn1, 1, 'a', 'a']; 47 | const result = uniqueArray(input); 48 | expect(result).toEqual([fn1, fn2, 1, 'a']); 49 | }); 50 | 51 | 52 | it('should handle very large arrays efficiently', () => { 53 | const largeArray = new Array(100000).fill(1).map((_, i) => (i % 2 === 0 ? i : { num: i })); 54 | const result = uniqueArray(largeArray); 55 | expect(result.length).toBe(100000); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /package/src/utils/function/uniqueArray/uniqueArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The `uniqueArray` function removes duplicate elements from an array, 3 | * @param {T[]} arr - An array of elements of any type that you want to make unique. 4 | * @returns {T[]} The `uniqueArray` function returns an array of unique elements from the input array `arr`, 5 | * where uniqueness is determined based on the values of the elements. The function handles both 6 | * primitive values and non-primitive values (objects) separately to ensure uniqueness. 7 | */ 8 | export const uniqueArray = (arr: T[]): T[] => { 9 | const primitives = new Set(); 10 | const nonPrimitives = new Map(); 11 | const result: T[] = []; 12 | 13 | for (const item of arr) { 14 | if (typeof item === "object" && item !== null) { 15 | // for non primitives 16 | const stringified = JSON.stringify(item); 17 | if (!nonPrimitives.has(stringified)) { 18 | nonPrimitives.set(stringified, item); 19 | result.push(item); 20 | } 21 | } else { 22 | // primitives 23 | if (!primitives.has(item)) { 24 | primitives.add(item); 25 | result.push(item); 26 | } 27 | } 28 | } 29 | 30 | return result; 31 | }; 32 | -------------------------------------------------------------------------------- /package/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./function"; 2 | export * from "./string"; -------------------------------------------------------------------------------- /package/src/utils/string/index.ts: -------------------------------------------------------------------------------- 1 | export { toUpperCase } from "./toUpperCase/toUpperCase"; 2 | export { toLower } from "./toLower/toLower"; 3 | -------------------------------------------------------------------------------- /package/src/utils/string/toLower/toLower.test.ts: -------------------------------------------------------------------------------- 1 | import { toLower } from "./toLower"; 2 | 3 | describe('toLower', () => { 4 | it('should convert uppercase letters to lowercase', () => { 5 | expect(toLower("HELLO")).toBe("hello"); 6 | }); 7 | 8 | it('should convert mixed-case letters to lowercase', () => { 9 | expect(toLower("HeLLo WOrLD")).toBe("hello world"); 10 | }); 11 | 12 | it('should return the same string if it is already lowercase', () => { 13 | expect(toLower("hello")).toBe("hello"); 14 | }); 15 | 16 | it('should return the same numeric string', () => { 17 | expect(toLower("12345")).toBe("12345"); 18 | }); 19 | 20 | it('should return an empty string if input is an empty string', () => { 21 | expect(toLower("")).toBe(""); 22 | }); 23 | 24 | it('should handle strings with special characters without modification', () => { 25 | expect(toLower("HELLO!@#$%^&*()")).toBe("hello!@#$%^&*()"); 26 | }); 27 | 28 | it('should handle strings with leading/trailing whitespace', () => { 29 | expect(toLower(" HeLLo ")).toBe(" hello "); 30 | }); 31 | 32 | it('should correctly handle locale-specific characters', () => { 33 | expect(toLower("İSTANBUL")).toBe("i̇stanbul"); // Turkish dotted I 34 | }); 35 | 36 | it('should throw TypeError if input is null', () => { 37 | expect(() => toLower(null as unknown as string)).toThrow( 38 | new TypeError("Input cannot be null or undefined") 39 | ); 40 | }); 41 | 42 | it('should throw TypeError if input is undefined', () => { 43 | expect(() => toLower(undefined as unknown as string)).toThrow( 44 | new TypeError("Input cannot be null or undefined") 45 | ); 46 | }); 47 | 48 | it('should throw TypeError if input is a number', () => { 49 | expect(() => toLower(123 as unknown as string)).toThrow( 50 | new TypeError("Input must be a string") 51 | ); 52 | }); 53 | 54 | it('should throw TypeError if input is an array', () => { 55 | expect(() => toLower([1, 2, 3] as unknown as string)).toThrow( 56 | new TypeError("Input must be a string") 57 | ); 58 | }); 59 | 60 | it('should throw TypeError if input is an object', () => { 61 | expect(() => toLower({ key: "value" } as unknown as string)).toThrow( 62 | new TypeError("Input must be a string") 63 | ); 64 | }); 65 | 66 | it('should throw TypeError if input is a boolean', () => { 67 | expect(() => toLower(true as unknown as string)).toThrow( 68 | new TypeError("Input must be a string") 69 | ); 70 | }); 71 | 72 | it('should handle international characters correctly (locale-aware)', () => { 73 | expect(toLower("ĞÜŞİÖÇ")).toBe("ğüşi̇öç"); 74 | }); 75 | 76 | it('should return the same string if input is whitespace only', () => { 77 | expect(toLower(" ")).toBe(" "); 78 | }); 79 | 80 | it('should return the string with numeric values unchanged', () => { 81 | expect(toLower("123AbC")).toBe("123abc"); 82 | }); 83 | 84 | it('should handle strings with emojis without modification', () => { 85 | expect(toLower("HELLO 🌟 WORLD")).toBe("hello 🌟 world"); 86 | }); 87 | 88 | it('handles non-string input', () => { 89 | expect(() => toLower(null)).toThrow(); 90 | expect(() => toLower(undefined)).toThrow(); 91 | expect(() => toLower(123 as unknown as string)).toThrow(); 92 | }); 93 | 94 | it('handles strings with only special characters or numbers', () => { 95 | expect(toLower('123!@#')).toBe('123!@#'); 96 | }); 97 | 98 | it('handles very long strings', () => { 99 | const longString = 'A'.repeat(1000000); 100 | const start = performance.now(); 101 | expect(toLower(longString)).toBe(longString.toLowerCase()); 102 | const end = performance.now(); 103 | expect(end - start).toBeLessThan(100); 104 | }); 105 | 106 | it('handles strings with Unicode characters', () => { 107 | expect(toLower('CAFÉ')).toBe('café'); 108 | expect(toLower('ΕΛΛΗΝΙΚΆ')).toBe('ελληνικά'); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /package/src/utils/string/toLower/toLower.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a given string to lowercase. 3 | * 4 | * @param {unknown} input - The input value to convert to lowercase. 5 | * @returns {string} - The lowercase version of the input string. 6 | * @throws {TypeError} - Throws if the input is not a valid string or cannot be converted. 7 | */ 8 | export function toLower(input: unknown): string { 9 | if (input === null || input === undefined) { 10 | throw new TypeError("Input cannot be null or undefined"); 11 | } 12 | 13 | if (typeof input !== "string") { 14 | throw new TypeError("Input must be a string"); 15 | } 16 | 17 | if (input.length === 0) { 18 | return input; 19 | } 20 | 21 | return input.toLocaleLowerCase(); 22 | } 23 | -------------------------------------------------------------------------------- /package/src/utils/string/toUpperCase/toUpperCase.test.ts: -------------------------------------------------------------------------------- 1 | import { toUpperCase } from "./toUpperCase"; 2 | 3 | describe("toUpperCase", () => { 4 | test("should convert a valid string to uppercase", () => { 5 | const result = toUpperCase("hello"); 6 | expect(result).toBe("HELLO"); 7 | }); 8 | 9 | test("should return an empty string when input is an empty string", () => { 10 | const result = toUpperCase(""); 11 | expect(result).toBe(""); 12 | }); 13 | 14 | test.each([ 15 | [123], 16 | [true], 17 | [null], 18 | [undefined], 19 | [Symbol("symbol")], 20 | [BigInt(123)], 21 | [[]], 22 | [{} as any], 23 | ])("should throw a TypeError for non-string input (%s)", (input) => { 24 | const expectedType = "string"; 25 | const receivedType = typeof input; 26 | 27 | const expectedError = new TypeError( 28 | `rsc: error from toUpperCase: Expected type ${expectedType} but received ${receivedType}.` 29 | ); 30 | 31 | expect(() => toUpperCase(input)).toThrow(expectedError); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /package/src/utils/string/toUpperCase/toUpperCase.ts: -------------------------------------------------------------------------------- 1 | import ErrorHandler from "../../../services/error-handler.service"; 2 | 3 | /** 4 | * The function `toUpperCase` takes a string input and returns the input string converted to uppercase, 5 | * with error handling for non-string inputs. 6 | * @param {string} str - string 7 | * @returns the input string `str` converted to uppercase using the `toUpperCase` method. 8 | */ 9 | export function toUpperCase(str: string): string { 10 | const errorHandler = new ErrorHandler("toUpperCase"); 11 | 12 | if (typeof str !== "string") { 13 | errorHandler.throwError("TypeError", "string", str); 14 | } 15 | return str.toUpperCase(); 16 | } 17 | -------------------------------------------------------------------------------- /package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES2023", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | "lib": ["DOM", "DOM.Iterable", "ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | "jsx": "react", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "ESNext", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 43 | "resolveJsonModule": true, /* Enable importing .json files. */ 44 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 46 | 47 | /* JavaScript Support */ 48 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 49 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 50 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 51 | 52 | /* Emit */ 53 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 54 | "declarationMap": true, /* Create sourcemaps for d.ts files. */ 55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 56 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 57 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 58 | // "noEmit": true, /* Disable emitting files from a compilation. */ 59 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 60 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 61 | // "removeComments": true, /* Disable emitting comments. */ 62 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | 75 | /* Interop Constraints */ 76 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 77 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 78 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 92 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 93 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 94 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 95 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 96 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 97 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 98 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 99 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 100 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 101 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 102 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 103 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 104 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 105 | 106 | /* Completeness */ 107 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 108 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 109 | }, 110 | "exclude": ["node_modules"] 111 | } 112 | -------------------------------------------------------------------------------- /package/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | format: ["cjs", "esm"], // Build for commonJS and ESmodules 6 | dts: true, // Generate declaration file (.d.ts) 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | }); -------------------------------------------------------------------------------- /smart-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | yarn.lock 27 | package-lock.json 28 | -------------------------------------------------------------------------------- /smart-app/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | -------------------------------------------------------------------------------- /smart-app/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /smart-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /smart-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-smart-utils", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@nextui-org/react": "^2.4.8", 14 | "framer-motion": "^11.11.10", 15 | "nextui-cli": "^0.3.4", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1", 18 | "react-smart-utils": "file:../package" 19 | }, 20 | "devDependencies": { 21 | "@eslint/js": "^9.11.1", 22 | "@types/react": "^18.3.10", 23 | "@types/react-dom": "^18.3.0", 24 | "@vitejs/plugin-react-swc": "^3.5.0", 25 | "eslint": "^9.11.1", 26 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 27 | "eslint-plugin-react-refresh": "^0.4.12", 28 | "globals": "^15.9.0", 29 | "tailwindcss": "^3.4.14", 30 | "typescript": "^5.5.3", 31 | "typescript-eslint": "^8.7.0", 32 | "vite": "^5.4.8" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /smart-app/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /smart-app/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | margin:0; 3 | padding: 0 4 | } 5 | 6 | .logo { 7 | height: 6em; 8 | padding: 1.5em; 9 | will-change: filter; 10 | transition: filter 300ms; 11 | } 12 | .logo:hover { 13 | filter: drop-shadow(0 0 2em #646cffaa); 14 | } 15 | .logo.react:hover { 16 | filter: drop-shadow(0 0 2em #61dafbaa); 17 | } 18 | 19 | @keyframes logo-spin { 20 | from { 21 | transform: rotate(0deg); 22 | } 23 | to { 24 | transform: rotate(360deg); 25 | } 26 | } 27 | 28 | @media (prefers-reduced-motion: no-preference) { 29 | a:nth-of-type(2) .logo { 30 | animation: logo-spin infinite 20s linear; 31 | } 32 | } 33 | 34 | .card { 35 | padding: 2em; 36 | } 37 | 38 | .read-the-docs { 39 | color: #888; 40 | } 41 | -------------------------------------------------------------------------------- /smart-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { SectionWrapper } from "./components/common/section-wrapper"; 3 | import UseClipboard from "./components/hooks/UseClipboard"; 4 | import UseCountDown from "./components/hooks/UseCountdown"; 5 | import UseLocalStorage from "./components/hooks/UseLocalStorage"; 6 | import UsePrevious from "./components/hooks/UsePrevious"; 7 | 8 | function App() { 9 | const rsu = [ 10 | { 11 | title: "Hooks", 12 | children: [ 13 | { 14 | title: "UseCountdown", 15 | node: , 16 | }, 17 | { 18 | title: "UsePrevious", 19 | node: , 20 | }, 21 | { 22 | title: "UseLocalStorage", 23 | node: , 24 | }, 25 | { 26 | title: "UseClipboard", 27 | node: , 28 | }, 29 | 30 | ], 31 | }, 32 | { 33 | title: "Utils", 34 | children: [ 35 | { 36 | title: "sleep", 37 | node: , 38 | }, 39 | ], 40 | }, 41 | ]; 42 | 43 | return ( 44 | <> 45 |
52 |

Content

53 | 54 |
    55 | {rsu.map((_rsu, _index) => ( 56 |
    57 |
  • {_rsu.title}
  • 58 |
      59 | {_rsu.children.map((__rsu, __index) => ( 60 |
    1. 61 | {__rsu.title} 62 |
    2. 63 | ))} 64 |
    65 |
    66 | ))} 67 |
68 |
69 | 70 | {rsu.map((_rsu) => { 71 | return ( 72 |
73 | 74 | #{_rsu.title} 75 | 76 | {_rsu.children.map((__rsu) => ( 77 | 82 | {__rsu.node} 83 | 84 | ))} 85 |
86 | ); 87 | })} 88 | 89 | ); 90 | } 91 | 92 | export default App; 93 | -------------------------------------------------------------------------------- /smart-app/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /smart-app/src/components/common/section-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { type ReactNode } from "react"; 2 | import { useWindowSize } from "react-smart-utils"; 3 | 4 | export const SectionWrapper = ({ 5 | title, 6 | to, 7 | children, 8 | }: { 9 | title: string; 10 | to: string; 11 | children: ReactNode; 12 | }) => { 13 | const { width } = useWindowSize(); 14 | return ( 15 |
21 | 22 | #{title} 23 | 24 | 25 |
{children}
26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /smart-app/src/components/hooks/UseClipboard.tsx: -------------------------------------------------------------------------------- 1 | import { useClipboard } from 'react-smart-utils'; 2 | 3 | const UseClipboard = () => { 4 | const { isCopied, copy, copiedText } = useClipboard(); 5 | 6 | const handleCopy = () => { 7 | copy("Hello"); 8 | }; 9 | 10 | return ( 11 |
12 | 21 | {isCopied &&

Copied: {copiedText}

} 22 |
23 | ); 24 | }; 25 | 26 | export default UseClipboard; -------------------------------------------------------------------------------- /smart-app/src/components/hooks/UseCountdown.tsx: -------------------------------------------------------------------------------- 1 | import { useCountdown } from "react-smart-utils"; 2 | import { SectionWrapper } from "../common/section-wrapper"; 3 | 4 | const UseCountDown = () => { 5 | const endTime = 100; // 60 seconds countdown 6 | const [ 7 | timeLeft, 8 | { start, pause, resume, reset, increaseTime, decreaseTime }, 9 | ] = useCountdown(endTime, { 10 | interval: 1000, 11 | format: "hh:mm:ss", 12 | onTick: () => { 13 | console.log(`Count is ${timeLeft} s`); 14 | }, 15 | onComplete: () => { 16 | console.log("completed"); 17 | }, 18 | }); 19 | 20 | return ( 21 |
22 | {timeLeft !== null ? ( 23 |

24 | Time remaining: {typeof timeLeft} {timeLeft} seconds 25 |

26 | ) : ( 27 |

Countdown is not active.

28 | )} 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 | ); 40 | }; 41 | 42 | export default UseCountDown; 43 | -------------------------------------------------------------------------------- /smart-app/src/components/hooks/UseLocalStorage.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLocalStorage } from "react-smart-utils"; 3 | 4 | const UseLocalStorage = () => { 5 | const [value, setValue] = useLocalStorage<"dark" | "light">("theme", undefined); 6 | 7 | console.log(value) 8 | return ( 9 |
10 | 13 |

Check the localstorage to see the result

14 |
15 | ); 16 | }; 17 | 18 | export default UseLocalStorage; 19 | -------------------------------------------------------------------------------- /smart-app/src/components/hooks/UsePrevious.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { usePrevious } from "react-smart-utils"; 3 | import { SectionWrapper } from "../common/section-wrapper"; 4 | 5 | function UsePrevious() { 6 | const [count, setCount] = useState(0); 7 | const prevCount = usePrevious(count); // Track the previous value of count 8 | 9 | useEffect(() => { 10 | console.log(`Previous count: ${prevCount}, Current count: ${count}`); 11 | }, [count, prevCount]); 12 | 13 | return ( 14 | <> 15 |

Current count: {count}

16 |

Previous count: {prevCount}

17 | 18 | 19 | ); 20 | } 21 | 22 | export default UsePrevious; 23 | -------------------------------------------------------------------------------- /smart-app/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 7 | line-height: 1.5; 8 | font-weight: 400; 9 | 10 | color-scheme: light dark; 11 | color: rgba(255, 255, 255, 0.87); 12 | background-color: #242424; 13 | 14 | font-synthesis: none; 15 | text-rendering: optimizeLegibility; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | } 19 | 20 | a { 21 | font-weight: 800; 22 | color: rgba(255, 255, 255, 0.87); 23 | font-size: 2.4rem; 24 | text-decoration: underline; 25 | } 26 | a:hover { 27 | color: #535bf2; 28 | } 29 | 30 | body { 31 | margin: 0; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #646cff; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | @media (prefers-color-scheme: light) { 59 | :root { 60 | color: #213547; 61 | background-color: #ffffff; 62 | } 63 | a:hover { 64 | color: #747bff; 65 | } 66 | button { 67 | background-color: #f9f9f9; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /smart-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /smart-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /smart-app/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./src/**/*.{js,jsx,ts,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | 12 | -------------------------------------------------------------------------------- /smart-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /smart-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /smart-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /smart-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------