@ninesstudios/whiteboard 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # Whiteboard
2
+
3
+ A tailored React Whiteboard component that supports drawing, shapes, text, images, and more. It comes with built-in state management and a customizable toolbar.
4
+
5
+ ## Features
6
+
7
+ - **Drawing Tools**: Pencil, Text, Rectangle, Circle, Triangle, Diamond, Star, Heart, Hexagon, Octagon, Arrow.
8
+ - **Image Support**: Insert and manipulate images on the canvas.
9
+ - **Canvas Controls**: Pan and Zoom functionality.
10
+ - **Grid System**: Toggleable background grid.
11
+ - **Selection**: Select, move, and modify elements.
12
+ - **Export**: Export the whiteboard content as a PNG image.
13
+ - **Locking Mechanism**: Set the whiteboard to read-only mode with custom messages.
14
+ - **Action Handling**: Hook into internal actions for external syncing or logging.
15
+
16
+ ## Installation
17
+
18
+ Install the package and its peer dependencies:
19
+
20
+ ```bash
21
+ npm install whiteboard react react-dom
22
+ ```
23
+
24
+ > **Note**: If this package is not yet published to npm, you can install it locally or link it.
25
+
26
+ ## Usage
27
+
28
+ ### Basic Usage
29
+
30
+ Import the `Whiteboard` component and the stylesheet. The component takes up 100% of the parent container's width and height.
31
+
32
+ ```jsx
33
+ import React from 'react';
34
+ import Whiteboard from 'whiteboard';
35
+ import 'whiteboard/style.css'; // Import styles
36
+
37
+ function App() {
38
+ return (
39
+ <div style={{ width: '100vw', height: '100vh', border: '1px solid #ccc' }}>
40
+ <Whiteboard />
41
+ </div>
42
+ );
43
+ }
44
+
45
+ export default App;
46
+ ```
47
+
48
+ ### Advanced Usage
49
+
50
+ You can control the whiteboard state and handle events using props and a ref.
51
+
52
+ ```jsx
53
+ import React, { useRef } from 'react';
54
+ import Whiteboard from 'whiteboard';
55
+ import 'whiteboard/style.css';
56
+
57
+ function App() {
58
+ const whiteboardRef = useRef(null);
59
+
60
+ const handleAction = (action) => {
61
+ console.log('Action occurred:', action);
62
+ // Send action to server for real-time collaboration
63
+ };
64
+
65
+ const handleLockChange = (isLocked) => {
66
+ console.log('Lock state changed:', isLocked);
67
+ };
68
+
69
+ return (
70
+ <div style={{ width: '100vw', height: '100vh' }}>
71
+ <Whiteboard
72
+ ref={whiteboardRef}
73
+ onAction={handleAction}
74
+ isLocked={false}
75
+ lockText="This board is currently locked"
76
+ onLockChange={handleLockChange}
77
+ />
78
+ </div>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ### Programmatic Updates (applyAction)
84
+
85
+ You can programmatically apply actions to the whiteboard using the `applyAction` method exposed via the ref. This is essential for implementing real-time collaboration where you need to apply actions received from a server.
86
+
87
+ ```jsx
88
+ import React, { useEffect, useRef } from 'react';
89
+ import Whiteboard from 'whiteboard';
90
+
91
+ function App() {
92
+ const whiteboardRef = useRef(null);
93
+
94
+ useEffect(() => {
95
+ // Example: mimicking receiving an action from a websocket
96
+ const mockIncomingAction = {
97
+ type: 'whiteboard/addElement',
98
+ payload: {
99
+ id: 'remote-1',
100
+ type: 'rectangle',
101
+ x: 200,
102
+ y: 200,
103
+ width: 100,
104
+ height: 100,
105
+ fillColor: '#FF0000'
106
+ }
107
+ };
108
+
109
+ // Apply the action to the whiteboard
110
+ if (whiteboardRef.current) {
111
+ // In a real app, this would be inside a socket event listener
112
+ setTimeout(() => {
113
+ whiteboardRef.current.applyAction(mockIncomingAction);
114
+ }, 1000);
115
+ }
116
+ }, []);
117
+
118
+ return (
119
+ <div style={{ width: '100vw', height: '100vh' }}>
120
+ <Whiteboard ref={whiteboardRef} />
121
+ </div>
122
+ );
123
+ }
124
+ ```
125
+
126
+ ## API Reference
127
+
128
+ ### Props
129
+
130
+ | Prop | Type | Default | Description |
131
+ |------|------|---------|-------------|
132
+ | `onAction` | `(action: object) => void` | `undefined` | Callback fired whenever a state-changing action occurs (e.g., drawing, moving). |
133
+ | `isLocked` | `boolean` | `false` | When `true`, disables all editing tools and shows a lock indicator. |
134
+ | `lockText` | `string` | `undefined` | Custom text to display on the lock indicator when `isLocked` is true. |
135
+ | `onLockChange` | `(isLocked: boolean) => void` | `undefined` | Callback fired when the lock state changes. |
136
+
137
+ ### Ref Methods
138
+
139
+ You can access these methods by passing a `ref` to the `Whiteboard` component.
140
+
141
+ | Method | Arguments | Description |
142
+ |--------|-----------|-------------|
143
+ | `applyAction` | `(action: object)` | Dispatches an action to the internal Redux store. Useful for applying remote updates. |
144
+ | `canvas` | - | Returns the underlying HTMLCanvasElement. |
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1 @@
1
+ .watermark{position:absolute;bottom:0;right:0;padding:8px 12px;background-color:#fffc;border-top-left-radius:8px;font-family:sans-serif;font-size:14px;color:#666;pointer-events:none;z-index:1000;-webkit-user-select:none;user-select:none;text-decoration:none}.toolbar{position:absolute;left:10px;top:50%;transform:translateY(-50%);width:44px;padding:1px;margin:10px auto;z-index:1000;border:1px solid #ccc;background:#fff;border-radius:8px;display:flex;flex-direction:column;justify-content:space-between;align-items:center;box-shadow:0 2px 8px #0000001a}.toolbar>button:first-child{border-top-left-radius:8px;border-top-right-radius:8px}.toolbar>button:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}.toolbar>.language-container:last-child button{border-bottom-left-radius:8px;border-bottom-right-radius:8px}.ellipsis-container{position:relative}.shapes-popup{position:absolute;left:50px;top:50%;transform:translateY(-50%);background:#fff;border:1px solid #ccc;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;min-width:160px;z-index:1001}.shapes-popup-header{font-size:12px;font-weight:600;color:#666;padding:4px 8px 8px;border-bottom:1px solid #eee;margin-bottom:8px}.shapes-popup-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.shapes-popup-grid button{width:36px;height:36px;padding:0;display:flex;align-items:center;justify-content:center;font-size:16px}.shapes-popup-grid button:hover{background:#e9e9e9}.shapes-popup-grid button.active{background:#d0e8ff;color:#06c}.language-container{position:relative}.language-popup{position:absolute;left:50px;top:50%;transform:translateY(-50%);background:#fff;border:1px solid #ccc;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;min-width:140px;z-index:1001}.language-popup-header{font-size:12px;font-weight:600;color:#666;padding:4px 8px 8px;border-bottom:1px solid #eee;margin-bottom:8px}.language-options{display:flex;flex-direction:column;gap:4px}.language-options button{width:100%;height:32px;padding:0 8px;display:flex;align-items:center;justify-content:flex-start;font-size:14px;gap:8px;border-radius:4px}.language-options button:hover{background:#e9e9e9}.language-options button.active{background:#d0e8ff;color:#06c}.prop-tools{position:absolute;bottom:10px;left:10px;padding:1px;margin:10px auto;z-index:1000;border:1px solid #ccc;background:#fff;border-radius:8px;display:flex;flex-direction:row;justify-content:space-between;align-items:center;box-shadow:0 2px 8px #0000001a}.prop-tools>button:first-child{border-top-left-radius:8px;border-top-right-radius:8px}.prop-tools>button:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}button{width:40px;height:40px;border:none;border-radius:4px;background-color:#fff;cursor:pointer;transition:background .3s;text-align:center;line-height:40px;display:flex;justify-content:center;align-items:center}button:hover{background:#e9e9e9}button.active{background:#d0e8ff;color:#06c}select{width:36px;height:36px;border:none;border-radius:4px;background-color:#fff;cursor:pointer;text-align:center;line-height:36px;display:flex;justify-content:center;align-items:center;font-size:14px}.prop-tool-item{min-width:80px;margin:2px 4px;display:flex;flex-direction:column;justify-content:space-between;align-items:center}.prop-tool-item>*{text-align:center;flex:auto;min-height:30px;width:100%;box-sizing:border-box;padding-left:4px;padding-right:4px}.prop-tool-item>span{font-size:12px;color:#555;margin-bottom:4px;display:flex;justify-content:center;align-items:center;text-align:center;white-space:nowrap}.layer-buttons{display:flex;flex-direction:row;gap:4px;justify-content:center}.layer-buttons button{width:36px;height:36px;padding:0;display:flex;justify-content:center;align-items:center}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;min-height:100vh;display:flex;justify-content:center;align-items:center;background:#f5f5f5}html,body{width:100%;height:100%;margin:0;padding:0;overflow:hidden}#root{width:100%;height:100%}