apex-ui-kit 2.2.0 → 2.3.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.
Files changed (55) hide show
  1. package/dist/ApexUI-Kit/templates/AiChat/AiChat.jsx +171 -0
  2. package/dist/ApexUI-Kit/templates/AiModelPrompt/AiModelPrompt.jsx +221 -0
  3. package/dist/ApexUI-Kit/templates/AiModelPrompt/index.js +2 -0
  4. package/dist/ApexUI-Kit/templates/AiModelPrompt/models.js +15 -0
  5. package/dist/ApexUI-Kit/templates/AiPromptBox/AiPromptBox.jsx +173 -0
  6. package/dist/ApexUI-Kit/templates/AiResponse/AiResponse.jsx +172 -0
  7. package/dist/ApexUI-Kit/templates/AiThinking/AiThinking.jsx +106 -0
  8. package/dist/ApexUI-Kit/templates/AiVoiceBars/AiVoiceBars.jsx +78 -0
  9. package/dist/ApexUI-Kit/templates/AiVoiceBlob/AiVoiceBlob.jsx +105 -0
  10. package/dist/ApexUI-Kit/templates/AiVoiceOrb/AiVoiceOrb.jsx +74 -0
  11. package/dist/ApexUI-Kit/templates/AiVoiceRing/AiVoiceRing.jsx +120 -0
  12. package/dist/ApexUI-Kit/templates/AiVoiceWave/AiVoiceWave.jsx +107 -0
  13. package/dist/ApexUI-Kit/templates/AnimatedSwitch/AnimatedSwitch.jsx +80 -0
  14. package/dist/ApexUI-Kit/templates/AnimatedTabs/AnimatedTabs.jsx +12 -4
  15. package/dist/ApexUI-Kit/templates/AuroraBackground/AuroraBackground.jsx +48 -0
  16. package/dist/ApexUI-Kit/templates/BounceCards/BounceCards.jsx +53 -0
  17. package/dist/ApexUI-Kit/templates/CardSwap3D/CardSwap3D.jsx +80 -0
  18. package/dist/ApexUI-Kit/templates/CircularText/CircularText.jsx +49 -0
  19. package/dist/ApexUI-Kit/templates/ClickSpark/ClickSpark.jsx +74 -0
  20. package/dist/ApexUI-Kit/templates/Coverflow3D/Coverflow3D.jsx +108 -0
  21. package/dist/ApexUI-Kit/templates/CubeGallery/CubeGallery.jsx +168 -0
  22. package/dist/ApexUI-Kit/templates/CursorTrail/CursorTrail.jsx +200 -0
  23. package/dist/ApexUI-Kit/templates/DotGlobe/DotGlobe.jsx +1 -0
  24. package/dist/ApexUI-Kit/templates/DynamicIsland/DynamicIsland.jsx +1 -1
  25. package/dist/ApexUI-Kit/templates/ElasticSlider/ElasticSlider.jsx +161 -0
  26. package/dist/ApexUI-Kit/templates/ExtrudedText/ExtrudedText.jsx +78 -0
  27. package/dist/ApexUI-Kit/templates/FaqAccordion/FaqAccordion.jsx +1 -1
  28. package/dist/ApexUI-Kit/templates/FlipBook/FlipBook.jsx +191 -0
  29. package/dist/ApexUI-Kit/templates/FloatingNavbar/FloatingNavbar.jsx +162 -0
  30. package/dist/ApexUI-Kit/templates/Folder/Folder.jsx +101 -0
  31. package/dist/ApexUI-Kit/templates/GlitchText/GlitchText.jsx +56 -0
  32. package/dist/ApexUI-Kit/templates/GooeyNav/GooeyNav.jsx +312 -0
  33. package/dist/ApexUI-Kit/templates/HoldToConfirm/HoldToConfirm.jsx +169 -0
  34. package/dist/ApexUI-Kit/templates/HoldToConfirm/UndoNotice.jsx +58 -0
  35. package/dist/ApexUI-Kit/templates/HoloCard/HoloCard.jsx +92 -0
  36. package/dist/ApexUI-Kit/templates/ImageCompare/ImageCompare.jsx +97 -0
  37. package/dist/ApexUI-Kit/templates/IsoStack/IsoStack.jsx +86 -0
  38. package/dist/ApexUI-Kit/templates/LoadingScreen/LoadingScreen.jsx +545 -101
  39. package/dist/ApexUI-Kit/templates/MagnetLines/MagnetLines.jsx +67 -0
  40. package/dist/ApexUI-Kit/templates/MagneticCursor/MagneticCursor.jsx +2 -160
  41. package/dist/ApexUI-Kit/templates/PixelTransition/PixelTransition.jsx +75 -0
  42. package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +1 -1
  43. package/dist/ApexUI-Kit/templates/PricingCards/PricingCards.jsx +115 -0
  44. package/dist/ApexUI-Kit/templates/ProximityText/ProximityText.jsx +66 -0
  45. package/dist/ApexUI-Kit/templates/RotatingText/RotatingText.jsx +50 -0
  46. package/dist/ApexUI-Kit/templates/ShinyText/ShinyText.jsx +36 -0
  47. package/dist/ApexUI-Kit/templates/StarBorder/StarBorder.jsx +33 -0
  48. package/dist/ApexUI-Kit/templates/Stepper/Stepper.jsx +142 -0
  49. package/dist/ApexUI-Kit/templates/SwipeCards/SwipeCards.jsx +123 -0
  50. package/dist/ApexUI-Kit/templates/TextRing3D/TextRing3D.jsx +75 -0
  51. package/dist/ApexUI-Kit/templates/TrueFocus/TrueFocus.jsx +64 -0
  52. package/dist/ApexUI-Kit/templates/TypewriterText/TypewriterText.jsx +70 -0
  53. package/dist/cli/index.js +43 -0
  54. package/package.json +1 -1
  55. package/dist/ApexUI-Kit/templates/Carousel/DragCarousel.jsx +0 -105
@@ -0,0 +1,142 @@
1
+ import React, { useId, useState } from "react";
2
+ import { AnimatePresence, motion } from "framer-motion";
3
+ import { Check } from "lucide-react";
4
+
5
+ /*
6
+ * Stepper: a multi-step flow. The indicators fill and tick as you move on,
7
+ * the connector lines fill in between, and each step's content slides in
8
+ * from the side you are heading while the panel height follows smoothly.
9
+ */
10
+
11
+ const DEFAULT_STEPS = [
12
+ { title: "Account", content: <p>Welcome! Pick a username to get started, you can change it later.</p> },
13
+ { title: "Profile", content: <p>Add a photo and a short bio so people know who you are.</p> },
14
+ { title: "Theme", content: <p>Choose light, dark or follow your system. Your choice syncs everywhere.</p> },
15
+ { title: "Done", content: <p>All set. Press Complete to finish onboarding.</p> },
16
+ ];
17
+
18
+ const variants = {
19
+ enter: (dir) => ({ x: dir > 0 ? "40%" : "-40%", opacity: 0 }),
20
+ center: { x: 0, opacity: 1 },
21
+ exit: (dir) => ({ x: dir > 0 ? "-40%" : "40%", opacity: 0 }),
22
+ };
23
+
24
+ function StepBody({ step, total, title, content, accentColor, dark }) {
25
+ return (
26
+ <>
27
+ <p className="text-[0.72rem] font-semibold uppercase tracking-[0.18em]" style={{ color: accentColor }}>
28
+ Step {step + 1} of {total}
29
+ </p>
30
+ <h3 className="mt-1 text-xl font-semibold">{title}</h3>
31
+ <div className={`mt-2 text-[0.92rem] leading-relaxed ${dark ? "text-white/65" : "text-neutral-600"}`}>{content}</div>
32
+ </>
33
+ );
34
+ }
35
+
36
+ const Stepper = ({ steps = DEFAULT_STEPS, initialStep = 0, onStepChange, onComplete, accentColor = "#a3e635", theme = "dark", className = "" }) => {
37
+ const id = useId();
38
+ const [step, setStep] = useState(initialStep);
39
+ const [dir, setDir] = useState(1);
40
+ const [done, setDone] = useState(false);
41
+ const dark = theme !== "light";
42
+ const last = step === steps.length - 1;
43
+
44
+ const go = (to) => {
45
+ if (to < 0 || to >= steps.length) return;
46
+ setDir(to > step ? 1 : -1);
47
+ setStep(to);
48
+ onStepChange?.(to);
49
+ };
50
+
51
+ return (
52
+ <div className={`w-full max-w-md rounded-[1.6rem] border p-6 ${dark ? "border-white/10 bg-neutral-950 text-white" : "border-black/10 bg-white text-neutral-900"} ${className}`}>
53
+ <div className="flex items-center">
54
+ {steps.map((s, i) => {
55
+ const complete = done || i < step;
56
+ const current = !done && i === step;
57
+ return (
58
+ <React.Fragment key={i}>
59
+ <button type="button" onClick={() => !done && go(i)} aria-label={`Step ${i + 1}: ${s.title}`} aria-current={current ? "step" : undefined} className="relative grid h-9 w-9 shrink-0 place-items-center rounded-full text-sm font-semibold">
60
+ <motion.span
61
+ className="absolute inset-0 rounded-full"
62
+ initial={false}
63
+ animate={{ backgroundColor: complete || current ? accentColor : dark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.06)", scale: current ? 1.08 : 1 }}
64
+ transition={{ duration: 0.3 }}
65
+ />
66
+ {current && <motion.span layoutId={`${id}-ring`} className="absolute -inset-1 rounded-full border-2" style={{ borderColor: `color-mix(in srgb, ${accentColor} 45%, transparent)` }} />}
67
+ <span className={`relative ${complete || current ? "text-black" : "opacity-60"}`}>
68
+ {complete ? (
69
+ <motion.span initial={{ scale: 0, rotate: -45 }} animate={{ scale: 1, rotate: 0 }} className="block">
70
+ <Check className="h-4 w-4" strokeWidth={3} />
71
+ </motion.span>
72
+ ) : (
73
+ i + 1
74
+ )}
75
+ </span>
76
+ </button>
77
+ {i < steps.length - 1 && (
78
+ <div className={`mx-2 h-1 flex-1 overflow-hidden rounded-full ${dark ? "bg-white/[0.08]" : "bg-black/[0.06]"}`}>
79
+ <motion.div className="h-full rounded-full" style={{ background: accentColor }} initial={false} animate={{ width: done || i < step ? "100%" : "0%" }} transition={{ duration: 0.4, ease: [0.22, 1, 0.36, 1] }} />
80
+ </div>
81
+ )}
82
+ </React.Fragment>
83
+ );
84
+ })}
85
+ </div>
86
+
87
+ <div className="relative mt-6 grid overflow-hidden">
88
+ {/* Invisible copies of every step reserve the tallest height, so the
89
+ card and its buttons never move up and down between steps. */}
90
+ {steps.map((s, i) => (
91
+ <div key={i} aria-hidden="true" className="invisible col-start-1 row-start-1 pb-1">
92
+ <StepBody step={i} total={steps.length} title={s.title} content={s.content} accentColor={accentColor} dark={dark} />
93
+ </div>
94
+ ))}
95
+ <AnimatePresence mode="popLayout" initial={false} custom={dir}>
96
+ <motion.div key={done ? "done" : step} custom={dir} variants={variants} initial="enter" animate="center" exit="exit" transition={{ type: "spring", stiffness: 300, damping: 32 }} className="col-start-1 row-start-1 pb-1">
97
+ {done ? (
98
+ <div className="py-4 text-center">
99
+ <p className="text-lg font-semibold">You are all set 🎉</p>
100
+ <p className={`mt-1 text-sm ${dark ? "text-white/55" : "text-neutral-500"}`}>Every step is complete.</p>
101
+ </div>
102
+ ) : (
103
+ <StepBody step={step} total={steps.length} title={steps[step].title} content={steps[step].content} accentColor={accentColor} dark={dark} />
104
+ )}
105
+ </motion.div>
106
+ </AnimatePresence>
107
+ </div>
108
+
109
+ <div className="mt-6 flex items-center justify-between">
110
+ {done ? (
111
+ <button type="button" onClick={() => {
112
+ setDone(false);
113
+ go(0);
114
+ }} className={`rounded-full px-4 py-2 text-sm font-medium ${dark ? "bg-white/[0.06] hover:bg-white/10" : "bg-black/[0.05] hover:bg-black/[0.08]"}`}>
115
+ Start over
116
+ </button>
117
+ ) : (
118
+ <>
119
+ <button type="button" onClick={() => go(step - 1)} disabled={step === 0} className={`rounded-full px-4 py-2 text-sm font-medium transition disabled:pointer-events-none disabled:opacity-0 ${dark ? "text-white/70 hover:text-white" : "text-neutral-500 hover:text-neutral-900"}`}>
120
+ Back
121
+ </button>
122
+ <motion.button
123
+ whileTap={{ scale: 0.95 }}
124
+ type="button"
125
+ onClick={() => {
126
+ if (!last) return go(step + 1);
127
+ setDone(true);
128
+ onComplete?.();
129
+ }}
130
+ className="rounded-full px-5 py-2 text-sm font-semibold text-black"
131
+ style={{ background: accentColor }}
132
+ >
133
+ {last ? "Complete" : "Continue"}
134
+ </motion.button>
135
+ </>
136
+ )}
137
+ </div>
138
+ </div>
139
+ );
140
+ };
141
+
142
+ export default Stepper;
@@ -0,0 +1,123 @@
1
+ import React, { useState } from "react";
2
+ import { AnimatePresence, motion, useMotionValue, useTransform } from "framer-motion";
3
+ import { Heart, RotateCcw, X } from "lucide-react";
4
+
5
+ /*
6
+ * Swipe Cards: a deck you swipe left or right. The top card tilts with the
7
+ * drag and shows a LIKE / NOPE stamp; let go past the threshold and it flies
8
+ * off, or use the buttons and arrow keys. The deck refills when it runs out.
9
+ */
10
+
11
+ const IK = "https://ik.imagekit.io/ny6yinyut/apexui/assets";
12
+ const DEFAULT_CARDS = [
13
+ { image: `${IK}/spiderman.png?tr=f-auto,q-80,w-600`, title: "Spider-Man", subtitle: "Queens · 2.3 km away" },
14
+ { image: `${IK}/hulk.png?tr=f-auto,q-80,w-600`, title: "Hulk", subtitle: "New York · 5 km away" },
15
+ { image: `${IK}/black-panther.png?tr=f-auto,q-80,w-600`, title: "Black Panther", subtitle: "Wakanda · 8,412 km away" },
16
+ { image: `${IK}/doctorStrange.png?tr=f-auto,q-80,w-600`, title: "Doctor Strange", subtitle: "Sanctum · 3 km away" },
17
+ { image: `${IK}/captainamerica.png?tr=f-auto,q-80,w-600`, title: "Captain America", subtitle: "Brooklyn · 1 km away" },
18
+ ];
19
+
20
+ function TopCard({ card, onSwipe, threshold, likeColor, nopeColor }) {
21
+ const x = useMotionValue(0);
22
+ const rotate = useTransform(x, [-240, 240], [-18, 18]);
23
+ const likeOpacity = useTransform(x, [20, threshold], [0, 1]);
24
+ const nopeOpacity = useTransform(x, [-threshold, -20], [1, 0]);
25
+
26
+ return (
27
+ <motion.div
28
+ className="absolute inset-0 z-10 cursor-grab touch-none overflow-hidden rounded-3xl bg-neutral-900 shadow-[0_30px_60px_-24px_rgba(0,0,0,0.6)] active:cursor-grabbing"
29
+ style={{ x, rotate }}
30
+ drag="x"
31
+ dragSnapToOrigin
32
+ dragElastic={0.9}
33
+ onDragEnd={(_, info) => {
34
+ if (info.offset.x > threshold || info.velocity.x > 700) onSwipe("right");
35
+ else if (info.offset.x < -threshold || info.velocity.x < -700) onSwipe("left");
36
+ }}
37
+ exit={(dir) => ({ x: dir === "right" ? 520 : -520, rotate: dir === "right" ? 26 : -26, opacity: 0, transition: { duration: 0.35, ease: [0.22, 1, 0.36, 1] } })}
38
+ >
39
+ <img src={card.image} alt="" draggable="false" className="pointer-events-none h-full w-full object-cover" />
40
+ <div className="pointer-events-none absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/30 to-transparent p-5 pt-16 text-white">
41
+ <p className="text-xl font-bold">{card.title}</p>
42
+ {card.subtitle && <p className="text-sm text-white/70">{card.subtitle}</p>}
43
+ </div>
44
+ <motion.span className="pointer-events-none absolute left-5 top-6 -rotate-12 rounded-lg border-[3px] px-3 py-1 text-2xl font-black tracking-widest" style={{ opacity: likeOpacity, color: likeColor, borderColor: likeColor }}>
45
+ LIKE
46
+ </motion.span>
47
+ <motion.span className="pointer-events-none absolute right-5 top-6 rotate-12 rounded-lg border-[3px] px-3 py-1 text-2xl font-black tracking-widest" style={{ opacity: nopeOpacity, color: nopeColor, borderColor: nopeColor }}>
48
+ NOPE
49
+ </motion.span>
50
+ </motion.div>
51
+ );
52
+ }
53
+
54
+ const SwipeCards = ({ cards = DEFAULT_CARDS, threshold = 110, likeColor = "#a3e635", nopeColor = "#f43f5e", onSwipe, className = "" }) => {
55
+ const [index, setIndex] = useState(0);
56
+ const [dir, setDir] = useState("right");
57
+ const [history, setHistory] = useState([]);
58
+ const visible = [0, 1, 2].map((o) => cards[(index + o) % cards.length]);
59
+
60
+ const swipe = (direction) => {
61
+ const card = cards[index % cards.length];
62
+ setDir(direction);
63
+ setHistory((h) => [...h, { card, direction }]);
64
+ onSwipe?.(card, direction);
65
+ setIndex((i) => i + 1);
66
+ };
67
+ const undo = () => {
68
+ if (!history.length) return;
69
+ setDir("left");
70
+ setHistory((h) => h.slice(0, -1));
71
+ setIndex((i) => Math.max(0, i - 1));
72
+ };
73
+
74
+ return (
75
+ <div
76
+ className={`flex flex-col items-center outline-none ${className}`}
77
+ tabIndex={0}
78
+ onKeyDown={(e) => {
79
+ if (e.key === "ArrowRight") swipe("right");
80
+ if (e.key === "ArrowLeft") swipe("left");
81
+ }}
82
+ aria-label="Card deck: use the arrow keys or the buttons to swipe"
83
+ >
84
+ <div className="relative aspect-[3/4] w-[min(18rem,78vw)]">
85
+ {/* Cards waiting underneath, slightly smaller and lower. */}
86
+ {visible
87
+ .slice(1)
88
+ .reverse()
89
+ .map((card, i, arr) => {
90
+ const depth = arr.length - i;
91
+ return (
92
+ <motion.div
93
+ key={`${index + depth}-${card.title}`}
94
+ className="absolute inset-0 overflow-hidden rounded-3xl bg-neutral-900"
95
+ initial={false}
96
+ animate={{ scale: 1 - depth * 0.05, y: depth * 14, opacity: 1 - depth * 0.25 }}
97
+ transition={{ type: "spring", stiffness: 300, damping: 28 }}
98
+ >
99
+ <img src={card.image} alt="" draggable="false" className="h-full w-full object-cover" />
100
+ </motion.div>
101
+ );
102
+ })}
103
+ <AnimatePresence custom={dir} initial={false}>
104
+ <TopCard key={`${index}-${visible[0].title}`} card={visible[0]} onSwipe={swipe} threshold={threshold} likeColor={likeColor} nopeColor={nopeColor} />
105
+ </AnimatePresence>
106
+ </div>
107
+
108
+ <div className="mt-8 flex items-center gap-4">
109
+ <motion.button whileTap={{ scale: 0.88 }} whileHover={{ scale: 1.06 }} type="button" aria-label="Nope" onClick={() => swipe("left")} className="grid h-14 w-14 place-items-center rounded-full border-2 bg-black/5 shadow-lg" style={{ borderColor: nopeColor, color: nopeColor }}>
110
+ <X className="h-6 w-6" strokeWidth={3} />
111
+ </motion.button>
112
+ <motion.button whileTap={{ scale: 0.88 }} whileHover={{ scale: 1.06 }} type="button" aria-label="Undo" onClick={undo} disabled={!history.length} className="grid h-10 w-10 place-items-center rounded-full border border-current/20 opacity-70 disabled:opacity-30">
113
+ <RotateCcw className="h-4 w-4" />
114
+ </motion.button>
115
+ <motion.button whileTap={{ scale: 0.88 }} whileHover={{ scale: 1.06 }} type="button" aria-label="Like" onClick={() => swipe("right")} className="grid h-14 w-14 place-items-center rounded-full border-2 bg-black/5 shadow-lg" style={{ borderColor: likeColor, color: likeColor }}>
116
+ <Heart className="h-6 w-6" fill="currentColor" />
117
+ </motion.button>
118
+ </div>
119
+ </div>
120
+ );
121
+ };
122
+
123
+ export default SwipeCards;
@@ -0,0 +1,75 @@
1
+ import React, { useRef } from "react";
2
+ import { useAnimationFrame, useReducedMotion } from "framer-motion";
3
+
4
+ /*
5
+ * 3D Text Ring: letters stand on a ring in real 3D space and the ring
6
+ * turns, so text sweeps around the back and comes forward again. Letters
7
+ * fade and soften as they pass behind. Drag to spin it yourself.
8
+ */
9
+
10
+ const TextRing3D = ({ text = "APEX UI · 3D TYPE · ", radius = 9, speed = 30, tilt = -14, fontSize = 2, color = "currentColor", accentColor = "#a3e635", className = "" }) => {
11
+ const reduced = useReducedMotion();
12
+ const ring = useRef(null);
13
+ const letters = useRef([]);
14
+ const angle = useRef(0);
15
+ const vel = useRef(0);
16
+ const drag = useRef(null);
17
+ const chars = Array.from(text);
18
+ const step = 360 / chars.length;
19
+
20
+ useAnimationFrame((_, delta) => {
21
+ const dt = Math.min(0.05, delta / 1000);
22
+ if (!drag.current) {
23
+ vel.current *= 0.95;
24
+ angle.current += (reduced ? 0 : speed) * dt + vel.current * dt;
25
+ }
26
+ if (ring.current) ring.current.style.transform = `rotateX(${tilt}deg) rotateY(${-angle.current}deg)`;
27
+ letters.current.forEach((el, i) => {
28
+ if (!el) return;
29
+ // How much this letter faces the viewer (1 front, -1 back).
30
+ const facing = Math.cos(((i * step - angle.current) * Math.PI) / 180);
31
+ el.style.opacity = String(0.18 + 0.82 * ((facing + 1) / 2));
32
+ el.style.color = facing > 0.9 ? accentColor : "";
33
+ });
34
+ });
35
+
36
+ return (
37
+ <div
38
+ className={`grid cursor-grab touch-none select-none place-items-center active:cursor-grabbing [perspective:900px] ${className}`}
39
+ style={{ width: `${radius * 2.4}rem`, maxWidth: "100%", height: `${fontSize * 3.2 + 4}rem`, color }}
40
+ onPointerDown={(e) => {
41
+ // Block text selection / native drag, which cancel the drag midway.
42
+ e.preventDefault();
43
+ e.currentTarget.setPointerCapture(e.pointerId);
44
+ drag.current = { x: e.clientX, a: angle.current, t: performance.now() };
45
+ }}
46
+ onPointerMove={(e) => {
47
+ const d = drag.current;
48
+ if (!d) return;
49
+ const now = performance.now();
50
+ const next = d.a - (e.clientX - d.x) * 0.4;
51
+ vel.current = ((next - angle.current) / Math.max(1, now - d.t)) * 1000;
52
+ d.t = now;
53
+ angle.current = next;
54
+ }}
55
+ onPointerUp={() => (drag.current = null)}
56
+ aria-label={text}
57
+ role="img"
58
+ >
59
+ <div ref={ring} className="relative h-0 w-0 [transform-style:preserve-3d]">
60
+ {chars.map((c, i) => (
61
+ <span
62
+ key={i}
63
+ ref={(el) => (letters.current[i] = el)}
64
+ className="absolute left-0 top-0 -translate-x-1/2 -translate-y-1/2 font-black leading-none transition-colors duration-300"
65
+ style={{ fontSize: `${fontSize}rem`, transform: `rotateY(${i * step}deg) translateZ(${radius}rem)` }}
66
+ >
67
+ {c === " " ? " " : c}
68
+ </span>
69
+ ))}
70
+ </div>
71
+ </div>
72
+ );
73
+ };
74
+
75
+ export default TextRing3D;
@@ -0,0 +1,64 @@
1
+ import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import { motion } from "framer-motion";
3
+
4
+ /*
5
+ * True Focus: one word of a sentence is sharp while the rest are blurred,
6
+ * and a set of corner brackets glides from word to word. Cycles on its
7
+ * own, or follows the pointer in manual mode.
8
+ */
9
+
10
+ const TrueFocus = ({ sentence = "Design Build Ship Repeat", manual = false, blur = 5, interval = 1600, borderColor = "#a3e635", className = "" }) => {
11
+ const words = sentence.split(" ");
12
+ const [active, setActive] = useState(0);
13
+ const [box, setBox] = useState(null);
14
+ const wrap = useRef(null);
15
+ const refs = useRef([]);
16
+
17
+ useEffect(() => {
18
+ if (manual) return undefined;
19
+ const id = setInterval(() => setActive((i) => (i + 1) % words.length), interval);
20
+ return () => clearInterval(id);
21
+ }, [manual, interval, words.length]);
22
+
23
+ useLayoutEffect(() => {
24
+ const measure = () => {
25
+ const el = refs.current[active];
26
+ const parent = wrap.current;
27
+ if (!el || !parent) return;
28
+ const a = el.getBoundingClientRect();
29
+ const p = parent.getBoundingClientRect();
30
+ setBox({ x: a.left - p.left, y: a.top - p.top, w: a.width, h: a.height });
31
+ };
32
+ measure();
33
+ const ro = new ResizeObserver(measure);
34
+ ro.observe(wrap.current);
35
+ return () => ro.disconnect();
36
+ }, [active, sentence]);
37
+
38
+ const corner = "absolute h-[0.42em] w-[0.42em] border-[0.09em]";
39
+ return (
40
+ <div ref={wrap} className={`relative inline-flex flex-wrap justify-center gap-x-[0.45em] gap-y-2 font-black ${className}`}>
41
+ {words.map((w, i) => (
42
+ <span
43
+ key={i}
44
+ ref={(el) => (refs.current[i] = el)}
45
+ onPointerEnter={() => manual && setActive(i)}
46
+ className="relative px-[0.12em] transition-[filter,opacity] duration-500"
47
+ style={{ filter: i === active ? "blur(0px)" : `blur(${blur}px)`, opacity: i === active ? 1 : 0.55 }}
48
+ >
49
+ {w}
50
+ </span>
51
+ ))}
52
+ {box && (
53
+ <motion.div aria-hidden="true" className="pointer-events-none absolute left-0 top-0" initial={false} animate={{ x: box.x - 6, y: box.y - 4, width: box.w + 12, height: box.h + 8 }} transition={{ type: "spring", stiffness: 260, damping: 28 }}>
54
+ <span className={`${corner} -left-0 -top-0 rounded-tl-[0.12em] border-b-0 border-r-0`} style={{ borderColor, filter: `drop-shadow(0 0 6px ${borderColor})` }} />
55
+ <span className={`${corner} -right-0 -top-0 rounded-tr-[0.12em] border-b-0 border-l-0`} style={{ borderColor, filter: `drop-shadow(0 0 6px ${borderColor})` }} />
56
+ <span className={`${corner} -bottom-0 -left-0 rounded-bl-[0.12em] border-r-0 border-t-0`} style={{ borderColor, filter: `drop-shadow(0 0 6px ${borderColor})` }} />
57
+ <span className={`${corner} -bottom-0 -right-0 rounded-br-[0.12em] border-l-0 border-t-0`} style={{ borderColor, filter: `drop-shadow(0 0 6px ${borderColor})` }} />
58
+ </motion.div>
59
+ )}
60
+ </div>
61
+ );
62
+ };
63
+
64
+ export default TrueFocus;
@@ -0,0 +1,70 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useReducedMotion } from "framer-motion";
3
+
4
+ /*
5
+ * Typewriter Text: types a phrase, pauses, deletes it and moves on to the
6
+ * next, with a blinking caret. Put static text before or after with
7
+ * `prefix` / `suffix`; only the rotating words are typed.
8
+ */
9
+
10
+ const TypewriterText = ({
11
+ words = ["beautiful interfaces.", "smooth animations.", "accessible components.", "faster launches."],
12
+ prefix = "We build ",
13
+ suffix = "",
14
+ typeSpeed = 70,
15
+ deleteSpeed = 40,
16
+ pause = 1600,
17
+ loop = true,
18
+ caret = "bar",
19
+ accentColor = "#a3e635",
20
+ as: Tag = "p",
21
+ className = "",
22
+ }) => {
23
+ const reduced = useReducedMotion();
24
+ const [index, setIndex] = useState(0);
25
+ const [text, setText] = useState("");
26
+ const [deleting, setDeleting] = useState(false);
27
+ const word = words[index % words.length] ?? "";
28
+
29
+ useEffect(() => {
30
+ if (reduced) return undefined;
31
+ let delay = deleting ? deleteSpeed : typeSpeed + Math.random() * 40;
32
+ if (!deleting && text === word) {
33
+ if (!loop && index === words.length - 1) return undefined;
34
+ delay = pause;
35
+ } else if (deleting && text === "") delay = 300;
36
+
37
+ const id = setTimeout(() => {
38
+ if (!deleting && text === word) setDeleting(true);
39
+ else if (deleting && text === "") {
40
+ setDeleting(false);
41
+ setIndex((i) => (i + 1) % words.length);
42
+ } else setText(word.slice(0, text.length + (deleting ? -1 : 1)));
43
+ }, delay);
44
+ return () => clearTimeout(id);
45
+ }, [text, deleting, word, index, words.length, typeSpeed, deleteSpeed, pause, loop, reduced]);
46
+
47
+ const shown = reduced ? word : text;
48
+ const caretEl =
49
+ caret === "none" ? null : (
50
+ <span
51
+ aria-hidden="true"
52
+ className={`ml-[0.06em] inline-block animate-[apex-caret_1s_steps(1)_infinite] align-baseline ${caret === "block" ? "h-[1em] w-[0.55em] translate-y-[0.12em]" : caret === "underscore" ? "h-[0.12em] w-[0.6em]" : "h-[1em] w-[0.08em] translate-y-[0.12em]"}`}
53
+ style={{ background: accentColor }}
54
+ />
55
+ );
56
+
57
+ return (
58
+ <Tag className={className} aria-label={`${prefix}${words.join(", ")}${suffix}`}>
59
+ <style>{"@keyframes apex-caret{0%,100%{opacity:1}50%{opacity:0}}"}</style>
60
+ <span aria-hidden="true">
61
+ {prefix}
62
+ <span style={{ color: accentColor }}>{shown}</span>
63
+ {caretEl}
64
+ {suffix}
65
+ </span>
66
+ </Tag>
67
+ );
68
+ };
69
+
70
+ export default TypewriterText;
package/dist/cli/index.js CHANGED
@@ -71,6 +71,49 @@ const componentMap = {
71
71
  "toast-stack": "ToastStack",
72
72
  "blur-reveal-text": "BlurRevealText",
73
73
  "flip-clock": "FlipClock",
74
+ "cursor-trail": "CursorTrail",
75
+ "typewriter-text": "TypewriterText",
76
+ "image-compare": "ImageCompare",
77
+ "pricing-cards": "PricingCards",
78
+ "swipe-cards": "SwipeCards",
79
+ "animated-switch": "AnimatedSwitch",
80
+ "animated-switches": "AnimatedSwitch",
81
+ "aurora-background": "AuroraBackground",
82
+ "holo-card": "HoloCard",
83
+ "cube-gallery": "CubeGallery",
84
+ "flip-book": "FlipBook",
85
+ "extruded-text": "ExtrudedText",
86
+ "iso-stack": "IsoStack",
87
+ "floating-navbar": "FloatingNavbar",
88
+ "ai-chat": "AiChat",
89
+ "ai-prompt-box": "AiPromptBox",
90
+ "ai-thinking": "AiThinking",
91
+ "ai-voice-orb": "AiVoiceOrb",
92
+ "ai-response": "AiResponse",
93
+ "ai-model-prompt": "AiModelPrompt",
94
+ "hold-to-confirm": "HoldToConfirm",
95
+ "ai-voice-wave": "AiVoiceWave",
96
+ "ai-voice-bars": "AiVoiceBars",
97
+ "ai-voice-ring": "AiVoiceRing",
98
+ "ai-voice-blob": "AiVoiceBlob",
99
+ "shiny-text": "ShinyText",
100
+ "glitch-text": "GlitchText",
101
+ "rotating-text": "RotatingText",
102
+ "circular-text": "CircularText",
103
+ "proximity-text": "ProximityText",
104
+ "true-focus": "TrueFocus",
105
+ "text-ring-3d": "TextRing3D",
106
+ "star-border": "StarBorder",
107
+ "click-spark": "ClickSpark",
108
+ "elastic-slider": "ElasticSlider",
109
+ "stepper": "Stepper",
110
+ "coverflow-3d": "Coverflow3D",
111
+ "magnet-lines": "MagnetLines",
112
+ "pixel-transition": "PixelTransition",
113
+ "folder": "Folder",
114
+ "bounce-cards": "BounceCards",
115
+ "card-swap-3d": "CardSwap3D",
116
+ "gooey-nav": "GooeyNav",
74
117
  "water-drop-reveal": "MaskView",
75
118
  };
76
119
  // Packages only some components need, installed on top of the shared ones.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-ui-kit",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "license": "MIT",
5
5
  "description": "Apex UI Kit - A React component library for building modern user interfaces",
6
6
  "keywords": [
@@ -1,105 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
-
3
- // To use GSAP in this environment, we'll import it from a CDN.
4
- // In a local project: `npm install gsap`
5
- import { gsap } from 'gsap';
6
- import { Draggable } from 'gsap/Draggable';
7
-
8
- // GSAP Draggable plugin ko register karna zaroori hai
9
- gsap.registerPlugin(Draggable);
10
-
11
- // --- GSAP Drag Carousel Component ---
12
- const DragCarousel = ({ images }) => {
13
- const [activeIndex, setActiveIndex] = useState(0);
14
- const carouselRef = useRef(null);
15
- const cardsRef = useRef([]);
16
- const dragProxyRef = useRef(null);
17
- useEffect(() => {
18
- const cards = cardsRef.current;
19
- gsap.to(cards, {
20
- duration: 0.4,
21
- ease: 'power4.Out',
22
- stagger: 0.05,
23
- // Har card ke liye naya transform calculate karta hai
24
- transform: (i) => {
25
- let positionOffset = i - activeIndex;
26
- if (positionOffset > images.length / 2) {
27
- positionOffset -= images.length;
28
- } else if (positionOffset < -images.length / 2) {
29
- positionOffset += images.length;
30
- }
31
-
32
- const translateX = positionOffset * 50;
33
- const translateZ = -Math.abs(positionOffset) * 250;
34
- const scale = 1 - Math.abs(positionOffset) * 0.15;
35
-
36
- return `translateX(${translateX}%) translateZ(${translateZ}px) scale(${scale})`;
37
- },
38
- zIndex: (i) => {
39
- let positionOffset = i - activeIndex;
40
- if (positionOffset > images.length / 2) positionOffset -= images.length;
41
- if (positionOffset < -images.length / 2) positionOffset += images.length;
42
- return images.length - Math.abs(positionOffset);
43
- },
44
- opacity: (i) => {
45
- let positionOffset = i - activeIndex;
46
- if (positionOffset > images.length / 2) positionOffset -= images.length;
47
- if (positionOffset < -images.length / 2) positionOffset += images.length;
48
- return Math.abs(positionOffset) > 2 ? 0 : 1;
49
- }
50
- });
51
- }, [activeIndex, images.length]);
52
-
53
- // GSAP Draggable ko setup karta hai
54
- useEffect(() => {
55
- // BUG FIX: Draggable ab proxy element par lagaya gaya hai
56
- const draggableInstance = Draggable.create(dragProxyRef.current, {
57
- type: "x",
58
- trigger: carouselRef.current,
59
- inertia: true,
60
- cursor: 'grab',
61
- activeCursor: 'grabbing',
62
- onDragEnd: function () {
63
- gsap.to(this.target, { x: 0, duration: 0.3 });
64
- if (this.getDirection("start") === "left") {
65
- setActiveIndex(prev => (prev + 1) % images.length);
66
- } else if (this.getDirection("start") === "right") {
67
- setActiveIndex(prev => (prev - 1 + images.length) % images.length);
68
- }
69
- }
70
- });
71
-
72
- // Cleanup function
73
- return () => {
74
- if (draggableInstance && draggableInstance[0]) {
75
- draggableInstance[0].kill();
76
- }
77
- };
78
- }, [images.length]);
79
-
80
- return (
81
- <div
82
- ref={carouselRef}
83
- className="relative w-full h-[490px] lg:h-[590px] cursor-grab"
84
- style={{ perspective: '1200px', transformStyle: 'preserve-3d' }}
85
- >
86
- <div ref={dragProxyRef} className="absolute inset-0 z-20"></div>
87
-
88
- {images.map((src, index) => (
89
- <div
90
- key={index}
91
- ref={el => cardsRef.current[index] = el}
92
- className="card-item absolute h-[480px] w-[300px] lg:h-[550px] lg:w-[350px] rounded-xl overflow-hidden shadow-2xl top-[25px] -left-4 md:left-36 lg:left-70"
93
- >
94
- <img
95
- src={src}
96
- alt={`carousel-item-${index}`}
97
- className="w-full h-full object-cover pointer-events-none"
98
- />
99
- </div>
100
- ))}
101
- </div>
102
- );
103
- };
104
-
105
- export default DragCarousel;