├── .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 | [](https://www.npmjs.com/package/@hugocxl/react-to-image)
6 | [](https://www.npmjs.com/package/@hugocxl/react-to-image)
7 | [](https://bundlephobia.com/result?p=@hugocxl/react-to-image)
8 | [](https://github.com/hugocxl/react-to-image/actions/workflows/release.yml)
9 | [](https://coveralls.io/github/hugocxl/react-to-image)
10 | [](https://www.typescriptlang.org)
11 | [](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 | Convert to PNG
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 | Copy to clipboard
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 | Download
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 | Download
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 | Copy to clipboard
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 |
Convert
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 | Inject image
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 | Inject canvas
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 |
--------------------------------------------------------------------------------