├── .env.template ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .husky └── _ │ ├── pre-commit │ └── prepare-commit-msg ├── .storybook ├── main.ts └── preview.ts ├── LICENSE.md ├── README.md ├── biome.json ├── degit.json ├── lefthook.yml ├── package.json ├── pnpm-lock.yaml ├── src ├── Example.css ├── Example.tsx ├── index.ts └── stories │ └── Example.stories.tsx ├── tests ├── Example.test.tsx ├── __snapshots__ │ └── Example.test.tsx.snap └── setup.js ├── tsconfig.json ├── tsup.config.ts └── vitest.config.mts /.env.template: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimMikeladze/typescript-react-package-starter/c5c52fd4751c6b1a58b8b950457a23e069505a26/.env.template -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: linesofcodedev 2 | custom: ['https://www.paypal.me/TimMikeladze'] 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | target-branch: 'main' 5 | directory: '/' 6 | schedule: 7 | interval: 'monthly' 8 | groups: 9 | minor-and-patch-updates: 10 | patterns: 11 | - '*' 12 | update-types: 13 | - 'minor' 14 | - 'patch' 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: [push] 4 | 5 | jobs: 6 | main: 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | 10 | name: Test & Build 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up pnpm 21 | uses: pnpm/action-setup@v4 22 | with: 23 | version: latest 24 | run_install: false 25 | 26 | - name: Install Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: 20 30 | cache: 'pnpm' 31 | 32 | - name: Get pnpm store directory 33 | shell: bash 34 | run: | 35 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 36 | 37 | - uses: actions/cache@v4 38 | name: Setup pnpm cache 39 | with: 40 | path: ${{ env.STORE_PATH }} 41 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 42 | restore-keys: | 43 | ${{ runner.os }}-pnpm-store- 44 | 45 | - name: Install dependencies 46 | run: pnpm install --frozen-lockfile 47 | 48 | - name: Check types 49 | run: pnpm tsc 50 | 51 | - name: Check linting 52 | run: pnpm lint:ci 53 | 54 | - name: Run tests 55 | run: pnpm test:ci 56 | 57 | - name: Build storybook 58 | run: pnpm storybook:build 59 | 60 | - name: Build package 61 | run: pnpm build 62 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Publish 5 | 6 | on: 7 | workflow_dispatch: 8 | release: 9 | types: [published] 10 | 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | jobs: 17 | publish: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Set up pnpm 26 | uses: pnpm/action-setup@v4 27 | with: 28 | version: latest 29 | run_install: false 30 | 31 | - name: Install Node.js 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: 20 35 | cache: 'pnpm' 36 | 37 | - name: Get pnpm store directory 38 | shell: bash 39 | run: | 40 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 41 | 42 | - uses: actions/cache@v4 43 | name: Setup pnpm cache 44 | with: 45 | path: ${{ env.STORE_PATH }} 46 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 47 | restore-keys: | 48 | ${{ runner.os }}-pnpm-store- 49 | 50 | - name: Deploy Storybook 51 | uses: bitovi/github-actions-storybook-to-github-pages@v1.0.2 52 | with: 53 | install_command: pnpm install --frozen-lockfile 54 | build_command: pnpm storybook:build 55 | path: storybook-static 56 | env: 57 | GH_TOKEN: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} 58 | 59 | # - name: Build and publish to npm 60 | # if: github.ref == 'refs/tags/v*' # Only run on version tags 61 | # run: | 62 | # pnpm build 63 | # npm login --registry=https://registry.npmjs.org/ --scope=your-scope 64 | # npm publish 65 | # env: 66 | # NODE_AUTH_TOKEN: ${{ secrets.YOUR_NPM_AUTH_TOKEN }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # JetBrains IDE files 9 | .idea/ 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /dist 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | tsconfig.tsbuildinfo 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | storybook-static 35 | -------------------------------------------------------------------------------- /.husky/_/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then 4 | set -x 5 | fi 6 | 7 | if [ "$LEFTHOOK" = "0" ]; then 8 | exit 0 9 | fi 10 | 11 | call_lefthook() 12 | { 13 | if test -n "$LEFTHOOK_BIN" 14 | then 15 | "$LEFTHOOK_BIN" "$@" 16 | elif lefthook -h >/dev/null 2>&1 17 | then 18 | lefthook "$@" 19 | else 20 | dir="$(git rev-parse --show-toplevel)" 21 | osArch=$(uname | tr '[:upper:]' '[:lower:]') 22 | cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') 23 | if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" 24 | then 25 | "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" 26 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" 27 | then 28 | "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" 29 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" 30 | then 31 | "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" 32 | elif test -f "$dir/node_modules/lefthook/bin/index.js" 33 | then 34 | "$dir/node_modules/lefthook/bin/index.js" "$@" 35 | 36 | elif bundle exec lefthook -h >/dev/null 2>&1 37 | then 38 | bundle exec lefthook "$@" 39 | elif yarn lefthook -h >/dev/null 2>&1 40 | then 41 | yarn lefthook "$@" 42 | elif pnpm lefthook -h >/dev/null 2>&1 43 | then 44 | pnpm lefthook "$@" 45 | elif swift package plugin lefthook >/dev/null 2>&1 46 | then 47 | swift package --disable-sandbox plugin lefthook "$@" 48 | elif command -v mint >/dev/null 2>&1 49 | then 50 | mint run csjones/lefthook-plugin "$@" 51 | elif command -v npx >/dev/null 2>&1 52 | then 53 | npx lefthook "$@" 54 | else 55 | echo "Can't find lefthook in PATH" 56 | fi 57 | fi 58 | } 59 | 60 | call_lefthook run "pre-commit" "$@" 61 | -------------------------------------------------------------------------------- /.husky/_/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then 4 | set -x 5 | fi 6 | 7 | if [ "$LEFTHOOK" = "0" ]; then 8 | exit 0 9 | fi 10 | 11 | call_lefthook() 12 | { 13 | if test -n "$LEFTHOOK_BIN" 14 | then 15 | "$LEFTHOOK_BIN" "$@" 16 | elif lefthook -h >/dev/null 2>&1 17 | then 18 | lefthook "$@" 19 | else 20 | dir="$(git rev-parse --show-toplevel)" 21 | osArch=$(uname | tr '[:upper:]' '[:lower:]') 22 | cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') 23 | if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" 24 | then 25 | "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" 26 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" 27 | then 28 | "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" 29 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" 30 | then 31 | "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" 32 | elif test -f "$dir/node_modules/lefthook/bin/index.js" 33 | then 34 | "$dir/node_modules/lefthook/bin/index.js" "$@" 35 | 36 | elif bundle exec lefthook -h >/dev/null 2>&1 37 | then 38 | bundle exec lefthook "$@" 39 | elif yarn lefthook -h >/dev/null 2>&1 40 | then 41 | yarn lefthook "$@" 42 | elif pnpm lefthook -h >/dev/null 2>&1 43 | then 44 | pnpm lefthook "$@" 45 | elif swift package plugin lefthook >/dev/null 2>&1 46 | then 47 | swift package --disable-sandbox plugin lefthook "$@" 48 | elif command -v mint >/dev/null 2>&1 49 | then 50 | mint run csjones/lefthook-plugin "$@" 51 | elif command -v npx >/dev/null 2>&1 52 | then 53 | npx lefthook "$@" 54 | else 55 | echo "Can't find lefthook in PATH" 56 | fi 57 | fi 58 | } 59 | 60 | call_lefthook run "prepare-commit-msg" "$@" 61 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/react-webpack5"; 2 | const config: StorybookConfig = { 3 | stories: ["../src/**/*.stories.@(js|jsx|ts|tsx|mdx)"], 4 | addons: [ 5 | "@storybook/addon-links", 6 | "@storybook/addon-essentials", 7 | "@storybook/addon-interactions", 8 | "@storybook/addon-webpack5-compiler-swc", 9 | ], 10 | framework: { 11 | name: "@storybook/react-webpack5", 12 | options: { 13 | builder: { 14 | useSWC: true, 15 | }, 16 | }, 17 | }, 18 | swc: () => ({ 19 | jsc: { 20 | transform: { 21 | react: { 22 | runtime: "automatic", 23 | }, 24 | }, 25 | }, 26 | }), 27 | docs: { 28 | autodocs: "tag", 29 | }, 30 | }; 31 | export default config; 32 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@storybook/react"; 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/, 9 | }, 10 | }, 11 | }, 12 | }; 13 | 14 | export default preview; 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Tim Mikeladze 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📦 Typescript • React • Package Starter 2 | 3 | A slightly opinionated starter kit for developing TypeScript and/or React NPM packages. It comes with a several pre-configured tools, so you could focus on coding instead of configuring a project for the nth time. From building to releasing a package, this starter kit has you covered. 4 | 5 | > 👋 Hello there! Follow me [@linesofcode](https://twitter.com/linesofcode) or visit [linesofcode.dev](https://linesofcode.dev) for more cool projects like this one. 6 | 7 | ## 🏃 Getting started 8 | 9 | ```console 10 | npx degit TimMikeladze/typescript-react-package-starter my-package 11 | 12 | cd my-package && git init 13 | 14 | pnpm install && pnpm dev 15 | ``` 16 | 17 | ❗Important note: This project uses [pnpm](https://pnpm.io/) for managing dependencies. If you want to use another package manager, remove the `pnpm-lock.yaml` and control-f for usages of `pnpm` in the project and replace them with your package manager of choice. If you don't have `pnpm` installed and want to use it, you can install it by running `npm install -g pnpm`. 18 | 19 | ## What's included? 20 | 21 | - ⚡️ [tsup](https://github.com/egoist/tsup) - The simplest and fastest way to bundle your TypeScript libraries. Used to bundle package as ESM and CJS modules. Supports TypeScript, Code Splitting, PostCSS, and more out of the box. 22 | - 📖 [Storybook](https://storybook.js.org/) - Build UI components and pages in isolation. It streamlines UI development, testing, and documentation. 23 | - 🧪 [Vitest](https://vitest.dev/) - A testing framework for JavaScript. Preconfigured to work with TypeScript and JSX. 24 | - ✅ [Biome](https://biomejs.dev/) - Format, lint, and more in a fraction of a second. 25 | - 🪝 [Lefthook](https://github.com/evilmartians/lefthook) — Run pre-commit hooks, lints staged files, executes tests, and more. 26 | - 🔼 [Release-it](https://github.com/release-it/release-it/) - release-it is a command line tool to automatically generate a new GitHub Release and populates it with the changes (commits) made since the last release. 27 | - 🐙 [Test & Publish via Github Actions](https://docs.github.com/en/actions) - CI/CD workflows for your package. Run tests on every commit plus integrate with Github Releases to automate publishing package to NPM and Storybook to Github Pages. 28 | - 🤖 [Dependabot](https://docs.github.com/en/code-security/dependabot) - Github powered dependency update tool that fits into your workflows. Configured to periodically check your dependencies for updates and send automated pull requests. 29 | - 🏃‍♀️‍➡️ [TSX](https://github.com/privatenumber/tsx) - Execute TypeScript files with zero-config in a Node.js environment. 30 | 31 | ## Usage 32 | 33 | ### 💻 Developing 34 | 35 | Watch and rebuild code with `tsup` and runs Storybook to preview your UI during development. 36 | 37 | ```console 38 | pnpm dev 39 | ``` 40 | 41 | Run all tests and watch for changes 42 | 43 | ```console 44 | pnpm test 45 | ``` 46 | 47 | ### 🏗️ Building 48 | 49 | Build package with `tsup` for production. 50 | 51 | ```console 52 | pnpm build 53 | ``` 54 | 55 | ### ▶️ Running files written in TypeScript 56 | 57 | To execute a file written in TypeScript inside a Node.js environment, use the `tsx` command. This will detect your `tsconfig.json` and run the file with the correct configuration. This is perfect for running custom scripts while remaining type-safe. 58 | 59 | ```console 60 | pnpm tsx ./path/to/file.ts 61 | ``` 62 | 63 | This is useful for running scripts, starting a server, or any other code you want to run while remaining type-safe. 64 | 65 | ### 🖇️ Linking 66 | 67 | Often times you want to `link` this package to another project when developing locally, circumventing the need to publish to NPM to consume it. 68 | 69 | In a project where you want to consume your package run: 70 | 71 | ```console 72 | pnpm link my-package --global 73 | ``` 74 | 75 | Learn more about package linking [here](https://pnpm.io/cli/link). 76 | 77 | ### 📩 Committing 78 | 79 | When you are ready to commit simply run the following command to get a well formatted commit message. All staged files will automatically be linted and fixed as well. 80 | 81 | ```console 82 | pnpm commit 83 | ``` 84 | 85 | ### ✅ Linting 86 | 87 | To lint and reformat your code at any time, simply run the following command. Under the hood, this uses [Biome](https://biomejs.dev/). If you use VSCode, I suggest installing the official [biome extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome). 88 | 89 | ```console 90 | pnpm lint 91 | ``` 92 | 93 | ### 🔖 Releasing, tagging & publishing to NPM 94 | 95 | Create a semantic version tag and publish to Github Releases. When a new release is detected a Github Action will automatically build the package and publish it to NPM. Additionally, a Storybook will be published to Github pages. 96 | 97 | Learn more about how to use the `release-it` command [here](https://github.com/release-it/release-it). 98 | 99 | ```console 100 | pnpm release 101 | ``` 102 | 103 | When you are ready to publish to NPM simply run the following command: 104 | 105 | ```console 106 | pnpm publish 107 | ``` 108 | 109 | #### 🤖 Auto publish after Github Release (or manually by dispatching the Publish workflow) 110 | 111 | ❗Important note: in order to automatically publish a Storybook on Github Pages you need to open your repository settings, navigate to "Actions" and enable **"Read & write permissions"** for Workflows. Then navigate to "Pages" and choose **"GitHub Actions"** as the source for the Build and Deployment. After a successful deployment you can find your Storybook at `https://.github.io//`. 112 | 113 | ❗Important note: in order to publish package to NPM you must add your token as a Github Action secret. Learn more on how to configure your repository and publish packages through Github Actions [here](https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages). 114 | 115 | ## 🎨 CSS & PostCSS 116 | 117 | To bundle CSS files with your package that you intend on users to import within their own project, a few extra steps are required. 118 | 119 | 1. Add your CSS files to the `src` directory. For example, `src/styles.css`. 120 | 2. Modify `tsup.config.ts` file to include your CSS file as an entry point. For example: 121 | 122 | ```ts 123 | import { defineConfig } from "tsup"; 124 | 125 | export default defineConfig({ 126 | entry: ["src/index.ts", "src/styles.css"], 127 | // ... 128 | }); 129 | ``` 130 | 131 | 3. Modify `package.json` to include the CSS file as an `exports` entry. For example: 132 | 133 | ```json 134 | { 135 | "exports": { 136 | "./styles.css": "./dist/styles.css" 137 | } 138 | } 139 | ``` 140 | 141 | 4. Now consumers of your package can import your CSS file anywhere in their project. For example: 142 | 143 | ```ts 144 | import "your-package/styles.css"; 145 | ``` 146 | 147 | Alternatively, if your package has a hard dependency on a CSS file and you want it to always be loaded when your package is imported, you can import it anywhere within your package's code and it will be bundled with-in your package. 148 | 149 | [tsup](https://github.com/egoist/tsup) supports PostCSS out of the box. Simply run `pnpm add postcss -D` add a `postcss.config.js` file to the root of your project, then add any plugins you need. Learn more how to configure PostCSS [here](https://tsup.egoist.dev/#css-support). 150 | 151 | Additionally consider using the [tsup](https://github.com/egoist/tsup) configuration option `injectStyle` to inject the CSS directly into your Javascript bundle instead of outputting a separate CSS file. 152 | 153 | ## 🚀 Built something using this starter-kit? 154 | 155 | That's awesome! Feel free to add it to the list. 156 | 157 | 🗃️ **[Next Upload](https://github.com/TimMikeladze/next-upload)** - Turn-key solution for integrating Next.js with signed & secure file-uploads to an S3 compliant storage service such as R2, AWS, or Minio. 158 | 159 | 🏁 **[Next Flag](https://github.com/TimMikeladze/next-flag)** - Feature flags powered by GitHub issues and NextJS. Toggle the features of your app by ticking a checkbox in a GitHub issue. Supports server-side rendering, multiple environments, and can be deployed as a stand-alone feature flag server. 160 | 161 | 🔒 **[Next Protect](https://github.com/TimMikeladze/next-protect)** - Password protect a Next.js site. Supports App Router, Middleware and Edge Runtime. 162 | 163 | 📮 **[Next Invite](https://github.com/TimMikeladze/next-invite)** - A drop-in invite system for your Next.js app. Generate and share invite links for users to join your app. 164 | 165 | 🔐 **[Next Auth MUI](https://github.com/TimMikeladze/next-auth-mui)** - Sign-in dialog component for NextAuth built with Material UI and React. Detects configured OAuth and Email providers and renders buttons or input fields for each respectively. Fully themeable, extensible and customizable to support custom credential flows. 166 | 167 | ⌚️ **[Next Realtime](https://github.com/TimMikeladze/next-realtime)** - Experimental drop-in solution for real-time data leveraging the Next.js Data Cache. 168 | 169 | ✅ **[Mui Joy Confirm](https://github.com/TimMikeladze/mui-joy-confirm)** - Confirmation dialogs built on top of [@mui/joy](https://mui.com/joy-ui/getting-started/) and react hooks. 170 | 171 | 🗂️ **[Use FS](https://github.com/TimMikeladze/use-fs)** - A React hook for integrating with the File System Access API. 172 | 173 | 🐙 **[Use Octokit](https://github.com/TimMikeladze/use-octokit)** - A data-fetching hook built on top of the Octokit and SWR for interacting with the Github API. Use this inside a React component for a type-safe, data-fetching experience with caching, polling, and more. 174 | 175 | 🐌 **[Space Slug](https://github.com/TimMikeladze/space-slug)** - Generate unique slugs, usernames, numbers, custom words, and more using an intuitive api with zero dependencies. 176 | 177 | 🌡️ **[TSC Baseline](https://github.com/TimMikeladze/tsc-baseline/)** - Save a baseline of TypeScript errors and compare new errors against it. Useful for type-safe feature development in TypeScript projects that have a lot of errors. This tool will filter out errors that are already in the baseline and only show new errors. 178 | 179 | ✅ **[react-ai-translator](https://github.com/CodeThicket/react-ai-translator)** - A React hook for local, secure, on-demand translations powered by the Xenova/nllb-200-distilled-600M model. This package utilizes the WebGPU capabilities of the device on which the app runs, ensuring data privacy and enabling you to translate text without sending data to third-party APIs. 180 | 181 | ♾️ **[react-infinite-observer](https://github.com/Tasin5541/react-infinite-observer)** - A simple hook to implement infinite scroll in react component, with full control over the behavior. Implemented with IntersectionObserver. 182 | 183 | **[react-simple-devicons](https://github.com/shawilly/react-simple-devicons)** - A straightforward React implementation that provides access to SVG dev icons from (devicon.dev)[https://devicon.dev], allowing customization of color, size, and styling. 184 | 185 | 🎋 **[GitHub Issue to Branch](https://github.com/TimMikeladze/github-issue-to-branch)** - CLI tool to quickly create well-named branches from GitHub issues. 186 | 187 | 📏 **[React DevBar](https://github.com/TimMikeladze/react-devbar/)** - A customizable floating toolbar for React applications. Build and integrate your own dev tools with a draggable interface inspired by the Vercel toolbar. Perfect for adding debugging panels, theme controls, and other development utilities for your app. 188 | 189 | ⏲️ **[Fake Time Series](https://github.com/TimMikeladze/fake-time-series/)** - A flexible CLI tool and library for generating fake time series data. Perfect for testing, development, and demonstration purposes. 190 | 191 | 📡 **[Install Command](https://github.com/TimMikeladze/react-install-command/)** - A React component for rendering a 'npm install ' command block. Supports multiple package managers. 192 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "files": { 4 | "ignore": [ 5 | "**/dist/**", 6 | "**/storybook-static/**", 7 | "**/coverage/**", 8 | "**/.next/**", 9 | "**/node_modules/**" 10 | ] 11 | }, 12 | "organizeImports": { 13 | "enabled": true 14 | }, 15 | "css": { 16 | "formatter": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true 21 | } 22 | }, 23 | 24 | "linter": { 25 | "enabled": true, 26 | "rules": { 27 | "nursery": { 28 | "useSortedClasses": { 29 | "fix": "safe", 30 | "level": "error", 31 | "options": { 32 | "attributes": ["classList"], 33 | "functions": ["clsx", "cm", "cva", "tw"] 34 | } 35 | } 36 | }, 37 | "all": true, 38 | "style": { 39 | "noDefaultExport": "off", 40 | "useFilenamingConvention": "off", 41 | "useNamingConvention": { 42 | "level": "error", 43 | "options": { 44 | "strictCase": false 45 | } 46 | } 47 | }, 48 | "performance": { 49 | "noReExportAll": "off", 50 | "noBarrelFile": "off" 51 | }, 52 | "suspicious": { 53 | "noReactSpecificProps": "off", 54 | "noConsole": { 55 | "fix": "none", 56 | "level": "warn" 57 | }, 58 | "noConsoleLog": { 59 | "fix": "none", 60 | "level": "warn" 61 | } 62 | }, 63 | "correctness": { 64 | "useImportExtensions": "off", 65 | "noUndeclaredDependencies": "off", 66 | "noUnusedVariables": "off", 67 | "noNodejsModules": "off" 68 | } 69 | } 70 | }, 71 | "formatter": { 72 | "enabled": true 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /degit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "action": "remove", 4 | "files": ["LICENSE.md", ".github/FUNDING.yml"] 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | lint: 5 | run: pnpm biome check --write --unsafe --staged --no-errors-on-unmatched && git add -u 6 | typecheck: 7 | run: pnpm tsc 8 | build: 9 | run: pnpm build 10 | test: 11 | run: pnpm test:ci 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-react-package-starter", 3 | "description": "", 4 | "version": "0.0.0", 5 | "author": "", 6 | "license": "", 7 | "keywords": [], 8 | "repository": { 9 | "type": "git", 10 | "url": "" 11 | }, 12 | "scripts": { 13 | "dev": "concurrently \"pnpm build --watch\" \"pnpm storybook\" \"pnpm test\" ", 14 | "build": "tsup", 15 | "lint": "biome check --write --unsafe .", 16 | "lint:ci": "biome check --unsafe .", 17 | "test": "vitest", 18 | "test:ci": "vitest run --coverage", 19 | "commit": "cz", 20 | "storybook": "storybook dev -p 6006", 21 | "storybook:build": "storybook build", 22 | "release": "pnpm build && pnpm release-it", 23 | "link:self": "pnpm link --global", 24 | "prepare": "lefthook install" 25 | }, 26 | "types": "./dist/index.d.ts", 27 | "exports": { 28 | ".": { 29 | "require": "./dist/index.js", 30 | "import": "./dist/index.mjs" 31 | } 32 | }, 33 | "files": ["dist"], 34 | "config": { 35 | "commitizen": { 36 | "path": "./node_modules/@ryansonshine/cz-conventional-changelog" 37 | } 38 | }, 39 | "release-it": { 40 | "git": { 41 | "commitMessage": "chore(release): v${version}" 42 | }, 43 | "github": { 44 | "release": true 45 | }, 46 | "npm": { 47 | "publish": false 48 | } 49 | }, 50 | "engines": { 51 | "node": ">=18.0.0" 52 | }, 53 | "devDependencies": { 54 | "@biomejs/biome": "1.9.4", 55 | "@ryansonshine/commitizen": "4.2.8", 56 | "@ryansonshine/cz-conventional-changelog": "3.3.4", 57 | "@storybook/addon-essentials": "8.6.8", 58 | "@storybook/addon-interactions": "8.6.8", 59 | "@storybook/addon-links": "8.6.8", 60 | "@storybook/addon-webpack5-compiler-swc": "3.0.0", 61 | "@storybook/blocks": "8.6.8", 62 | "@storybook/react": "8.6.8", 63 | "@storybook/react-webpack5": "8.6.8", 64 | "@storybook/test": "8.6.8", 65 | "@testing-library/jest-dom": "6.6.3", 66 | "@testing-library/react": "16.2.0", 67 | "@types/node": "22.13.11", 68 | "@types/react": "18.3.13", 69 | "@types/react-dom": "18.3.1", 70 | "@types/react-test-renderer": "18.3.0", 71 | "@vitest/coverage-v8": "3.0.9", 72 | "concurrently": "9.1.2", 73 | "dotenv": "^16.4.7", 74 | "jsdom": "26.0.0", 75 | "lefthook": "1.11.3", 76 | "prop-types": "15.8.1", 77 | "react": "18.3.1", 78 | "react-dom": "18.3.1", 79 | "react-test-renderer": "18.3.1", 80 | "release-it": "18.1.2", 81 | "storybook": "8.6.8", 82 | "ts-node": "10.9.2", 83 | "tsconfig-paths": "4.2.0", 84 | "tsup": "8.4.0", 85 | "tsx": "4.19.3", 86 | "typescript": "5.8.2", 87 | "vitest": "3.0.9" 88 | }, 89 | "peerDependencies": { 90 | "react": ">=17", 91 | "react-dom": ">=17" 92 | }, 93 | "pnpm": { 94 | "overrides": { 95 | "micromatch@<4.0.8": ">=4.0.8" 96 | }, 97 | "onlyBuiltDependencies": [ 98 | "@biomejs/biome", 99 | "@swc/core", 100 | "esbuild", 101 | "lefthook" 102 | ] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Example.css: -------------------------------------------------------------------------------- 1 | #example-button { 2 | color: red; 3 | font-size: 24px; 4 | } 5 | -------------------------------------------------------------------------------- /src/Example.tsx: -------------------------------------------------------------------------------- 1 | import "./Example.css"; 2 | 3 | import React from "react"; 4 | 5 | export type ExampleProps = { 6 | text?: string; 7 | }; 8 | 9 | export function Example(props: ExampleProps) { 10 | const [count, setCount] = React.useState(0); 11 | return ( 12 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Example"; 2 | -------------------------------------------------------------------------------- /src/stories/Example.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryFn } from "@storybook/react"; 2 | 3 | import { Example } from ".."; 4 | 5 | export default { 6 | title: "Example", 7 | component: Example, 8 | argTypes: {}, 9 | } as Meta; 10 | 11 | const Template: StoryFn = (args) => ; 12 | 13 | export const Primary = Template.bind({}); 14 | 15 | Primary.args = { 16 | text: "Clicked this many times:", 17 | }; 18 | -------------------------------------------------------------------------------- /tests/Example.test.tsx: -------------------------------------------------------------------------------- 1 | import renderer from "react-test-renderer"; 2 | import { expect, it } from "vitest"; 3 | import { Example } from "../src"; 4 | 5 | it("renders correctly", () => { 6 | const tree = renderer 7 | .create() 8 | .toJSON(); 9 | expect(tree).toMatchSnapshot(); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/__snapshots__/Example.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`renders correctly 1`] = ` 4 | 11 | `; 12 | -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | // biome-ignore lint/style/noNamespaceImport: 2 | import "dotenv/config"; 3 | import * as matchers from "@testing-library/jest-dom/matchers"; 4 | import { cleanup } from "@testing-library/react"; 5 | import { afterEach, expect } from "vitest"; 6 | 7 | expect.extend(matchers); 8 | 9 | afterEach(() => { 10 | cleanup(); 11 | }); 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "jsx": "react-jsx", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "noEmit": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import childProcess from "node:child_process"; 2 | import fs from "node:fs"; 3 | import { readFile } from "node:fs/promises"; 4 | import path from "node:path"; 5 | import { type Options, defineConfig } from "tsup"; 6 | 7 | const common: Options = { 8 | entry: ["src/index.ts"], 9 | treeshake: false, 10 | sourcemap: "inline", 11 | minify: true, 12 | clean: true, 13 | dts: true, 14 | splitting: false, 15 | format: ["cjs", "esm"], 16 | external: ["react"], 17 | injectStyle: false, 18 | }; 19 | 20 | const getPackageName = async () => { 21 | try { 22 | const packageJson = JSON.parse( 23 | await readFile(path.join(__dirname, "package.json"), "utf-8"), 24 | ); 25 | return packageJson.name; 26 | } catch (_error) { 27 | return "package-name"; 28 | } 29 | }; 30 | 31 | const _addUseStatement = async ( 32 | basePath: string, 33 | type: "server" | "client", 34 | ) => { 35 | const fullPath = path.join(__dirname, basePath); 36 | const files = fs.readdirSync(fullPath); 37 | 38 | for (const file of files) { 39 | if (file.endsWith(".js") || file.endsWith(".mjs")) { 40 | const filePath = path.join(fullPath, file); 41 | let content = await readFile(filePath, "utf-8"); 42 | content = `"use ${type}";\n${content}`; 43 | fs.writeFileSync(filePath, content, "utf-8"); 44 | } 45 | } 46 | }; 47 | 48 | const linkSelf = async () => { 49 | await new Promise((resolve) => { 50 | childProcess.exec("pnpm link:self", (error, _stdout, _stderr) => { 51 | if (error) { 52 | // biome-ignore lint/suspicious/noConsole: 53 | console.error(`exec error: ${error}`); 54 | return; 55 | } 56 | 57 | resolve(undefined); 58 | }); 59 | }); 60 | 61 | // biome-ignore lint/suspicious/noConsoleLog: 62 | // biome-ignore lint/suspicious/noConsole: 63 | console.log( 64 | `Run 'pnpm link ${await getPackageName()} --global' inside another project to consume this package.`, 65 | ); 66 | }; 67 | 68 | export default defineConfig({ 69 | async onSuccess() { 70 | // If you want need to add a use statement to files, you can use the following code: 71 | // await _addUseStatement('dist/react', 'client'); 72 | 73 | await linkSelf(); 74 | }, 75 | ...common, 76 | }); 77 | -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: "jsdom", 6 | setupFiles: "./tests/setup.js", 7 | passWithNoTests: true, 8 | coverage: { 9 | include: ["{src,tests}/**/*"], 10 | }, 11 | }, 12 | }); 13 | --------------------------------------------------------------------------------