├── jest.config.js ├── src ├── index.tsx ├── hooks │ ├── useCounter.spec.tsx │ └── useCounter.tsx ├── stories │ └── Button.stories.mdx └── components │ ├── Button.spec.tsx │ └── Button.tsx ├── babel.config.js ├── .gitignore ├── tsconfig.json ├── .storybook └── main.js ├── LICENSE ├── .eslintrc.js ├── README.md └── package.json /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom' 4 | }; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | export { Button } from './components/Button'; 2 | export { useCounter } from './hooks/useCounter'; -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | '@babel/preset-typescript' 11 | ], 12 | ], 13 | }; -------------------------------------------------------------------------------- /src/hooks/useCounter.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useCounter } from './useCounter' 3 | 4 | test('should increment counter', () => { 5 | const { result } = renderHook(() => useCounter()) 6 | 7 | act(() => { 8 | result.current.increment() 9 | }) 10 | 11 | expect(result.current.count).toBe(1) 12 | }) -------------------------------------------------------------------------------- /src/hooks/useCounter.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react' 2 | 3 | interface UseCounterProps { 4 | count: number; 5 | increment: () => void; 6 | } 7 | 8 | export const useCounter = (): UseCounterProps => { 9 | const [count, setCount] = useState(0) 10 | 11 | const increment = useCallback(() => setCount((x) => x + 1), []) 12 | 13 | return { count, increment } 14 | } 15 | -------------------------------------------------------------------------------- /src/stories/Button.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks"; 2 | import { Button } from "../components/Button"; 3 | 4 | 5 | 6 | 7 | The world's most boring button! 8 | 9 | # Preview 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/Button.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '@testing-library/jest-dom/extend-expect'; 3 | import {render, screen} from '@testing-library/react' 4 | import { Button } from './Button' 5 | 6 | test('shows the children of the button', () => { 7 | const testMessage = 'Test Message' 8 | render() 9 | expect(screen.getByText(testMessage)).toBeInTheDocument() 10 | }) -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from "react"; 2 | 3 | interface ButtonProps { 4 | /** 5 | * Simple click handler 6 | */ 7 | onClick?: () => void; 8 | } 9 | 10 | /** 11 | * The world's most _basic_ button 12 | */ 13 | export const Button: FunctionComponent = ({ children, onClick }) => ( 14 | 17 | ); -------------------------------------------------------------------------------- /.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 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /storybook-static 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "declaration": true, 5 | "target": "es5", 6 | "lib": [ 7 | "dom", 8 | "dom.iterable", 9 | "esnext" 10 | ], 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ], 26 | "exclude": [ 27 | "src/**/*.spec.tsx", 28 | "src/setupTests.tsx", 29 | "src/stories", 30 | "src/*.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | stories: ['../src/**/*.stories.mdx'], 6 | addons: [ 7 | '@storybook/addon-actions', 8 | '@storybook/addon-links', 9 | '@storybook/addon-docs', 10 | { 11 | name: '@storybook/preset-typescript', 12 | options: { 13 | tsLoaderOptions: { 14 | configFile: path.resolve(__dirname, '../tsconfig.json'), 15 | }, 16 | tsDocgenLoaderOptions: { 17 | tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), 18 | }, 19 | forkTsCheckerWebpackPluginOptions: { 20 | colors: false, // disables built-in colors in logger messages 21 | }, 22 | include: [path.resolve(__dirname, '../src')], 23 | }, 24 | }, 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | ----------- 3 | 4 | Copyright (c) 2020 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "plugin:jest/recommended", 11 | "plugin:jest-dom/recommended", 12 | "plugin:@typescript-eslint/eslint-recommended", 13 | "plugin:@typescript-eslint/recommended" 14 | ], 15 | "globals": { 16 | "Atomics": "readonly", 17 | "SharedArrayBuffer": "readonly" 18 | }, 19 | "parser": "@typescript-eslint/parser", 20 | "parserOptions": { 21 | "ecmaFeatures": { 22 | "jsx": true 23 | }, 24 | "ecmaVersion": 2018, 25 | "sourceType": "module" 26 | }, 27 | "plugins": [ 28 | "react", 29 | "jest", 30 | "jest-dom", 31 | "react-hooks", 32 | "@typescript-eslint" 33 | ], 34 | "rules": { 35 | "react-hooks/rules-of-hooks": "error", 36 | "react-hooks/exhaustive-deps": "warn", 37 | "react/prop-types": 0 38 | }, 39 | "settings": { 40 | "react": { 41 | "version": "detect" 42 | } 43 | } 44 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated. Just use [TSDX](https://github.com/jaredpalmer/tsdx) from @jaredpalmer instead. 2 | 3 | 4 | ## react-library-template 5 | ### (formerly Storybook TypeScript React Component Library) 6 | 7 | This repo attempts to solve the problem of how to share react hooks, components, and utilities with your team (or all of your personal projects). There are other solutions out there, such as create-react-library, but they are no longer updated and out of date. 8 | 9 | By utilitizing Storybook, we are able to bypass any need for webpack and create-react-app. All component development can be done in isolation and can be published to a github page for other stakeholders to see. 10 | 11 | This library uses [TypeScript](https://www.typescriptlang.org/), [react-testing-library](https://github.com/testing-library/react-testing-library), [Jest](https://jestjs.io/), and [ESLint](https://eslint.org/) and has everything preconfigured so you can skip straight to developing. 12 | 13 | ## Setup 14 | 15 | * Clone this template repo by clicking "Use this template" 16 | * Rename it to your liking (e.g., my-company-react-library) and clone it locally 17 | * Rename the `package.json` name field, add any dependencies you need, and remove any parts of this README you no longer need 18 | * Run this project using the scripts below! 19 | 20 | ## Developing with another library 21 | 22 | Sometimes it is helpful be able to use the latest code as you develop in another project that you intend to consume this library with. 23 | 24 | In order to do so: 25 | 26 | * Run `npm run build:watch` in this project 27 | * In your other project, run `npm link ../react-library-template` replacing the folder name with your new repo's name 28 | * Start your other project and you will have access to the compiled code as you write it! 29 | 30 | ## Available scripts 31 | 32 | ### `start` 33 | 34 | Starts the storybook site at http://localhost:6006/ 35 | 36 | ### `build` 37 | **Executes the following sub scripts:** 38 | #### `build:lint` 39 | Lints using eslint (using plugins from jest, jest-dom, react, and react-hooks) 40 | #### `build:library` 41 | Builds your library and copies all css / woff2 / svgs / png files to the build folder 42 | #### `build:storybook` 43 | Creates a static storybook site that you can host 44 | 45 | ### `test` 46 | 47 | Runs all tests with Jest and react-testing-library in watch mode 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-library-template", 3 | "version": "0.1.0", 4 | "main": "build/index.js", 5 | "files": [ 6 | "build" 7 | ], 8 | "scripts": { 9 | "start": "npm run storybook", 10 | "build": "npm run lint && npm run build:library && npm run build:storybook", 11 | "build:library": "rimraf build && tsc && npm run copy-css-to-lib && npm run copy-svg-to-lib && npm run copy-png-to-lib && npm run copy-woff2-to-lib", 12 | "build:storybook": "build-storybook -s public", 13 | "build:watch": "rimraf build && tsc --watch", 14 | "test": "jest --watch", 15 | "lint": "eslint 'src/**/*.{ts,tsx}'", 16 | "storybook": "start-storybook -p 6006", 17 | "copy-css-to-lib": "cpx \"./src/**/*.css\" ./build", 18 | "copy-woff2-to-lib": "cpx \"./src/**/*.woff2\" ./build", 19 | "copy-svg-to-lib": "cpx \"./src/**/*.svg\" ./build", 20 | "copy-png-to-lib": "cpx \"./src/**/*.png\" ./build", 21 | "build-storybook": "build-storybook" 22 | }, 23 | "peerDependencies": { 24 | "react": "^16.12.0", 25 | "react-dom": "^16.12.0" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.8.3", 29 | "@babel/preset-env": "^7.8.3", 30 | "@babel/preset-typescript": "^7.8.3", 31 | "@storybook/addon-actions": "^5.3.3", 32 | "@storybook/addon-docs": "^5.3.3", 33 | "@storybook/addon-links": "^5.3.3", 34 | "@storybook/addons": "^5.3.3", 35 | "@storybook/preset-typescript": "^1.2.0", 36 | "@storybook/react": "^5.3.3", 37 | "@testing-library/jest-dom": "^4.2.4", 38 | "@testing-library/react": "^9.3.2", 39 | "@testing-library/react-hooks": "^3.2.1", 40 | "@testing-library/user-event": "^7.1.2", 41 | "@types/jest": "^24.0.25", 42 | "@types/node": "^12.0.0", 43 | "@types/react": "^16.9.0", 44 | "@types/react-dom": "^16.9.0", 45 | "@typescript-eslint/eslint-plugin": "^2.16.0", 46 | "@typescript-eslint/parser": "^2.16.0", 47 | "babel-jest": "^24.9.0", 48 | "babel-loader": "^8.0.6", 49 | "cpx": "^1.5.0", 50 | "eslint": "^6.8.0", 51 | "eslint-plugin-jest": "^23.6.0", 52 | "eslint-plugin-jest-dom": "^2.0.0", 53 | "eslint-plugin-react": "^7.17.0", 54 | "eslint-plugin-react-hooks": "^2.3.0", 55 | "fork-ts-checker-webpack-plugin": "^3.1.1", 56 | "jest": "^24.9.0", 57 | "react-docgen-typescript-loader": "^3.6.0", 58 | "react-test-renderer": "^16.12.0", 59 | "rimraf": "^3.0.0", 60 | "ts-jest": "^24.3.0", 61 | "ts-loader": "^6.2.1", 62 | "typescript": "^3.7.4" 63 | }, 64 | "dependencies": {} 65 | } 66 | --------------------------------------------------------------------------------