apex-ui-kit 2.0.6 → 2.1.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.
- package/dist/ApexUI-Kit/templates/AccordionMarquee/AccordionMarquee.jsx +74 -53
- package/dist/ApexUI-Kit/templates/BeamBar/BeamBar.jsx +5 -3
- package/dist/ApexUI-Kit/templates/BluePrintBackground/BluePrintBackground.jsx +14 -2
- package/dist/ApexUI-Kit/templates/CardStack/CardStack.jsx +129 -0
- package/dist/ApexUI-Kit/templates/Cards/BasicCard.jsx +3 -3
- package/dist/ApexUI-Kit/templates/Carousel/ParallaxCarousel.jsx +0 -161
- package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +39 -7
- package/dist/ApexUI-Kit/templates/EdgeGlowForm/EdgeGlowFrom.jsx +116 -98
- package/dist/ApexUI-Kit/templates/FlipProfile/FlipProfile.jsx +69 -37
- package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +2 -2
- package/dist/ApexUI-Kit/templates/HoverText/HoverText.jsx +14 -13
- package/dist/ApexUI-Kit/templates/HyperCard/HyperCard.jsx +59 -42
- package/dist/ApexUI-Kit/templates/KineticThreadsBackground/KineticThreadBackground.jsx +17 -2
- package/dist/ApexUI-Kit/templates/LensFlareBackground/LensFlareBackground.jsx +18 -3
- package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +15 -1
- package/dist/ApexUI-Kit/templates/MaskView/MaskView.jsx +345 -0
- package/dist/ApexUI-Kit/templates/MeteorCard/MeteorCard.jsx +34 -45
- package/dist/ApexUI-Kit/templates/NavDropDown/NavDropDown.jsx +272 -101
- package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +8 -2
- package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +13 -11
- package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +60 -67
- package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +69 -55
- package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +34 -14
- package/dist/ApexUI-Kit/templates/ScrollMarquee/CardMarquee.jsx +0 -0
- package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +4 -3
- package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +35 -16
- package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +11 -8
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeAnimation.js +40 -69
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +5 -1
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.jsx +25 -7
- package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +71 -52
- package/dist/cli/index.js +25 -14
- package/package.json +2 -2
- package/dist/ApexUI-Kit/templates/GlassCardStack/GlassCardStack.jsx +0 -111
- package/dist/ApexUI-Kit/templates/WaterDropReveal/WaterDropReveal.jsx +0 -151
|
@@ -24,12 +24,12 @@ const Testimonials = ({ testimonials }) => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<div className="w-full max-w-4xl mx-auto bg-slate-950 rounded-2xl p-8 md:p-12 border border-slate-800 grid md:grid-cols-2 gap-12 items-center overflow-hidden">
|
|
27
|
+
<div className="w-full max-w-4xl mx-auto bg-slate-950 rounded-2xl p-6 sm:p-8 md:p-12 border border-slate-800 grid md:grid-cols-2 gap-8 md:gap-12 items-center overflow-hidden">
|
|
28
28
|
|
|
29
29
|
{/* Left Part: Text Content and Navigation */}
|
|
30
30
|
<div className="flex flex-col justify-center h-full text-center md:text-left order-2 md:order-1">
|
|
31
|
-
{/* Title and
|
|
32
|
-
<div className="relative h-
|
|
31
|
+
{/* Title, subtitle and quote with animation; the min height keeps the buttons from jumping. */}
|
|
32
|
+
<div className="relative min-h-[13rem] sm:min-h-[12rem]">
|
|
33
33
|
<AnimatePresence initial={false} mode="wait">
|
|
34
34
|
<motion.div
|
|
35
35
|
key={activeIndex}
|
|
@@ -38,11 +38,11 @@ const Testimonials = ({ testimonials }) => {
|
|
|
38
38
|
animate="center"
|
|
39
39
|
exit="exit"
|
|
40
40
|
transition={{ duration: 0.4, ease: "easeInOut" }}
|
|
41
|
-
className="
|
|
41
|
+
className="w-full"
|
|
42
42
|
>
|
|
43
|
-
<h2 className="text-3xl font-bold text-white tracking-tight">{testimonials[activeIndex].author}</h2>
|
|
43
|
+
<h2 className="text-2xl sm:text-3xl font-bold text-white tracking-tight">{testimonials[activeIndex].author}</h2>
|
|
44
44
|
<p className="mt-2 text-lime-400 font-medium">{testimonials[activeIndex].title}</p>
|
|
45
|
-
<p className="mt-4 text-slate-400 text-sm leading-relaxed
|
|
45
|
+
<p className="mt-4 text-slate-400 text-sm leading-relaxed">"{testimonials[activeIndex].text}"</p>
|
|
46
46
|
</motion.div>
|
|
47
47
|
</AnimatePresence>
|
|
48
48
|
</div>
|
|
@@ -71,7 +71,9 @@ const Testimonials = ({ testimonials }) => {
|
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
73
|
{/* Right Part: Stacked Images */}
|
|
74
|
-
<div className="
|
|
74
|
+
<div className="flex w-full items-center justify-center order-1 md:order-2">
|
|
75
|
+
{/* Sized to the card plus the fan, so the stack stays centred and inside the panel. */}
|
|
76
|
+
<div className="relative h-[calc(16rem+30px)] w-[calc(12rem+60px)] sm:h-[calc(20rem+30px)] sm:w-[calc(16rem+60px)]">
|
|
75
77
|
{testimonials.map((testimonial, index) => {
|
|
76
78
|
// Determine the position of the card in the stack
|
|
77
79
|
const position = (index - activeIndex + testimonials.length) % testimonials.length;
|
|
@@ -82,7 +84,7 @@ const Testimonials = ({ testimonials }) => {
|
|
|
82
84
|
return (
|
|
83
85
|
<motion.div
|
|
84
86
|
key={testimonial.id || index}
|
|
85
|
-
className="absolute w-64 h-80 bg-slate-800 rounded-xl shadow-2xl overflow-hidden border-2 border-slate-700"
|
|
87
|
+
className="absolute w-48 h-64 sm:w-64 sm:h-80 bg-slate-800 rounded-xl shadow-2xl overflow-hidden border-2 border-slate-700"
|
|
86
88
|
initial={false}
|
|
87
89
|
animate={{
|
|
88
90
|
scale: 1 - position * 0.08,
|
|
@@ -107,6 +109,7 @@ const Testimonials = ({ testimonials }) => {
|
|
|
107
109
|
</motion.div>
|
|
108
110
|
);
|
|
109
111
|
})}
|
|
112
|
+
</div>
|
|
110
113
|
</div>
|
|
111
114
|
</div>
|
|
112
115
|
);
|
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
// config/animations.js
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
3
|
+
// Soft-edged circle that grows from `at` until it covers the screen. It animates
|
|
4
|
+
// a radial-gradient mask through a registered property, which stays smooth;
|
|
5
|
+
// scaling a blurred SVG mask to screen size re-rasterises every frame and flickers.
|
|
6
|
+
const circleReveal = (at) => `
|
|
7
|
+
@property --vt-r { syntax: '<length>'; inherits: false; initial-value: 0px; }
|
|
8
|
+
::view-transition-new(root) {
|
|
9
|
+
-webkit-mask-image: radial-gradient(circle at ${at}, #000 var(--vt-r), transparent calc(var(--vt-r) + 12vmax));
|
|
10
|
+
mask-image: radial-gradient(circle at ${at}, #000 var(--vt-r), transparent calc(var(--vt-r) + 12vmax));
|
|
11
|
+
animation: vt-circle var(--vt-duration) var(--vt-ease);
|
|
12
|
+
}
|
|
13
|
+
::view-transition-old(root) { animation: none; }
|
|
14
|
+
@keyframes vt-circle {
|
|
15
|
+
from { --vt-r: -12vmax; }
|
|
16
|
+
to { --vt-r: 150vmax; }
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
// Holds the last frame until the transition ends (no flash of the old theme)
|
|
21
|
+
// and keeps the browser's default plus-lighter blending out of the reveal.
|
|
22
|
+
const footer = `
|
|
23
|
+
::view-transition-old(root), ::view-transition-new(root) {
|
|
24
|
+
animation-fill-mode: both;
|
|
25
|
+
mix-blend-mode: normal;
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
5
28
|
|
|
6
29
|
export function buildAnimationCSS(animation, opts = {}) {
|
|
30
|
+
return presetCSS(animation, opts) + footer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function presetCSS(animation, opts = {}) {
|
|
7
34
|
const duration = opts.duration || "1.5s";
|
|
8
35
|
const ease = opts.ease || "var(--vt-ease)";
|
|
9
36
|
|
|
37
|
+
// `var(--vt-ease)` would refer to itself here, so only set a real easing.
|
|
10
38
|
const header = `
|
|
11
|
-
:root { --vt-duration: ${duration}
|
|
39
|
+
:root { --vt-duration: ${duration};${ease === "var(--vt-ease)" ? "" : ` --vt-ease: ${ease};`} }
|
|
12
40
|
::view-transition-group(root){ animation-timing-function: var(--vt-ease); }
|
|
13
41
|
`;
|
|
14
42
|
|
|
@@ -37,72 +65,15 @@ export function buildAnimationCSS(animation, opts = {}) {
|
|
|
37
65
|
const preset = typeof animation === "string" ? animation : animation?.type;
|
|
38
66
|
|
|
39
67
|
switch (preset) {
|
|
40
|
-
case "circle":
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
44
|
-
return header + `
|
|
45
|
-
::view-transition-new(root) {
|
|
46
|
-
mask: url('data:image/svg+xml,${circleSVG}') center / 0 no-repeat;
|
|
47
|
-
-webkit-mask: url('data:image/svg+xml,${circleSVG}') center / 0 no-repeat;
|
|
48
|
-
animation: vt-scale var(--vt-duration);
|
|
49
|
-
}
|
|
50
|
-
::view-transition-old(root), .dark::view-transition-old(root) {
|
|
51
|
-
animation: vt-scale var(--vt-duration);
|
|
52
|
-
}
|
|
53
|
-
@keyframes vt-scale {
|
|
54
|
-
to { mask-size: 200vmax; -webkit-mask-size: 200vmax; }
|
|
55
|
-
}
|
|
56
|
-
`;
|
|
57
|
-
}
|
|
68
|
+
case "circle":
|
|
69
|
+
// Grows from the clicked button (ThemeToggle sets --cx / --cy).
|
|
70
|
+
return header + circleReveal("var(--cx, 50%) var(--cy, 50%)");
|
|
58
71
|
|
|
59
|
-
case "circle-left":
|
|
60
|
-
|
|
61
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><filter id="b"><feGaussianBlur stdDeviation="2"/></filter></defs><circle cx="0" cy="0" r="18" fill="white" filter="url(%23b)"/></svg>`
|
|
62
|
-
);
|
|
63
|
-
return header + `
|
|
64
|
-
::view-transition-new(root) {
|
|
65
|
-
mask: url('data:image/svg+xml,${svg}') top left / 0 no-repeat;
|
|
66
|
-
-webkit-mask: url('data:image/svg+xml,${svg}') top left / 0 no-repeat;
|
|
67
|
-
transform-origin: top left;
|
|
68
|
-
animation: vt-scale-tl var(--vt-duration);
|
|
69
|
-
}
|
|
70
|
-
::view-transition-old(root), .dark::view-transition-old(root) {
|
|
71
|
-
transform-origin: top left;
|
|
72
|
-
animation: vt-scale-tl var(--vt-duration);
|
|
73
|
-
}
|
|
74
|
-
@keyframes vt-scale-tl { to { mask-size: 350vmax; -webkit-mask-size: 350vmax; } }
|
|
75
|
-
`;
|
|
76
|
-
}
|
|
77
|
-
case "circle-right": {
|
|
78
|
-
const svg = esc(
|
|
79
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
|
80
|
-
<defs><filter id="b"><feGaussianBlur stdDeviation="2"/></filter></defs>
|
|
81
|
-
<circle cx="40" cy="0" r="18" fill="white" filter="url(%23b)"/>
|
|
82
|
-
</svg>`
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
return header + `
|
|
86
|
-
::view-transition-new(root) {
|
|
87
|
-
mask: url('data:image/svg+xml,${svg}') top right / 0 no-repeat;
|
|
88
|
-
-webkit-mask: url('data:image/svg+xml,${svg}') top right / 0 no-repeat;
|
|
89
|
-
transform-origin: top right;
|
|
90
|
-
animation: vt-scale-tr 0.9s;
|
|
91
|
-
}
|
|
72
|
+
case "circle-left":
|
|
73
|
+
return header + circleReveal("0 0");
|
|
92
74
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
animation: vt-scale-tr 0.9s;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@keyframes vt-scale-tr {
|
|
99
|
-
to {
|
|
100
|
-
mask-size: 350vmax;
|
|
101
|
-
-webkit-mask-size: 350vmax;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
`;
|
|
105
|
-
}
|
|
75
|
+
case "circle-right":
|
|
76
|
+
return header + circleReveal("100% 0");
|
|
106
77
|
case "split-reveal-horizontal": {
|
|
107
78
|
return header + `
|
|
108
79
|
::view-transition-new(root) {
|
|
@@ -241,7 +212,7 @@ export function buildAnimationCSS(animation, opts = {}) {
|
|
|
241
212
|
}
|
|
242
213
|
`;
|
|
243
214
|
}
|
|
244
|
-
|
|
215
|
+
// Add more animation
|
|
245
216
|
|
|
246
217
|
|
|
247
218
|
|
|
@@ -249,4 +220,4 @@ export function buildAnimationCSS(animation, opts = {}) {
|
|
|
249
220
|
default:
|
|
250
221
|
return header; // no-op if unknown
|
|
251
222
|
}
|
|
252
|
-
}
|
|
223
|
+
}
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
--color-aside-beam: #fff;
|
|
72
72
|
|
|
73
73
|
/* animation defaults (can be overridden) */
|
|
74
|
-
--vt-duration:
|
|
74
|
+
--vt-duration: 1.7s;
|
|
75
75
|
--vt-ease: ease-in-out;
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -95,6 +95,8 @@
|
|
|
95
95
|
--color-template-border: #fdc700;
|
|
96
96
|
--color-template-text: #fdc700;
|
|
97
97
|
--color-scan: #460809;
|
|
98
|
+
--selection-bg: #460809;
|
|
99
|
+
--selection-text: #fdc700;
|
|
98
100
|
|
|
99
101
|
/* Hero Component */
|
|
100
102
|
--right-color-bg: #e5c287;
|
|
@@ -246,6 +248,8 @@
|
|
|
246
248
|
--color-btn-hover-text: #d9f99d;
|
|
247
249
|
--color-stroke-color: #9ae600;
|
|
248
250
|
--color-scan: #9ae600;
|
|
251
|
+
--selection-bg: #9ae600;
|
|
252
|
+
--selection-text: #1e2939;
|
|
249
253
|
/* Hero Component */
|
|
250
254
|
--color-stroke-color: #9ae600;
|
|
251
255
|
--color-template: #101828;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useId } from "react";
|
|
2
|
+
import { flushSync } from "react-dom";
|
|
2
3
|
import { FiSun, FiMoon } from "react-icons/fi";
|
|
3
4
|
import { buildAnimationCSS } from "./ThemeAnimation.js";
|
|
4
5
|
|
|
6
|
+
// Swaps the theme-* class on <html>.
|
|
7
|
+
function applyThemeClass(theme) {
|
|
8
|
+
const html = document.documentElement;
|
|
9
|
+
[...html.classList].forEach((c) => c.startsWith("theme-") && html.classList.remove(c));
|
|
10
|
+
html.classList.add(`theme-${theme}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export default function ThemeToggle({
|
|
6
14
|
animation = "circle",
|
|
7
15
|
duration = "1.5s",
|
|
@@ -29,12 +37,19 @@ export default function ThemeToggle({
|
|
|
29
37
|
// ------------------------------
|
|
30
38
|
useEffect(() => {
|
|
31
39
|
const rawCSS = buildAnimationCSS(animation, { duration, ease });
|
|
40
|
+
const suffix = uid.replace(/[^\w-]/g, "");
|
|
32
41
|
|
|
33
|
-
// ✅ Scope selectors with html[vt-owner]
|
|
42
|
+
// ✅ Scope selectors with html[vt-owner]; :root variables and keyframe names
|
|
43
|
+
// too, so several toggles on one page don't override each other's settings.
|
|
34
44
|
const scopedCSS = rawCSS
|
|
45
|
+
.replace(/(?<![-\w])vt-(?=[a-z])/g, `vt-${suffix}-`)
|
|
46
|
+
.replace(/:root\b/g, `html[vt-owner="${uid}"]`)
|
|
35
47
|
.replaceAll("::view-transition-new", `html[vt-owner="${uid}"]::view-transition-new`)
|
|
36
48
|
.replaceAll("::view-transition-old", `html[vt-owner="${uid}"]::view-transition-old`)
|
|
37
|
-
.replaceAll("::view-transition-group", `html[vt-owner="${uid}"]::view-transition-group`)
|
|
49
|
+
.replaceAll("::view-transition-group", `html[vt-owner="${uid}"]::view-transition-group`)
|
|
50
|
+
// No CSS transitions while the view transition runs, otherwise elements are
|
|
51
|
+
// captured mid-fade in the old colours and snap to the new ones afterwards.
|
|
52
|
+
+ `html[vt-owner="${uid}"] *, html[vt-owner="${uid}"] *::before, html[vt-owner="${uid}"] *::after { transition: none !important; }`;
|
|
38
53
|
|
|
39
54
|
let styleEl = document.getElementById(`vt-style-${uid}`);
|
|
40
55
|
if (!styleEl) {
|
|
@@ -49,9 +64,7 @@ export default function ThemeToggle({
|
|
|
49
64
|
// Apply theme globally + notify
|
|
50
65
|
// ------------------------------
|
|
51
66
|
useEffect(() => {
|
|
52
|
-
|
|
53
|
-
html.classList.forEach((c) => c.startsWith("theme-") && html.classList.remove(c));
|
|
54
|
-
html.classList.add(`theme-${theme}`);
|
|
67
|
+
applyThemeClass(theme);
|
|
55
68
|
|
|
56
69
|
try {
|
|
57
70
|
localStorage.setItem("apexui-theme", theme);
|
|
@@ -88,7 +101,12 @@ export default function ThemeToggle({
|
|
|
88
101
|
// ✅ Use html instead of body
|
|
89
102
|
document.documentElement.setAttribute("vt-owner", uid);
|
|
90
103
|
|
|
91
|
-
|
|
104
|
+
// Apply the theme synchronously: the browser takes the transition's "new"
|
|
105
|
+
// snapshot as soon as this returns, before a normal React update would land.
|
|
106
|
+
const run = () => {
|
|
107
|
+
applyThemeClass(nextTheme);
|
|
108
|
+
flushSync(() => setTheme(nextTheme));
|
|
109
|
+
};
|
|
92
110
|
|
|
93
111
|
if (document.startViewTransition) {
|
|
94
112
|
const vt = document.startViewTransition(run);
|
|
@@ -115,4 +133,4 @@ export default function ThemeToggle({
|
|
|
115
133
|
{isLight ? <FiMoon /> : <FiSun />}
|
|
116
134
|
</button>
|
|
117
135
|
);
|
|
118
|
-
}
|
|
136
|
+
}
|
|
@@ -5,81 +5,100 @@ const ToolTip = ({ items }) => {
|
|
|
5
5
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
6
6
|
const tooltipRefs = useRef([]);
|
|
7
7
|
const avatarRefs = useRef([]);
|
|
8
|
+
const last = items.length - 1;
|
|
8
9
|
|
|
9
10
|
useEffect(() => {
|
|
10
|
-
//
|
|
11
|
+
// Lift the active avatar, dim the others
|
|
11
12
|
items.forEach((_, idx) => {
|
|
12
13
|
const isHovered = hoveredIndex === idx;
|
|
13
|
-
|
|
14
|
-
// Animate the avatar image
|
|
15
14
|
gsap.to(avatarRefs.current[idx], {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
y: isHovered ? -6 : 0,
|
|
16
|
+
scale: isHovered ? 1.12 : 1,
|
|
17
|
+
opacity: hoveredIndex === null || isHovered ? 1 : 0.55,
|
|
18
|
+
duration: 0.3,
|
|
19
|
+
ease: "power3.out",
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Pop the tooltip in
|
|
24
24
|
if (hoveredIndex !== null && tooltipRefs.current[hoveredIndex]) {
|
|
25
25
|
gsap.fromTo(
|
|
26
26
|
tooltipRefs.current[hoveredIndex],
|
|
27
|
-
{ opacity: 0, y:
|
|
28
|
-
{
|
|
29
|
-
opacity: 1,
|
|
30
|
-
y: 0,
|
|
31
|
-
scale: 1,
|
|
32
|
-
duration: 0.4,
|
|
33
|
-
ease: "power3.out",
|
|
34
|
-
}
|
|
27
|
+
{ opacity: 0, y: 12, scale: 0.85, rotate: 0 },
|
|
28
|
+
{ opacity: 1, y: 0, scale: 1, duration: 0.45, ease: "back.out(1.8)" }
|
|
35
29
|
);
|
|
36
30
|
}
|
|
37
31
|
}, [hoveredIndex, items]);
|
|
38
32
|
|
|
33
|
+
// The tooltip swings toward the pointer while it moves across the avatar.
|
|
34
|
+
const handlePointerMove = (e, idx) => {
|
|
35
|
+
const tip = tooltipRefs.current[idx];
|
|
36
|
+
if (!tip || e.pointerType !== "mouse") return;
|
|
37
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
38
|
+
const offset = ((e.clientX - rect.left) / rect.width - 0.5) * 2; // -1 … 1
|
|
39
|
+
gsap.to(tip, { rotate: offset * 10, x: offset * 12, duration: 0.5, ease: "power3.out", overwrite: "auto" });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Keep the end tooltips inside small screens: align them to their outer edge.
|
|
43
|
+
const placement = (idx) =>
|
|
44
|
+
idx === 0 && last > 0 ? { box: "left-0", arrow: "left-6" }
|
|
45
|
+
: idx === last && last > 0 ? { box: "right-0", arrow: "right-6" }
|
|
46
|
+
: { box: "left-1/2 -translate-x-1/2", arrow: "left-1/2 -translate-x-1/2" };
|
|
47
|
+
|
|
39
48
|
return (
|
|
40
|
-
<div className="flex items-end -space-x-4">
|
|
41
|
-
{items.map((item, idx) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
<div className="flex items-end -space-x-3 sm:-space-x-4">
|
|
50
|
+
{items.map((item, idx) => {
|
|
51
|
+
const { box, arrow } = placement(idx);
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
key={item.id ?? item.name}
|
|
55
|
+
onPointerEnter={(e) => e.pointerType === "mouse" && setHoveredIndex(idx)}
|
|
56
|
+
onPointerLeave={(e) => e.pointerType === "mouse" && setHoveredIndex(null)}
|
|
57
|
+
onPointerMove={(e) => handlePointerMove(e, idx)}
|
|
58
|
+
// Tap to toggle on touch screens
|
|
59
|
+
onClick={() => setHoveredIndex((cur) => (cur === idx ? null : idx))}
|
|
60
|
+
className="relative cursor-pointer"
|
|
61
|
+
style={{ zIndex: hoveredIndex === idx ? 50 : 10 + idx }}
|
|
62
|
+
>
|
|
63
|
+
{hoveredIndex === idx && (
|
|
64
|
+
<div className={`pointer-events-none absolute bottom-full mb-4 z-50 ${box}`}>
|
|
65
|
+
{/* The card and its arrow animate together, so the arrow follows every swing. */}
|
|
66
|
+
<div ref={(el) => (tooltipRefs.current[idx] = el)} className="relative origin-bottom">
|
|
67
|
+
<div className="relative overflow-hidden rounded-xl border border-white/10 bg-zinc-950 px-3.5 py-2.5 shadow-[0_16px_40px_-12px_rgba(0,0,0,0.7)]">
|
|
68
|
+
<span className="absolute inset-x-3 top-0 h-px bg-gradient-to-r from-transparent via-lime-400 to-cyan-400" />
|
|
69
|
+
<span className="flex items-center gap-1.5 whitespace-nowrap text-sm font-semibold text-white">
|
|
70
|
+
<span className="h-1.5 w-1.5 rounded-full bg-lime-400 shadow-[0_0_8px_#a3e635]" />
|
|
71
|
+
{item.name}
|
|
72
|
+
</span>
|
|
73
|
+
<span className="mt-0.5 block whitespace-nowrap text-xs text-zinc-400">{item.designation}</span>
|
|
74
|
+
</div>
|
|
75
|
+
{/* Downward arrow: filled triangle that covers the card's bottom border, with the border on its two sides only */}
|
|
76
|
+
<svg width="16" height="8" viewBox="0 0 16 8" className={`absolute top-full -mt-px ${arrow}`} aria-hidden="true">
|
|
77
|
+
<path d="M0 0 L8 8 L16 0 Z" fill="#09090b" />
|
|
78
|
+
<path d="M0.5 0 L8 7.5 L15.5 0" fill="none" stroke="rgba(255,255,255,0.1)" />
|
|
79
|
+
</svg>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
<div ref={(el) => (avatarRefs.current[idx] = el)} className="rounded-full bg-gradient-to-br from-lime-400 via-emerald-400 to-cyan-400 p-[2px] shadow-lg shadow-black/30">
|
|
84
|
+
<img
|
|
85
|
+
src={item.image}
|
|
86
|
+
alt={item.name}
|
|
87
|
+
draggable="false"
|
|
88
|
+
className="h-12 w-12 sm:h-14 sm:w-14 rounded-full border-2 border-zinc-950 object-cover object-top"
|
|
89
|
+
/>
|
|
61
90
|
</div>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
src={item.image}
|
|
66
|
-
alt={item.name}
|
|
67
|
-
className="h-14 w-14 object-cover object-center rounded-full border-2 border-white shadow-lg transition-transform"
|
|
68
|
-
style={{
|
|
69
|
-
zIndex: hoveredIndex === idx ? 50 : 10,
|
|
70
|
-
transition: "all 0.25s ease-in-out",
|
|
71
|
-
}}
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
74
|
-
))}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
75
94
|
</div>
|
|
76
95
|
);
|
|
77
96
|
};
|
|
78
97
|
|
|
79
98
|
export default function ToolTipInner({ items }) {
|
|
80
99
|
return (
|
|
81
|
-
<div className="flex flex-row items-center justify-center">
|
|
100
|
+
<div className="flex flex-row items-center justify-center pt-24">
|
|
82
101
|
<ToolTip items={items} />
|
|
83
102
|
</div>
|
|
84
103
|
);
|
|
85
|
-
}
|
|
104
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { execSync } from "child_process";
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = path.dirname(__filename);
|
|
11
11
|
const componentRoot = path.join(__dirname, "..", "ApexUI-Kit", "templates");
|
|
12
|
-
// Component name map
|
|
12
|
+
// Component name map (CLI name -> folder in templates). Older names stay as aliases.
|
|
13
13
|
const componentMap = {
|
|
14
14
|
"avatar": "Avatar",
|
|
15
15
|
"accordion-marquee": "AccordionMarquee",
|
|
@@ -19,10 +19,10 @@ const componentMap = {
|
|
|
19
19
|
"parallax-carousel": "ParallaxCarousel",
|
|
20
20
|
"drag-carousel": "DragCarousel",
|
|
21
21
|
"basic-card": "Cards",
|
|
22
|
-
"edge-glow-
|
|
22
|
+
"edge-glow-form": "EdgeGlowForm",
|
|
23
|
+
"edge-glow-from": "EdgeGlowForm",
|
|
23
24
|
"glare-card": "GlareCard",
|
|
24
|
-
"
|
|
25
|
-
"glass-profile": "GlassProfile",
|
|
25
|
+
"card-stack": "CardStack",
|
|
26
26
|
"hover-text": "HoverText",
|
|
27
27
|
"hyper-card": "HyperCard",
|
|
28
28
|
"kinetic-threads-background": "KineticThreadsBackground",
|
|
@@ -33,6 +33,7 @@ const componentMap = {
|
|
|
33
33
|
"nav-dropdown": "NavDropDown",
|
|
34
34
|
"orbs-background": "OrbsBackground",
|
|
35
35
|
"pointer-follower": "PointerFollower",
|
|
36
|
+
"flip-profile": "FlipProfile",
|
|
36
37
|
"flip-profiler": "FlipProfile",
|
|
37
38
|
"parallax-profile": "ParallaxProfile",
|
|
38
39
|
"parallax-card": "ParallaxCard",
|
|
@@ -41,10 +42,18 @@ const componentMap = {
|
|
|
41
42
|
"scroll-marquee": "ScrollMarquee",
|
|
42
43
|
"smart-grid-card": "SmartGridCard",
|
|
43
44
|
"smoke-background": "SmokeBackground",
|
|
45
|
+
"tooltip": "ToolTip",
|
|
44
46
|
"tool-tip": "ToolTip",
|
|
45
47
|
"theme-toggle": "ThemeToggle",
|
|
46
48
|
"testimonials": "Testimonials",
|
|
47
|
-
"
|
|
49
|
+
"mask-view": "MaskView",
|
|
50
|
+
"water-drop-reveal": "MaskView",
|
|
51
|
+
};
|
|
52
|
+
// Packages only some components need, installed on top of the shared ones.
|
|
53
|
+
const extraDeps = {
|
|
54
|
+
Cards: ["react-router-dom"],
|
|
55
|
+
LuminousParticleOcean: ["@react-three/fiber"],
|
|
56
|
+
SmokeBackground: ["ogl"],
|
|
48
57
|
};
|
|
49
58
|
async function main() {
|
|
50
59
|
const args = process.argv.slice(2);
|
|
@@ -52,7 +61,7 @@ async function main() {
|
|
|
52
61
|
const rawInput = args[1];
|
|
53
62
|
if (command !== "add" || !rawInput) {
|
|
54
63
|
console.log(chalk.yellow("Usage:"));
|
|
55
|
-
console.log(" npx apex-ui-
|
|
64
|
+
console.log(" npx apex-ui-kit add <component-name>");
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
58
67
|
const input = rawInput.toLowerCase();
|
|
@@ -60,7 +69,7 @@ async function main() {
|
|
|
60
69
|
if (!matchedName) {
|
|
61
70
|
console.log(chalk.red(`❌ Component '${rawInput}' not found in ApexUI-Kit.`));
|
|
62
71
|
console.log(chalk.gray("👉 Available components:"));
|
|
63
|
-
Object.
|
|
72
|
+
Object.keys(componentMap).forEach((c) => console.log(` • ${c}`));
|
|
64
73
|
return;
|
|
65
74
|
}
|
|
66
75
|
// Validate package.json
|
|
@@ -74,7 +83,7 @@ async function main() {
|
|
|
74
83
|
type: "text",
|
|
75
84
|
name: "dirInput",
|
|
76
85
|
message: "📁 Install in which folder? (use . for current)",
|
|
77
|
-
initial: "."
|
|
86
|
+
initial: ".",
|
|
78
87
|
});
|
|
79
88
|
let basePath = "";
|
|
80
89
|
if (dirInput.trim() === ".") {
|
|
@@ -84,8 +93,8 @@ async function main() {
|
|
|
84
93
|
message: "📍 Are you currently inside the folder where ApexUI-Kit should be?",
|
|
85
94
|
choices: [
|
|
86
95
|
{ title: "Yes (install here)", value: "yes" },
|
|
87
|
-
{ title: "No (I'll choose a path)", value: "no" }
|
|
88
|
-
]
|
|
96
|
+
{ title: "No (I'll choose a path)", value: "no" },
|
|
97
|
+
],
|
|
89
98
|
});
|
|
90
99
|
if (confirmInside === "yes") {
|
|
91
100
|
basePath = process.cwd();
|
|
@@ -95,7 +104,7 @@ async function main() {
|
|
|
95
104
|
type: "text",
|
|
96
105
|
name: "targetPath",
|
|
97
106
|
message: "📂 Enter target folder path (or use . for current)",
|
|
98
|
-
initial: "."
|
|
107
|
+
initial: ".",
|
|
99
108
|
});
|
|
100
109
|
basePath = path.resolve(targetPath.trim());
|
|
101
110
|
}
|
|
@@ -110,8 +119,8 @@ async function main() {
|
|
|
110
119
|
message: "🧠 Choose language",
|
|
111
120
|
choices: [
|
|
112
121
|
{ title: "JavaScript (JSX)", value: "jsx" },
|
|
113
|
-
{ title: "TypeScript (Coming Soon)", value: "ts", disabled: true }
|
|
114
|
-
]
|
|
122
|
+
{ title: "TypeScript (Coming Soon)", value: "ts", disabled: true },
|
|
123
|
+
],
|
|
115
124
|
});
|
|
116
125
|
// Install dependencies
|
|
117
126
|
console.log(chalk.cyan("📦 Installing dependencies..."));
|
|
@@ -122,7 +131,9 @@ async function main() {
|
|
|
122
131
|
"@fortawesome/fontawesome-svg-core",
|
|
123
132
|
"@fortawesome/free-solid-svg-icons",
|
|
124
133
|
"@fortawesome/react-fontawesome",
|
|
125
|
-
"react-icons"
|
|
134
|
+
"react-icons",
|
|
135
|
+
"lucide-react",
|
|
136
|
+
...(extraDeps[matchedName] ?? []),
|
|
126
137
|
];
|
|
127
138
|
try {
|
|
128
139
|
execSync(`npm install ${deps.join(" ")}`, { stdio: "inherit" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apex-ui-kit",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Apex UI Kit - A React component library for building modern user interfaces",
|
|
6
6
|
"keywords": [
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"main": "dist/cli/index.js",
|
|
25
25
|
"bin": {
|
|
26
|
-
"apex-ui-kit": "
|
|
26
|
+
"apex-ui-kit": "dist/cli/index.js"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist"
|