├── .babelrc
├── .eslintrc
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
├── ReactPainter.tsx
├── helpers
│ ├── canvasHelpers.ts
│ ├── eventHelpers.ts
│ ├── importImageHelpers.ts
│ ├── miscHelpers.ts
│ ├── objectUrlHelpers.ts
│ └── saveCanvasHelpers.ts
└── index.ts
├── stories
├── index.stories.js
└── storybookComponent.jsx
├── test
└── typescript.tsx
├── tsconfig.json
└── tslint.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "targets": {
7 | "browsers": ["last 2 versions", "safari >= 7"]
8 | }
9 | }
10 | ],
11 | "stage-2",
12 | "react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-error.log
3 | dist
4 | storybook-static
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aml2610/react-painter/25e183086fafbadfd46bff39c46d0c04697cfbd7/.npmignore
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .storybook
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aml2610/react-painter/25e183086fafbadfd46bff39c46d0c04697cfbd7/.prettierrc
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import "@storybook/addon-actions/register";
2 | import "@storybook/addon-links/register";
3 | import "@storybook/addon-knobs/register";
4 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | // automatically import all files ending in *.stories.js
4 | const req = require.context('../stories', true, /.stories.js$/);
5 | function loadStories() {
6 | req.keys().forEach(filename => req(filename));
7 | }
8 |
9 | configure(loadStories, module);
10 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const genDefaultConfig = require('@storybook/react/dist/server/config/defaults/webpack.config.js');
2 | module.exports = (baseConfig, env) => {
3 | const config = genDefaultConfig(baseConfig, env);
4 | // Extend it as you need.
5 | // For example, add typescript loader:
6 | config.module.rules.push({
7 | test: /\.(ts|tsx)$/,
8 | loader: require.resolve('awesome-typescript-loader')
9 | });
10 | config.resolve.extensions.push('.ts', '.tsx');
11 | return config;
12 | };
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at andrei.longhin1@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Pull requests are welcome if you'd like to contribute to this project.
2 |
3 | Before you make a pull request can you please open an issue so we can discuss features added or bugs fixed
4 | prior to any work being done.
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Andrei-Marius Longhin
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 | # react-painter
2 |
3 | [](https://www.npmjs.com/package/react-painter)
4 | 
5 |
6 | [DEMO](https://aml2610.github.io/react-painter/)
7 |
8 |
0 dependency React component that can be used to draw on a canvas with mouse/touch
9 |
10 |
11 |
12 | ## The problem
13 |
14 | You want simple functionality to allow the user to write/draw on image/blank canvas, then save the output to be uploaded to a server/locally.
15 |
16 | ## The solution
17 |
18 | This is a simple component that utilises HTML5 canvases and the File API.
19 | It uses a render prop which gives you maximum flexibility with a minimal API because you are able to extend functionality and render the result as you wish.
20 |
21 | ## Installation
22 |
23 | This module is distributed via [npm][npm] which is bundled with [node][node] and
24 | should be installed as one of your project's `dependencies`:
25 |
26 | ```
27 | npm install --save react-painter
28 | ```
29 |
30 | > This package also depends on `react` and `prop-types`. Make sure they are installed in your project.
31 |
32 | ## Usage
33 |
34 | ```jsx
35 | import { ReactPainter } from 'react-painter';
36 |
37 | const Drawable = () => (
38 | console.log(blob)}
42 | render={({ triggerSave, canvas }) => (
43 |
44 |
45 |
{canvas}
46 |
47 | )}
48 | />
49 | );
50 | ```
51 |
52 | ## Props
53 |
54 | ### height?: number
55 |
56 | > defaults to `300`
57 |
58 | Set the height of the canvas
59 | This value should not be changed after ReactPainter is mounted as it will mess up the resolution. Remount a new instance when the height changed.
60 |
61 | ### width?: number
62 |
63 | > defaults to `300`
64 |
65 | Set the width of the canvas
66 | Similar to height, this value should not be changed after mounted.
67 |
68 | ### initialColor?: string
69 |
70 | > defaults to `#000`
71 |
72 | Set the initial stroke color.
73 | Stroke can be changed dynamically using `setColor`.
74 |
75 | ### initialLineWidth?: number
76 |
77 | > defaults to `5`
78 |
79 | Set the initial stroke line width.
80 | Line width can be changed dynamically using `setLineWidth`.
81 |
82 | ### lineCap?: 'round' | 'butt' | 'square'
83 |
84 | > defaults to `round`
85 |
86 | Set the initial stroke line cap.
87 | Line cap can be changed dynamically using `setLineCap`.
88 |
89 | ### initialLineJoin?: 'round' | 'bevel' | 'miter'
90 |
91 | > defaults to `round`
92 |
93 | Set the initial stroke line join.
94 | Line join type can be changed dynamically using `setLineJoin`.
95 |
96 | ### onSave?: (blob: Blob) => void
97 |
98 | Your handler when the canvas is saved.
99 |
100 | ### image?: File | string
101 |
102 | The image that would takes up the whole canvas. If it is a string, then it should be an URL for an image.
103 |
104 | > Note: It the image is not accessible publicly or via cookies, the image will not be shown. You can check the result via `imageCanDownload` property.
105 |
106 | ### render?: (props: RenderProps) => ReactNode
107 |
108 | This is called with an object. Read more about the properties of the object in the section [Render Prop Function](#render-prop-function).
109 |
110 | > Note: If you do not provide the render function. A canvas element will be mounted as default. However, this is not really useful because you cannot trigger the save of the canvas.
111 |
112 | ## Render Prop Function
113 |
114 | This is where you want to render the canvas and the function to trigger save. It is a regular prop called `render`: ``
115 | The properties of the object passed to this function are listed below.
116 |
117 | ### canvas: ReactNode
118 |
119 | This is the canvas node that you can used to mount in your component. Example:
120 |
121 | ```jsx
122 | (
124 |
125 |
Awesome heading
126 |
{canvas}
127 |
128 | )}
129 | />
130 | ```
131 |
132 | ### triggerSave: () => void
133 |
134 | This is the function that you invoke when you want to save the canvas.
135 |
136 | Example:
137 |
138 | ```jsx
139 | (
141 |
142 |
Awesome heading
143 |
{canvas}
144 |
145 |
146 | )}
147 | />
148 | ```
149 |
150 | ### setColor: (color: string) => void
151 |
152 | Set the color of the line.
153 |
154 | Example:
155 |
156 | ```jsx
157 | (
159 |
184 | )}
185 | />
186 | ```
187 |
188 | ### setLineJoin: (type: 'round' | 'bevel' | 'miter') => void
189 |
190 | Set the join type of the line.
191 |
192 | Example:
193 |
194 | ```jsx
195 | (
197 |
198 |
Awesome heading
199 |
204 |
{canvas}
205 |
206 |
207 | )}
208 | />
209 | ```
210 |
211 | ### setLineCap: (type: 'round' | 'butt' | 'square') => void
212 |
213 | Set the cap type of the line.
214 |
215 | Example:
216 |
217 | ```jsx
218 | (
220 |
221 |
Awesome heading
222 |
227 |
{canvas}
228 |
229 |
230 | )}
231 | />
232 | ```
233 |
234 | ### imageCanDownload: boolean
235 |
236 | This properties let you know if the image is inserted successfully. By default it is `null` until the checking of image import is successful.
237 |
238 | Example:
239 |
240 | ```jsx
241 | (
244 |
245 |
Awesome heading
246 | {imageCanDownload ?
Sorry, the image that you have provided is not accessible.
: null}
247 |
{canvas}
248 |
249 |
250 | )}
251 | ```
252 |
253 | ### imageDownloadUrl: string;
254 |
255 | This properties is the URL you can use to allow user to download the saved image after invoke `triggerSave`. By default it is `null` until the `triggerSave` is invoked.
256 |
257 | Example:
258 |
259 | ```jsx
260 | (
262 |
272 | )}
273 | />
274 | ```
275 |
276 | ### getCanvasProps: (any) => CanvasProps;
277 |
278 | Prop getter for advanced use case. If you wish to extend the functionality of ReactPainter by adding additional properties to the canvas/getting the `ref` of the canvas, then call this function with those properties and spread the result of this function to the canvas.
279 |
280 | > Note: Only callback ref is supported. The new `React.createRef` is not supported.
281 |
282 | Example:
283 |
284 | ```jsx
285 | (
287 |