├── .changeset ├── README.md └── config.json ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── documentation_request.yml │ └── feature_request.yml ├── composite-actions │ └── install │ │ └── action.yml ├── pull_request_template.md └── workflows │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.js ├── lint-staged.config.js ├── package.json ├── pnpm-lock.yaml ├── prettier.config.js ├── public └── cover.png ├── src ├── hooks │ ├── constants.ts │ ├── factory.ts │ ├── hooks.ts │ ├── index.ts │ ├── test.tsx │ └── types.ts ├── index.ts ├── lib.ts └── types.ts ├── tsconfig.json └── tsup.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # flow-typed 34 | flow-typed/npm/* 35 | !flow-typed/npm/module_vx.x.x.js 36 | 37 | # App packaged 38 | release 39 | build 40 | public 41 | 42 | .idea 43 | npm-debug.log.* 44 | __snapshots__ 45 | 46 | # Package.json 47 | package.json 48 | 49 | .travis.yml 50 | # Created by .ignore support plugin (hsz.mobi) 51 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 4 | "env": { 5 | "node": true, 6 | "browser": true 7 | }, 8 | "rules": { 9 | "no-prototype-builtins": "off", 10 | "@typescript-eslint/no-explicit-any": "off", 11 | "@typescript-eslint/explicit-module-boundary-types": "off", 12 | "@typescript-eslint/no-non-null-assertion": "off", 13 | "@typescript-eslint/ban-ts-comment": "off", 14 | "@typescript-eslint/no-var-requires": "off", 15 | "@typescript-eslint/ban-types": "off", 16 | "@typescript-eslint/no-unused-vars": [ 17 | "error", 18 | { 19 | "varsIgnorePattern": "^_", 20 | "argsIgnorePattern": "^_", 21 | "ignoreRestSiblings": true 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 'Bug Report' 2 | description: 'File a bug report' 3 | body: 4 | - type: 'markdown' 5 | attributes: 6 | value: | 7 | Thanks for creating an issue 😄! 8 | 9 | Please search open/closed issues before submitting. Someone 10 | might have asked the same thing before 😉! 11 | 12 | We're all volunteers here, so help us help you by taking the time to 13 | accurately fill out this template. ❤️ 14 | - type: 'textarea' 15 | id: 'description' 16 | attributes: 17 | label: 'Description' 18 | description: 'A brief description of the issue.' 19 | placeholder: | 20 | When I ____, I expected ____ to happen but ____ happened instead. 21 | validations: 22 | required: true 23 | - type: 'input' 24 | id: 'reproduction' 25 | attributes: 26 | label: 'Link to Reproduction' 27 | description: | 28 | A link to a Stackblitz reproduction which demonstrates the bug 29 | placeholder: 'https://stackblitz.com/edit/vitejs-vite-lfwyue?file=src%2FApp.tsx&terminal=dev' 30 | validations: 31 | required: true 32 | - type: 'textarea' 33 | id: 'steps' 34 | attributes: 35 | label: 'Steps to reproduce' 36 | description: | 37 | Explain how to cause the issue in the provided reproduction. 38 | value: | 39 | 1. Go to '...' 40 | 2. Click on '...' 41 | 3. Scroll down to '...' 42 | 4. See error 43 | - type: 'input' 44 | id: 'version' 45 | attributes: 46 | label: 'Version' 47 | description: 'The version of @hugocxl/react-to-image you use.' 48 | placeholder: 'x.x.x' 49 | validations: 50 | required: true 51 | - type: 'input' 52 | id: 'browser' 53 | attributes: 54 | label: 'Browser' 55 | description: 'The browser(s) this issue occurred with.' 56 | placeholder: 'Google Chrome 93' 57 | - type: 'checkboxes' 58 | id: 'operating-system' 59 | attributes: 60 | label: 'Operating System' 61 | description: 'The operating system(s) this issue occurred with.' 62 | options: 63 | - label: 'macOS' 64 | - label: 'Windows' 65 | - label: 'Linux' 66 | - type: 'textarea' 67 | id: 'additional-information' 68 | attributes: 69 | label: 'Additional Information' 70 | description: | 71 | Use this section to provide any additional information you might have 72 | like screenshots, notes, or links to ideas. 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.yml: -------------------------------------------------------------------------------- 1 | name: 'Documentation Request' 2 | description: 'Request for documentation to be added/altered' 3 | labels: ['needs triage', 'Topic: Documentation 📚'] 4 | body: 5 | - type: 'markdown' 6 | attributes: 7 | value: | 8 | Thanks for filing a documentation request! 9 | 10 | If you have an idea for a new documentation topic, noticed that 11 | something is not properly documented, or feel that something is 12 | incorrect with the current documentation, you're in the right place! 13 | - type: 'input' 14 | id: 'subject' 15 | attributes: 16 | label: 'Subject' 17 | description: 18 | 'What is the subject (component, function, topic) of this request?' 19 | placeholder: 'Presets' 20 | validations: 21 | required: true 22 | - type: 'textarea' 23 | id: 'description' 24 | attributes: 25 | label: 'Description' 26 | description: 27 | "What about the subject's documentation should be added or changed?" 28 | placeholder: 'Add a usage example of RadioGroup in action' 29 | validations: 30 | required: true 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 'Feature Request' 2 | description: 'Request a feature or enhancement' 3 | labels: ['needs triage'] 4 | body: 5 | - type: 'markdown' 6 | attributes: 7 | value: | 8 | Thanks for filing an issue 😄! 9 | 10 | Please search open/closed issues before submitting. Someone 11 | might have asked the same thing before 😉! 12 | - type: 'textarea' 13 | id: 'description' 14 | attributes: 15 | label: 'Description' 16 | description: 'Please describe your request in one or two sentences.' 17 | validations: 18 | required: true 19 | - type: 'textarea' 20 | id: 'justification' 21 | attributes: 22 | label: 'Problem Statement/Justification' 23 | description: | 24 | Please provide valid reason(s) why this should be added to Chakra UI 25 | 26 | If this feature is related to a problem you've noticed, mention it as 27 | well. 28 | validations: 29 | required: true 30 | - type: 'textarea' 31 | id: 'proposed-solution' 32 | attributes: 33 | label: 'Proposed Solution or API' 34 | description: | 35 | Please provide code snippets, gists, or links to the ideal 36 | design or API. 37 | validations: 38 | required: true 39 | - type: 'textarea' 40 | id: 'alternatives' 41 | attributes: 42 | label: 'Alternatives' 43 | description: | 44 | What alternative solutions have you considered before making this 45 | request? 46 | - type: 'textarea' 47 | id: 'additional-information' 48 | attributes: 49 | label: 'Additional Information' 50 | description: | 51 | What resources (links, screenshots, etc.) do you have to assist this 52 | effort? 53 | -------------------------------------------------------------------------------- /.github/composite-actions/install/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Install' 2 | description: 'Sets up Node.js and runs install' 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup PNPM 8 | uses: pnpm/action-setup@master 9 | with: 10 | version: 8 11 | 12 | - name: Setup Node.js 13 | uses: actions/setup-node@main 14 | with: 15 | node-version: 18 16 | registry-url: https://registry.npmjs.org/ 17 | cache: 'pnpm' 18 | 19 | - name: Setup Git Configuration 20 | shell: bash 21 | run: | 22 | git config --global user.email "corta.hugo@gmail.com" 23 | git config --global user.name "hugocxl" 24 | 25 | - name: Install dependencies 26 | shell: bash 27 | run: pnpm i --no-frozen-lockfile 28 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | Closes # 11 | 12 | ## 📝 Description 13 | 14 | > Add a brief description 15 | 16 | ## ⛳️ Current behavior (updates) 17 | 18 | > Please describe the current behavior that you are modifying 19 | 20 | ## 🚀 New behavior 21 | 22 | > Please describe the behavior or changes this PR adds 23 | 24 | ## 💣 Is this a breaking change (Yes/No) 25 | 26 | 27 | 28 | ## 📝 Additional Information 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | paths: 6 | - '.changeset/**' 7 | - 'src/**' 8 | branches: 9 | - master 10 | 11 | jobs: 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Install 22 | uses: ./.github/composite-actions/install 23 | 24 | - name: Build packages 25 | run: pnpm build 26 | 27 | - name: Publish packages 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | publish: pnpm release 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 34 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 35 | 36 | - name: Release to dev tag 37 | if: steps.changesets.outputs.published != 'true' 38 | run: | 39 | git checkout master 40 | pnpm changeset version --snapshot dev 41 | pnpm changeset publish --tag dev 42 | env: 43 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 44 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 45 | 46 | create_timestamp: 47 | runs-on: ubuntu-latest 48 | 49 | outputs: 50 | timestamp: ${{ steps.set_timestamp.outputs.timestamp }} 51 | 52 | steps: 53 | - name: Set Timestamp 54 | id: set_timestamp 55 | run: | 56 | echo "::set-output name=timestamp::$(date +'%s' | cut -c1-8)" 57 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@main 13 | 14 | - name: Setup PNPM 15 | uses: pnpm/action-setup@master 16 | with: 17 | version: 8 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@main 21 | with: 22 | node-version: 18 23 | 24 | - name: Install dependencies 25 | run: pnpm i 26 | 27 | - name: Run tests 28 | run: pnpm test:coverage 29 | 30 | - name: Coveralls Parallel 31 | uses: coverallsapp/github-action@master 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | parallel: true 35 | 36 | finish: 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Coveralls Finished 41 | uses: coverallsapp/github-action@master 42 | with: 43 | github-token: ${{ secrets.GITHUB_TOKEN }} 44 | parallel-finished: true 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn 127 | .pnp.* 128 | 129 | .DS_Store -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged --allow-empty && pnpm test 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | .next 5 | build 6 | *.hbs 7 | *.d.ts 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.8](https://github.com/hugocxl/react-to-image/compare/v0.0.7...v0.0.8) (2023-10-12) 2 | 3 | ## 0.0.9 4 | 5 | ### Patch Changes 6 | 7 | - 3acf0ad: Update deps 8 | 9 | ## [0.0.7](https://github.com/hugocxl/react-to-image/compare/v0.0.6...v0.0.7) (2023-10-12) 10 | 11 | ### Features 12 | 13 | - replaced rollup with tsup and improved config ([d338613](https://github.com/hugocxl/react-to-image/commit/d338613c17b58131738c7a8580e55845386c9c82)) 14 | 15 | ## [0.0.6](https://github.com/hugocxl/react-to-image/compare/v0.0.5...v0.0.6) (2023-08-05) 16 | 17 | ### Bug Fixes 18 | 19 | - github actions config ([c8171e8](https://github.com/hugocxl/react-to-image/commit/c8171e86cad8951e4773954ee31bed04392b16de)) 20 | - preversion add pull changes ([4d77d73](https://github.com/hugocxl/react-to-image/commit/4d77d73cac2278f2f88cc2047b4c5456f89b4730)) 21 | - update dependencies ([9b75c8c](https://github.com/hugocxl/react-to-image/commit/9b75c8cfc3b031af52f32ae432117ae13e96e387)) 22 | 23 | ## [0.0.5](https://github.com/hugocxl/react-to-image/compare/v0.0.4...v0.0.5) (2023-07-16) 24 | 25 | ### Bug Fixes 26 | 27 | - github actions ([66035a8](https://github.com/hugocxl/react-to-image/commit/66035a8f53f673a31db0424f24c1796aa224ccfc)) 28 | 29 | ## [0.0.4](https://github.com/hugocxl/react-to-image/compare/v0.0.3...v0.0.4) (2023-07-16) 30 | 31 | ### Bug Fixes 32 | 33 | - github actions ([988683c](https://github.com/hugocxl/react-to-image/commit/988683c0bc3ce909a64d66fd4bbd693a919e57af)) 34 | 35 | ## [0.0.3](https://github.com/hugocxl/react-to-image/compare/v0.0.2...v0.0.3) (2023-07-09) 36 | 37 | ## [0.0.2](https://github.com/hugocxl/react-to-image/compare/v0.0.1...v0.0.2) (2023-07-09) 38 | 39 | ## [0.0.1](https://github.com/hugocxl/react-to-image/compare/v0.0.0...v0.0.1) (2023-07-09) 40 | 41 | # 0.0.0 (2023-07-05) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hugo Corta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | [![NPM](https://img.shields.io/npm/dm/@hugocxl/react-to-image.svg?&logo=npm)](https://www.npmjs.com/package/@hugocxl/react-to-image) 6 | [![Version](https://img.shields.io/npm/v/@hugocxl/react-to-image.svg?logo=npm)](https://www.npmjs.com/package/@hugocxl/react-to-image) 7 | [![Size](https://img.shields.io/bundlephobia/minzip/@hugocxl/react-to-image)](https://bundlephobia.com/result?p=@hugocxl/react-to-image) 8 | [![Build](https://img.shields.io/github/actions/workflow/status/hugocxl/react-to-image/release.yml?branch=master)](https://github.com/hugocxl/react-to-image/actions/workflows/release.yml) 9 | [![Tests Coverage](https://img.shields.io/coverallsCoverage/github/hugocxl/react-to-image)](https://coveralls.io/github/hugocxl/react-to-image) 10 | [![Language](https://img.shields.io/badge/language-TypeScript-blue.svg)](https://www.typescriptlang.org) 11 | [![License](https://img.shields.io/github/license/hugocxl/react-to-image)](https://www.npmjs.com/package/@hugocxl/react-to-image) 12 | 13 |
14 | 15 | ## Features 16 | 17 | - ✨ **Simple**: is simple to use and has no external dependencies at all 18 | - 🌱 **Lightweight**: just 983b gzipped 19 | - 💎 **TypeScript**: full-written in TypeScript 20 | - ⚛️ **State Management**: control conversion states 21 | - ⌛️ **Async Logic**: event handlers for asynchronous logic 22 | - 🏆 **MIT Licensed**: free for personal and commercial use 23 | - 🚀 **React Hooks** 24 | 25 | ## Table of Contents 26 | 27 | - [Installation](#installation) 28 | - [Usage](#usage) 29 | - [Docs](#docs) 30 | - [State](#state) 31 | - [Ref](#ref) 32 | - [Fn](#fn) 33 | - [Option](#options) 34 | - [Examples](#examples) 35 | - [Contributing](#contributing) 36 | - [Code of Conduct](#code-of-conduct) 37 | - [License](#license) 38 | 39 | ## Installation 40 | 41 | In order to use **`@hugocxl/react-to-image`**, all you need to do is install the 42 | npm package: 43 | 44 | ```sh 45 | npm i @hugocxl/react-to-image 46 | 47 | pnpm add @hugocxl/react-to-image 48 | ``` 49 | 50 | ## Introduction 51 | 52 | [`html-to-image`](https://github.com/bubkoo/html-to-image/tree/master#options) is an invaluable utility library that enables the generation of 53 | images from a DOM node utilising the power of HTML5 canvas and SVG. It provides 54 | a seamless way to convert HTML elements into visual representations. 55 | 56 | **`react-to-image`** further enhances the integration of this library with React leveraging the capabilities of `html-to-image` and offering a simplified and intuitive approach for generating images from React components. 57 | 58 | ## Usage 59 | 60 | To start using `@hugocxl/react-to-image`, you just need to import any of the 61 | hooks from the package. 62 | 63 | ```tsx 64 | import { useToSvg } from '@hugocxl/react-to-image' 65 | 66 | function App() { 67 | const [state, convertToSvg, ref] = useToSvg({ 68 | onSuccess: data => { 69 | console.log(data) 70 | } 71 | }) 72 | 73 | return ( 74 |
75 |

My component

76 | 77 |
78 | ) 79 | } 80 | ``` 81 | 82 | ## Docs 83 | 84 | ### Hooks 85 | 86 | ```tsx 87 | const [state: State, fn: Fn, ref: Ref] = hook(options: Options) 88 | ``` 89 | 90 | The current hooks are being exported: 91 | 92 | - `useToSvg` 93 | - `useToPng` 94 | - `useToJpeg` 95 | - `useToCanvas` 96 | - `useToBlob` 97 | 98 | ### `State` 99 | 100 | | name | type | description | 101 | | ----------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------- | 102 | | **data** | `ReturnType` | The last successfully resolved data for the conversion | 103 | | **error** | `string` | If the conversion attempt resulted in an error. The corresponding `error` property has the error received from the attempted conversion | 104 | | **status** | `string` | state of the conversion. Posible values: `IDLE`, `LOADING`, `SUCCESS`, `ERROR` | 105 | | **isIdle** | `boolean` | if the conversion is idle. It's only true if no conversion has been initialized yet | 106 | | **isLoading** | `boolean` | If the conversion is currently being done | 107 | | **isError** | `boolean` | If the conversion has failed | 108 | | **isSuccess** | `boolean` | If the conversion has succesfully finished | 109 | 110 | ### `Fn` 111 | 112 | The function to be called to convert the image. 113 | 114 | ### `Ref` 115 | 116 | The ref to be passed down to the HTML element that you want to capture as an 117 | image. 118 | 119 | ### `Options` 120 | 121 | Apart from the following, you have all the options available to 122 | [`html-to-image`](https://github.com/bubkoo/html-to-image/tree/master#options) 123 | 124 | | name | type | description | 125 | | ----------- | :---------: | ---------------------------------------------------------- | 126 | | **selector** | `string` | A valid `querySelector()` argument. If passed, the ref will be ommited | 127 | | **onStart** | `boolean` | Callback called if the conversion is starting | 128 | | **onSuccess** | `boolean` | Callback called if the conversion has finished succesfully | 129 | | **onError** | `boolean` | Callback called if the conversion has thrown an error | 130 | 131 | ## Examples 132 | 133 | ### Using `selector` 134 | 135 | Use the selector option to specify the element that you want to capture instead of the ref. Useful if you need to convert elements that are far in the application structure. 136 | 137 | ```tsx 138 | import { useToPng } from '@hugocxl/react-to-image' 139 | 140 | export default function App() { 141 | const [state, convert] = useToPng({ 142 | selector: '#my-element', 143 | onSuccess: data => console.log('Converted #my-element to PNG!', data), 144 | }) 145 | 146 | return ( 147 |
148 | 149 |
150 | ) 151 | } 152 | ``` 153 | 154 | ### Using callbacks (`onStart`, `onSuccess`, `onError`) 155 | 156 | ```tsx 157 | import { useToBlob } from '@hugocxl/react-to-image' 158 | 159 | export default function App() { 160 | const [_, convert, ref] = useToBlob({ 161 | onStart: data => console.log('Starting...'), 162 | onSuccess: data => console.log('Success', data), 163 | onError: error => console.log('Error', error), 164 | }) 165 | 166 | return ( 167 |
168 |

My component

169 | 170 |
171 | ) 172 | } 173 | ``` 174 | 175 | ### Save and download a compressed JPEG image 176 | 177 | ```tsx 178 | import { useToPng } from '@hugocxl/react-to-image' 179 | 180 | export default function App() { 181 | const [_, convert, ref] = useToPng({ 182 | quality: 0.8, 183 | onSuccess: data => { 184 | const link = document.createElement('a'); 185 | link.download = 'my-image-name.jpeg'; 186 | link.href = data; 187 | link.click(); 188 | } 189 | }) 190 | 191 | return ( 192 |
193 |

My component

194 | 195 |
196 | ) 197 | } 198 | ``` 199 | 200 | ### Clip to clipboard 201 | 202 | Convert a component to a PNG and copy the image to the clipboard 203 | 204 | ```tsx 205 | import { useToPng } from '@hugocxl/react-to-image' 206 | 207 | export default function App() { 208 | const [{ isSuccess }, convert, ref] = useToPng({ 209 | onSuccess: data => navigator.clipboard.writeText(data) 210 | }) 211 | 212 | return ( 213 |
214 |

My component

215 | 216 | 217 | {isSuccess && Image copied to the clipboard!} 218 |
219 | ) 220 | } 221 | ``` 222 | 223 | ### Display an error message if the conversion fails 224 | 225 | ```tsx 226 | import { useToSvg } from '@hugocxl/react-to-image' 227 | 228 | export default function App() { 229 | const [{ isError, error }, convert, ref] = useToSvg() 230 | 231 | return ( 232 |
233 | 234 |

235 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 236 |

237 | 238 | 239 | {isError && {`Sorry, there was an error converting the image: ${error}`}} 240 |
241 | ) 242 | } 243 | ``` 244 | 245 | ### Get a PNG image base64-encoded data URL and append it to the document 246 | 247 | ```tsx 248 | import { useToPng } from '@hugocxl/react-to-image' 249 | 250 | export default function App() { 251 | const [{ isLoading }, convert, ref] = useToPng({ 252 | onSuccess: dataUrl => { 253 | const img = new Image(); 254 | img.src = dataUrl; 255 | document.body.appendChild(img); 256 | } 257 | }) 258 | 259 | return ( 260 |
261 |

My component

262 | 263 | {isLoading && Loading...} 264 |
265 | ) 266 | } 267 | ``` 268 | 269 | ### Get a HTMLCanvasElement, and display it right away 270 | 271 | ```tsx 272 | import { useToCanvas } from '@hugocxl/react-to-image' 273 | 274 | export default function App() { 275 | const [{ isLoading }, convert, ref] = useToCanvas({ 276 | onSuccess: canvas => document.body.appendChild(canvas); 277 | }) 278 | 279 | return ( 280 |
281 |

My component

282 | 283 |
284 | ) 285 | } 286 | ``` 287 | 288 | ## Contributing 289 | 290 | No one’s perfect. If you’ve found any errors, want to suggest enhancements, or 291 | expand on a topic, please feel free to open an Issue or collaborate by PR. 292 | 293 | ## Code of Conduct 294 | 295 | [Contributor Code of Conduct](public/docs/CODE_OF_CONDUCT.md). By participating 296 | in this project you agree to abide by its terms. 297 | 298 | ## License 299 | 300 | **@hugocxl/react-to-image** is open source software licensed as MIT © 301 | [Hugo Corta](https://github.com/hugocxl). 302 | 303 | --- 304 | 305 | **Made with ♥ by [@hugocxl](https://hugocxl.me)** 306 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'jsdom', 5 | }; -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '{packages,core}/**/*': [ 3 | 'pnpm --reporter=silent lint:fix', 4 | 'pnpm --reporter=silent format:fix' 5 | ], 6 | '{packages,core}/**/*.css': ['pnpm --reporter=silent styles:fix'], 7 | '{packages,core}/**/*.{ts,tsx}': "bash -c 'pnpm types:check'" 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hugocxl/react-to-image", 3 | "description": "Transform your React components into images", 4 | "productName": "@hugocxl/react-to-image", 5 | "version": "0.0.9", 6 | "license": "MIT", 7 | "private": false, 8 | "author": "hugocxl (http://github.com/hugocxl)", 9 | "homepage": "https://hugocxl.github.io/react-to-image/", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/hugocxl/react-to-image" 13 | }, 14 | "bugs": { 15 | "url": "https://hugocxl.github.io/react-to-image/issues" 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "main": "dist/index.js", 21 | "module": "dist/index.mjs", 22 | "types": "dist/index.d.ts", 23 | "source": "src/index.ts", 24 | "scripts": { 25 | "dev": "tsup --format=esm,cjs --no-dts --watch", 26 | "build": "tsup", 27 | "test": "jest", 28 | "test:coverage": "jest --coverage", 29 | "clean": "rm -rf dist && rm -rf node_modules", 30 | "fix": "pnpm lint:fix . && pnpm format:fix .", 31 | "lint": "eslint --cache", 32 | "lint:check": "pnpm lint", 33 | "lint:fix": "pnpm lint --fix", 34 | "styles": "stylelint --cache", 35 | "styles:check": "pnpm styles", 36 | "styles:fix": "pnpm styles --fix", 37 | "format": "prettier --cache", 38 | "format:check": "pnpm format --check", 39 | "format:fix": "pnpm format --write", 40 | "types": "tsc", 41 | "types:check": "pnpm types --noEmit", 42 | "prepare": "husky install", 43 | "release": "changeset publish", 44 | "release-dev": "changeset version --snapshot dev && changeset publish --tag dev", 45 | "postversion": "git push && git push --tags" 46 | }, 47 | "peerDependencies": { 48 | "html-to-image": ">=1", 49 | "react": ">=16" 50 | }, 51 | "devDependencies": { 52 | "@changesets/changelog-github": "^0", 53 | "@changesets/cli": "^2", 54 | "@testing-library/dom": "^9", 55 | "@testing-library/jest-dom": "^6", 56 | "@testing-library/react": "^14", 57 | "@types/jest": "^29", 58 | "@types/react": "^18", 59 | "@typescript-eslint/eslint-plugin": "^6", 60 | "@typescript-eslint/parser": "^6", 61 | "eslint": "^8", 62 | "husky": "^8", 63 | "jest-environment-jsdom": "^29", 64 | "jest": "^29", 65 | "lint-staged": "^14", 66 | "prettier": "^3", 67 | "ts-jest": "^29", 68 | "tsup": "^8", 69 | "typescript": "^5" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSpacing: true, 4 | htmlWhitespaceSensitivity: 'css', 5 | insertPragma: false, 6 | jsxSingleQuote: true, 7 | printWidth: 80, 8 | proseWrap: 'always', 9 | quoteProps: 'as-needed', 10 | requirePragma: false, 11 | semi: false, 12 | singleQuote: true, 13 | tabWidth: 2, 14 | trailingComma: 'none', 15 | useTabs: false 16 | } 17 | -------------------------------------------------------------------------------- /public/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugocxl/react-to-image/1b95db685d3e30ebafaf24ac7311c736450a6c6f/public/cover.png -------------------------------------------------------------------------------- /src/hooks/constants.ts: -------------------------------------------------------------------------------- 1 | import { HookStateStatus } from './types' 2 | 3 | export const INITIAL_STATE = { 4 | status: HookStateStatus.Idle, 5 | error: null, 6 | data: null 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/factory.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import { useReducer, useRef } from 'react' 3 | 4 | // Constants 5 | import { INITIAL_STATE } from './constants' 6 | 7 | // Types 8 | import { HookStateStatus } from './types' 9 | import type { LibFn, LibFnReturn } from '../types' 10 | import type { 11 | Hook, 12 | HookExtendedState, 13 | HookOptions, 14 | HookReturn, 15 | HookState, 16 | HookStateReducer 17 | } from './types' 18 | 19 | export function createHook(libFn: F): Hook { 20 | const stateReducer: HookStateReducer = (state, action) => { 21 | switch (action.type) { 22 | case HookStateStatus.Loading: 23 | return { ...state, status: HookStateStatus.Loading } 24 | case HookStateStatus.Success: 25 | return { ...state, status: HookStateStatus.Success, data: action.data } 26 | case HookStateStatus.Error: 27 | return { 28 | ...state, 29 | status: HookStateStatus.Error, 30 | error: action.error 31 | } 32 | default: 33 | return state 34 | } 35 | } 36 | 37 | function getExtendedState({ 38 | status, 39 | ...state 40 | }: HookState>): HookExtendedState> { 41 | return { 42 | ...state, 43 | status, 44 | isError: status === HookStateStatus.Error, 45 | isLoading: status === HookStateStatus.Loading, 46 | isSuccess: status === HookStateStatus.Success, 47 | isIdle: status === HookStateStatus.Idle 48 | } 49 | } 50 | 51 | return function (options?: HookOptions): HookReturn { 52 | const nodeRef = useRef() 53 | const [state, dispatchAction] = useReducer(stateReducer, INITIAL_STATE) 54 | const extendedState = getExtendedState(state) 55 | 56 | function setNodeRef(node: E): void { 57 | if (!nodeRef.current && node) nodeRef.current = node 58 | } 59 | 60 | async function getImage(): Promise | null> { 61 | try { 62 | if (!nodeRef.current && !options?.selector) { 63 | throw new Error( 64 | 'A dom element must be selected: use the selector option or the ref' 65 | ) 66 | } 67 | 68 | if (options?.onStart) options.onStart() 69 | 70 | dispatchAction({ type: HookStateStatus.Loading }) 71 | 72 | if (options?.onLoading) options.onLoading() 73 | 74 | const element = ( 75 | options?.selector 76 | ? document.querySelector(options.selector) 77 | : nodeRef.current 78 | ) as HTMLElement 79 | const data = (await libFn(element, options)) as LibFnReturn 80 | 81 | dispatchAction({ type: HookStateStatus.Success, data }) 82 | 83 | if (options?.onSuccess) options.onSuccess(data) 84 | 85 | return data 86 | } catch (error) { 87 | const message = (error as Error)?.message || 'Unknown error' 88 | console.error('Error generating image from component:', message) 89 | 90 | dispatchAction({ type: HookStateStatus.Error, error: message }) 91 | 92 | if (options?.onError) options.onError(message) 93 | 94 | return null 95 | } 96 | } 97 | 98 | return [ 99 | extendedState, 100 | () => { 101 | getImage() 102 | }, 103 | setNodeRef 104 | ] 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/hooks/hooks.ts: -------------------------------------------------------------------------------- 1 | import { createHook } from './factory' 2 | import { coreLib } from '../lib' 3 | 4 | export const useToSvg = createHook(coreLib.toSvg) 5 | 6 | export const useToJpeg = createHook(coreLib.toJpeg) 7 | 8 | export const useToPng = createHook(coreLib.toPng) 9 | 10 | export const useToBlob = createHook(coreLib.toBlob) 11 | 12 | export const useToCanvas = createHook(coreLib.toCanvas) 13 | 14 | export const useToPixelData = createHook( 15 | coreLib.toPixelData 16 | ) 17 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | useToSvg, 3 | useToJpeg, 4 | useToPng, 5 | useToBlob, 6 | useToCanvas, 7 | useToPixelData 8 | } from './hooks' 9 | -------------------------------------------------------------------------------- /src/hooks/test.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react' 2 | import { createHook } from './factory' 3 | import { coreLib } from '../lib' 4 | import { 5 | useToBlob, 6 | useToCanvas, 7 | useToJpeg, 8 | useToPixelData, 9 | useToPng, 10 | useToSvg 11 | } from './hooks' 12 | 13 | const coreLibFns = [ 14 | { name: 'toPng', value: coreLib.toPng }, 15 | { name: 'toSvg', value: coreLib.toSvg }, 16 | { name: 'toJpeg', value: coreLib.toJpeg }, 17 | { name: 'toBlob', value: coreLib.toBlob }, 18 | { name: 'toCanvas', value: coreLib.toCanvas }, 19 | { name: 'toPixelData', value: coreLib.toPixelData } 20 | ] 21 | const hooks = [ 22 | { name: 'useToPng', hook: useToPng }, 23 | { name: 'useToSvg', hook: useToSvg }, 24 | { name: 'useToJpeg', hook: useToJpeg }, 25 | { name: 'useToBlob', hook: useToBlob }, 26 | { name: 'useToCanvas', hook: useToCanvas }, 27 | { name: 'useToPixelData', hook: useToPixelData } 28 | ] 29 | 30 | describe('createHook', () => { 31 | it.each(coreLibFns)( 32 | 'should generate a hook from $name function', 33 | ({ value }) => { 34 | const hook = createHook(value) 35 | 36 | expect(hook).toBeDefined() 37 | } 38 | ) 39 | }) 40 | 41 | describe('hooks', () => { 42 | it.each(hooks)('$name hook runs correctly', ({ hook }) => { 43 | const { result } = renderHook(() => hook()) 44 | 45 | expect(result.current).toBeDefined() 46 | expect(Array.isArray(result.current)).toBe(true) 47 | expect(result.current).toHaveLength(3) 48 | }) 49 | 50 | it.each(hooks)('$name hook returns state with all properties', ({ hook }) => { 51 | const [state] = renderHook(() => hook()).result.current 52 | 53 | expect(state).toHaveProperty('status') 54 | expect(state).toHaveProperty('error') 55 | expect(state).toHaveProperty('data') 56 | expect(state).toHaveProperty('isError') 57 | expect(state).toHaveProperty('isLoading') 58 | expect(state).toHaveProperty('isSuccess') 59 | expect(state).toHaveProperty('isIdle') 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /src/hooks/types.ts: -------------------------------------------------------------------------------- 1 | import type { LibFn, LibFnReturn } from '../types' 2 | 3 | /** 4 | * Options for the hook 5 | */ 6 | export type HookOptions = Parameters[1] & { 7 | selector?: Parameters[0] 8 | /** 9 | * This function will fire before the conversion starts 10 | */ 11 | onStart?: () => unknown 12 | /** 13 | * This function will fire when the conversion is being processed 14 | */ 15 | onLoading?: () => unknown 16 | /** 17 | * This function will fire if the conversion encounters an error and will be passed the error 18 | */ 19 | onError?: (error: string) => unknown 20 | /** 21 | * This function will fire when the conversion is successful and will be passed the conversion's result 22 | */ 23 | onSuccess?: (data: LibFnReturn) => unknown 24 | } 25 | 26 | /** 27 | * Return type of the hook 28 | */ 29 | export type HookReturn = [ 30 | /** 31 | * State of the hook 32 | */ 33 | HookExtendedState>, 34 | /** 35 | * Function to start the conversion 36 | */ 37 | () => void, 38 | /** 39 | * Function to set the ref for the hook 40 | */ 41 | (domNode: E) => void 42 | ] 43 | 44 | /** 45 | * Status of the hook state 46 | */ 47 | export enum HookStateStatus { 48 | Idle = 'idle', 49 | Loading = 'loading', 50 | Success = 'success', 51 | Error = 'error' 52 | } 53 | 54 | /** 55 | * State of the hook 56 | */ 57 | export type HookState = { 58 | /** 59 | * Current status of the conversion 60 | */ 61 | status: HookStateStatus 62 | /** 63 | * Error message if an error occurred 64 | */ 65 | error: string | null 66 | /** 67 | * The last successfully resolved data for the conversion 68 | */ 69 | data: D | null 70 | } 71 | 72 | /** 73 | * Derived state of the hook 74 | */ 75 | export type HookDerivedState = { 76 | /** 77 | * Flag indicating if the hook is idle 78 | */ 79 | isIdle: boolean 80 | /** 81 | * Flag indicating if the hook is loading 82 | */ 83 | isLoading: boolean 84 | /** 85 | * Flag indicating if an error occurred 86 | */ 87 | isError: boolean 88 | /** 89 | * Flag indicating if the hook was successful 90 | */ 91 | isSuccess: boolean 92 | } 93 | 94 | /** 95 | * Extended state of the hook 96 | */ 97 | export type HookExtendedState = HookState & HookDerivedState 98 | 99 | /** 100 | * Action to update the hook state 101 | */ 102 | export type HookStateAction = 103 | | { type: HookStateStatus.Loading } 104 | | { type: HookStateStatus.Success; data: LibFnReturn } 105 | | { type: HookStateStatus.Error; error: string } 106 | 107 | export type HookStateReducer = ( 108 | state: HookState>, 109 | action: HookStateAction 110 | ) => HookState> 111 | 112 | export type Hook = ( 113 | options?: HookOptions 114 | ) => HookReturn 115 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks' 2 | -------------------------------------------------------------------------------- /src/lib.ts: -------------------------------------------------------------------------------- 1 | export * as coreLib from 'html-to-image' 2 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'html-to-image/lib/types' 2 | 3 | export type LibFn = (node: HTMLElement, options?: Options) => Promise 4 | export type LibFnReturn = Awaited> 5 | export type LibOptions = Options 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "declaration": true, 6 | "downlevelIteration": true, 7 | "esModuleInterop": true, 8 | "incremental": true, 9 | "isolatedModules": true, 10 | "jsx": "react-jsx", 11 | "lib": ["dom", "dom.iterable", "esnext"], 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "noEmit": true, 15 | "noImplicitReturns": false, 16 | "preserveSymlinks": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "target": "ESNext", 21 | "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" 22 | }, 23 | "exclude": ["node_modules"], 24 | "include": ["src"] 25 | } 26 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | clean: true, 5 | dts: true, 6 | entry: ['src'], 7 | format: ['esm', 'cjs'], 8 | minify: true, 9 | shims: true, 10 | sourcemap: true, 11 | splitting: true 12 | }) 13 | --------------------------------------------------------------------------------