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 +251 -75
- package/dist/ApexUI-Kit/templates/AiChat/AiChat.jsx +1 -1
- package/dist/ApexUI-Kit/templates/AnimatedTabs/AnimatedTabs.jsx +2 -2
- package/dist/ApexUI-Kit/templates/CardSwap3D/CardSwap3D.jsx +1 -1
- package/dist/ApexUI-Kit/templates/ClickSpark/ClickSpark.jsx +4 -2
- package/dist/ApexUI-Kit/templates/CubeGallery/CubeGallery.jsx +2 -0
- package/dist/ApexUI-Kit/templates/DotGlobe/DotGlobe.jsx +1 -0
- package/dist/ApexUI-Kit/templates/DynamicIsland/DynamicIsland.jsx +1 -1
- package/dist/ApexUI-Kit/templates/ElasticSlider/ElasticSlider.jsx +15 -6
- package/dist/ApexUI-Kit/templates/FaqAccordion/FaqAccordion.jsx +1 -1
- package/dist/ApexUI-Kit/templates/FlipBook/FlipBook.jsx +1 -1
- package/dist/ApexUI-Kit/templates/GooeyNav/GooeyNav.jsx +4 -2
- package/dist/ApexUI-Kit/templates/ImageCompare/ImageCompare.jsx +5 -2
- package/dist/ApexUI-Kit/templates/PixelTransition/PixelTransition.jsx +1 -1
- package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +1 -1
- package/dist/ApexUI-Kit/templates/ShinyText/ShinyText.jsx +12 -6
- package/dist/ApexUI-Kit/templates/Stepper/Stepper.jsx +24 -11
- package/dist/ApexUI-Kit/templates/TextRing3D/TextRing3D.jsx +2 -0
- package/package.json +22 -14
- package/dist/ApexUI-Kit/templates/Carousel/DragCarousel.jsx +0 -105
package/ReadMe.md
CHANGED
|
@@ -1,75 +1,251 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
# ApexUI Kit
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/apex-ui-kit)
|
|
4
|
+
[](https://www.npmjs.com/package/apex-ui-kit)
|
|
5
|
+
[](./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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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();
|
|
@@ -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
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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, ((
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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-
|
|
85
|
-
style={{
|
|
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'Challa
|
|
17
|
+
<p className="mt-2 text-sm text-white/60">King T'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}
|
|
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
|
|
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
|
|
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(
|
|
18
|
-
backgroundSize: "
|
|
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:
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
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
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"ui
|
|
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;
|