apex-ui-kit 2.10.0 → 2.11.0

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.
@@ -1,235 +1,237 @@
1
- import React, { useEffect, useRef } from "react";
2
- import { Renderer, Program, Mesh, Triangle } from "ogl";
3
-
4
- const hexToRgb = (hex) => {
5
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
6
- if (!result) return [1, 0.5, 0.2];
7
- return [
8
- parseInt(result[1], 16) / 255,
9
- parseInt(result[2], 16) / 255,
10
- parseInt(result[3], 16) / 255,
11
- ];
12
- };
13
-
14
- const vertex = `#version 300 es
15
- precision highp float;
16
- in vec2 position;
17
- in vec2 uv;
18
- out vec2 vUv;
19
- void main() {
20
- vUv = uv;
21
- gl_Position = vec4(position, 0.0, 1.0);
22
- }
23
- `;
24
-
25
- const fragment = `#version 300 es
26
- precision highp float;
27
- uniform vec2 iResolution;
28
- uniform float iTime;
29
- uniform vec3 uCustomColor;
30
- uniform float uUseCustomColor;
31
- uniform float uSpeed;
32
- uniform float uDirection;
33
- uniform float uScale;
34
- uniform float uOpacity;
35
- uniform vec2 uMouse;
36
- uniform float uMouseInteractive;
37
- out vec4 fragColor;
38
-
39
- void mainImage(out vec4 o, vec2 C) {
40
- vec2 center = iResolution.xy * 0.5;
41
- C = (C - center) / uScale + center;
42
-
43
- vec2 mouseOffset = (uMouse - center) * 0.0002;
44
- C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);
45
-
46
- float i, d, z, T = iTime * uSpeed * uDirection;
47
- vec3 O, p, S;
48
-
49
- // loop count reduced to 30
50
- for (vec2 r = iResolution.xy, Q; ++i < 35.; O += o.w/d*o.xyz) {
51
- p = z*normalize(vec3(C-.5*r,r.y));
52
- p.z -= 4.;
53
- S = p;
54
- d = p.y-T;
55
-
56
- p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);
57
- Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));
58
- z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;
59
- o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));
60
- }
61
-
62
- o.xyz = tanh(O/1e4);
63
- }
64
-
65
- bool finite1(float x){ return !(isnan(x) || isinf(x)); }
66
- vec3 sanitize(vec3 c){
67
- return vec3(
68
- finite1(c.r) ? c.r : 0.0,
69
- finite1(c.g) ? c.g : 0.0,
70
- finite1(c.b) ? c.b : 0.0
71
- );
72
- }
73
-
74
- void main() {
75
- vec4 o = vec4(0.0);
76
- mainImage(o, gl_FragCoord.xy);
77
- vec3 rgb = sanitize(o.rgb);
78
-
79
- float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;
80
- vec3 customColor = intensity * uCustomColor;
81
- vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));
82
-
83
- float alpha = length(rgb) * uOpacity;
84
- fragColor = vec4(finalColor, alpha);
85
- }
86
- `;
87
-
88
-
89
- export const SmokeBackground = ({
90
- color = "#00c950",
91
- speed = 1,
92
- direction = "forward",
93
- scale = 1,
94
- opacity = 1,
95
- mouseInteractive = true,
96
- className = "",
97
- maxWidth = 'max-w-4xl',
98
- }) => {
99
- const containerRef = useRef(null);
100
- const mousePos = useRef({ x: 0, y: 0 });
101
-
102
- useEffect(() => {
103
- const mountNode = containerRef.current;
104
- if (!mountNode) return;
105
-
106
- const useCustomColor = color ? 1.0 : 0.0;
107
- const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];
108
-
109
- let directionMultiplier = direction === "backward" ? -1.0 : 1.0;
110
-
111
- // cap DPR depending on mount width to avoid heavy pixel loads on small devices
112
- const mountWidth = mountNode.clientWidth || window.innerWidth;
113
- const dprCap = mountWidth > 1200 ? 1.8 : (mountWidth > 768 ? 1.4 : 1.0);
114
- const dpr = Math.min(window.devicePixelRatio || 1, dprCap);
115
-
116
- const renderer = new Renderer({
117
- webgl: 2,
118
- alpha: true,
119
- antialias: false,
120
- dpr,
121
- });
122
-
123
- const gl = renderer.gl;
124
- const canvas = gl.canvas;
125
- canvas.style.display = "block";
126
- canvas.style.width = "100%";
127
- canvas.style.height = "100%";
128
- mountNode.appendChild(canvas);
129
-
130
- const geometry = new Triangle(gl);
131
-
132
- const program = new Program(gl, {
133
- vertex: vertex,
134
- fragment: fragment,
135
- uniforms: {
136
- iTime: { value: 0 },
137
- iResolution: { value: new Float32Array([1, 1]) },
138
- uCustomColor: { value: new Float32Array(customColorRgb) },
139
- uUseCustomColor: { value: useCustomColor },
140
- uSpeed: { value: speed * 0.4 },
141
- uDirection: { value: directionMultiplier },
142
- uScale: { value: scale },
143
- uOpacity: { value: opacity },
144
- uMouse: { value: new Float32Array([0, 0]) },
145
- uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },
146
- },
147
- });
148
-
149
- const mesh = new Mesh(gl, { geometry, program });
150
-
151
- // 🖱 Mouse throttled to RAF
152
- let mouseNeedsUpdate = false;
153
- const handleMouseMove = (e) => {
154
- if (!mouseInteractive) return;
155
- const rect = mountNode.getBoundingClientRect();
156
- mousePos.current.x = e.clientX - rect.left;
157
- mousePos.current.y = rect.height - (e.clientY - rect.top);
158
- mouseNeedsUpdate = true;
159
- };
160
- if (mouseInteractive) {
161
- mountNode.addEventListener("mousemove", handleMouseMove, { passive: true });
162
- }
163
-
164
- const setSize = () => {
165
- const rect = mountNode.getBoundingClientRect();
166
- const width = Math.max(1, Math.floor(rect.width));
167
- const height = Math.max(1, Math.floor(rect.height));
168
- renderer.setSize(width, height);
169
- const res = program.uniforms.iResolution.value;
170
- res[0] = gl.drawingBufferWidth;
171
- res[1] = gl.drawingBufferHeight;
172
- };
173
-
174
- const ro = new ResizeObserver(() => {
175
- requestAnimationFrame(setSize);
176
- });
177
- ro.observe(mountNode);
178
- setSize();
179
-
180
- let raf = 0;
181
- // Only render while the background is on screen.
182
- let visible = true;
183
- const t0 = performance.now();
184
- const loop = (t) => {
185
- raf = 0;
186
- if (!visible) return;
187
- let timeValue = (t - t0) * 0.001;
188
-
189
- if (direction === "funny") {
190
- const cycle = Math.sin(timeValue * 0.5);
191
- program.uniforms.uDirection.value = cycle;
192
- }
193
-
194
- if (mouseNeedsUpdate) {
195
- const mouseUniform = program.uniforms.uMouse.value;
196
- mouseUniform[0] = mousePos.current.x;
197
- mouseUniform[1] = mousePos.current.y;
198
- mouseNeedsUpdate = false;
199
- }
200
-
201
- program.uniforms.iTime.value = timeValue;
202
- renderer.render({ scene: mesh });
203
- raf = requestAnimationFrame(loop);
204
- };
205
- raf = requestAnimationFrame(loop);
206
- const visibilityObserver = new IntersectionObserver(([entry]) => {
207
- visible = entry.isIntersecting;
208
- if (visible && !raf) raf = requestAnimationFrame(loop);
209
- });
210
- visibilityObserver.observe(mountNode);
211
-
212
- return () => {
213
- cancelAnimationFrame(raf);
214
- visibilityObserver.disconnect();
215
- ro.disconnect();
216
- if (mouseInteractive && mountNode) {
217
- mountNode.removeEventListener("mousemove", handleMouseMove);
218
- }
219
- try {
220
- mountNode.removeChild(canvas);
221
- } catch (error) { console.error("Error removing canvas:", error); }
222
- gl.getExtension("WEBGL_lose_context")?.loseContext();
223
- };
224
- }, [color, speed, direction, scale, opacity, mouseInteractive, className]);
225
-
226
- return (
227
- <div className={`w-full ${className}`}>
228
- <div className={`mx-auto w-full ${maxWidth} h-[100vh] lg:h-[90vh] overflow-hidden relative`}>
229
- <div ref={containerRef} className="absolute inset-0 w-full h-full" />
230
- </div>
231
- </div>
232
- );
233
- };
234
-
235
- export default SmokeBackground;
1
+ import React, { useEffect, useRef } from "react";
2
+ import { Renderer, Program, Mesh, Triangle } from "ogl";
3
+
4
+ const hexToRgb = (hex) => {
5
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
6
+ if (!result) return [1, 0.5, 0.2];
7
+ return [
8
+ parseInt(result[1], 16) / 255,
9
+ parseInt(result[2], 16) / 255,
10
+ parseInt(result[3], 16) / 255,
11
+ ];
12
+ };
13
+
14
+ const vertex = `#version 300 es
15
+ precision highp float;
16
+ in vec2 position;
17
+ in vec2 uv;
18
+ out vec2 vUv;
19
+ void main() {
20
+ vUv = uv;
21
+ gl_Position = vec4(position, 0.0, 1.0);
22
+ }
23
+ `;
24
+
25
+ const fragment = `#version 300 es
26
+ precision highp float;
27
+ uniform vec2 iResolution;
28
+ uniform float iTime;
29
+ uniform vec3 uCustomColor;
30
+ uniform float uUseCustomColor;
31
+ uniform float uSpeed;
32
+ uniform float uDirection;
33
+ uniform float uScale;
34
+ uniform float uOpacity;
35
+ uniform vec2 uMouse;
36
+ uniform float uMouseInteractive;
37
+ out vec4 fragColor;
38
+
39
+ void mainImage(out vec4 o, vec2 C) {
40
+ vec2 center = iResolution.xy * 0.5;
41
+ C = (C - center) / uScale + center;
42
+
43
+ vec2 mouseOffset = (uMouse - center) * 0.0002;
44
+ C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);
45
+
46
+ float i, d, z, T = iTime * uSpeed * uDirection;
47
+ vec3 O, p, S;
48
+
49
+ // loop count reduced to 30
50
+ for (vec2 r = iResolution.xy, Q; ++i < 35.; O += o.w/d*o.xyz) {
51
+ p = z*normalize(vec3(C-.5*r,r.y));
52
+ p.z -= 4.;
53
+ S = p;
54
+ d = p.y-T;
55
+
56
+ p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);
57
+ Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));
58
+ z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;
59
+ o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));
60
+ }
61
+
62
+ o.xyz = tanh(O/1e4);
63
+ }
64
+
65
+ bool finite1(float x){ return !(isnan(x) || isinf(x)); }
66
+ vec3 sanitize(vec3 c){
67
+ return vec3(
68
+ finite1(c.r) ? c.r : 0.0,
69
+ finite1(c.g) ? c.g : 0.0,
70
+ finite1(c.b) ? c.b : 0.0
71
+ );
72
+ }
73
+
74
+ void main() {
75
+ vec4 o = vec4(0.0);
76
+ mainImage(o, gl_FragCoord.xy);
77
+ vec3 rgb = sanitize(o.rgb);
78
+
79
+ float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;
80
+ vec3 customColor = intensity * uCustomColor;
81
+ vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));
82
+
83
+ float alpha = length(rgb) * uOpacity;
84
+ fragColor = vec4(finalColor, alpha);
85
+ }
86
+ `;
87
+
88
+
89
+ export const SmokeBackground = ({
90
+ color = "#00c950",
91
+ speed = 1,
92
+ direction = "forward",
93
+ scale = 1,
94
+ opacity = 1,
95
+ mouseInteractive = true,
96
+ className = "",
97
+ maxWidth = 'max-w-4xl',
98
+ }) => {
99
+ const containerRef = useRef(null);
100
+ const mousePos = useRef({ x: 0, y: 0 });
101
+
102
+ useEffect(() => {
103
+ const mountNode = containerRef.current;
104
+ if (!mountNode) return;
105
+
106
+ const useCustomColor = color ? 1.0 : 0.0;
107
+ const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];
108
+
109
+ let directionMultiplier = direction === "backward" ? -1.0 : 1.0;
110
+
111
+ // cap DPR depending on mount width to avoid heavy pixel loads on small devices
112
+ const mountWidth = mountNode.clientWidth || window.innerWidth;
113
+ const dprCap = mountWidth > 1200 ? 1.8 : (mountWidth > 768 ? 1.4 : 1.0);
114
+ // The smoke is soft, so rendering at ~70% resolution and letting the browser upscale
115
+ // looks the same while the 35-step raymarch runs on half the pixels.
116
+ const dpr = Math.min(window.devicePixelRatio || 1, dprCap) * 0.7;
117
+
118
+ const renderer = new Renderer({
119
+ webgl: 2,
120
+ alpha: true,
121
+ antialias: false,
122
+ dpr,
123
+ });
124
+
125
+ const gl = renderer.gl;
126
+ const canvas = gl.canvas;
127
+ canvas.style.display = "block";
128
+ canvas.style.width = "100%";
129
+ canvas.style.height = "100%";
130
+ mountNode.appendChild(canvas);
131
+
132
+ const geometry = new Triangle(gl);
133
+
134
+ const program = new Program(gl, {
135
+ vertex: vertex,
136
+ fragment: fragment,
137
+ uniforms: {
138
+ iTime: { value: 0 },
139
+ iResolution: { value: new Float32Array([1, 1]) },
140
+ uCustomColor: { value: new Float32Array(customColorRgb) },
141
+ uUseCustomColor: { value: useCustomColor },
142
+ uSpeed: { value: speed * 0.4 },
143
+ uDirection: { value: directionMultiplier },
144
+ uScale: { value: scale },
145
+ uOpacity: { value: opacity },
146
+ uMouse: { value: new Float32Array([0, 0]) },
147
+ uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },
148
+ },
149
+ });
150
+
151
+ const mesh = new Mesh(gl, { geometry, program });
152
+
153
+ // 🖱 Mouse throttled to RAF
154
+ let mouseNeedsUpdate = false;
155
+ const handleMouseMove = (e) => {
156
+ if (!mouseInteractive) return;
157
+ const rect = mountNode.getBoundingClientRect();
158
+ mousePos.current.x = e.clientX - rect.left;
159
+ mousePos.current.y = rect.height - (e.clientY - rect.top);
160
+ mouseNeedsUpdate = true;
161
+ };
162
+ if (mouseInteractive) {
163
+ mountNode.addEventListener("mousemove", handleMouseMove, { passive: true });
164
+ }
165
+
166
+ const setSize = () => {
167
+ const rect = mountNode.getBoundingClientRect();
168
+ const width = Math.max(1, Math.floor(rect.width));
169
+ const height = Math.max(1, Math.floor(rect.height));
170
+ renderer.setSize(width, height);
171
+ const res = program.uniforms.iResolution.value;
172
+ res[0] = gl.drawingBufferWidth;
173
+ res[1] = gl.drawingBufferHeight;
174
+ };
175
+
176
+ const ro = new ResizeObserver(() => {
177
+ requestAnimationFrame(setSize);
178
+ });
179
+ ro.observe(mountNode);
180
+ setSize();
181
+
182
+ let raf = 0;
183
+ // Only render while the background is on screen.
184
+ let visible = true;
185
+ const t0 = performance.now();
186
+ const loop = (t) => {
187
+ raf = 0;
188
+ if (!visible) return;
189
+ let timeValue = (t - t0) * 0.001;
190
+
191
+ if (direction === "funny") {
192
+ const cycle = Math.sin(timeValue * 0.5);
193
+ program.uniforms.uDirection.value = cycle;
194
+ }
195
+
196
+ if (mouseNeedsUpdate) {
197
+ const mouseUniform = program.uniforms.uMouse.value;
198
+ mouseUniform[0] = mousePos.current.x;
199
+ mouseUniform[1] = mousePos.current.y;
200
+ mouseNeedsUpdate = false;
201
+ }
202
+
203
+ program.uniforms.iTime.value = timeValue;
204
+ renderer.render({ scene: mesh });
205
+ raf = requestAnimationFrame(loop);
206
+ };
207
+ raf = requestAnimationFrame(loop);
208
+ const visibilityObserver = new IntersectionObserver(([entry]) => {
209
+ visible = entry.isIntersecting;
210
+ if (visible && !raf) raf = requestAnimationFrame(loop);
211
+ });
212
+ visibilityObserver.observe(mountNode);
213
+
214
+ return () => {
215
+ cancelAnimationFrame(raf);
216
+ visibilityObserver.disconnect();
217
+ ro.disconnect();
218
+ if (mouseInteractive && mountNode) {
219
+ mountNode.removeEventListener("mousemove", handleMouseMove);
220
+ }
221
+ try {
222
+ mountNode.removeChild(canvas);
223
+ } catch (error) { console.error("Error removing canvas:", error); }
224
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
225
+ };
226
+ }, [color, speed, direction, scale, opacity, mouseInteractive, className]);
227
+
228
+ return (
229
+ <div className={`w-full ${className}`}>
230
+ <div className={`mx-auto w-full ${maxWidth} h-[100vh] lg:h-[90vh] overflow-hidden relative`}>
231
+ <div ref={containerRef} className="absolute inset-0 w-full h-full" />
232
+ </div>
233
+ </div>
234
+ );
235
+ };
236
+
237
+ export default SmokeBackground;
package/dist/cli/index.js CHANGED
@@ -153,6 +153,31 @@ const extraDeps = {
153
153
  WebGLCarousel: ["ogl"],
154
154
  CrossedMarquee: ["simple-icons"],
155
155
  };
156
+ // Packages the app already has (or that ship with Node), never installed by us.
157
+ const BUILTIN = new Set(["react", "react-dom", "path", "fs", "url"]);
158
+ // The npm packages a component folder imports, read from its source files.
159
+ async function importedPackages(dir) {
160
+ const found = new Set();
161
+ const walk = async (d) => {
162
+ for (const entry of await fs.readdir(d, { withFileTypes: true })) {
163
+ const full = path.join(d, entry.name);
164
+ if (entry.isDirectory())
165
+ await walk(full);
166
+ else if (/\.(jsx?|tsx?|mjs)$/.test(entry.name)) {
167
+ const code = await fs.readFile(full, "utf8");
168
+ const re = /(?:from\s+|import\s*\(\s*|import\s+)["']([^"'.\/][^"']*)["']/g;
169
+ for (let m = re.exec(code); m; m = re.exec(code)) {
170
+ const spec = m[1];
171
+ const name = spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
172
+ if (!BUILTIN.has(name))
173
+ found.add(name);
174
+ }
175
+ }
176
+ }
177
+ };
178
+ await walk(dir);
179
+ return [...found];
180
+ }
156
181
  async function main() {
157
182
  const args = process.argv.slice(2);
158
183
  const command = args[0];
@@ -220,26 +245,6 @@ async function main() {
220
245
  { title: "TypeScript (Coming Soon)", value: "ts", disabled: true },
221
246
  ],
222
247
  });
223
- // Install dependencies
224
- console.log(chalk.cyan("📦 Installing dependencies..."));
225
- const deps = [
226
- "gsap",
227
- "framer-motion",
228
- "three",
229
- "@fortawesome/fontawesome-svg-core",
230
- "@fortawesome/free-solid-svg-icons",
231
- "@fortawesome/react-fontawesome",
232
- "react-icons",
233
- "lucide-react",
234
- ...(extraDeps[matchedName] ?? []),
235
- ];
236
- try {
237
- execSync(`npm install ${deps.join(" ")}`, { stdio: "inherit" });
238
- }
239
- catch (err) {
240
- console.log(chalk.red("❌ Failed to install dependencies."));
241
- return;
242
- }
243
248
  // Prepare destination
244
249
  const apexKitPath = path.join(basePath, "ApexUI-Kit");
245
250
  const dest = path.join(apexKitPath, matchedName);
@@ -250,6 +255,21 @@ async function main() {
250
255
  console.log(chalk.red(`❌ Internal error: Component folder '${matchedName}' not found.`));
251
256
  return;
252
257
  }
258
+ // Install only what this component imports, so the app stays light.
259
+ const deps = [...new Set([...(await importedPackages(source)), ...(extraDeps[matchedName] ?? [])])];
260
+ if (deps.length) {
261
+ console.log(chalk.cyan(`📦 Installing ${deps.join(", ")}...`));
262
+ try {
263
+ execSync(`npm install ${deps.join(" ")}`, { stdio: "inherit" });
264
+ }
265
+ catch (err) {
266
+ console.log(chalk.red("❌ Failed to install dependencies."));
267
+ return;
268
+ }
269
+ }
270
+ else {
271
+ console.log(chalk.cyan("📦 No extra packages needed."));
272
+ }
253
273
  await fs.ensureDir(dest);
254
274
  await fs.copy(source, dest);
255
275
  console.log(chalk.green(`✅ '${matchedName}' added to ${dest}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-ui-kit",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "license": "MIT",
5
5
  "description": "118 free, animated UI components for React, styled with Tailwind CSS. Add any of them with one CLI command.",
6
6
  "homepage": "https://apex-ui.in",
@@ -60,5 +60,11 @@
60
60
  "@types/node": "^20.4.0",
61
61
  "copyfiles": "^2.4.1",
62
62
  "typescript": "^5.4.0"
63
+ },
64
+ "sideEffects": [
65
+ "*.css"
66
+ ],
67
+ "engines": {
68
+ "node": ">=18"
63
69
  }
64
70
  }