apex-ui-kit 2.3.0 → 2.3.2

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/ReadMe.md CHANGED
@@ -1,75 +1,251 @@
1
- # Apex UI Kit
2
-
3
- Apex UI Kit is a modern, blazing-fast, beautifully animated React UI library built with Tailwind CSS, GSAP, and Framer Motion. Perfect for modern apps, portfolios, dashboards, and high-performance UIs.
4
-
5
- ---
6
-
7
- ## 🌐 Website & Docs
8
-
9
- - **Main Site:** [https://apex-ui.in/](https://apex-ui.in/)
10
- - **Introduction:** [https://apex-ui.in/components/docs/getting-started/introduction](https://apex-ui.in/components/docs/getting-started/introduction)
11
- - **Components:** [https://apex-ui.in/components/glass-navbar](https://apex-ui.in/components/glass-navbar)
12
-
13
- ## 🚀 Installation
14
-
15
- ### 1. Install the package
16
-
17
- ```bash
18
- npm install apex-ui-kit
19
- ```
20
-
21
- ### 2. Add a component using the CLI
22
-
23
- ```bash
24
- npx apex-ui-kit add hyper-card
25
- ```
26
-
27
- ---
28
-
29
- ## 🧩 Usage Example
30
-
31
- ```jsx
32
- import HyperCard from "./ApexUI-Kit/HyperCard/HyperCard.jsx";
33
-
34
- const App = () => {
35
- return (
36
- <div>
37
- <HyperCard
38
- starColor="#9f7aea"
39
- glow={true}
40
- beamCount={10}
41
- starCount={250}
42
- />
43
- </div>
44
- );
45
- };
46
-
47
- export default App;
48
- ```
49
-
50
- ---
51
-
52
- ## 📦 Features
53
-
54
- - Modern, animated React components
55
- - Easy CLI for adding components
56
- - Built with Tailwind CSS, GSAP, Framer Motion
57
- - Perfect for dashboards, portfolios, and modern web apps
58
-
59
- ---
60
-
61
- ## 📚 Documentation
62
-
63
- - More components and usage: [Coming Soon]
64
-
65
- ---
66
-
67
- ## 🛠️ Contributing
68
-
69
- Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
70
-
71
- ---
72
-
73
- ## 📝 License
74
-
75
- MIT
1
+ # ApexUI Kit
2
+
3
+ [![npm version](https://img.shields.io/npm/v/apex-ui-kit?color=a3e635&label=npm)](https://www.npmjs.com/package/apex-ui-kit)
4
+ [![npm downloads](https://img.shields.io/npm/dm/apex-ui-kit?color=a3e635)](https://www.npmjs.com/package/apex-ui-kit)
5
+ [![license](https://img.shields.io/npm/l/apex-ui-kit?color=a3e635)](./Licence.md)
6
+
7
+ **90 free, animated React components for Tailwind CSS.** Pick one, add it with a single command, and the source lands in your project so you can read it and change anything.
8
+
9
+ Built with React, Tailwind CSS and Framer Motion, plus GSAP, Canvas and WebGL where a component needs them.
10
+
11
+ - Website: [apex-ui.in](https://apex-ui.in)
12
+ - All components: [apex-ui.in/components](https://apex-ui.in/components)
13
+ - Getting started: [Introduction](https://apex-ui.in/components/docs/getting-started/introduction)
14
+ - CLI guide: [ApexUI CLI setup](https://apex-ui.in/components/docs/getting-started/installation/apexui-cli)
15
+
16
+ ## Why ApexUI
17
+
18
+ - **90 components** across backgrounds, cards, text effects, navigation, buttons, interactive UI, AI interfaces, loaders and maps.
19
+ - **You own the code.** The CLI copies plain `.jsx` files into your project, no runtime package to keep in sync.
20
+ - **Every docs page has** a live preview, a playground to tweak props, a Desktop / Tablet / Mobile preview, the full props table and a ready prompt you can paste into ChatGPT or Claude.
21
+ - **Smooth on real devices.** Animations stick to transform and opacity, respect reduced motion, and work with mouse, touch and keyboard.
22
+ - **Light and dark** themes on the components that have a surface (`theme="light" | "dark"`).
23
+
24
+ ## Requirements
25
+
26
+ - React 18 or newer
27
+ - Tailwind CSS (v4 recommended, see the [Tailwind setup guide](https://apex-ui.in/components/docs/getting-started/installation/tailwind-setup))
28
+
29
+ ## Quick start
30
+
31
+ 1. Install the CLI package:
32
+
33
+ ```bash
34
+ npm install apex-ui-kit
35
+ ```
36
+
37
+ 2. Add any component by its CLI name:
38
+
39
+ ```bash
40
+ npx apex-ui-kit add gooey-nav
41
+ ```
42
+
43
+ The CLI asks where to put the files, installs the shared dependencies (Framer Motion, GSAP, Three.js, Lucide icons and a few more, plus anything that one component needs, such as `ogl`), and copies the component to:
44
+
45
+ ```
46
+ <your folder>/ApexUI-Kit/<ComponentName>/
47
+ ```
48
+
49
+ 3. Import it and use it:
50
+
51
+ ```jsx
52
+ import GooeyNav from "./ApexUI-Kit/GooeyNav/GooeyNav.jsx";
53
+
54
+ export default function App() {
55
+ return (
56
+ <GooeyNav
57
+ accentColor="#a3e635"
58
+ items={[
59
+ { label: "Home", href: "/" },
60
+ { label: "Components", href: "/components" },
61
+ { label: "Docs", href: "/docs" },
62
+ ]}
63
+ />
64
+ );
65
+ }
66
+ ```
67
+
68
+ ## Example: AI prompt with a model picker
69
+
70
+ ```bash
71
+ npx apex-ui-kit add ai-model-prompt
72
+ ```
73
+
74
+ ```jsx
75
+ import { useState } from "react";
76
+ import { DEFAULT_LLM_MODELS, AiModelPrompt } from "./ApexUI-Kit/AiModelPrompt";
77
+
78
+ export function PromptComposer() {
79
+ const [modelValue, setModelValue] = useState("claude-fable-5");
80
+ const [configurations, setConfigurations] = useState({});
81
+ const [prompt, setPrompt] = useState("");
82
+
83
+ return (
84
+ <AiModelPrompt
85
+ models={DEFAULT_LLM_MODELS}
86
+ value={modelValue}
87
+ onModelChange={(model) => setModelValue(model.value)}
88
+ configurations={configurations}
89
+ onConfigurationChange={(_, __, next) => setConfigurations(next)}
90
+ prompt={prompt}
91
+ onPromptChange={setPrompt}
92
+ onSubmit={({ model, configuration, prompt }) => sendToYourApi({ model, configuration, prompt })}
93
+ />
94
+ );
95
+ }
96
+ ```
97
+
98
+ Every component's props, variants and copy-ready code are on its docs page.
99
+
100
+ ## Components
101
+
102
+ ### Backgrounds (11)
103
+
104
+ | Component | CLI name |
105
+ | --- | --- |
106
+ | [Aurora Background](https://apex-ui.in/components/aurora-background) | `aurora-background` |
107
+ | [BluePrint Background](https://apex-ui.in/components/blueprint-background) | `blueprint-background` |
108
+ | [Dot Wave Background](https://apex-ui.in/components/dot-wave-background) | `dot-wave-background` |
109
+ | [Kinetic Threads Background](https://apex-ui.in/components/kinetic-threads-background) | `kinetic-threads-background` |
110
+ | [Lens Flare Background](https://apex-ui.in/components/lens-flare-background) | `lens-flare-background` |
111
+ | [Liquid Gradient](https://apex-ui.in/components/liquid-gradient) | `liquid-gradient` |
112
+ | [Luminous Particle Ocean](https://apex-ui.in/components/luminous-particle-ocean) | `luminous-particle-ocean` |
113
+ | [Orbs Background](https://apex-ui.in/components/orbs-background) | `orbs-background` |
114
+ | [Rain Background](https://apex-ui.in/components/rain-background) | `rain-background` |
115
+ | [Ripple Background](https://apex-ui.in/components/ripple-background) | `ripple-background` |
116
+ | [Smoke Background](https://apex-ui.in/components/smoke-background) | `smoke-background` |
117
+
118
+ ### Cards (17)
119
+
120
+ | Component | CLI name |
121
+ | --- | --- |
122
+ | [Bounce Cards](https://apex-ui.in/components/bounce-cards) | `bounce-cards` |
123
+ | [Card Stack](https://apex-ui.in/components/card-stack) | `card-stack` |
124
+ | [Card Swap 3D](https://apex-ui.in/components/card-swap-3d) | `card-swap-3d` |
125
+ | [Cards](https://apex-ui.in/components/cards) | `basic-card`, `meteor-card` |
126
+ | [Folder](https://apex-ui.in/components/folder) | `folder` |
127
+ | [Glare Card](https://apex-ui.in/components/glare-card) | `glare-card` |
128
+ | [Holo Card](https://apex-ui.in/components/holo-card) | `holo-card` |
129
+ | [Hyper Card](https://apex-ui.in/components/hyper-card) | `hyper-card` |
130
+ | [Iso Stack](https://apex-ui.in/components/iso-stack) | `iso-stack` |
131
+ | [Parallax Card](https://apex-ui.in/components/parallax-card) | `parallax-card` |
132
+ | [Pixel Transition](https://apex-ui.in/components/pixel-transition) | `pixel-transition` |
133
+ | [Pricing Cards](https://apex-ui.in/components/pricing-cards) | `pricing-cards` |
134
+ | [Profile](https://apex-ui.in/components/profile) | `flip-profile`, `parallax-profile`, `profile-card` |
135
+ | [Smart Grid Card](https://apex-ui.in/components/smart-grid-card) | `smart-grid-card` |
136
+ | [Spotlight Grid](https://apex-ui.in/components/spotlight-grid) | `spotlight-grid` |
137
+ | [Swipe Cards](https://apex-ui.in/components/swipe-cards) | `swipe-cards` |
138
+ | [Testimonials](https://apex-ui.in/components/testimonials) | `testimonials` |
139
+
140
+ ### Text & Motion (18)
141
+
142
+ | Component | CLI name |
143
+ | --- | --- |
144
+ | [3D Text Ring](https://apex-ui.in/components/text-ring-3d) | `text-ring-3d` |
145
+ | [Accordion Marquee](https://apex-ui.in/components/accordion-marquee) | `accordion-marquee` |
146
+ | [Beam Bar](https://apex-ui.in/components/beam-bar) | `beam-bar` |
147
+ | [Blur Reveal Text](https://apex-ui.in/components/blur-reveal-text) | `blur-reveal-text` |
148
+ | [Circular Text](https://apex-ui.in/components/circular-text) | `circular-text` |
149
+ | [Extruded Text](https://apex-ui.in/components/extruded-text) | `extruded-text` |
150
+ | [Flip Clock](https://apex-ui.in/components/flip-clock) | `flip-clock` |
151
+ | [Glitch Text](https://apex-ui.in/components/glitch-text) | `glitch-text` |
152
+ | [Hover Text](https://apex-ui.in/components/hover-text) | `hover-text` |
153
+ | [Mask View](https://apex-ui.in/components/mask-view) | `mask-view` |
154
+ | [Proximity Text](https://apex-ui.in/components/proximity-text) | `proximity-text` |
155
+ | [Rolling Counter](https://apex-ui.in/components/rolling-counter) | `rolling-counter` |
156
+ | [Rotating Text](https://apex-ui.in/components/rotating-text) | `rotating-text` |
157
+ | [Scramble Text](https://apex-ui.in/components/scramble-text) | `scramble-text` |
158
+ | [Scroll Marquee](https://apex-ui.in/components/scroll-marquee) | `scroll-marquee` |
159
+ | [Shiny Text](https://apex-ui.in/components/shiny-text) | `shiny-text` |
160
+ | [True Focus](https://apex-ui.in/components/true-focus) | `true-focus` |
161
+ | [Typewriter Text](https://apex-ui.in/components/typewriter-text) | `typewriter-text` |
162
+
163
+ ### Navigation (8)
164
+
165
+ | Component | CLI name |
166
+ | --- | --- |
167
+ | [Animated Tabs](https://apex-ui.in/components/animated-tabs) | `animated-tabs` |
168
+ | [Floating Navbar](https://apex-ui.in/components/floating-navbar) | `floating-navbar` |
169
+ | [Gooey Nav](https://apex-ui.in/components/gooey-nav) | `gooey-nav` |
170
+ | [Magnify Dock](https://apex-ui.in/components/magnify-dock) | `magnify-dock` |
171
+ | [Nav DropDown](https://apex-ui.in/components/nav-drop-down) | `nav-dropdown` |
172
+ | [Nav Menu](https://apex-ui.in/components/nav-menu) | `nav-menu` |
173
+ | [Sidebar Nav](https://apex-ui.in/components/sidebar-nav) | `sidebar-nav` |
174
+ | [Theme Toggle](https://apex-ui.in/components/theme-toggle) | `theme-toggle` |
175
+
176
+ ### Buttons (3)
177
+
178
+ | Component | CLI name |
179
+ | --- | --- |
180
+ | [Animated Buttons](https://apex-ui.in/components/animated-buttons) | `animated-button`, `hold-to-confirm` |
181
+ | [Animated Switches](https://apex-ui.in/components/animated-switches) | `animated-switch` |
182
+ | [Star Border](https://apex-ui.in/components/star-border) | `star-border` |
183
+
184
+ ### Interactive (20)
185
+
186
+ | Component | CLI name |
187
+ | --- | --- |
188
+ | [3D Carousel](https://apex-ui.in/components/3d-carousel) | `3d-carousel`, `webgl-carousel` |
189
+ | [Avatar](https://apex-ui.in/components/avatar) | `avatar` |
190
+ | [Carousel](https://apex-ui.in/components/carousel) | `carousel`, `parallax-carousel`, `drag-carousel` |
191
+ | [Click Spark](https://apex-ui.in/components/click-spark) | `click-spark` |
192
+ | [Coverflow 3D](https://apex-ui.in/components/coverflow-3d) | `coverflow-3d` |
193
+ | [Cube Gallery](https://apex-ui.in/components/cube-gallery) | `cube-gallery` |
194
+ | [Cursor Trail](https://apex-ui.in/components/cursor-trail) | `cursor-trail` |
195
+ | [Dynamic Island](https://apex-ui.in/components/dynamic-island) | `dynamic-island` |
196
+ | [Edge Glow Form](https://apex-ui.in/components/edge-glow-form) | `edge-glow-form` |
197
+ | [Elastic Slider](https://apex-ui.in/components/elastic-slider) | `elastic-slider` |
198
+ | [FAQ Accordion](https://apex-ui.in/components/faq-accordion) | `faq-accordion` |
199
+ | [Flip Book](https://apex-ui.in/components/flip-book) | `flip-book` |
200
+ | [Image Compare](https://apex-ui.in/components/image-compare) | `image-compare` |
201
+ | [Magnet Lines](https://apex-ui.in/components/magnet-lines) | `magnet-lines` |
202
+ | [Magnetic Cursor](https://apex-ui.in/components/magnetic-cursor) | `magnetic-cursor` |
203
+ | [Pointer Follower](https://apex-ui.in/components/pointer-follower) | `pointer-follower` |
204
+ | [Receipt Printer](https://apex-ui.in/components/receipt-printer) | `receipt-printer` |
205
+ | [Stepper](https://apex-ui.in/components/stepper) | `stepper` |
206
+ | [Toast Stack](https://apex-ui.in/components/toast-stack) | `toast-stack` |
207
+ | [ToolTip](https://apex-ui.in/components/tool-tip) | `tooltip` |
208
+
209
+ ### AI (10)
210
+
211
+ | Component | CLI name |
212
+ | --- | --- |
213
+ | [AI Chat](https://apex-ui.in/components/ai-chat) | `ai-chat` |
214
+ | [AI Model Prompt](https://apex-ui.in/components/ai-model-prompt) | `ai-model-prompt` |
215
+ | [AI Prompt Box](https://apex-ui.in/components/ai-prompt-box) | `ai-prompt-box` |
216
+ | [AI Response](https://apex-ui.in/components/ai-response) | `ai-response` |
217
+ | [AI Thinking](https://apex-ui.in/components/ai-thinking) | `ai-thinking` |
218
+ | [AI Voice Bars](https://apex-ui.in/components/ai-voice-bars) | `ai-voice-bars` |
219
+ | [AI Voice Blob](https://apex-ui.in/components/ai-voice-blob) | `ai-voice-blob` |
220
+ | [AI Voice Orb](https://apex-ui.in/components/ai-voice-orb) | `ai-voice-orb` |
221
+ | [AI Voice Ring](https://apex-ui.in/components/ai-voice-ring) | `ai-voice-ring` |
222
+ | [AI Voice Wave](https://apex-ui.in/components/ai-voice-wave) | `ai-voice-wave` |
223
+
224
+ ### Loaders (1)
225
+
226
+ | Component | CLI name |
227
+ | --- | --- |
228
+ | [Loading Screens](https://apex-ui.in/components/loading-screens) | `loading-screen` |
229
+
230
+ ### Maps (2)
231
+
232
+ | Component | CLI name |
233
+ | --- | --- |
234
+ | [Dot Globe](https://apex-ui.in/components/dot-globe) | `dot-globe` |
235
+ | [World Map](https://apex-ui.in/components/world-map) | `world-map` |
236
+
237
+ ## What's new in 2.3
238
+
239
+ - 21 new components, including the AI voice family (Wave, Bars, Ring, Blob), Gooey Nav (WebGL metaball pill), Coverflow 3D, Card Swap 3D, 3D Text Ring, Elastic Slider, Stepper and Pixel Transition.
240
+ - AI Model Prompt now exports `DEFAULT_LLM_MODELS` and an index file, so you can import both from the folder.
241
+ - Hold to Confirm follows a delete flow with an `UndoNotice` that counts down before the action is final.
242
+ - Sliders and drag components no longer stop midway when the page text gets selected.
243
+ - Stepper and Animated Tabs keep a steady height, Shiny Text loops seamlessly, Cube Gallery is a real 3D cube and Flip Book is a hardcover book you can drag.
244
+
245
+ ## Contributing
246
+
247
+ Issues and pull requests are welcome on [GitHub](https://github.com/Mohan-Kumar-Dalei/ApexUI). For bigger changes, please open an issue first so we can talk it through.
248
+
249
+ ## License
250
+
251
+ MIT
@@ -11,7 +11,7 @@ import { ArrowUp, Bot, Sparkles } from "lucide-react";
11
11
 
12
12
  const DEMO_REPLIES = [
13
13
  "Sure! Wrap your section in a container with `grid` and `gap-6`, then let the cards span two columns on large screens. Want me to write the code?",
14
- "Great question. Animate only `transform` and `opacity` — they run on the GPU and never trigger layout, so the motion stays at 60fps.",
14
+ "Great question. Animate only `transform` and `opacity`, they run on the GPU and never trigger layout, so the motion stays at 60fps.",
15
15
  "Here's a quick idea: use a spring with stiffness 380 and damping 30. It feels snappy without bouncing too much.",
16
16
  ];
17
17
 
@@ -8,10 +8,10 @@ import { AnimatePresence, motion } from "framer-motion";
8
8
  */
9
9
 
10
10
  const DEFAULT_TABS = [
11
- { id: "overview", label: "Overview", content: "A quick look at everything in one place — usage, limits and the latest activity." },
11
+ { id: "overview", label: "Overview", content: "A quick look at everything in one place, usage, limits and the latest activity." },
12
12
  { id: "analytics", label: "Analytics", content: "Visitors, conversions and retention, updated every few seconds with zero setup." },
13
13
  { id: "reports", label: "Reports", content: "Export weekly PDF reports or schedule them to land in your team's inbox." },
14
- { id: "settings", label: "Settings", content: "Workspace name, members, billing and the danger zone — all in one tidy screen." },
14
+ { id: "settings", label: "Settings", content: "Workspace name, members, billing and the danger zone, all in one tidy screen." },
15
15
  ];
16
16
 
17
17
  const AnimatedTabs = ({ tabs = DEFAULT_TABS, defaultTab, variant = "pill", accentColor = "#a3e635", theme = "dark", onChange, className = "" }) => {
@@ -12,7 +12,7 @@ const DEFAULT_CARDS = [
12
12
  { icon: Sparkles, title: "Smooth", body: "Springs tuned to feel natural, never floaty." },
13
13
  { icon: Layers, title: "Composable", body: "Drop components in and style them with Tailwind." },
14
14
  { icon: Code2, title: "Yours", body: "Copy the source or add it with one CLI command." },
15
- { icon: Zap, title: "Fast", body: "Transforms and opacity only — 60fps on phones." },
15
+ { icon: Zap, title: "Fast", body: "Transforms and opacity only, 60fps on phones." },
16
16
  ];
17
17
 
18
18
  const CardSwap3D = ({ cards = DEFAULT_CARDS, delay = 3200, distanceX = 3.6, distanceY = 3.2, skew = 5, pauseOnHover = true, accentColor = "#a3e635", theme = "dark", width = 18, className = "" }) => {
@@ -16,15 +16,17 @@ const ClickSpark = ({ sparkColor = "#a3e635", sparkCount = 10, sparkRadius = 26,
16
16
 
17
17
  useEffect(() => {
18
18
  const c = canvas.current;
19
+ // Hold the element itself: the ref is cleared on unmount before cleanup runs.
20
+ const el = wrap.current;
19
21
  const resize = () => {
20
- const r = wrap.current.getBoundingClientRect();
22
+ const r = el.getBoundingClientRect();
21
23
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
22
24
  c.width = r.width * dpr;
23
25
  c.height = r.height * dpr;
24
26
  c.getContext("2d").setTransform(dpr, 0, 0, dpr, 0, 0);
25
27
  };
26
28
  const ro = new ResizeObserver(resize);
27
- ro.observe(wrap.current);
29
+ ro.observe(el);
28
30
  resize();
29
31
  return () => {
30
32
  ro.disconnect();
@@ -105,6 +105,8 @@ const CubeGallery = ({ faces = DEFAULT_FACES, size = 13, autoRotate = true, spee
105
105
  className="relative h-full w-full cursor-grab touch-none active:cursor-grabbing [transform-style:preserve-3d]"
106
106
  style={{ rotateX: rotX, rotateY: rotY }}
107
107
  onPointerDown={(e) => {
108
+ // Block text selection / native drag, which cancel the drag midway.
109
+ e.preventDefault();
108
110
  e.currentTarget.setPointerCapture(e.pointerId);
109
111
  rotY.stop();
110
112
  rotX.stop();
@@ -236,6 +236,7 @@ const DotGlobe = ({
236
236
  };
237
237
 
238
238
  const onDown = (e) => {
239
+ e.preventDefault();
239
240
  drag = { x: e.clientX, yaw, lastX: e.clientX, lastT: performance.now() };
240
241
  canvas.setPointerCapture?.(e.pointerId);
241
242
  };
@@ -51,7 +51,7 @@ const DynamicIsland = ({
51
51
  accentColor = "#a3e635",
52
52
  music = { title: "Midnight City", artist: "Apex Sessions", cover: `${IK}/spiderman.png?tr=f-auto,q-80,w-200` },
53
53
  call = { name: "Tony Stark", subtitle: "Mobile", avatar: `${IK}/ironman.png?tr=f-auto,q-80,w-160` },
54
- notification = { app: "Messages", title: "Peter Parker", message: "On my way — saving the city first 🕷️" },
54
+ notification = { app: "Messages", title: "Peter Parker", message: "On my way, saving the city first 🕷️" },
55
55
  timerSeconds = 300,
56
56
  onViewChange,
57
57
  className = "",
@@ -32,6 +32,8 @@ const ElasticSlider = ({
32
32
  const [hover, setHover] = useState(false);
33
33
  const track = useRef(null);
34
34
  const drag = useRef(false);
35
+ // Latest value, readable synchronously from pointer handlers (state lags a render).
36
+ const valueRef = useRef(clamp(defaultValue));
35
37
 
36
38
  const pct = useMotionValue(((clamp(defaultValue) - min) / (max - min)) * 100);
37
39
  const overflow = useMotionValue(0); // px past an end, negative = left
@@ -44,10 +46,11 @@ const ElasticSlider = ({
44
46
 
45
47
  const commit = (raw) => {
46
48
  const v = clamp(Math.round(raw / step) * step);
47
- setValue((prev) => {
48
- if (prev !== v) onChange?.(v);
49
- return v;
50
- });
49
+ if (v !== valueRef.current) {
50
+ valueRef.current = v;
51
+ setValue(v);
52
+ onChange?.(v);
53
+ }
51
54
  return v;
52
55
  };
53
56
 
@@ -66,7 +69,7 @@ const ElasticSlider = ({
66
69
  setDragging(false);
67
70
  animate(overflow, 0, { type: "spring", stiffness: 380, damping: 12 });
68
71
  // Snap the fill to the stepped value.
69
- animate(pct, ((value - min) / (max - min)) * 100, { duration: 0.15 });
72
+ animate(pct, ((valueRef.current - min) / (max - min)) * 100, { duration: 0.12 });
70
73
  };
71
74
 
72
75
  // Keep the fill in sync for keyboard changes.
@@ -74,7 +77,7 @@ const ElasticSlider = ({
74
77
  if (!drag.current) animate(pct, ((value - min) / (max - min)) * 100, { type: "spring", stiffness: 500, damping: 40 });
75
78
  }, [value, min, max, pct]);
76
79
 
77
- const nudge = (delta) => commit(value + delta);
80
+ const nudge = (delta) => commit(valueRef.current + delta);
78
81
  const big = Math.max(step, (max - min) / 10);
79
82
  const thick = dragging || hover;
80
83
 
@@ -98,7 +101,13 @@ const ElasticSlider = ({
98
101
  onPointerLeave={() => setHover(false)}
99
102
  onPointerDown={(e) => {
100
103
  if (e.button !== 0) return;
104
+ // No text selection / native drag: those fire pointercancel mid-drag.
105
+ e.preventDefault();
106
+ e.currentTarget.focus({ preventScroll: true });
101
107
  e.currentTarget.setPointerCapture(e.pointerId);
108
+ // Stop any spring still settling from the last drag.
109
+ pct.stop();
110
+ overflow.stop();
102
111
  drag.current = true;
103
112
  setDragging(true);
104
113
  fromPointer(e.clientX);
@@ -9,7 +9,7 @@ import { Plus } from "lucide-react";
9
9
  */
10
10
 
11
11
  const DEFAULT_ITEMS = [
12
- { q: "Is ApexUI free to use?", a: "Yes. Every component is free and open source — copy it into your project or add it with one CLI command." },
12
+ { q: "Is ApexUI free to use?", a: "Yes. Every component is free and open source, copy it into your project or add it with one CLI command." },
13
13
  { q: "Does it work with Next.js?", a: "Yes. The components are plain React with Tailwind CSS, so they work in Vite, Next.js, Remix and anything else that runs React." },
14
14
  { q: "Can I change the colors?", a: "Every component takes props for its accent colors, and the Tailwind classes are right there in the code if you want to go further." },
15
15
  { q: "Do the animations respect reduced motion?", a: "Yes. Components check the visitor's reduced-motion setting and tone their movement down or switch it off." },
@@ -13,7 +13,7 @@ const IK = "https://ik.imagekit.io/ny6yinyut/apexui/assets";
13
13
  const DEFAULT_PAGES = [
14
14
  { title: "ApexUI", body: "A field guide to motion", cover: true },
15
15
  { title: "Chapter 1", body: "Start small. One spring, one easing, one idea per interaction." },
16
- { title: "Chapter 2", body: "Motion should explain — where things come from and where they go.", image: `${IK}/spiderman.png?tr=f-auto,q-80,w-500` },
16
+ { title: "Chapter 2", body: "Motion should explain, where things come from and where they go.", image: `${IK}/spiderman.png?tr=f-auto,q-80,w-500` },
17
17
  { title: "Chapter 3", body: "Keep it fast. Animate transform and opacity, never layout." },
18
18
  { title: "Chapter 4", body: "Respect reduced motion. Calm is a feature too.", image: `${IK}/doctorStrange.png?tr=f-auto,q-80,w-500` },
19
19
  { title: "Chapter 5", body: "Details compound. Tiny shadows and easing make it feel real." },
@@ -160,8 +160,10 @@ const GooeyNav = ({ items = DEFAULT_ITEMS, initialActive = 0, onChange, accentCo
160
160
  };
161
161
  sim.current = s;
162
162
 
163
+ // Hold the element itself: the ref is cleared on unmount before cleanup runs.
164
+ const barEl = bar.current;
163
165
  const resize = () => {
164
- const r = bar.current.getBoundingClientRect();
166
+ const r = barEl.getBoundingClientRect();
165
167
  s.dpr = Math.min(window.devicePixelRatio || 1, 2);
166
168
  const w = r.width + PAD * 2;
167
169
  const h = r.height + PAD * 2;
@@ -182,7 +184,7 @@ const GooeyNav = ({ items = DEFAULT_ITEMS, initialActive = 0, onChange, accentCo
182
184
  };
183
185
  resize();
184
186
  const ro = new ResizeObserver(resize);
185
- ro.observe(bar.current);
187
+ ro.observe(barEl);
186
188
 
187
189
  const dropData = new Float32Array(MAX_DROPS * 4);
188
190
  let raf = 0;
@@ -45,6 +45,9 @@ const ImageCompare = ({
45
45
  <div
46
46
  ref={ref}
47
47
  onPointerDown={(e) => {
48
+ // Block text selection / native drag, which cancel the drag midway.
49
+ e.preventDefault();
50
+ e.currentTarget.focus?.({ preventScroll: true });
48
51
  e.currentTarget.setPointerCapture(e.pointerId);
49
52
  setHeld(true);
50
53
  fromPointer(e);
@@ -81,8 +84,8 @@ const ImageCompare = ({
81
84
  aria-valuenow={Math.round(pos)}
82
85
  aria-orientation={vertical ? "vertical" : "horizontal"}
83
86
  onKeyDown={onKeyDown}
84
- className={`pointer-events-auto absolute left-1/2 top-1/2 grid h-10 w-10 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full text-black shadow-[0_6px_20px_-4px_rgba(0,0,0,0.6)] outline-none transition-transform duration-200 focus-visible:ring-4 focus-visible:ring-white/50 ${held ? "scale-110" : "group-hover:scale-105"}`}
85
- style={{ background: accentColor }}
87
+ className={`pointer-events-auto absolute left-1/2 top-1/2 grid h-10 w-10 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full bg-white text-neutral-800 shadow-[0_6px_20px_-4px_rgba(0,0,0,0.55)] outline-none transition-transform duration-200 focus-visible:ring-4 focus-visible:ring-white/50 ${held ? "scale-110" : "group-hover:scale-105"}`}
88
+ style={{ boxShadow: `0 0 0 2px ${accentColor}, 0 6px 20px -4px rgba(0,0,0,0.55)` }}
86
89
  >
87
90
  <ChevronsLeftRight className={`h-5 w-5 ${vertical ? "rotate-90" : ""}`} />
88
91
  </button>
@@ -14,7 +14,7 @@ const DefaultSecond = () => (
14
14
  <div>
15
15
  <p className="text-[0.7rem] font-semibold uppercase tracking-[0.3em] text-white/50">Wakanda</p>
16
16
  <p className="mt-2 text-3xl font-black">Black Panther</p>
17
- <p className="mt-2 text-sm text-white/60">King T&apos;Challa — protector of his people.</p>
17
+ <p className="mt-2 text-sm text-white/60">King T&apos;Challa, protector of his people.</p>
18
18
  </div>
19
19
  </div>
20
20
  );
@@ -132,7 +132,7 @@ const PointerFollower = ({
132
132
  >
133
133
  <div className="px-4 py-3 rounded-full text-[11px] overflow-hidden font-semibold shadow-[0_2px_8px_rgba(0,0,0,0.25)] border border-black/10" style={{ backgroundColor: badgeColor }}>
134
134
  <span className="text-[12px] font-medium tracking-wide whitespace-nowrap drop-shadow" style={{ color: badgeTextColor }}>
135
- {person.name} — {person.job}
135
+ {person.name} none {person.job}
136
136
  </span>
137
137
  </div>
138
138
  </div>
@@ -3,24 +3,30 @@ import { motion, useReducedMotion } from "framer-motion";
3
3
 
4
4
  /*
5
5
  * Shiny Text: a bright band of light sweeps across the text on a loop, like
6
- * light catching metal. The text keeps its own color between sweeps.
6
+ * light catching metal. The band starts and ends fully outside the text, so
7
+ * the loop is seamless — it never pauses on the last letters or jumps back.
7
8
  */
8
9
 
9
- const ShinyText = ({ text = "Shipping soon — join the waitlist", as = "span", speed = 2.8, color = "#71717a", shineColor = "#ffffff", spread = 22, disabled = false, className = "" }) => {
10
+ const ShinyText = ({ text = "Shipping soon, join the waitlist", as = "span", speed = 2.8, color = "#71717a", shineColor = "#ffffff", spread = 10, pause = 0.6, disabled = false, className = "" }) => {
10
11
  const reduced = useReducedMotion();
11
12
  const Tag = motion[as] ?? motion.span;
12
13
  const still = disabled || reduced;
14
+ // The gradient is 3× the text width with the band in its middle. Moving the
15
+ // background from 100% to 0% carries the band from fully left of the text
16
+ // to fully right of it, so both ends of the loop look identical.
17
+ const band = Math.min(16, Math.max(4, spread));
13
18
  return (
14
19
  <Tag
15
20
  className={`inline-block bg-clip-text text-transparent ${className}`}
16
21
  style={{
17
- backgroundImage: `linear-gradient(110deg, ${color} ${50 - spread}%, ${shineColor} 50%, ${color} ${50 + spread}%)`,
18
- backgroundSize: "250% 100%",
22
+ backgroundImage: `linear-gradient(100deg, ${color} ${50 - band}%, ${shineColor} 50%, ${color} ${50 + band}%)`,
23
+ backgroundSize: "300% 100%",
24
+ backgroundRepeat: "no-repeat",
19
25
  WebkitBackgroundClip: "text",
26
+ backgroundPosition: "100% 0",
20
27
  }}
21
- initial={{ backgroundPosition: "100% 0" }}
22
28
  animate={still ? { backgroundPosition: "100% 0" } : { backgroundPosition: ["100% 0", "0% 0"] }}
23
- transition={still ? undefined : { duration: speed, ease: "linear", repeat: Infinity, repeatDelay: speed * 0.25 }}
29
+ transition={still ? undefined : { duration: speed, ease: "linear", repeat: Infinity, repeatDelay: pause }}
24
30
  >
25
31
  {text}
26
32
  </Tag>
@@ -9,7 +9,7 @@ import { Check } from "lucide-react";
9
9
  */
10
10
 
11
11
  const DEFAULT_STEPS = [
12
- { title: "Account", content: <p>Welcome! Pick a username to get started — you can change it later.</p> },
12
+ { title: "Account", content: <p>Welcome! Pick a username to get started, you can change it later.</p> },
13
13
  { title: "Profile", content: <p>Add a photo and a short bio so people know who you are.</p> },
14
14
  { title: "Theme", content: <p>Choose light, dark or follow your system. Your choice syncs everywhere.</p> },
15
15
  { title: "Done", content: <p>All set. Press Complete to finish onboarding.</p> },
@@ -21,6 +21,18 @@ const variants = {
21
21
  exit: (dir) => ({ x: dir > 0 ? "-40%" : "40%", opacity: 0 }),
22
22
  };
23
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
+
24
36
  const Stepper = ({ steps = DEFAULT_STEPS, initialStep = 0, onStepChange, onComplete, accentColor = "#a3e635", theme = "dark", className = "" }) => {
25
37
  const id = useId();
26
38
  const [step, setStep] = useState(initialStep);
@@ -72,26 +84,27 @@ const Stepper = ({ steps = DEFAULT_STEPS, initialStep = 0, onStepChange, onCompl
72
84
  })}
73
85
  </div>
74
86
 
75
- <motion.div layout className="relative mt-6 overflow-hidden" transition={{ type: "spring", stiffness: 300, damping: 32 }}>
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
+ ))}
76
95
  <AnimatePresence mode="popLayout" initial={false} custom={dir}>
77
- <motion.div key={done ? "done" : step} custom={dir} variants={variants} initial="enter" animate="center" exit="exit" transition={{ type: "spring", stiffness: 300, damping: 32 }} className="pb-1">
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">
78
97
  {done ? (
79
98
  <div className="py-4 text-center">
80
99
  <p className="text-lg font-semibold">You are all set 🎉</p>
81
100
  <p className={`mt-1 text-sm ${dark ? "text-white/55" : "text-neutral-500"}`}>Every step is complete.</p>
82
101
  </div>
83
102
  ) : (
84
- <>
85
- <p className="text-[0.72rem] font-semibold uppercase tracking-[0.18em]" style={{ color: accentColor }}>
86
- Step {step + 1} of {steps.length}
87
- </p>
88
- <h3 className="mt-1 text-xl font-semibold">{steps[step].title}</h3>
89
- <div className={`mt-2 text-[0.92rem] leading-relaxed ${dark ? "text-white/65" : "text-neutral-600"}`}>{steps[step].content}</div>
90
- </>
103
+ <StepBody step={step} total={steps.length} title={steps[step].title} content={steps[step].content} accentColor={accentColor} dark={dark} />
91
104
  )}
92
105
  </motion.div>
93
106
  </AnimatePresence>
94
- </motion.div>
107
+ </div>
95
108
 
96
109
  <div className="mt-6 flex items-center justify-between">
97
110
  {done ? (
@@ -38,6 +38,8 @@ const TextRing3D = ({ text = "APEX UI · 3D TYPE · ", radius = 9, speed = 30, t
38
38
  className={`grid cursor-grab touch-none select-none place-items-center active:cursor-grabbing [perspective:900px] ${className}`}
39
39
  style={{ width: `${radius * 2.4}rem`, maxWidth: "100%", height: `${fontSize * 3.2 + 4}rem`, color }}
40
40
  onPointerDown={(e) => {
41
+ // Block text selection / native drag, which cancel the drag midway.
42
+ e.preventDefault();
41
43
  e.currentTarget.setPointerCapture(e.pointerId);
42
44
  drag.current = { x: e.clientX, a: angle.current, t: performance.now() };
43
45
  }}
package/package.json CHANGED
@@ -1,24 +1,32 @@
1
1
  {
2
2
  "name": "apex-ui-kit",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "license": "MIT",
5
- "description": "Apex UI Kit - A React component library for building modern user interfaces",
5
+ "description": "90 free, animated React components for Tailwind CSS. Add any of them to your project with one CLI command.",
6
+ "homepage": "https://apex-ui.in",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Mohan-Kumar-Dalei/ApexUI.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/Mohan-Kumar-Dalei/ApexUI/issues"
13
+ },
6
14
  "keywords": [
7
15
  "react",
8
- "ui",
9
- "components",
10
- "library",
11
- "design",
12
- "kit",
13
- "modern",
14
- "user interface",
15
- "frontend",
16
- "web development",
17
16
  "react components",
17
+ "tailwind",
18
+ "tailwindcss",
19
+ "framer-motion",
20
+ "animation",
21
+ "animated components",
22
+ "ui",
23
+ "ui library",
18
24
  "ui components",
19
- "react ui library",
20
- "apex ui kit",
21
- "ui components library"
25
+ "cli",
26
+ "webgl",
27
+ "ai ui",
28
+ "apexui",
29
+ "apex ui kit"
22
30
  ],
23
31
  "type": "module",
24
32
  "main": "dist/cli/index.js",
@@ -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;