react-manga-effects 0.1.0 → 0.1.7
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/LICENSE +21 -21
- package/README.md +167 -167
- package/dist/components/SpeedLines/types.d.ts +0 -2
- package/dist/components/SpeedLines/utils/lineGenerator.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +110 -114
- package/package.json +78 -78
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 erutobusiness
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 erutobusiness
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
# 🎬 react-manga-effects
|
|
2
|
-
|
|
3
|
-
> Add dramatic anime/manga impact effects to your React applications with zero fuss.
|
|
4
|
-
|
|
5
|
-
 to emphasize action or shock.
|
|
20
|
-
- **📘 TypeScript Support**: Fully typed props and exports.
|
|
21
|
-
- **🎈 Lightweight**: Zero runtime dependencies (other than React).
|
|
22
|
-
- **🚀 SSR Compatible**: Works with Next.js, Remix, and Gatsby.
|
|
23
|
-
- **🎨 Highly Customizable**: Control colors, density, speed, easing, and positioning.
|
|
24
|
-
|
|
25
|
-
## Installation
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm install
|
|
29
|
-
# or
|
|
30
|
-
yarn add
|
|
31
|
-
# or
|
|
32
|
-
pnpm add
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Quick Start
|
|
36
|
-
|
|
37
|
-
Import the components and use them in your React app:
|
|
38
|
-
|
|
39
|
-
```tsx
|
|
40
|
-
import React, { useState } from 'react';
|
|
41
|
-
import { IrisWipe, SpeedLines } from '
|
|
42
|
-
|
|
43
|
-
const App = () => {
|
|
44
|
-
const [isOpen, setIsOpen] = useState(true);
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
|
|
48
|
-
{/* Background Content */}
|
|
49
|
-
<img src="/my-manga-scene.jpg" alt="Scene" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
50
|
-
|
|
51
|
-
{/* Overlay Effects */}
|
|
52
|
-
<SpeedLines
|
|
53
|
-
animated
|
|
54
|
-
color="rgba(0,0,0,0.5)"
|
|
55
|
-
center={{ x: 50, y: 50 }}
|
|
56
|
-
/>
|
|
57
|
-
|
|
58
|
-
<IrisWipe
|
|
59
|
-
isOpen={isOpen}
|
|
60
|
-
duration={1000}
|
|
61
|
-
center={{ x: 50, y: 50 }}
|
|
62
|
-
onComplete={() => console.log('Transition Complete')}
|
|
63
|
-
>
|
|
64
|
-
{/* Content to be revealed/hidden inside the wipe can go here if needed,
|
|
65
|
-
typically IrisWipe is used as an overlay. */}
|
|
66
|
-
</IrisWipe>
|
|
67
|
-
|
|
68
|
-
<button
|
|
69
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
70
|
-
style={{ position: 'absolute', bottom: 20, left: 20, zIndex: 100 }}
|
|
71
|
-
>
|
|
72
|
-
Toggle Transition
|
|
73
|
-
</button>
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export default App;
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Components API
|
|
82
|
-
|
|
83
|
-
### IrisWipe
|
|
84
|
-
|
|
85
|
-
A circular masking transition effect.
|
|
86
|
-
|
|
87
|
-
| Prop | Type | Default | Description |
|
|
88
|
-
|------|------|---------|-------------|
|
|
89
|
-
| `isOpen` | `boolean` | **Required** | `true` to reveal content (open iris), `false` to hide (close iris). |
|
|
90
|
-
| `duration` | `number` | `500` | Animation duration in milliseconds. |
|
|
91
|
-
| `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | Center point of the iris in percentage (0-100). |
|
|
92
|
-
| `easing` | `string` | `'easeInOut'` | CSS transition timing function (e.g., `'linear'`, `'ease-out'`, `'cubic-bezier(...)`'). |
|
|
93
|
-
| `onComplete` | `() => void` | `undefined` | Callback function fired when the transition finishes. |
|
|
94
|
-
| `className` | `string` | `''` | Additional CSS classes for the container. |
|
|
95
|
-
| `style` | `CSSProperties` | `{}` | Inline styles for the container. |
|
|
96
|
-
|
|
97
|
-
#### Example: Custom Center
|
|
98
|
-
```tsx
|
|
99
|
-
<IrisWipe
|
|
100
|
-
isOpen={show}
|
|
101
|
-
center={{ x: 80, y: 20 }} // Focus on top-right
|
|
102
|
-
duration={1200}
|
|
103
|
-
/>
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### SpeedLines
|
|
107
|
-
|
|
108
|
-
Concentration lines typically used to show speed, shock, or intense focus.
|
|
109
|
-
|
|
110
|
-
| Prop | Type | Default | Description |
|
|
111
|
-
|------|------|---------|-------------|
|
|
112
|
-
| `lineCount` | `number` | `60` | Number of lines to draw. Higher values create a denser effect. |
|
|
113
|
-
| `color` | `string` | `'rgba(0, 0, 0, 0.6)'` | Color of the lines. Supports valid CSS colors. |
|
|
114
|
-
| `minLength` | `number` | `10` | Minimum length of lines as a percentage of the container size. |
|
|
115
|
-
| `maxLength` | `number` | `30` | Maximum length of lines as a percentage of the container size. |
|
|
116
|
-
| `innerRadius` | `number` | `0` | Radius of the empty safe zone in the center (percentage). |
|
|
117
|
-
| `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | The convergence point of the lines. |
|
|
118
|
-
| `animated` | `boolean` | `false` | If `true`, lines will animate (opacity pulse/shake). |
|
|
119
|
-
| `animationSpeed`| `number` | `1` | Speed multiplier for the animation. |
|
|
120
|
-
| `className` | `string` | `''` | Additional CSS classes. |
|
|
121
|
-
| `style` | `CSSProperties` | `{}` | Inline styles. |
|
|
122
|
-
|
|
123
|
-
#### Example: Intense Action
|
|
124
|
-
```tsx
|
|
125
|
-
<SpeedLines
|
|
126
|
-
lineCount={120}
|
|
127
|
-
color="red"
|
|
128
|
-
innerRadius={10}
|
|
129
|
-
animated={true}
|
|
130
|
-
animationSpeed={1.5}
|
|
131
|
-
/>
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Storybook
|
|
135
|
-
|
|
136
|
-
We use Storybook for development and testing.
|
|
137
|
-
|
|
138
|
-
1. Clone the repository
|
|
139
|
-
2. Install dependencies: `npm install`
|
|
140
|
-
3. Run Storybook:
|
|
141
|
-
```bash
|
|
142
|
-
npm run storybook
|
|
143
|
-
```
|
|
144
|
-
4. Open [http://localhost:6006](http://localhost:6006) to view the components.
|
|
145
|
-
|
|
146
|
-
## Development
|
|
147
|
-
|
|
148
|
-
Contributions are welcome!
|
|
149
|
-
|
|
150
|
-
1. Clone the repo:
|
|
151
|
-
```bash
|
|
152
|
-
git clone https://github.com/erutobusiness/react-manga-effects.git
|
|
153
|
-
cd react-manga-effects
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
2. Install dependencies:
|
|
157
|
-
```bash
|
|
158
|
-
npm install
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
3. Start development server (Storybook):
|
|
162
|
-
```bash
|
|
163
|
-
npm run storybook
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## License
|
|
167
|
-
|
|
1
|
+
# 🎬 react-manga-effects
|
|
2
|
+
|
|
3
|
+
> Add dramatic anime/manga impact effects to your React applications with zero fuss.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
[](https://github.com/erutobusiness/react-manga-effects/actions/workflows/ci.yml)
|
|
9
|
+
|
|
10
|
+
**react-manga-effects** provides lightweight, customizable high-impact visual effects common in anime and manga, such as circular iris wipes and dynamic focus lines. Built with TypeScript and optimizing for performance.
|
|
11
|
+
|
|
12
|
+
## Demo
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **🌀 IrisWipe**: Classic anime-style circular transition to open/close scenes.
|
|
19
|
+
- **⚡ SpeedLines**: Dynamic focus/concentration lines (motion lines) to emphasize action or shock.
|
|
20
|
+
- **📘 TypeScript Support**: Fully typed props and exports.
|
|
21
|
+
- **🎈 Lightweight**: Zero runtime dependencies (other than React).
|
|
22
|
+
- **🚀 SSR Compatible**: Works with Next.js, Remix, and Gatsby.
|
|
23
|
+
- **🎨 Highly Customizable**: Control colors, density, speed, easing, and positioning.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install react-manga-effects
|
|
29
|
+
# or
|
|
30
|
+
yarn add react-manga-effects
|
|
31
|
+
# or
|
|
32
|
+
pnpm add react-manga-effects
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
Import the components and use them in your React app:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import React, { useState } from 'react';
|
|
41
|
+
import { IrisWipe, SpeedLines } from 'react-manga-effects';
|
|
42
|
+
|
|
43
|
+
const App = () => {
|
|
44
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
|
|
48
|
+
{/* Background Content */}
|
|
49
|
+
<img src="/my-manga-scene.jpg" alt="Scene" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
50
|
+
|
|
51
|
+
{/* Overlay Effects */}
|
|
52
|
+
<SpeedLines
|
|
53
|
+
animated
|
|
54
|
+
color="rgba(0,0,0,0.5)"
|
|
55
|
+
center={{ x: 50, y: 50 }}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<IrisWipe
|
|
59
|
+
isOpen={isOpen}
|
|
60
|
+
duration={1000}
|
|
61
|
+
center={{ x: 50, y: 50 }}
|
|
62
|
+
onComplete={() => console.log('Transition Complete')}
|
|
63
|
+
>
|
|
64
|
+
{/* Content to be revealed/hidden inside the wipe can go here if needed,
|
|
65
|
+
typically IrisWipe is used as an overlay. */}
|
|
66
|
+
</IrisWipe>
|
|
67
|
+
|
|
68
|
+
<button
|
|
69
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
70
|
+
style={{ position: 'absolute', bottom: 20, left: 20, zIndex: 100 }}
|
|
71
|
+
>
|
|
72
|
+
Toggle Transition
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default App;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Components API
|
|
82
|
+
|
|
83
|
+
### IrisWipe
|
|
84
|
+
|
|
85
|
+
A circular masking transition effect.
|
|
86
|
+
|
|
87
|
+
| Prop | Type | Default | Description |
|
|
88
|
+
|------|------|---------|-------------|
|
|
89
|
+
| `isOpen` | `boolean` | **Required** | `true` to reveal content (open iris), `false` to hide (close iris). |
|
|
90
|
+
| `duration` | `number` | `500` | Animation duration in milliseconds. |
|
|
91
|
+
| `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | Center point of the iris in percentage (0-100). |
|
|
92
|
+
| `easing` | `string` | `'easeInOut'` | CSS transition timing function (e.g., `'linear'`, `'ease-out'`, `'cubic-bezier(...)`'). |
|
|
93
|
+
| `onComplete` | `() => void` | `undefined` | Callback function fired when the transition finishes. |
|
|
94
|
+
| `className` | `string` | `''` | Additional CSS classes for the container. |
|
|
95
|
+
| `style` | `CSSProperties` | `{}` | Inline styles for the container. |
|
|
96
|
+
|
|
97
|
+
#### Example: Custom Center
|
|
98
|
+
```tsx
|
|
99
|
+
<IrisWipe
|
|
100
|
+
isOpen={show}
|
|
101
|
+
center={{ x: 80, y: 20 }} // Focus on top-right
|
|
102
|
+
duration={1200}
|
|
103
|
+
/>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### SpeedLines
|
|
107
|
+
|
|
108
|
+
Concentration lines typically used to show speed, shock, or intense focus.
|
|
109
|
+
|
|
110
|
+
| Prop | Type | Default | Description |
|
|
111
|
+
|------|------|---------|-------------|
|
|
112
|
+
| `lineCount` | `number` | `60` | Number of lines to draw. Higher values create a denser effect. |
|
|
113
|
+
| `color` | `string` | `'rgba(0, 0, 0, 0.6)'` | Color of the lines. Supports valid CSS colors. |
|
|
114
|
+
| `minLength` | `number` | `10` | Minimum length of lines as a percentage of the container size. |
|
|
115
|
+
| `maxLength` | `number` | `30` | Maximum length of lines as a percentage of the container size. |
|
|
116
|
+
| `innerRadius` | `number` | `0` | Radius of the empty safe zone in the center (percentage). |
|
|
117
|
+
| `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | The convergence point of the lines. |
|
|
118
|
+
| `animated` | `boolean` | `false` | If `true`, lines will animate (opacity pulse/shake). |
|
|
119
|
+
| `animationSpeed`| `number` | `1` | Speed multiplier for the animation. |
|
|
120
|
+
| `className` | `string` | `''` | Additional CSS classes. |
|
|
121
|
+
| `style` | `CSSProperties` | `{}` | Inline styles. |
|
|
122
|
+
|
|
123
|
+
#### Example: Intense Action
|
|
124
|
+
```tsx
|
|
125
|
+
<SpeedLines
|
|
126
|
+
lineCount={120}
|
|
127
|
+
color="red"
|
|
128
|
+
innerRadius={10}
|
|
129
|
+
animated={true}
|
|
130
|
+
animationSpeed={1.5}
|
|
131
|
+
/>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Storybook
|
|
135
|
+
|
|
136
|
+
We use Storybook for development and testing.
|
|
137
|
+
|
|
138
|
+
1. Clone the repository
|
|
139
|
+
2. Install dependencies: `npm install`
|
|
140
|
+
3. Run Storybook:
|
|
141
|
+
```bash
|
|
142
|
+
npm run storybook
|
|
143
|
+
```
|
|
144
|
+
4. Open [http://localhost:6006](http://localhost:6006) to view the components.
|
|
145
|
+
|
|
146
|
+
## Development
|
|
147
|
+
|
|
148
|
+
Contributions are welcome!
|
|
149
|
+
|
|
150
|
+
1. Clone the repo:
|
|
151
|
+
```bash
|
|
152
|
+
git clone https://github.com/erutobusiness/react-manga-effects.git
|
|
153
|
+
cd react-manga-effects
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
2. Install dependencies:
|
|
157
|
+
```bash
|
|
158
|
+
npm install
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
3. Start development server (Storybook):
|
|
162
|
+
```bash
|
|
163
|
+
npm run storybook
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
168
|
MIT © [erutobusiness](https://github.com/erutobusiness)
|
|
@@ -3,4 +3,4 @@ import { Line, SpeedLinesConfig } from '../types';
|
|
|
3
3
|
|
|
4
4
|
export declare const generateLines: (config: SpeedLinesConfig) => Line[];
|
|
5
5
|
export declare const drawLine: (ctx: CanvasRenderingContext2D, line: Line, center: Point, maxRadius: number, innerRadiusPct: number, // 0-100
|
|
6
|
-
color: string, width: number, height: number
|
|
6
|
+
color: string, width: number, height: number) => void;
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const A=require("react/jsx-runtime"),i=require("react"),D=({text:e="Manga Effect Placeholder"})=>A.jsx("div",{style:{padding:"2rem",border:"4px dashed #333",textAlign:"center",fontFamily:"sans-serif",fontWeight:"bold",backgroundColor:"#f0f0f0"},children:e}),L=({children:e,isOpen:r,center:n={x:50,y:50},duration:u=1e3,easing:c="easeInOut",onComplete:o,className:t="",style:s})=>{const R=i.useRef(null);let y="ease-in-out";if(typeof c=="string")switch(c){case"linear":y="linear";break;case"easeIn":y="ease-in";break;case"easeOut":y="ease-out";break;case"easeInOut":y="ease-in-out";break;default:y=c}const h=l=>{l.propertyName==="clip-path"&&o&&o()},f={"--iris-cx":`${n.x}%`,"--iris-cy":`${n.y}%`,"--iris-duration":`${u}ms`,"--iris-easing":y,...s};return A.jsx("div",{ref:R,className:`iris-wipe ${t}`,"data-state":r?"open":"closed",style:f,onTransitionEnd:h,children:e})},O=(e,r)=>{const n=i.useRef(),u=i.useRef(),c=o=>{if(u.current!==void 0){const t=o-u.current;e(t)}u.current=o,n.current=requestAnimationFrame(c)};i.useEffect(()=>(r?n.current=requestAnimationFrame(c):(n.current&&cancelAnimationFrame(n.current),u.current=void 0),()=>{n.current&&cancelAnimationFrame(n.current)}),[r,e])},W=(e,r,n,u)=>{const c=Math.cos(r),o=Math.sin(r);let t=1/0;if(Math.abs(c)>1e-4)if(c>0){const s=(n-e.x)/c;s>=0&&(t=Math.min(t,s))}else{const s=(0-e.x)/c;s>=0&&(t=Math.min(t,s))}if(Math.abs(o)>1e-4)if(o>0){const s=(u-e.y)/o;s>=0&&(t=Math.min(t,s))}else{const s=(0-e.y)/o;s>=0&&(t=Math.min(t,s))}return t===1/0?0:t},q=e=>Array.from({length:e.lineCount},()=>({angle:Math.random()*Math.PI*2,length:(e.minLength+Math.random()*(e.maxLength-e.minLength))/100,width:2+Math.random()*8,opacity:.6+Math.random()*.4})),C=(e,r,n,u,c,o,t,s)=>{const R=u*(c/100),y=r.opacity,f=W(n,r.angle,t,s)+2;let l=f-f*r.length;if(l<R&&(l=R),l>=f)return;const p=Math.cos(r.angle),x=Math.sin(r.angle),M=n.x+p*f,v=n.y+x*f,E=n.x+p*l,I=n.y+x*l,b=-x,w=p,a=r.width/2;e.beginPath(),e.moveTo(M+b*a,v+w*a),e.lineTo(M-b*a,v-w*a),e.lineTo(E,I),e.closePath(),e.fillStyle=o,e.globalAlpha=y,e.fill(),e.globalAlpha=1},z=({center:e={x:50,y:50},lineCount:r=60,color:n="rgba(0, 0, 0, 0.6)",minLength:u=30,maxLength:c=60,innerRadius:o=0,animated:t=!1,animationSpeed:s=1,className:R="",style:y})=>{const h=i.useRef(null),f=i.useRef(null),l=i.useRef([]),p=i.useRef(0),x=i.useRef(e),M=i.useRef(0),v=i.useRef(0),[E,I]=i.useState({width:0,height:0});i.useEffect(()=>{x.current=e},[e.x,e.y]),i.useEffect(()=>{const a=t?3:1;l.current=Array.from({length:a},()=>q({lineCount:r,minLength:u,maxLength:c})),p.current=0},[r,u,c,t]);const b=i.useCallback(()=>{if(f.current&&h.current){const{clientWidth:a,clientHeight:d}=f.current,m=window.devicePixelRatio||1;if(h.current.width!==a*m||h.current.height!==d*m){h.current.width=a*m,h.current.height=d*m,h.current.style.width=`${a}px`,h.current.style.height=`${d}px`;const g=h.current.getContext("2d");g&&g.scale(m,m),I({width:a,height:d})}}},[]);i.useEffect(()=>{b();const a=new ResizeObserver(b);return f.current&&a.observe(f.current),()=>a.disconnect()},[b]);const w=i.useCallback(a=>{const d=h.current,m=f.current,g=d==null?void 0:d.getContext("2d");if(!d||!m||!g||E.width===0)return;g.save(),g.setTransform(1,0,0,1,0,0),g.clearRect(0,0,d.width,d.height),g.restore();const S=m.clientWidth,P=m.clientHeight,T={x:x.current.x/100*S,y:x.current.y/100*P},F=Math.max(T.x,S-T.x),$=Math.max(T.y,P-T.y),j=Math.hypot(F,$);if(t){M.current+=a;const k=80/Math.max(.1,s);M.current-v.current>k&&(p.current=(p.current+1)%l.current.length,v.current=M.current)}(l.current[p.current]||[]).forEach(k=>{C(g,k,T,j,o,n,S,P)})},[n,o,t,s,E]);return O(w,t),i.useEffect(()=>{t||w(0)},[w,t,r,u,c,o,n,E]),A.jsx("div",{ref:f,className:`speed-lines-container ${R}`,style:{width:"100%",height:"100%",position:"relative",overflow:"hidden",pointerEvents:"none",...y},children:A.jsx("canvas",{ref:h,style:{width:"100%",height:"100%",display:"block"}})})};exports.IrisWipe=L;exports.Placeholder=D;exports.SpeedLines=z;
|
package/dist/index.js
CHANGED
|
@@ -1,197 +1,193 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import
|
|
3
|
-
const
|
|
1
|
+
import { jsx as I } from "react/jsx-runtime";
|
|
2
|
+
import W, { useRef as y, useEffect as A, useCallback as P } from "react";
|
|
3
|
+
const X = ({ text: t = "Manga Effect Placeholder" }) => /* @__PURE__ */ I("div", { style: {
|
|
4
4
|
padding: "2rem",
|
|
5
5
|
border: "4px dashed #333",
|
|
6
6
|
textAlign: "center",
|
|
7
7
|
fontFamily: "sans-serif",
|
|
8
8
|
fontWeight: "bold",
|
|
9
9
|
backgroundColor: "#f0f0f0"
|
|
10
|
-
}, children: t }),
|
|
10
|
+
}, children: t }), Y = ({
|
|
11
11
|
children: t,
|
|
12
|
-
isOpen:
|
|
12
|
+
isOpen: r,
|
|
13
13
|
center: n = { x: 50, y: 50 },
|
|
14
|
-
duration: a =
|
|
14
|
+
duration: a = 1e3,
|
|
15
15
|
easing: i = "easeInOut",
|
|
16
16
|
onComplete: o,
|
|
17
17
|
className: e = "",
|
|
18
|
-
style:
|
|
18
|
+
style: s
|
|
19
19
|
}) => {
|
|
20
|
-
const
|
|
21
|
-
let
|
|
20
|
+
const M = y(null);
|
|
21
|
+
let d = "ease-in-out";
|
|
22
22
|
if (typeof i == "string")
|
|
23
23
|
switch (i) {
|
|
24
24
|
case "linear":
|
|
25
|
-
|
|
25
|
+
d = "linear";
|
|
26
26
|
break;
|
|
27
27
|
case "easeIn":
|
|
28
|
-
|
|
28
|
+
d = "ease-in";
|
|
29
29
|
break;
|
|
30
30
|
case "easeOut":
|
|
31
|
-
|
|
31
|
+
d = "ease-out";
|
|
32
32
|
break;
|
|
33
33
|
case "easeInOut":
|
|
34
|
-
|
|
34
|
+
d = "ease-in-out";
|
|
35
35
|
break;
|
|
36
36
|
default:
|
|
37
|
-
|
|
37
|
+
d = i;
|
|
38
38
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
},
|
|
39
|
+
const u = (l) => {
|
|
40
|
+
l.propertyName === "clip-path" && o && o();
|
|
41
|
+
}, h = {
|
|
42
42
|
"--iris-cx": `${n.x}%`,
|
|
43
43
|
"--iris-cy": `${n.y}%`,
|
|
44
44
|
"--iris-duration": `${a}ms`,
|
|
45
|
-
"--iris-easing":
|
|
46
|
-
...
|
|
45
|
+
"--iris-easing": d,
|
|
46
|
+
...s
|
|
47
47
|
};
|
|
48
|
-
return /* @__PURE__ */
|
|
48
|
+
return /* @__PURE__ */ I(
|
|
49
49
|
"div",
|
|
50
50
|
{
|
|
51
|
-
ref:
|
|
51
|
+
ref: M,
|
|
52
52
|
className: `iris-wipe ${e}`,
|
|
53
|
-
"data-state":
|
|
54
|
-
style:
|
|
55
|
-
onTransitionEnd:
|
|
53
|
+
"data-state": r ? "open" : "closed",
|
|
54
|
+
style: h,
|
|
55
|
+
onTransitionEnd: u,
|
|
56
56
|
children: t
|
|
57
57
|
}
|
|
58
58
|
);
|
|
59
|
-
},
|
|
60
|
-
const n =
|
|
59
|
+
}, q = (t, r) => {
|
|
60
|
+
const n = y(), a = y(), i = (o) => {
|
|
61
61
|
if (a.current !== void 0) {
|
|
62
62
|
const e = o - a.current;
|
|
63
63
|
t(e);
|
|
64
64
|
}
|
|
65
65
|
a.current = o, n.current = requestAnimationFrame(i);
|
|
66
66
|
};
|
|
67
|
-
A(() => (
|
|
67
|
+
A(() => (r ? n.current = requestAnimationFrame(i) : (n.current && cancelAnimationFrame(n.current), a.current = void 0), () => {
|
|
68
68
|
n.current && cancelAnimationFrame(n.current);
|
|
69
|
-
}), [
|
|
70
|
-
},
|
|
71
|
-
const i = Math.cos(
|
|
69
|
+
}), [r, t]);
|
|
70
|
+
}, z = (t, r, n, a) => {
|
|
71
|
+
const i = Math.cos(r), o = Math.sin(r);
|
|
72
72
|
let e = 1 / 0;
|
|
73
73
|
if (Math.abs(i) > 1e-4)
|
|
74
74
|
if (i > 0) {
|
|
75
|
-
const
|
|
76
|
-
|
|
75
|
+
const s = (n - t.x) / i;
|
|
76
|
+
s >= 0 && (e = Math.min(e, s));
|
|
77
77
|
} else {
|
|
78
|
-
const
|
|
79
|
-
|
|
78
|
+
const s = (0 - t.x) / i;
|
|
79
|
+
s >= 0 && (e = Math.min(e, s));
|
|
80
80
|
}
|
|
81
81
|
if (Math.abs(o) > 1e-4)
|
|
82
82
|
if (o > 0) {
|
|
83
|
-
const
|
|
84
|
-
|
|
83
|
+
const s = (a - t.y) / o;
|
|
84
|
+
s >= 0 && (e = Math.min(e, s));
|
|
85
85
|
} else {
|
|
86
|
-
const
|
|
87
|
-
|
|
86
|
+
const s = (0 - t.y) / o;
|
|
87
|
+
s >= 0 && (e = Math.min(e, s));
|
|
88
88
|
}
|
|
89
89
|
return e === 1 / 0 ? 0 : e;
|
|
90
|
-
},
|
|
90
|
+
}, C = (t) => Array.from({ length: t.lineCount }, () => ({
|
|
91
91
|
angle: Math.random() * Math.PI * 2,
|
|
92
92
|
// Convert percentage 0-100 to 0-1
|
|
93
93
|
length: (t.minLength + Math.random() * (t.maxLength - t.minLength)) / 100,
|
|
94
94
|
// Thick at start (outer edge), tapers to thin
|
|
95
95
|
width: 2 + Math.random() * 8,
|
|
96
|
-
opacity: 0.6 + Math.random() * 0.4
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const O = 0.5 + (Math.sin(E * s.pulseSpeed * c + s.pulseOffset) + 1) * 0.25;
|
|
105
|
-
x *= O;
|
|
106
|
-
}
|
|
107
|
-
const m = q(n, s.angle, e, r) + 2;
|
|
108
|
-
let y = m - m * s.length;
|
|
109
|
-
if (y < f && (y = f), y >= m) return;
|
|
110
|
-
const T = Math.cos(s.angle), w = Math.sin(s.angle), b = n.x + T * m, l = n.y + w * m, h = n.x + T * y, u = n.y + w * y, d = -w, v = T, g = s.width / 2;
|
|
111
|
-
t.beginPath(), t.moveTo(b + d * g, l + v * g), t.lineTo(b - d * g, l - v * g), t.lineTo(h, u), t.closePath(), t.fillStyle = o, t.globalAlpha = x, t.fill(), t.globalAlpha = 1;
|
|
112
|
-
}, X = ({
|
|
96
|
+
opacity: 0.6 + Math.random() * 0.4
|
|
97
|
+
})), N = (t, r, n, a, i, o, e, s) => {
|
|
98
|
+
const M = a * (i / 100), d = r.opacity, h = z(n, r.angle, e, s) + 2;
|
|
99
|
+
let l = h - h * r.length;
|
|
100
|
+
if (l < M && (l = M), l >= h) return;
|
|
101
|
+
const p = Math.cos(r.angle), x = Math.sin(r.angle), w = n.x + p * h, R = n.y + x * h, T = n.x + p * l, F = n.y + x * l, b = -x, v = p, c = r.width / 2;
|
|
102
|
+
t.beginPath(), t.moveTo(w + b * c, R + v * c), t.lineTo(w - b * c, R - v * c), t.lineTo(T, F), t.closePath(), t.fillStyle = o, t.globalAlpha = d, t.fill(), t.globalAlpha = 1;
|
|
103
|
+
}, B = ({
|
|
113
104
|
center: t = { x: 50, y: 50 },
|
|
114
|
-
lineCount:
|
|
105
|
+
lineCount: r = 60,
|
|
115
106
|
color: n = "rgba(0, 0, 0, 0.6)",
|
|
116
|
-
minLength: a =
|
|
117
|
-
//
|
|
118
|
-
maxLength: i =
|
|
119
|
-
//
|
|
107
|
+
minLength: a = 30,
|
|
108
|
+
// Increased from 10
|
|
109
|
+
maxLength: i = 60,
|
|
110
|
+
// Increased from 30
|
|
120
111
|
innerRadius: o = 0,
|
|
121
|
-
// Percentage
|
|
122
112
|
animated: e = !1,
|
|
123
|
-
animationSpeed:
|
|
124
|
-
className:
|
|
125
|
-
style:
|
|
113
|
+
animationSpeed: s = 1,
|
|
114
|
+
className: M = "",
|
|
115
|
+
style: d
|
|
126
116
|
}) => {
|
|
127
|
-
const
|
|
117
|
+
const u = y(null), h = y(null), l = y([]), p = y(0), x = y(t), w = y(0), R = y(0), [T, F] = W.useState({ width: 0, height: 0 });
|
|
128
118
|
A(() => {
|
|
129
|
-
|
|
119
|
+
x.current = t;
|
|
130
120
|
}, [t.x, t.y]), A(() => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
const c = e ? 3 : 1;
|
|
122
|
+
l.current = Array.from(
|
|
123
|
+
{ length: c },
|
|
124
|
+
() => C({
|
|
125
|
+
lineCount: r,
|
|
126
|
+
minLength: a,
|
|
127
|
+
maxLength: i
|
|
128
|
+
})
|
|
129
|
+
), p.current = 0;
|
|
130
|
+
}, [r, a, i, e]);
|
|
131
|
+
const b = P(() => {
|
|
132
|
+
if (h.current && u.current) {
|
|
133
|
+
const { clientWidth: c, clientHeight: f } = h.current, m = window.devicePixelRatio || 1;
|
|
134
|
+
if (u.current.width !== c * m || u.current.height !== f * m) {
|
|
135
|
+
u.current.width = c * m, u.current.height = f * m, u.current.style.width = `${c}px`, u.current.style.height = `${f}px`;
|
|
136
|
+
const g = u.current.getContext("2d");
|
|
137
|
+
g && g.scale(m, m), F({ width: c, height: f });
|
|
144
138
|
}
|
|
145
139
|
}
|
|
146
140
|
}, []);
|
|
147
141
|
A(() => {
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
return
|
|
151
|
-
}, [
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
x:
|
|
158
|
-
y:
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
142
|
+
b();
|
|
143
|
+
const c = new ResizeObserver(b);
|
|
144
|
+
return h.current && c.observe(h.current), () => c.disconnect();
|
|
145
|
+
}, [b]);
|
|
146
|
+
const v = P((c) => {
|
|
147
|
+
const f = u.current, m = h.current, g = f == null ? void 0 : f.getContext("2d");
|
|
148
|
+
if (!f || !m || !g || T.width === 0) return;
|
|
149
|
+
g.save(), g.setTransform(1, 0, 0, 1, 0, 0), g.clearRect(0, 0, f.width, f.height), g.restore();
|
|
150
|
+
const $ = m.clientWidth, k = m.clientHeight, E = {
|
|
151
|
+
x: x.current.x / 100 * $,
|
|
152
|
+
y: x.current.y / 100 * k
|
|
153
|
+
}, S = Math.max(E.x, $ - E.x), L = Math.max(E.y, k - E.y), O = Math.hypot(S, L);
|
|
154
|
+
if (e) {
|
|
155
|
+
w.current += c;
|
|
156
|
+
const D = 80 / Math.max(0.1, s);
|
|
157
|
+
w.current - R.current > D && (p.current = (p.current + 1) % l.current.length, R.current = w.current);
|
|
158
|
+
}
|
|
159
|
+
(l.current[p.current] || []).forEach((D) => {
|
|
160
|
+
N(
|
|
161
|
+
g,
|
|
163
162
|
D,
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
E,
|
|
164
|
+
O,
|
|
166
165
|
o,
|
|
167
166
|
n,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
m.current,
|
|
171
|
-
e,
|
|
172
|
-
r
|
|
167
|
+
$,
|
|
168
|
+
k
|
|
173
169
|
);
|
|
174
170
|
});
|
|
175
|
-
}, [n, o, e,
|
|
176
|
-
return
|
|
177
|
-
e ||
|
|
178
|
-
}, [
|
|
171
|
+
}, [n, o, e, s, T]);
|
|
172
|
+
return q(v, e), A(() => {
|
|
173
|
+
e || v(0);
|
|
174
|
+
}, [v, e, r, a, i, o, n, T]), /* @__PURE__ */ I(
|
|
179
175
|
"div",
|
|
180
176
|
{
|
|
181
|
-
ref:
|
|
182
|
-
className: `speed-lines-container ${
|
|
177
|
+
ref: h,
|
|
178
|
+
className: `speed-lines-container ${M}`,
|
|
183
179
|
style: {
|
|
184
180
|
width: "100%",
|
|
185
181
|
height: "100%",
|
|
186
182
|
position: "relative",
|
|
187
183
|
overflow: "hidden",
|
|
188
184
|
pointerEvents: "none",
|
|
189
|
-
...
|
|
185
|
+
...d
|
|
190
186
|
},
|
|
191
|
-
children: /* @__PURE__ */
|
|
187
|
+
children: /* @__PURE__ */ I(
|
|
192
188
|
"canvas",
|
|
193
189
|
{
|
|
194
|
-
ref:
|
|
190
|
+
ref: u,
|
|
195
191
|
style: { width: "100%", height: "100%", display: "block" }
|
|
196
192
|
}
|
|
197
193
|
)
|
|
@@ -199,7 +195,7 @@ const H = ({ text: t = "Manga Effect Placeholder" }) => /* @__PURE__ */ S("div",
|
|
|
199
195
|
);
|
|
200
196
|
};
|
|
201
197
|
export {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
198
|
+
Y as IrisWipe,
|
|
199
|
+
X as Placeholder,
|
|
200
|
+
B as SpeedLines
|
|
205
201
|
};
|
package/package.json
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "react-manga-effects",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Lightweight React components for manga/anime-style visual effects - iris wipes and speed lines",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"react",
|
|
7
|
-
"manga",
|
|
8
|
-
"anime",
|
|
9
|
-
"effects",
|
|
10
|
-
"iris-wipe",
|
|
11
|
-
"speed-lines",
|
|
12
|
-
"focus-lines",
|
|
13
|
-
"transition",
|
|
14
|
-
"animation",
|
|
15
|
-
"typescript"
|
|
16
|
-
],
|
|
17
|
-
"homepage": "https://github.com/erutobusiness/react-manga-effects#readme",
|
|
18
|
-
"bugs": {
|
|
19
|
-
"url": "https://github.com/erutobusiness/react-manga-effects/issues"
|
|
20
|
-
},
|
|
21
|
-
"repository": {
|
|
22
|
-
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/erutobusiness/react-manga-effects.git"
|
|
24
|
-
},
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"author": "erutobusiness",
|
|
27
|
-
"sideEffects": [
|
|
28
|
-
"*.css"
|
|
29
|
-
],
|
|
30
|
-
"files": [
|
|
31
|
-
"dist",
|
|
32
|
-
"README.md",
|
|
33
|
-
"LICENSE"
|
|
34
|
-
],
|
|
35
|
-
"type": "module",
|
|
36
|
-
"main": "./dist/index.cjs",
|
|
37
|
-
"module": "./dist/index.js",
|
|
38
|
-
"types": "./dist/index.d.ts",
|
|
39
|
-
"exports": {
|
|
40
|
-
".": {
|
|
41
|
-
"import": "./dist/index.js",
|
|
42
|
-
"require": "./dist/index.cjs",
|
|
43
|
-
"types": "./dist/index.d.ts"
|
|
44
|
-
},
|
|
45
|
-
"./style.css": "./dist/style.css"
|
|
46
|
-
},
|
|
47
|
-
"scripts": {
|
|
48
|
-
"dev": "vite",
|
|
49
|
-
"build": "tsc && vite build",
|
|
50
|
-
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
51
|
-
"preview": "vite preview",
|
|
52
|
-
"storybook": "storybook dev -p 6006",
|
|
53
|
-
"build-storybook": "storybook build",
|
|
54
|
-
"test": "vitest"
|
|
55
|
-
},
|
|
56
|
-
"peerDependencies": {
|
|
57
|
-
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
58
|
-
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
59
|
-
},
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@storybook/react": "^8.0.0",
|
|
62
|
-
"@storybook/react-vite": "^8.0.0",
|
|
63
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
64
|
-
"@testing-library/react": "^16.3.1",
|
|
65
|
-
"@types/node": "^20.0.0",
|
|
66
|
-
"@types/react": "^18.2.0",
|
|
67
|
-
"@types/react-dom": "^18.2.0",
|
|
68
|
-
"@vitejs/plugin-react": "^4.2.0",
|
|
69
|
-
"jsdom": "^27.0.1",
|
|
70
|
-
"react": "^18.2.0",
|
|
71
|
-
"react-dom": "^18.2.0",
|
|
72
|
-
"storybook": "^8.0.0",
|
|
73
|
-
"typescript": "^5.2.0",
|
|
74
|
-
"vite": "^5.0.0",
|
|
75
|
-
"vite-plugin-dts": "^3.0.0",
|
|
76
|
-
"vitest": "^1.0.0"
|
|
77
|
-
}
|
|
78
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "react-manga-effects",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Lightweight React components for manga/anime-style visual effects - iris wipes and speed lines",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"manga",
|
|
8
|
+
"anime",
|
|
9
|
+
"effects",
|
|
10
|
+
"iris-wipe",
|
|
11
|
+
"speed-lines",
|
|
12
|
+
"focus-lines",
|
|
13
|
+
"transition",
|
|
14
|
+
"animation",
|
|
15
|
+
"typescript"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/erutobusiness/react-manga-effects#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/erutobusiness/react-manga-effects/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/erutobusiness/react-manga-effects.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "erutobusiness",
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"*.css"
|
|
29
|
+
],
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"type": "module",
|
|
36
|
+
"main": "./dist/index.cjs",
|
|
37
|
+
"module": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"import": "./dist/index.js",
|
|
42
|
+
"require": "./dist/index.cjs",
|
|
43
|
+
"types": "./dist/index.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./style.css": "./dist/style.css"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"dev": "vite",
|
|
49
|
+
"build": "tsc && vite build",
|
|
50
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
51
|
+
"preview": "vite preview",
|
|
52
|
+
"storybook": "storybook dev -p 6006",
|
|
53
|
+
"build-storybook": "storybook build",
|
|
54
|
+
"test": "vitest"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
58
|
+
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@storybook/react": "^8.0.0",
|
|
62
|
+
"@storybook/react-vite": "^8.0.0",
|
|
63
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
64
|
+
"@testing-library/react": "^16.3.1",
|
|
65
|
+
"@types/node": "^20.0.0",
|
|
66
|
+
"@types/react": "^18.2.0",
|
|
67
|
+
"@types/react-dom": "^18.2.0",
|
|
68
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
69
|
+
"jsdom": "^27.0.1",
|
|
70
|
+
"react": "^18.2.0",
|
|
71
|
+
"react-dom": "^18.2.0",
|
|
72
|
+
"storybook": "^8.0.0",
|
|
73
|
+
"typescript": "^5.2.0",
|
|
74
|
+
"vite": "^5.0.0",
|
|
75
|
+
"vite-plugin-dts": "^3.0.0",
|
|
76
|
+
"vitest": "^1.0.0"
|
|
77
|
+
}
|
|
78
|
+
}
|