@stianlarsen/react-light-beam 2.1.2 → 3.1.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/LICENSE +0 -0
- package/README.md +24 -224
- package/dist/index.cjs +148 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +13 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +2 -13
- package/dist/index.js +80 -183
- package/dist/index.js.map +1 -1
- package/package.json +31 -29
- package/dist/index.d.mts +0 -28
- package/dist/index.mjs +0 -221
- package/dist/index.mjs.map +0 -1
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -1,150 +1,56 @@
|
|
|
1
1
|
# @stianlarsen/react-light-beam
|
|
2
2
|
|
|
3
|
-
## 🚀
|
|
3
|
+
## 🚀 New Feature Alert!
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- ⚡️ **40% faster** scroll performance
|
|
7
|
-
- 🎯 **Pixel-perfect scrubbing** in both directions
|
|
8
|
-
- 🔄 **Smoother animations** on all devices
|
|
9
|
-
- 📦 **Lighter bundle** with tree-shaking
|
|
10
|
-
- 🎨 **Live prop updates** without recreation
|
|
5
|
+
We've added a new prop: `scrollElement`. This allows you to specify which element should have the scroll listener attached, giving you greater flexibility in using the LightBeam component!
|
|
11
6
|
|
|
12
7
|
[](https://badge.fury.io/js/%40stianlarsen%2Freact-light-beam)
|
|
13
|
-
[](https://your-demo-url.vercel.app)
|
|
14
8
|
|
|
15
|
-
A
|
|
16
|
-
|
|
17
|
-
## ✨ Key Features
|
|
18
|
-
|
|
19
|
-
- 🚀 **Powered by GSAP** - Industry-leading animation performance
|
|
20
|
-
- 📜 **Scroll-driven** - Smooth scrubbing with GSAP ScrollTrigger
|
|
21
|
-
- 🌓 **Dark mode ready** - Auto-detects system preferences
|
|
22
|
-
- ⚙️ **Highly customizable** - Control width, colors, direction, and more
|
|
23
|
-
- 🎯 **Zero config** - Works out of the box with sensible defaults
|
|
24
|
-
- 💪 **TypeScript** - Full type definitions included
|
|
25
|
-
- 📦 **Tree-shakeable** - Optimized bundle size
|
|
9
|
+
A customizable React component that creates a light beam effect using conic gradients. The component is fully responsive and supports both light and dark modes. Ideal for adding dynamic and engaging visual elements to your web applications.
|
|
26
10
|
|
|
27
11
|
## Preview
|
|
28
12
|
|
|
29
|
-

|
|
30
14
|
|
|
31
15
|
_A preview of @stianlarsen/react-light-beam_
|
|
32
16
|
|
|
33
17
|
## Installation
|
|
34
18
|
|
|
35
19
|
```bash
|
|
36
|
-
npm install @stianlarsen/react-light-beam
|
|
20
|
+
npm install @stianlarsen/react-light-beam
|
|
37
21
|
```
|
|
38
22
|
|
|
39
|
-
|
|
23
|
+
or
|
|
40
24
|
|
|
41
25
|
```bash
|
|
42
|
-
|
|
26
|
+
yarn add @stianlarsen/react-light-beam
|
|
43
27
|
```
|
|
44
28
|
|
|
45
29
|
## Usage
|
|
46
30
|
|
|
47
|
-
### Basic Usage (Works Immediately - No CSS Import!)
|
|
48
|
-
|
|
49
|
-
The component works out of the box with default inline styles:
|
|
50
|
-
|
|
51
31
|
```jsx
|
|
52
32
|
import { LightBeam } from "@stianlarsen/react-light-beam";
|
|
33
|
+
import "your-css-file.css"; // Include the necessary styles
|
|
53
34
|
|
|
54
35
|
const App = () => {
|
|
55
36
|
return (
|
|
56
37
|
<div className="your-container-class">
|
|
57
38
|
<LightBeam
|
|
39
|
+
id="unique-lightbeam"
|
|
40
|
+
className="your-lightbeam-class"
|
|
58
41
|
colorDarkmode="rgba(255, 255, 255, 0.8)"
|
|
59
42
|
colorLightmode="rgba(0, 0, 0, 0.2)"
|
|
60
43
|
fullWidth={0.8}
|
|
61
44
|
maskLightByProgress={true}
|
|
62
|
-
|
|
45
|
+
invert={false}
|
|
46
|
+
scrollElement={window} // New prop to specify scroll element
|
|
63
47
|
/>
|
|
64
48
|
<YourContentHere />
|
|
65
49
|
</div>
|
|
66
50
|
);
|
|
67
51
|
};
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Customizing Styles (Multiple Options!)
|
|
71
|
-
|
|
72
|
-
#### Option 1: CSS Variables via className (Recommended!)
|
|
73
|
-
|
|
74
|
-
Override default styles using CSS variables - works with className!
|
|
75
|
-
|
|
76
|
-
```jsx
|
|
77
|
-
import { LightBeam } from "@stianlarsen/react-light-beam";
|
|
78
|
-
|
|
79
|
-
const App = () => {
|
|
80
|
-
return (
|
|
81
|
-
<LightBeam
|
|
82
|
-
className="custom-beam"
|
|
83
|
-
colorDarkmode="rgba(255, 255, 255, 0.8)"
|
|
84
|
-
/>
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Then in your CSS:
|
|
90
52
|
|
|
91
|
-
|
|
92
|
-
.custom-beam {
|
|
93
|
-
--react-light-beam-height: 800px;
|
|
94
|
-
--react-light-beam-width: 80vw;
|
|
95
|
-
/* Note: GSAP controls animations - transitions may not work as expected */
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**Available CSS Variables:**
|
|
100
|
-
- `--react-light-beam-height` (default: `500px`)
|
|
101
|
-
- `--react-light-beam-width` (default: `100vw`)
|
|
102
|
-
|
|
103
|
-
**Note:** CSS transitions are disabled by default to prevent conflicts with GSAP. GSAP handles all animations for optimal performance.
|
|
104
|
-
|
|
105
|
-
#### Option 2: Inline Styles via `style` prop
|
|
106
|
-
|
|
107
|
-
```jsx
|
|
108
|
-
<LightBeam
|
|
109
|
-
style={{
|
|
110
|
-
height: '800px',
|
|
111
|
-
width: '80vw',
|
|
112
|
-
marginTop: '-200px'
|
|
113
|
-
}}
|
|
114
|
-
colorDarkmode="rgba(255, 255, 255, 0.8)"
|
|
115
|
-
/>
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Advanced: Full CSS Control (className only)
|
|
119
|
-
|
|
120
|
-
For complete control via CSS, disable default inline styles:
|
|
121
|
-
|
|
122
|
-
```jsx
|
|
123
|
-
import { LightBeam } from "@stianlarsen/react-light-beam";
|
|
124
|
-
|
|
125
|
-
const App = () => {
|
|
126
|
-
return (
|
|
127
|
-
<LightBeam
|
|
128
|
-
disableDefaultStyles={true} // Disable all inline styles
|
|
129
|
-
className="my-custom-lightbeam"
|
|
130
|
-
colorDarkmode="rgba(255, 255, 255, 0.8)"
|
|
131
|
-
/>
|
|
132
|
-
);
|
|
133
|
-
};
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Then provide all styles via CSS:
|
|
137
|
-
|
|
138
|
-
```css
|
|
139
|
-
.my-custom-lightbeam {
|
|
140
|
-
height: 800px;
|
|
141
|
-
width: 100%;
|
|
142
|
-
position: absolute;
|
|
143
|
-
transition: all 0.3s ease;
|
|
144
|
-
user-select: none;
|
|
145
|
-
pointer-events: none;
|
|
146
|
-
/* Full control - you provide all styles */
|
|
147
|
-
}
|
|
53
|
+
export default App;
|
|
148
54
|
```
|
|
149
55
|
|
|
150
56
|
### Props
|
|
@@ -153,37 +59,28 @@ Then provide all styles via CSS:
|
|
|
153
59
|
| --------------------- | ---------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
154
60
|
| `id` | `string` | `undefined` | Optional string representing a unique ID for the LightBeam container. |
|
|
155
61
|
| `className` | `string` | `undefined` | Optional string representing custom classes to be added to the LightBeam container. |
|
|
156
|
-
| `style` | `React.CSSProperties` | `undefined` | Custom inline styles to merge with or override default styles. User styles take priority. Example: `style={{ height: '800px', width: '80vw' }}` |
|
|
157
62
|
| `colorLightmode` | `string` | `rgba(0,0,0, 0.5)` | Optional string representing the color of the light beam in light mode. |
|
|
158
63
|
| `colorDarkmode` | `string` | `rgba(255, 255, 255, 0.5)` | Optional string representing the color of the light beam in dark mode. |
|
|
159
64
|
| `fullWidth` | `number` | `1.0` | Optional number between `0` and `1` representing the maximum width the light beam can reach. |
|
|
160
65
|
| `maskLightByProgress` | `boolean` | `false` | If `true`, the `mask-image`'s linear gradient will start with the chosen color at 0% and the transparent part starting at 50%. As the user scrolls, it will dynamically change to have the transparent part at 95%, reducing the glow effect. If `false`, it will default to `linear-gradient(to bottom, chosenColor 25%, transparent 95%)`. |
|
|
161
66
|
| `invert` | `boolean` | `false` | Optional boolean to invert the scroll progress calculation. |
|
|
162
|
-
| `scrollElement` | `EventTarget` or `undefined` | `
|
|
67
|
+
| `scrollElement` | `EventTarget` or `undefined` | `window` | Optional prop for which element to attach the scroll listener to. This could be the `window`, `document.body`, or any other scrollable element. |
|
|
163
68
|
| `onLoaded` | `undefined or () => void` | `undefined` | Optional function to run when the component has mounted |
|
|
164
|
-
| `disableDefaultStyles` | `boolean` | `false` | Disable default inline styles. Set to `true` if you want to provide all styles yourself via className. Gives you complete CSS control without any default styling. |
|
|
165
69
|
|
|
166
70
|
### Default Configuration
|
|
167
71
|
|
|
168
|
-
The component
|
|
169
|
-
|
|
170
|
-
```
|
|
171
|
-
{
|
|
172
|
-
height:
|
|
173
|
-
width:
|
|
174
|
-
transition:
|
|
175
|
-
|
|
176
|
-
userSelect: "none",
|
|
177
|
-
pointerEvents: "none",
|
|
178
|
-
contain: "layout style paint" // Performance optimization
|
|
72
|
+
The component comes with the following default styles:
|
|
73
|
+
|
|
74
|
+
```css
|
|
75
|
+
.react__light__beam {
|
|
76
|
+
height: 500px;
|
|
77
|
+
width: 100vw;
|
|
78
|
+
transition: all 0.5s ease;
|
|
79
|
+
will-change: auto;
|
|
179
80
|
}
|
|
180
81
|
```
|
|
181
82
|
|
|
182
|
-
|
|
183
|
-
- ✅ Works immediately out of the box
|
|
184
|
-
- ✅ Easy to customize via className (just set CSS variables!)
|
|
185
|
-
- ✅ No CSS import required for basic usage
|
|
186
|
-
- ✅ Inline styles use CSS variables, so className overrides work perfectly
|
|
83
|
+
These default styles ensure that the component is immediately visible when added to your application. However, for more effective use, you might want to customize its position and behavior.
|
|
187
84
|
|
|
188
85
|
### Recommended Usage
|
|
189
86
|
|
|
@@ -234,103 +131,6 @@ The component automatically adjusts between light and dark modes based on the us
|
|
|
234
131
|
/>
|
|
235
132
|
```
|
|
236
133
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
The example Next.js app in `/example` can be easily deployed to Vercel, Netlify, or GitHub Pages:
|
|
240
|
-
|
|
241
|
-
### Quick Deploy to Vercel (Recommended - 2 minutes)
|
|
242
|
-
|
|
243
|
-
1. Push your code to GitHub
|
|
244
|
-
2. Go to [vercel.com](https://vercel.com)
|
|
245
|
-
3. Click "Add New Project"
|
|
246
|
-
4. Import your GitHub repository
|
|
247
|
-
5. Set **Root Directory** to `example`
|
|
248
|
-
6. Click "Deploy"
|
|
249
|
-
|
|
250
|
-
Done! You'll get a live URL like `https://your-project.vercel.app`
|
|
251
|
-
|
|
252
|
-
### Alternative: GitHub Pages with GitHub Actions
|
|
253
|
-
|
|
254
|
-
1. Add `.github/workflows/deploy.yml`:
|
|
255
|
-
|
|
256
|
-
```yaml
|
|
257
|
-
name: Deploy to GitHub Pages
|
|
258
|
-
|
|
259
|
-
on:
|
|
260
|
-
push:
|
|
261
|
-
branches: [ main ]
|
|
262
|
-
|
|
263
|
-
jobs:
|
|
264
|
-
deploy:
|
|
265
|
-
runs-on: ubuntu-latest
|
|
266
|
-
steps:
|
|
267
|
-
- uses: actions/checkout@v3
|
|
268
|
-
- uses: actions/setup-node@v3
|
|
269
|
-
with:
|
|
270
|
-
node-version: 18
|
|
271
|
-
- run: cd example && npm install && npm run build
|
|
272
|
-
- uses: peaceiris/actions-gh-pages@v3
|
|
273
|
-
with:
|
|
274
|
-
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
275
|
-
publish_dir: ./example/out
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
2. In `example/next.config.js`, add:
|
|
279
|
-
```js
|
|
280
|
-
/** @type {import('next').NextConfig} */
|
|
281
|
-
const nextConfig = {
|
|
282
|
-
output: 'export',
|
|
283
|
-
basePath: '/react-light-beam', // Your repo name
|
|
284
|
-
}
|
|
285
|
-
module.exports = nextConfig
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
3. Push to GitHub - your site will auto-deploy to `https://yourusername.github.io/react-light-beam`
|
|
289
|
-
|
|
290
|
-
### Alternative: Netlify
|
|
291
|
-
|
|
292
|
-
1. Go to [netlify.com](https://netlify.com)
|
|
293
|
-
2. Drag and drop your `/example` folder
|
|
294
|
-
3. Or connect to GitHub and set base directory to `example`
|
|
295
|
-
|
|
296
|
-
## 🤝 Contributing
|
|
297
|
-
|
|
298
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
299
|
-
|
|
300
|
-
### Development Setup
|
|
301
|
-
|
|
302
|
-
```bash
|
|
303
|
-
# Clone the repo
|
|
304
|
-
git clone https://github.com/stianalars1/react-light-beam.git
|
|
305
|
-
cd react-light-beam
|
|
306
|
-
|
|
307
|
-
# Install dependencies
|
|
308
|
-
npm install
|
|
309
|
-
|
|
310
|
-
# Build the package
|
|
311
|
-
npm run build
|
|
312
|
-
|
|
313
|
-
# Run the example
|
|
314
|
-
cd example
|
|
315
|
-
npm install
|
|
316
|
-
npm run dev
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
## 📝 Changelog
|
|
320
|
-
|
|
321
|
-
### v2.0.0 (2026-01-04)
|
|
322
|
-
- 🚀 **BREAKING:** Migrated from Framer Motion to GSAP ScrollTrigger
|
|
323
|
-
- ⚡️ 40% performance improvement
|
|
324
|
-
- 🐛 Fixed bidirectional scrolling issues
|
|
325
|
-
- 🐛 Fixed invert prop behavior
|
|
326
|
-
- 🐛 Fixed color switching glitches
|
|
327
|
-
- 🎨 Removed CSS transitions to prevent conflicts with GSAP
|
|
328
|
-
- 📦 Added `gsap` as peer dependency
|
|
329
|
-
|
|
330
|
-
## License
|
|
134
|
+
### License
|
|
331
135
|
|
|
332
136
|
MIT © [Stian Larsen](https://github.com/stianlarsen)
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
|
-
**Built with ❤️ using GSAP ScrollTrigger**
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.tsx
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
LightBeam: () => LightBeam
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var import_framer_motion = require("framer-motion");
|
|
28
|
+
var import_react2 = require("react");
|
|
29
|
+
|
|
30
|
+
// src/css/lightBeam.module.css
|
|
31
|
+
var lightBeam_default = {
|
|
32
|
+
react__light__beam: "lightBeam_react__light__beam"
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/hooks/useDarkmode.tsx
|
|
36
|
+
var import_react = require("react");
|
|
37
|
+
var useIsDarkmode = () => {
|
|
38
|
+
const [isDarkmode, setIsDarkmodeActive] = (0, import_react.useState)(false);
|
|
39
|
+
(0, import_react.useEffect)(() => {
|
|
40
|
+
const matchMedia = window.matchMedia("(prefers-color-scheme: dark)");
|
|
41
|
+
const handleChange = () => {
|
|
42
|
+
setIsDarkmodeActive(matchMedia.matches);
|
|
43
|
+
};
|
|
44
|
+
setIsDarkmodeActive(matchMedia.matches);
|
|
45
|
+
matchMedia.addEventListener("change", handleChange);
|
|
46
|
+
return () => {
|
|
47
|
+
matchMedia.removeEventListener("change", handleChange);
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
50
|
+
return { isDarkmode };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/index.tsx
|
|
54
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
55
|
+
var LightBeam = ({
|
|
56
|
+
className,
|
|
57
|
+
colorLightmode = "rgba(0,0,0, 0.5)",
|
|
58
|
+
colorDarkmode = "rgba(255, 255, 255, 0.5)",
|
|
59
|
+
maskLightByProgress = false,
|
|
60
|
+
fullWidth = 1,
|
|
61
|
+
// Default to full width
|
|
62
|
+
invert = false,
|
|
63
|
+
id = void 0,
|
|
64
|
+
onLoaded = void 0,
|
|
65
|
+
scrollElement
|
|
66
|
+
// Add this line
|
|
67
|
+
}) => {
|
|
68
|
+
const elementRef = (0, import_react2.useRef)(null);
|
|
69
|
+
const inViewProgress = (0, import_framer_motion.useMotionValue)(0);
|
|
70
|
+
const opacity = (0, import_framer_motion.useMotionValue)(0.839322);
|
|
71
|
+
const { isDarkmode } = useIsDarkmode();
|
|
72
|
+
const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;
|
|
73
|
+
(0, import_react2.useEffect)(() => {
|
|
74
|
+
onLoaded && onLoaded();
|
|
75
|
+
}, []);
|
|
76
|
+
(0, import_react2.useEffect)(() => {
|
|
77
|
+
if (typeof window !== "undefined") {
|
|
78
|
+
const handleScroll = () => {
|
|
79
|
+
if (elementRef.current) {
|
|
80
|
+
const rect = elementRef.current.getBoundingClientRect();
|
|
81
|
+
const windowHeight = window.innerHeight;
|
|
82
|
+
const adjustedFullWidth = 1 - fullWidth;
|
|
83
|
+
const progress = invert ? 0 + Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight)) : 1 - Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight));
|
|
84
|
+
inViewProgress.set(progress);
|
|
85
|
+
opacity.set(0.839322 + (1 - 0.839322) * progress);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const handleScrollThrottled = throttle(handleScroll);
|
|
89
|
+
const target = scrollElement || window;
|
|
90
|
+
target.addEventListener("scroll", handleScrollThrottled);
|
|
91
|
+
window.addEventListener("resize", handleScrollThrottled);
|
|
92
|
+
handleScroll();
|
|
93
|
+
return () => {
|
|
94
|
+
target.removeEventListener("scroll", handleScrollThrottled);
|
|
95
|
+
window.removeEventListener("resize", handleScrollThrottled);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}, [inViewProgress, opacity, scrollElement]);
|
|
99
|
+
const backgroundPosition = (0, import_framer_motion.useTransform)(
|
|
100
|
+
inViewProgress,
|
|
101
|
+
[0, 1],
|
|
102
|
+
[
|
|
103
|
+
`conic-gradient(from 90deg at 90% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 150% no-repeat, conic-gradient(from 270deg at 10% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,
|
|
104
|
+
`conic-gradient(from 90deg at 0% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 100% no-repeat, conic-gradient(from 270deg at 100% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`
|
|
105
|
+
]
|
|
106
|
+
);
|
|
107
|
+
const maskImageOpacity = (0, import_framer_motion.useTransform)(
|
|
108
|
+
inViewProgress,
|
|
109
|
+
[0, 1],
|
|
110
|
+
[
|
|
111
|
+
`linear-gradient(to bottom, ${chosenColor} 0%, transparent 50%)`,
|
|
112
|
+
`linear-gradient(to bottom, ${chosenColor} 0%, transparent 95%)`
|
|
113
|
+
]
|
|
114
|
+
);
|
|
115
|
+
const maskImage = maskLightByProgress ? maskImageOpacity : `linear-gradient(to bottom, ${chosenColor} 25%, transparent 95%)`;
|
|
116
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
117
|
+
import_framer_motion.motion.div,
|
|
118
|
+
{
|
|
119
|
+
style: {
|
|
120
|
+
background: backgroundPosition,
|
|
121
|
+
opacity,
|
|
122
|
+
maskImage,
|
|
123
|
+
WebkitMaskImage: maskImage,
|
|
124
|
+
willChange: "background, opacity"
|
|
125
|
+
},
|
|
126
|
+
ref: elementRef,
|
|
127
|
+
id,
|
|
128
|
+
className: `lightBeam ${className} ${lightBeam_default.react__light__beam}`
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
var throttle = (func) => {
|
|
133
|
+
let ticking = false;
|
|
134
|
+
return function(...args) {
|
|
135
|
+
if (!ticking) {
|
|
136
|
+
requestAnimationFrame(() => {
|
|
137
|
+
func.apply(this, args);
|
|
138
|
+
ticking = false;
|
|
139
|
+
});
|
|
140
|
+
ticking = true;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
145
|
+
0 && (module.exports = {
|
|
146
|
+
LightBeam
|
|
147
|
+
});
|
|
148
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx","../src/css/lightBeam.module.css","../src/hooks/useDarkmode.tsx"],"sourcesContent":["\"use client\";\nimport { motion, useMotionValue, useTransform } from \"framer-motion\";\nimport React, { useEffect, useRef } from \"react\";\nimport { LightBeamProps } from \"../types/types\";\nexport type { LightBeamProps } from \"../types/types\";\nimport styles from \"./css/lightBeam.module.css\";\nimport { useIsDarkmode } from \"./hooks/useDarkmode\";\n\nexport const LightBeam = ({\n className,\n colorLightmode = \"rgba(0,0,0, 0.5)\",\n colorDarkmode = \"rgba(255, 255, 255, 0.5)\",\n maskLightByProgress = false,\n fullWidth = 1.0, // Default to full width\n invert = false,\n id = undefined,\n onLoaded = undefined,\n scrollElement, // Add this line\n}: LightBeamProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const inViewProgress = useMotionValue(0);\n const opacity = useMotionValue(0.839322);\n const { isDarkmode } = useIsDarkmode();\n const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;\n\n useEffect(() => {\n onLoaded && onLoaded();\n }, []);\n\n useEffect(() => {\n if (typeof window !== \"undefined\") {\n const handleScroll = () => {\n if (elementRef.current) {\n const rect = elementRef.current.getBoundingClientRect();\n const windowHeight = window.innerHeight;\n\n // Invert the fullWidth value: 1 becomes 0, and 0 becomes 1\n const adjustedFullWidth = 1 - fullWidth;\n\n // Calculate progress\n const progress = invert\n ? 0 +\n Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight))\n : 1 -\n Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight));\n\n // Update motion values\n inViewProgress.set(progress);\n opacity.set(0.839322 + (1 - 0.839322) * progress);\n }\n };\n\n const handleScrollThrottled = throttle(handleScroll); // Approx 60fps\n\n const target = scrollElement || window;\n\n target.addEventListener(\"scroll\", handleScrollThrottled);\n window.addEventListener(\"resize\", handleScrollThrottled);\n\n // Initial call to handleScroll to set initial state\n handleScroll();\n\n return () => {\n target.removeEventListener(\"scroll\", handleScrollThrottled);\n window.removeEventListener(\"resize\", handleScrollThrottled);\n };\n }\n }, [inViewProgress, opacity, scrollElement]);\n\n const backgroundPosition = useTransform(\n inViewProgress,\n [0, 1],\n [\n `conic-gradient(from 90deg at 90% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 150% no-repeat, conic-gradient(from 270deg at 10% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,\n `conic-gradient(from 90deg at 0% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 100% no-repeat, conic-gradient(from 270deg at 100% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,\n ]\n );\n const maskImageOpacity = useTransform(\n inViewProgress,\n [0, 1],\n [\n `linear-gradient(to bottom, ${chosenColor} 0%, transparent 50%)`,\n `linear-gradient(to bottom, ${chosenColor} 0%, transparent 95%)`,\n ]\n );\n\n const maskImage = maskLightByProgress\n ? maskImageOpacity\n : `linear-gradient(to bottom, ${chosenColor} 25%, transparent 95%)`;\n\n return (\n <motion.div\n style={{\n background: backgroundPosition,\n opacity: opacity,\n maskImage: maskImage,\n WebkitMaskImage: maskImage,\n willChange: \"background, opacity\",\n }}\n ref={elementRef}\n id={id}\n className={`lightBeam ${className} ${styles.react__light__beam}`}\n />\n );\n};\n\nconst throttle = (func: Function) => {\n let ticking = false;\n return function (this: any, ...args: any[]) {\n if (!ticking) {\n requestAnimationFrame(() => {\n func.apply(this, args);\n ticking = false;\n });\n ticking = true;\n }\n };\n};\n",".react__light__beam {\n height: 500px;\n width: 100vw;\n transition: all 0.25s ease;\n will-change: all;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n pointer-events: none;\n -webkit-transition: all 0.25s ease;\n}/*# sourceMappingURL=lightBeam.module.css.map */","\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport const useIsDarkmode = () => {\n const [isDarkmode, setIsDarkmodeActive] = useState(false);\n\n useEffect(() => {\n const matchMedia = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n const handleChange = () => {\n setIsDarkmodeActive(matchMedia.matches);\n };\n\n // Set the initial value\n setIsDarkmodeActive(matchMedia.matches);\n\n // Listen for changes\n matchMedia.addEventListener(\"change\", handleChange);\n\n // Cleanup listener on unmount\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange);\n };\n }, []);\n\n return { isDarkmode };\n};\n"],"mappings":"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","names":["import_react","react__light__beam"]}
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* src/css/lightBeam.module.css */
|
|
2
|
+
.lightBeam_react__light__beam {
|
|
3
|
+
height: 500px;
|
|
4
|
+
width: 100vw;
|
|
5
|
+
transition: all 0.25s ease;
|
|
6
|
+
will-change: all;
|
|
7
|
+
-webkit-user-select: none;
|
|
8
|
+
-moz-user-select: none;
|
|
9
|
+
user-select: none;
|
|
10
|
+
pointer-events: none;
|
|
11
|
+
-webkit-transition: all 0.25s ease;
|
|
12
|
+
}
|
|
13
|
+
/*# sourceMappingURL=index.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/css/lightBeam.module.scss","../src/css/lightBeam.module.css"],"sourcesContent":[".react__light__beam {\n height: 500px;\n width: 100vw;\n transition: all 0.25s ease;\n will-change: all;\n user-select: none;\n pointer-events: none;\n -webkit-transition: all 0.25s ease;\n}\n",".react__light__beam {\n height: 500px;\n width: 100vw;\n transition: all 0.25s ease;\n will-change: all;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n pointer-events: none;\n -webkit-transition: all 0.25s ease;\n}/*# sourceMappingURL=lightBeam.module.css.map */"],"mappings":";AAAA,CAAAA;AACE,UAAA;AACA,SAAA;AACA,cAAA,IAAA,MAAA;AACA,eAAA;AACA,uBAAA;AAAA,oBAAA;AAAA,eAAA;AACA,kBAAA;AACA,sBAAA,IAAA,MAAA;ACCF;","names":["react__light__beam"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type LightBeamProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
fullWidth?: number;
|
|
6
|
+
colorLightmode?: string;
|
|
7
|
+
colorDarkmode?: string;
|
|
8
|
+
maskLightByProgress?: boolean;
|
|
9
|
+
invert?: boolean;
|
|
10
|
+
id?: string;
|
|
11
|
+
scrollElement?: EventTarget;
|
|
12
|
+
onLoaded?: () => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
declare const LightBeam: ({ className, colorLightmode, colorDarkmode, maskLightByProgress, fullWidth, invert, id, onLoaded, scrollElement, }: LightBeamProps) => react_jsx_runtime.JSX.Element;
|
|
16
|
+
|
|
17
|
+
export { LightBeam, type LightBeamProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,6 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
|
|
3
3
|
type LightBeamProps = {
|
|
4
4
|
className?: string;
|
|
5
|
-
/**
|
|
6
|
-
* Custom styles to merge with or override default styles.
|
|
7
|
-
* User styles take priority over defaults.
|
|
8
|
-
* @example style={{ height: '800px', width: '80vw' }}
|
|
9
|
-
*/
|
|
10
|
-
style?: React.CSSProperties;
|
|
11
5
|
fullWidth?: number;
|
|
12
6
|
colorLightmode?: string;
|
|
13
7
|
colorDarkmode?: string;
|
|
@@ -16,13 +10,8 @@ type LightBeamProps = {
|
|
|
16
10
|
id?: string;
|
|
17
11
|
scrollElement?: EventTarget;
|
|
18
12
|
onLoaded?: () => void;
|
|
19
|
-
/**
|
|
20
|
-
* Disable default inline styles. Set to true if you want to provide custom CSS via className only.
|
|
21
|
-
* @default false
|
|
22
|
-
*/
|
|
23
|
-
disableDefaultStyles?: boolean;
|
|
24
13
|
};
|
|
25
14
|
|
|
26
|
-
declare const LightBeam: ({ className,
|
|
15
|
+
declare const LightBeam: ({ className, colorLightmode, colorDarkmode, maskLightByProgress, fullWidth, invert, id, onLoaded, scrollElement, }: LightBeamProps) => react_jsx_runtime.JSX.Element;
|
|
27
16
|
|
|
28
|
-
export { LightBeam };
|
|
17
|
+
export { LightBeam, type LightBeamProps };
|