├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── favicon.ico
├── globals.css
├── layout.tsx
├── opengraph-image.png
├── page.tsx
├── text-variants
│ └── page.tsx
└── usage
│ └── page.tsx
├── components.json
├── components
├── landing
│ ├── blockquote.tsx
│ ├── features.tsx
│ ├── info.tsx
│ ├── intro-text.tsx
│ └── variant-carousel.tsx
├── shared
│ ├── footer.tsx
│ ├── main-nav.tsx
│ ├── mode-toggle.tsx
│ ├── nav-dropdown.tsx
│ ├── nav-links.tsx
│ ├── nav-sheet.tsx
│ ├── spotlight.tsx
│ └── vv-logo.tsx
├── theme-provider.tsx
├── ui
│ ├── accordion.tsx
│ ├── alert.tsx
│ ├── avatar.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── command.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── hover-card.tsx
│ ├── input.tsx
│ ├── scroll-area.tsx
│ ├── select.tsx
│ ├── sheet.tsx
│ ├── tabs.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ ├── tooltip.tsx
│ └── use-toast.ts
└── usage
│ ├── all-variants.tsx
│ ├── call-to-action.tsx
│ ├── copy.tsx
│ └── intro.tsx
├── lib
└── utils.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── landing.jpeg
├── next.svg
└── vercel.svg
├── tailwind.config.js
├── tsconfig.json
├── variants
├── code-snippets
│ └── index.ts
├── definitions
│ └── index.ts
└── variant-previews
│ └── index.tsx
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | ```
14 |
15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16 |
17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
18 |
19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChrisAbdo/MotionVariants/d0413146d159dcc4968d7dd68ab963e3c6c8360e/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 |
10 | --muted: 240 4.8% 95.9%;
11 | --muted-foreground: 240 3.8% 46.1%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 240 10% 3.9%;
15 |
16 | --card: 0 0% 100%;
17 | --card-foreground: 240 10% 3.9%;
18 |
19 | --border: 240 5.9% 90%;
20 | --input: 240 5.9% 90%;
21 |
22 | --primary: 240 5.9% 10%;
23 | --primary-foreground: 0 0% 98%;
24 |
25 | --secondary: 240 4.8% 95.9%;
26 | --secondary-foreground: 240 5.9% 10%;
27 |
28 | --accent: 240 4.8% 95.9%;
29 | --accent-foreground: ;
30 |
31 | --destructive: 0 84.2% 60.2%;
32 | --destructive-foreground: 0 0% 98%;
33 |
34 | --ring: 240 5% 64.9%;
35 |
36 | --radius: 0.5rem;
37 | }
38 |
39 | .dark {
40 | --background: 240 10% 3.9%;
41 | --foreground: 0 0% 98%;
42 |
43 | --muted: 240 3.7% 15.9%;
44 | --muted-foreground: 240 5% 64.9%;
45 |
46 | --popover: 240 10% 3.9%;
47 | --popover-foreground: 0 0% 98%;
48 |
49 | --card: 240 10% 3.9%;
50 | --card-foreground: 0 0% 98%;
51 |
52 | --border: 240 3.7% 15.9%;
53 | --input: 240 3.7% 15.9%;
54 |
55 | --primary: 0 0% 98%;
56 | --primary-foreground: 240 5.9% 10%;
57 |
58 | --secondary: 240 3.7% 15.9%;
59 | --secondary-foreground: 0 0% 98%;
60 |
61 | --accent: 240 3.7% 15.9%;
62 | --accent-foreground: ;
63 |
64 | --destructive: 0 62.8% 30.6%;
65 | --destructive-foreground: 0 85.7% 97.3%;
66 |
67 | --ring: 240 3.7% 15.9%;
68 | }
69 | }
70 |
71 | @layer base {
72 | * {
73 | @apply border-border;
74 | }
75 | body {
76 | @apply bg-background text-foreground;
77 | font-feature-settings: "rlig" 1, "calt" 1;
78 | }
79 | }
80 |
81 | @layer utilities {
82 | .parallax {
83 | overflow: hidden;
84 | letter-spacing: -2px;
85 | line-height: 0.9;
86 | margin: 0;
87 | white-space: nowrap;
88 | display: flex;
89 | flex-wrap: nowrap;
90 | }
91 |
92 | .parallax .scroller {
93 | font-weight: 600;
94 | text-transform: uppercase;
95 | font-size: 100px;
96 | display: flex;
97 | white-space: nowrap;
98 | display: flex;
99 | flex-wrap: nowrap;
100 | }
101 |
102 | .parallax span {
103 | display: block;
104 | margin-right: 30px;
105 | }
106 |
107 | .step {
108 | counter-increment: step;
109 | }
110 |
111 | .step:before {
112 | @apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background;
113 | @apply ml-[-50px] mt-[-4px];
114 | content: counter(step);
115 | }
116 | }
117 |
118 | @media (max-width: 640px) {
119 | .container {
120 | @apply px-4;
121 | }
122 | }
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 |
3 | import type { Metadata } from "next";
4 |
5 | import { Inter } from "next/font/google";
6 |
7 | import { ThemeProvider } from "@/components/theme-provider";
8 | import { Toaster } from "@/components/ui/toaster";
9 |
10 | import MainNav from "@/components/shared/main-nav";
11 | import Footer from "@/components/shared/footer";
12 |
13 | const inter = Inter({ subsets: ["latin"] });
14 |
15 | const title =
16 | "Variant Vault - A collection of Framer Motion variants for your next project.";
17 | const description =
18 | "Variant Vault is a collection of Framer Motion variants for your next project. All free to use and open source.";
19 |
20 | export const metadata: Metadata = {
21 | title,
22 | description,
23 | openGraph: {
24 | title,
25 | description,
26 | },
27 | twitter: {
28 | title,
29 | description,
30 | card: "summary_large_image",
31 | creator: "@abdo_eth",
32 | },
33 | metadataBase: new URL("https://variantvault.chrisabdo.dev"),
34 | themeColor: "#ffffff",
35 | };
36 | export default function RootLayout({
37 | children,
38 | }: {
39 | children: React.ReactNode;
40 | }) {
41 | return (
42 |
43 |
44 |
45 |
46 | {children}
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/app/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChrisAbdo/MotionVariants/d0413146d159dcc4968d7dd68ab963e3c6c8360e/app/opengraph-image.png
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import IntroText from "@/components/landing/intro-text";
2 | import Features from "@/components/landing/features";
3 | import Blockquote from "@/components/landing/blockquote";
4 | import Info from "@/components/landing/info";
5 |
6 | export default function Home() {
7 | return (
8 |
9 |
10 | {/* Hero section */}
11 |
12 |
24 |
25 |
26 |
27 |
39 |
40 |
41 | {/* Feature section */}
42 |
43 |
44 | {/* Testimonial section */}
45 |
46 |
47 |
48 |
60 |
72 |
73 |
74 |
75 |
76 |
77 | {/* Info section */}
78 |
79 |
80 |
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/app/text-variants/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import { Button } from "@/components/ui/button";
5 | import { Card, CardContent } from "@/components/ui/card";
6 | import {
7 | Tooltip,
8 | TooltipContent,
9 | TooltipProvider,
10 | TooltipTrigger,
11 | } from "@/components/ui/tooltip";
12 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
13 | import { ReloadIcon } from "@radix-ui/react-icons";
14 | import { ScrollArea } from "@/components/ui/scroll-area";
15 | import {
16 | BLUR_IN_CODE,
17 | FADE_DOWN_CODE,
18 | FADE_UP_CODE,
19 | GRADUAL_SPACING_CODE,
20 | MULTIDIRECTION_SLIDE_CODE,
21 | RIPPLE_PULL_UP_CODE,
22 | ROTATE_WORDS_CODE,
23 | SEPARATE_CODE,
24 | SLIGHT_FLIP_CODE,
25 | STAGGERED_FADE_IN_CODE,
26 | STAGGERED_PULL_UP_CODE,
27 | TYPING_EFFECT_CODE,
28 | VELOCITY_CODE,
29 | WAVY_TEXT_CODE,
30 | } from "@/variants/code-snippets";
31 | import {
32 | RotateText,
33 | FadeDownStagger,
34 | FadeUpStagger,
35 | LetterPullUp,
36 | MultiDirectionSlide,
37 | StaggeredFadeIn,
38 | TypingEffect,
39 | VelocityScroll,
40 | WordPullUp,
41 | SeparateAway,
42 | GradualSpacing,
43 | BlurIn,
44 | SlightFlip,
45 | WavyText,
46 | } from "@/variants/variant-previews";
47 | import { Input } from "@/components/ui/input";
48 | import Link from "next/link";
49 | import { Spotlight } from "@/components/shared/spotlight";
50 |
51 | export default function Home() {
52 | let generateZeros = (n: number) => Array(n).fill(0);
53 | let [keys, setKeys] = React.useState(generateZeros(20));
54 | let variants = [
55 | {
56 | name: "Fade Down with Stagger",
57 | preview: ,
58 | code: FADE_DOWN_CODE,
59 | },
60 | {
61 | name: "Fade Up with Stagger",
62 | preview: ,
63 | code: FADE_UP_CODE,
64 | },
65 | {
66 | name: "Multi Direction Slide",
67 | preview: ,
68 | code: MULTIDIRECTION_SLIDE_CODE,
69 | },
70 | {
71 | name: "Staggered Fade In",
72 | preview: ,
73 | code: STAGGERED_FADE_IN_CODE,
74 | },
75 | {
76 | name: "Staggered Letter Pull Up",
77 | preview: ,
78 | code: STAGGERED_PULL_UP_CODE,
79 | },
80 | {
81 | name: "Word Pull Up",
82 | preview: ,
83 | code: RIPPLE_PULL_UP_CODE,
84 | },
85 | {
86 | name: "Scroll Based Velocity",
87 | preview: ,
88 | code: VELOCITY_CODE,
89 | },
90 | {
91 | name: "Rotate Between Words",
92 | preview: ,
93 | code: ROTATE_WORDS_CODE,
94 | },
95 | {
96 | name: "Typing Effect",
97 | preview: ,
98 | code: TYPING_EFFECT_CODE,
99 | },
100 | {
101 | name: "Separate Away",
102 | preview: ,
103 | code: SEPARATE_CODE,
104 | },
105 | {
106 | name: "Gradual Spacing",
107 | preview: ,
108 | code: GRADUAL_SPACING_CODE,
109 | },
110 | {
111 | name: "Blur In",
112 | preview: ,
113 | code: BLUR_IN_CODE,
114 | },
115 | {
116 | name: "Slight Flip",
117 | preview: ,
118 | code: SLIGHT_FLIP_CODE,
119 | },
120 | {
121 | name: "Wavy Text",
122 | preview: ,
123 | code: WAVY_TEXT_CODE,
124 | },
125 | ];
126 |
127 | function restartAnimation(index: number) {
128 | setKeys((prevKeys) => {
129 | const newKeys = [...prevKeys]; // copy the previous keys
130 | newKeys[index] += 1; // increment the key at the given index
131 | return newKeys;
132 | });
133 | }
134 |
135 | let [query, setQuery] = React.useState("");
136 | let filteredVariants =
137 | query === ""
138 | ? variants
139 | : variants.filter((variant) => {
140 | return variant.name.toLowerCase().includes(query.toLowerCase());
141 | });
142 |
143 | return (
144 |
145 |
146 |
147 |
148 |
149 |
150 | {filteredVariants.length > 0 ? (
151 | filteredVariants.map((variant, index) => (
152 |
153 |
154 |
155 |
159 | {variant.name}
160 |
161 |
162 | restartAnimation(index)}
167 | >
168 |
169 |
170 |
171 |
172 |
173 | Preview
174 | Code
175 |
176 |
177 |
178 |
179 |
180 |
181 | restartAnimation(index)}
185 | >
186 |
187 |
188 |
189 |
190 | Restart Animation
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | {variant.preview}
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 | {variant.code}
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | ))
222 | ) : (
223 |
224 |
225 | No variants found.
226 |
227 |
228 | {" "}
229 | If you want to see a variant added, please message me on{" "}
230 |
236 | Twitter
237 |
238 | @abdo_eth or create a pull request on the GitHub repo.
239 |
240 |
241 | )}
242 |
243 |
244 |
245 | );
246 | }
247 |
--------------------------------------------------------------------------------
/app/usage/page.tsx:
--------------------------------------------------------------------------------
1 | import AllVariants from "@/components/usage/all-variants";
2 | import CallToAction from "@/components/usage/call-to-action";
3 | import Copy from "@/components/usage/copy";
4 | import Intro from "@/components/usage/intro";
5 |
6 | export default function Home() {
7 | const faqs = [
8 | {
9 | id: 1,
10 | question: "Why Use Variant Vault?",
11 | answer:
12 | "Variant Vault contains ready to use animations & variants for your next project. These are production-ready and can be used in any of your apps.",
13 | },
14 | {
15 | id: 2,
16 | question: "Why Framer Motion?",
17 | answer:
18 | "Framer Motion is one of the most popular animation libraries for React with an average of 2M+ weekly downloads on NPM. It's easy to use and has a great API.",
19 | },
20 | {
21 | id: 3,
22 | question: "Do I need to pay to use Variant Vault?",
23 | answer:
24 | "No!!! It's completely free to use. You can copy and paste the code into your own project.",
25 | },
26 | {
27 | id: 4,
28 | question: "Can I use Variant Vault in my commercial projects?",
29 | answer:
30 | "Yes! You can use Variant Vault in your personal and commercial projects.",
31 | },
32 | {
33 | id: 5,
34 | question: "Do I need to give credit to Variant Vault?",
35 | answer:
36 | "No, you don't need to give credit to Variant Vault. However, if you want to, you can link to Variant Vault on Twitter @abdo_eth.",
37 | },
38 |
39 | // More questions...
40 | ];
41 | return (
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {faqs.map((faq) => (
51 |
52 |
53 | {faq.question}
54 |
55 |
56 | {faq.answer}
57 |
58 |
59 | ))}
60 |
61 |
62 |
63 |
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tailwind": {
6 | "config": "tailwind.config.js",
7 | "css": "app/globals.css",
8 | "baseColor": "slate",
9 | "cssVariables": true
10 | },
11 | "aliases": {
12 | "components": "@/components",
13 | "utils": "@/lib/utils"
14 | }
15 | }
--------------------------------------------------------------------------------
/components/landing/blockquote.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { motion, useInView } from "framer-motion";
4 | import React from "react";
5 |
6 | export default function Blockquote() {
7 | const pullupVariant = {
8 | initial: { y: 100, opacity: 0 },
9 | animate: (i: any) => ({
10 | y: 0,
11 | opacity: 1,
12 | transition: {
13 | delay: i * 0.05, // Delay each letter's animation by 0.05 seconds
14 | },
15 | }),
16 | };
17 |
18 | const ref = React.useRef(null);
19 | const isInView = useInView(ref);
20 |
21 | const wordsfade = "Variant Vault";
22 | const lettersfade = wordsfade.split("");
23 | return (
24 |
25 |
26 |
27 |
41 |
42 | {lettersfade.map((letter, i) => (
43 |
51 | {letter === " " ? "\u00A0" : letter}
52 |
53 | ))}
54 |
55 |
56 |
57 |
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/components/landing/features.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { motion, useInView } from "framer-motion";
4 | import React from "react";
5 | // import { useInView } from "react-intersection-observer";
6 |
7 | export default function Features() {
8 | // const { ref: refAbout, inView: inViewAbout } = useInView({
9 | // triggerOnce: false,
10 | // });
11 | const ref = React.useRef(null);
12 | const isInView = useInView(ref);
13 |
14 | const FADE_UP_ANIMATION_VARIANTS = {
15 | hidden: { opacity: 0, y: 10 },
16 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
17 | };
18 | return (
19 |
33 |
34 |
35 |
39 | Everything you need
40 |
41 |
45 | Beautiful Framer Motion Animations
46 |
47 |
51 | This example contains the fade up animation variant.
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/components/landing/info.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | // import {
4 | // FADE_DOWN_ANIMATION_VARIANTS,
5 | // FADE_UP_ANIMATION_VARIANTS,
6 | // } from "@/lib/constants";
7 | import { motion, useInView } from "framer-motion";
8 | import React from "react";
9 | // import { useInView } from "react-intersection-observer";
10 | import Image from "next/image";
11 | import { HeartFilledIcon } from "@radix-ui/react-icons";
12 |
13 | const features = [
14 | {
15 | name: "2M+ Weekly Framer Motion Users.",
16 | description:
17 | "Framer Motion is one of the most popular animation library for React. Find some quick and easy to use animations for your next project.",
18 | icon: HeartFilledIcon,
19 | },
20 | {
21 | name: "Easy Integration.",
22 | description:
23 | "All the variants are super easy to integrate into your own project. Just copy and paste.",
24 | icon: HeartFilledIcon,
25 | },
26 | {
27 | name: "Beautiful Animations.",
28 | description:
29 | "Hand crafted animations that are simple, subtle, and beautiful.",
30 | icon: HeartFilledIcon,
31 | },
32 | ];
33 |
34 | export default function Info() {
35 | // const { ref: refBottom, inView: inViewBottom } = useInView({
36 | // triggerOnce: false,
37 | // });
38 | const ref = React.useRef(null);
39 | const isInView = useInView(ref);
40 |
41 | const FADE_DOWN_ANIMATION_VARIANTS = {
42 | hidden: { opacity: 0, y: -10 },
43 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
44 | };
45 | const FADE_UP_ANIMATION_VARIANTS = {
46 | hidden: { opacity: 0, y: 10 },
47 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
48 | };
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
69 |
73 | Get started quickly
74 |
75 |
79 | Beautiful Framer Motion Animations
80 |
81 |
85 | Ready to use animations for your next project. Just copy and
86 | paste.
87 |
88 |
89 |
90 | {features.map((feature) => (
91 |
96 |
97 |
101 | {feature.name}
102 | {" "}
103 | {feature.description}
104 |
105 | ))}
106 |
107 |
108 |
109 |
110 |
117 |
118 |
119 |
120 | );
121 | }
122 |
--------------------------------------------------------------------------------
/components/landing/intro-text.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { AnimatePresence, motion, useInView } from "framer-motion";
4 | import Image from "next/image";
5 | import Link from "next/link";
6 | import React from "react";
7 | import { Button } from "../ui/button";
8 | import VariantCarousel from "./variant-carousel";
9 |
10 | export default function IntroText() {
11 | const ref = React.useRef(null);
12 | const isInView = useInView(ref);
13 |
14 | const FADE_DOWN_ANIMATION_VARIANTS = {
15 | hidden: { opacity: 0, y: -10 },
16 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
17 | };
18 | return (
19 |
20 |
21 |
35 |
39 | Beautiful Framer Motion Animations
40 |
41 |
45 | A collection of handmade, free, and ready to use animations &
46 | variants for your next project.
47 |
48 |
49 |
53 |
54 | Get started
55 |
56 |
57 |
58 |
59 | Learn more →
60 |
61 |
62 |
63 |
64 |
65 |
66 |
72 |
73 |
79 |
80 |
81 | Variants Interactive Demo
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/components/landing/variant-carousel.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import {
5 | Card,
6 | CardContent,
7 | CardDescription,
8 | CardFooter,
9 | CardHeader,
10 | CardTitle,
11 | } from "@/components/ui/card";
12 | import { Button } from "../ui/button";
13 | import {
14 | CodeIcon,
15 | ReloadIcon,
16 | TriangleLeftIcon,
17 | TriangleRightIcon,
18 | } from "@radix-ui/react-icons";
19 | import {
20 | BlurIn,
21 | FadeDownStagger,
22 | FadeUpStagger,
23 | GradualSpacing,
24 | LetterPullUp,
25 | MultiDirectionSlide,
26 | RotateText,
27 | SeparateAway,
28 | SlightFlip,
29 | StaggeredFadeIn,
30 | TypingEffect,
31 | VelocityScroll,
32 | WavyText,
33 | WordPullUp,
34 | } from "@/variants/variant-previews";
35 | import {
36 | Select,
37 | SelectContent,
38 | SelectGroup,
39 | SelectItem,
40 | SelectLabel,
41 | SelectTrigger,
42 | SelectValue,
43 | } from "@/components/ui/select";
44 | import {
45 | Tooltip,
46 | TooltipContent,
47 | TooltipProvider,
48 | TooltipTrigger,
49 | } from "@/components/ui/tooltip";
50 | import { motion } from "framer-motion";
51 | import Link from "next/link";
52 |
53 | export default function VariantCarousel() {
54 | let generateZeros = (n: number) => Array(n).fill(0);
55 | let [keys, setKeys] = React.useState(generateZeros(20));
56 | let [index, setIndex] = React.useState(0);
57 | let [key, setKey] = React.useState(0); // new state for component key
58 |
59 | const nextVariant = () => {
60 | setIndex((prevIndex) => (prevIndex + 1) % variants.length);
61 | };
62 |
63 | const prevVariant = () => {
64 | setIndex(
65 | (prevIndex) => (prevIndex + variants.length - 1) % variants.length
66 | );
67 | };
68 |
69 | const resetVariant = () => {
70 | // resetting the animation by providing a new key to the component
71 | setKey((prevKey) => prevKey + 1);
72 | };
73 |
74 | let variants = [
75 | {
76 | name: "Fade Down with Stagger",
77 | // @ts-ignore
78 | preview: ,
79 | id: 1,
80 | },
81 | {
82 | name: "Fade Up with Stagger",
83 | // @ts-ignore
84 | preview: ,
85 | id: 2,
86 | },
87 | {
88 | name: "Multi Direction Slide",
89 | // @ts-ignore
90 | preview: ,
91 | id: 3,
92 | },
93 | {
94 | name: "Staggered Fade In",
95 | preview: ,
96 | id: 4,
97 | },
98 | {
99 | name: "Staggered Letter Pull Up",
100 | preview: ,
101 | id: 5,
102 | },
103 | {
104 | name: "Word Pull Up",
105 | preview: ,
106 | id: 6,
107 | },
108 | {
109 | name: "Scroll Based Velocity",
110 | preview: ,
111 | id: 7,
112 | },
113 | {
114 | name: "Rotate Between Words",
115 | preview: ,
116 | id: 8,
117 | },
118 | {
119 | name: "Typing Effect",
120 | preview: ,
121 | id: 9,
122 | },
123 | {
124 | name: "Separate Away",
125 | preview: ,
126 | id: 10,
127 | },
128 | {
129 | name: "Gradual Spacing",
130 | preview: ,
131 | id: 11,
132 | },
133 | {
134 | name: "Blur In",
135 | preview: ,
136 | id: 12,
137 | },
138 | {
139 | name: "Slight Flip",
140 | preview: ,
141 | id: 13,
142 | },
143 | {
144 | name: "Wavy Text",
145 | preview: ,
146 | id: 14,
147 | },
148 | ];
149 |
150 | let buttons = [
151 | {
152 | function: prevVariant,
153 | tooltipText: "Previous Variant",
154 | icon: TriangleLeftIcon,
155 | },
156 | {
157 | function: nextVariant,
158 | tooltipText: "Next Variant",
159 | icon: TriangleRightIcon,
160 | },
161 | {
162 | function: resetVariant,
163 | tooltipText: "Reset Variant",
164 | icon: ReloadIcon,
165 | },
166 | ];
167 |
168 | return (
169 |
170 |
171 |
172 |
173 |
174 |
setIndex(value - 1)}>
175 |
176 |
177 |
178 |
179 |
180 | Variants
181 | {variants.map((variant, i) => (
182 | // @ts-ignore
183 |
184 | {variant.name}
185 |
186 | ))}
187 |
188 |
189 |
190 |
191 | {buttons.map((button, i) => (
192 |
193 |
194 |
195 |
200 |
201 |
202 |
203 |
204 | {button.tooltipText}
205 |
206 |
207 |
208 | ))}
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | {React.cloneElement(variants[index].preview, { key: key })}
217 |
218 |
219 |
220 |
221 |
222 | {variants[index].name}
223 |
224 |
229 |
230 |
231 | View
232 |
233 |
234 |
235 |
236 |
237 | );
238 | }
239 |
--------------------------------------------------------------------------------
/components/shared/footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React from "react";
3 |
4 | export default function Footer() {
5 | return (
6 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/components/shared/main-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useState } from "react";
4 | import { GitHubLogoIcon, TwitterLogoIcon } from "@radix-ui/react-icons";
5 | import { Button } from "@/components/ui/button";
6 | import ModeToggle from "@/components/shared/mode-toggle";
7 | import { NavDropdown } from "@/components/shared/nav-dropdown";
8 | import NavLinks from "@/components/shared/nav-links";
9 | import VVLogo from "@/components/shared/vv-logo";
10 | import Link from "next/link";
11 | import { motion } from "framer-motion";
12 | import NavSheet from "./nav-sheet";
13 |
14 | export default function MainNav() {
15 | const [isScrolled, setIsScrolled] = useState(false);
16 |
17 | useEffect(() => {
18 | const handleScroll = () => {
19 | const offset = window.scrollY;
20 | if (offset > 20) {
21 | setIsScrolled(true);
22 | } else {
23 | setIsScrolled(false);
24 | }
25 | };
26 |
27 | // Call handleScroll on initial render
28 | handleScroll();
29 |
30 | window.addEventListener("scroll", handleScroll);
31 | return () => {
32 | window.removeEventListener("scroll", handleScroll);
33 | };
34 | }, []);
35 |
36 | return (
37 |
45 |
49 |
55 |
56 | {/* */}
57 |
58 |
59 |
60 |
65 |
66 |
67 |
68 |
69 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | );
83 | }
84 |
--------------------------------------------------------------------------------
/components/shared/mode-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { LaptopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons";
5 | import { useTheme } from "next-themes";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuItem,
12 | DropdownMenuTrigger,
13 | } from "@/components/ui/dropdown-menu";
14 |
15 | export default function ModeToggle() {
16 | const { setTheme } = useTheme();
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | Toggle theme
25 |
26 |
27 |
28 | setTheme("light")}>
29 |
30 | Light
31 |
32 | setTheme("dark")}>
33 |
34 | Dark
35 |
36 | setTheme("system")}>
37 |
38 | System
39 |
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/components/shared/nav-dropdown.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/components/ui/button";
4 | import {
5 | DropdownMenu,
6 | DropdownMenuContent,
7 | DropdownMenuGroup,
8 | DropdownMenuItem,
9 | DropdownMenuLabel,
10 | DropdownMenuPortal,
11 | DropdownMenuSeparator,
12 | DropdownMenuShortcut,
13 | DropdownMenuSub,
14 | DropdownMenuSubContent,
15 | DropdownMenuSubTrigger,
16 | DropdownMenuTrigger,
17 | } from "@/components/ui/dropdown-menu";
18 |
19 | // import { ArrowUpDown, MenuIcon, Text } from "lucide-react";
20 | import {
21 | HamburgerMenuIcon,
22 | LaptopIcon,
23 | MagicWandIcon,
24 | MoonIcon,
25 | MoveIcon,
26 | SunIcon,
27 | } from "@radix-ui/react-icons";
28 | import { useTheme } from "next-themes";
29 | import Link from "next/link";
30 |
31 | export function NavDropdown() {
32 | const { setTheme } = useTheme();
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Variant Vault
43 |
44 |
45 |
46 |
47 | Text Variants
48 |
49 |
50 |
51 |
52 |
53 | Page Variants
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Theme
63 |
64 |
65 | setTheme("light")}>
66 | Light
67 |
68 |
69 |
70 |
71 | setTheme("dark")}>
72 | Dark
73 |
74 |
75 |
76 |
77 | setTheme("system")}>
78 | System
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | GitHub
89 | Support
90 | API
91 |
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/components/shared/nav-links.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { cn } from "@/lib/utils";
4 | import { usePathname } from "next/navigation";
5 |
6 | import Link from "next/link";
7 | import React from "react";
8 |
9 | export default function NavLinks() {
10 | const pathname = usePathname();
11 | return (
12 |
13 |
20 | Usage
21 |
22 |
31 | Text Variants
32 |
33 |
41 | Page Variants
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/components/shared/nav-sheet.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Sheet,
3 | SheetContent,
4 | SheetDescription,
5 | SheetHeader,
6 | SheetTitle,
7 | SheetTrigger,
8 | } from "@/components/ui/sheet";
9 |
10 | import React from "react";
11 | import { Button } from "../ui/button";
12 | import {
13 | GitHubLogoIcon,
14 | HamburgerMenuIcon,
15 | HomeIcon,
16 | SunIcon,
17 | TwitterLogoIcon,
18 | } from "@radix-ui/react-icons";
19 | import ModeToggle from "./mode-toggle";
20 | import Link from "next/link";
21 | const projects = [
22 | {
23 | name: "Home",
24 | href: "/",
25 | },
26 | {
27 | name: "Usage",
28 | href: "/usage",
29 | },
30 | {
31 | name: "Text Variants",
32 | href: "/text-variants",
33 | },
34 | ];
35 | function classNames(...classes: any[]) {
36 | return classes.filter(Boolean).join(" ");
37 | }
38 | export default function NavSheet() {
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Variant Vault
49 |
50 |
51 |
52 |
57 |
62 |
63 |
64 |
65 |
70 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | {projects.map((project) => (
86 |
91 |
92 |
93 |
{project.name}
94 |
95 |
96 |
97 | ))}
98 |
99 |
100 |
101 |
102 |
103 |
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/components/shared/spotlight.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { CodeIcon } from "@radix-ui/react-icons";
5 |
6 | import {
7 | CommandDialog,
8 | CommandEmpty,
9 | CommandGroup,
10 | CommandInput,
11 | CommandItem,
12 | CommandList,
13 | } from "@/components/ui/command";
14 | import { Button } from "../ui/button";
15 | import { cn } from "@/lib/utils";
16 |
17 | export function Spotlight({ filteredVariants }: any) {
18 | const [open, setOpen] = React.useState(false);
19 |
20 | React.useEffect(() => {
21 | const down = (e: KeyboardEvent) => {
22 | if (e.key === "k" && e.metaKey) {
23 | setOpen((open) => !open);
24 | }
25 | };
26 |
27 | document.addEventListener("keydown", down);
28 | return () => document.removeEventListener("keydown", down);
29 | }, []);
30 |
31 | return (
32 |
33 | setOpen(true)}
39 | >
40 | Search variants...
41 | Search...
42 |
43 | ⌘ K
44 |
45 |
46 |
47 |
48 |
49 |
50 | No results found.
51 |
52 | {/*
53 |
54 | Calendar
55 | */}
56 | {filteredVariants.map((variant: any, index: number) => (
57 | {
60 | window.location.href = `#${variant.name
61 | .toLowerCase()
62 | .replace(" ", "-")}`;
63 | setOpen(false);
64 | }}
65 | >
66 |
67 | {variant.name}
68 |
69 | ))}
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/components/shared/vv-logo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 |
5 | import {
6 | ArrowDownIcon,
7 | CalendarIcon,
8 | CodeIcon,
9 | GitHubLogoIcon,
10 | LinkedInLogoIcon,
11 | TwitterLogoIcon,
12 | } from "@radix-ui/react-icons";
13 |
14 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
15 | import { Button } from "@/components/ui/button";
16 | import {
17 | HoverCard,
18 | HoverCardContent,
19 | HoverCardTrigger,
20 | } from "@/components/ui/hover-card";
21 | import Link from "next/link";
22 |
23 | export default function VVLogo() {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 | Variant Vault
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | VC
39 |
40 |
41 |
Built by Christopher Abdo
42 |
43 | Check out my other projects{" "}
44 |
45 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { ThemeProvider as NextThemesProvider } from "next-themes";
5 | import { type ThemeProviderProps } from "next-themes/dist/types";
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children} ;
9 | }
10 |
--------------------------------------------------------------------------------
/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
3 | import { ChevronDownIcon } from "@radix-ui/react-icons"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Accordion = AccordionPrimitive.Root
8 |
9 | const AccordionItem = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
18 | ))
19 | AccordionItem.displayName = "AccordionItem"
20 |
21 | const AccordionTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, children, ...props }, ref) => (
25 |
26 | svg]:rotate-180",
30 | className
31 | )}
32 | {...props}
33 | >
34 | {children}
35 |
36 |
37 |
38 | ))
39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40 |
41 | const AccordionContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, children, ...props }, ref) => (
45 |
53 | {children}
54 |
55 | ))
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
59 |
--------------------------------------------------------------------------------
/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ))
19 | Avatar.displayName = AvatarPrimitive.Root.displayName
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ))
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ))
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47 |
48 | export { Avatar, AvatarImage, AvatarFallback }
49 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { DialogProps } from "@radix-ui/react-dialog"
3 | import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
4 | import { Command as CommandPrimitive } from "cmdk"
5 |
6 | import { cn } from "@/lib/utils"
7 | import { Dialog, DialogContent } from "@/components/ui/dialog"
8 |
9 | const Command = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 | ))
22 | Command.displayName = CommandPrimitive.displayName
23 |
24 | interface CommandDialogProps extends DialogProps {}
25 |
26 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
27 | return (
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | const CommandInput = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
43 |
44 |
52 |
53 | ))
54 |
55 | CommandInput.displayName = CommandPrimitive.Input.displayName
56 |
57 | const CommandList = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
66 | ))
67 |
68 | CommandList.displayName = CommandPrimitive.List.displayName
69 |
70 | const CommandEmpty = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >((props, ref) => (
74 |
79 | ))
80 |
81 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82 |
83 | const CommandGroup = React.forwardRef<
84 | React.ElementRef,
85 | React.ComponentPropsWithoutRef
86 | >(({ className, ...props }, ref) => (
87 |
95 | ))
96 |
97 | CommandGroup.displayName = CommandPrimitive.Group.displayName
98 |
99 | const CommandSeparator = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110 |
111 | const CommandItem = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
123 | ))
124 |
125 | CommandItem.displayName = CommandPrimitive.Item.displayName
126 |
127 | const CommandShortcut = ({
128 | className,
129 | ...props
130 | }: React.HTMLAttributes) => {
131 | return (
132 |
139 | )
140 | }
141 | CommandShortcut.displayName = "CommandShortcut"
142 |
143 | export {
144 | Command,
145 | CommandDialog,
146 | CommandInput,
147 | CommandList,
148 | CommandEmpty,
149 | CommandGroup,
150 | CommandItem,
151 | CommandShortcut,
152 | CommandSeparator,
153 | }
154 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DialogPrimitive from "@radix-ui/react-dialog"
3 | import { Cross2Icon } from "@radix-ui/react-icons"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Dialog = DialogPrimitive.Root
8 |
9 | const DialogTrigger = DialogPrimitive.Trigger
10 |
11 | const DialogPortal = ({
12 | className,
13 | ...props
14 | }: DialogPrimitive.DialogPortalProps) => (
15 |
16 | )
17 | DialogPortal.displayName = DialogPrimitive.Portal.displayName
18 |
19 | const DialogOverlay = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef
22 | >(({ className, ...props }, ref) => (
23 |
31 | ))
32 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
33 |
34 | const DialogContent = React.forwardRef<
35 | React.ElementRef,
36 | React.ComponentPropsWithoutRef
37 | >(({ className, children, ...props }, ref) => (
38 |
39 |
40 |
48 | {children}
49 |
50 |
51 | Close
52 |
53 |
54 |
55 | ))
56 | DialogContent.displayName = DialogPrimitive.Content.displayName
57 |
58 | const DialogHeader = ({
59 | className,
60 | ...props
61 | }: React.HTMLAttributes) => (
62 |
69 | )
70 | DialogHeader.displayName = "DialogHeader"
71 |
72 | const DialogFooter = ({
73 | className,
74 | ...props
75 | }: React.HTMLAttributes) => (
76 |
83 | )
84 | DialogFooter.displayName = "DialogFooter"
85 |
86 | const DialogTitle = React.forwardRef<
87 | React.ElementRef,
88 | React.ComponentPropsWithoutRef
89 | >(({ className, ...props }, ref) => (
90 |
98 | ))
99 | DialogTitle.displayName = DialogPrimitive.Title.displayName
100 |
101 | const DialogDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | DialogDescription.displayName = DialogPrimitive.Description.displayName
112 |
113 | export {
114 | Dialog,
115 | DialogTrigger,
116 | DialogContent,
117 | DialogHeader,
118 | DialogFooter,
119 | DialogTitle,
120 | DialogDescription,
121 | }
122 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3 | import {
4 | CheckIcon,
5 | ChevronRightIcon,
6 | DotFilledIcon,
7 | } from "@radix-ui/react-icons"
8 |
9 | import { cn } from "@/lib/utils"
10 |
11 | const DropdownMenu = DropdownMenuPrimitive.Root
12 |
13 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
14 |
15 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
16 |
17 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
18 |
19 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
20 |
21 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
22 |
23 | const DropdownMenuSubTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef & {
26 | inset?: boolean
27 | }
28 | >(({ className, inset, children, ...props }, ref) => (
29 |
38 | {children}
39 |
40 |
41 | ))
42 | DropdownMenuSubTrigger.displayName =
43 | DropdownMenuPrimitive.SubTrigger.displayName
44 |
45 | const DropdownMenuSubContent = React.forwardRef<
46 | React.ElementRef,
47 | React.ComponentPropsWithoutRef
48 | >(({ className, ...props }, ref) => (
49 |
57 | ))
58 | DropdownMenuSubContent.displayName =
59 | DropdownMenuPrimitive.SubContent.displayName
60 |
61 | const DropdownMenuContent = React.forwardRef<
62 | React.ElementRef,
63 | React.ComponentPropsWithoutRef
64 | >(({ className, sideOffset = 4, ...props }, ref) => (
65 |
66 |
76 |
77 | ))
78 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
79 |
80 | const DropdownMenuItem = React.forwardRef<
81 | React.ElementRef,
82 | React.ComponentPropsWithoutRef & {
83 | inset?: boolean
84 | }
85 | >(({ className, inset, ...props }, ref) => (
86 |
95 | ))
96 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
97 |
98 | const DropdownMenuCheckboxItem = React.forwardRef<
99 | React.ElementRef,
100 | React.ComponentPropsWithoutRef
101 | >(({ className, children, checked, ...props }, ref) => (
102 |
111 |
112 |
113 |
114 |
115 |
116 | {children}
117 |
118 | ))
119 | DropdownMenuCheckboxItem.displayName =
120 | DropdownMenuPrimitive.CheckboxItem.displayName
121 |
122 | const DropdownMenuRadioItem = React.forwardRef<
123 | React.ElementRef,
124 | React.ComponentPropsWithoutRef
125 | >(({ className, children, ...props }, ref) => (
126 |
134 |
135 |
136 |
137 |
138 |
139 | {children}
140 |
141 | ))
142 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
143 |
144 | const DropdownMenuLabel = React.forwardRef<
145 | React.ElementRef,
146 | React.ComponentPropsWithoutRef & {
147 | inset?: boolean
148 | }
149 | >(({ className, inset, ...props }, ref) => (
150 |
159 | ))
160 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
161 |
162 | const DropdownMenuSeparator = React.forwardRef<
163 | React.ElementRef,
164 | React.ComponentPropsWithoutRef
165 | >(({ className, ...props }, ref) => (
166 |
171 | ))
172 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
173 |
174 | const DropdownMenuShortcut = ({
175 | className,
176 | ...props
177 | }: React.HTMLAttributes) => {
178 | return (
179 |
183 | )
184 | }
185 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
186 |
187 | export {
188 | DropdownMenu,
189 | DropdownMenuTrigger,
190 | DropdownMenuContent,
191 | DropdownMenuItem,
192 | DropdownMenuCheckboxItem,
193 | DropdownMenuRadioItem,
194 | DropdownMenuLabel,
195 | DropdownMenuSeparator,
196 | DropdownMenuShortcut,
197 | DropdownMenuGroup,
198 | DropdownMenuPortal,
199 | DropdownMenuSub,
200 | DropdownMenuSubContent,
201 | DropdownMenuSubTrigger,
202 | DropdownMenuRadioGroup,
203 | }
204 |
--------------------------------------------------------------------------------
/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const HoverCard = HoverCardPrimitive.Root
7 |
8 | const HoverCardTrigger = HoverCardPrimitive.Trigger
9 |
10 | const HoverCardContent = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14 |
24 | ))
25 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
26 |
27 | export { HoverCard, HoverCardTrigger, HoverCardContent }
28 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const ScrollArea = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, children, ...props }, ref) => (
10 |
15 |
16 | {children}
17 |
18 |
19 |
20 |
21 | ))
22 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
23 |
24 | const ScrollBar = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, orientation = "vertical", ...props }, ref) => (
28 |
41 |
42 |
43 | ))
44 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
45 |
46 | export { ScrollArea, ScrollBar }
47 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
3 | import * as SelectPrimitive from "@radix-ui/react-select"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Select = SelectPrimitive.Root
8 |
9 | const SelectGroup = SelectPrimitive.Group
10 |
11 | const SelectValue = SelectPrimitive.Value
12 |
13 | const SelectTrigger = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, children, ...props }, ref) => (
17 |
25 | {children}
26 |
27 |
28 |
29 |
30 | ))
31 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32 |
33 | const SelectContent = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, children, position = "popper", ...props }, ref) => (
37 |
38 |
49 |
56 | {children}
57 |
58 |
59 |
60 | ))
61 | SelectContent.displayName = SelectPrimitive.Content.displayName
62 |
63 | const SelectLabel = React.forwardRef<
64 | React.ElementRef,
65 | React.ComponentPropsWithoutRef
66 | >(({ className, ...props }, ref) => (
67 |
72 | ))
73 | SelectLabel.displayName = SelectPrimitive.Label.displayName
74 |
75 | const SelectItem = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, ...props }, ref) => (
79 |
87 |
88 |
89 |
90 |
91 |
92 | {children}
93 |
94 | ))
95 | SelectItem.displayName = SelectPrimitive.Item.displayName
96 |
97 | const SelectSeparator = React.forwardRef<
98 | React.ElementRef,
99 | React.ComponentPropsWithoutRef
100 | >(({ className, ...props }, ref) => (
101 |
106 | ))
107 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
108 |
109 | export {
110 | Select,
111 | SelectGroup,
112 | SelectValue,
113 | SelectTrigger,
114 | SelectContent,
115 | SelectLabel,
116 | SelectItem,
117 | SelectSeparator,
118 | }
119 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SheetPrimitive from "@radix-ui/react-dialog"
3 | import { Cross2Icon } from "@radix-ui/react-icons"
4 | import { cva, type VariantProps } from "class-variance-authority"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Sheet = SheetPrimitive.Root
9 |
10 | const SheetTrigger = SheetPrimitive.Trigger
11 |
12 | const SheetClose = SheetPrimitive.Close
13 |
14 | const SheetPortal = ({
15 | className,
16 | ...props
17 | }: SheetPrimitive.DialogPortalProps) => (
18 |
19 | )
20 | SheetPortal.displayName = SheetPrimitive.Portal.displayName
21 |
22 | const SheetOverlay = React.forwardRef<
23 | React.ElementRef,
24 | React.ComponentPropsWithoutRef
25 | >(({ className, ...props }, ref) => (
26 |
34 | ))
35 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
36 |
37 | const sheetVariants = cva(
38 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
39 | {
40 | variants: {
41 | side: {
42 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
43 | bottom:
44 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
45 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
46 | right:
47 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
48 | },
49 | },
50 | defaultVariants: {
51 | side: "right",
52 | },
53 | }
54 | )
55 |
56 | interface SheetContentProps
57 | extends React.ComponentPropsWithoutRef,
58 | VariantProps {}
59 |
60 | const SheetContent = React.forwardRef<
61 | React.ElementRef,
62 | SheetContentProps
63 | >(({ side = "right", className, children, ...props }, ref) => (
64 |
65 |
66 |
71 | {children}
72 |
73 |
74 | Close
75 |
76 |
77 |
78 | ))
79 | SheetContent.displayName = SheetPrimitive.Content.displayName
80 |
81 | const SheetHeader = ({
82 | className,
83 | ...props
84 | }: React.HTMLAttributes) => (
85 |
92 | )
93 | SheetHeader.displayName = "SheetHeader"
94 |
95 | const SheetFooter = ({
96 | className,
97 | ...props
98 | }: React.HTMLAttributes) => (
99 |
106 | )
107 | SheetFooter.displayName = "SheetFooter"
108 |
109 | const SheetTitle = React.forwardRef<
110 | React.ElementRef,
111 | React.ComponentPropsWithoutRef
112 | >(({ className, ...props }, ref) => (
113 |
118 | ))
119 | SheetTitle.displayName = SheetPrimitive.Title.displayName
120 |
121 | const SheetDescription = React.forwardRef<
122 | React.ElementRef,
123 | React.ComponentPropsWithoutRef
124 | >(({ className, ...props }, ref) => (
125 |
130 | ))
131 | SheetDescription.displayName = SheetPrimitive.Description.displayName
132 |
133 | export {
134 | Sheet,
135 | SheetTrigger,
136 | SheetClose,
137 | SheetContent,
138 | SheetHeader,
139 | SheetFooter,
140 | SheetTitle,
141 | SheetDescription,
142 | }
143 |
--------------------------------------------------------------------------------
/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TabsPrimitive from "@radix-ui/react-tabs"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Tabs = TabsPrimitive.Root
7 |
8 | const TabsList = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | TabsList.displayName = TabsPrimitive.List.displayName
22 |
23 | const TabsTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
35 | ))
36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37 |
38 | const TabsContent = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
50 | ))
51 | TabsContent.displayName = TabsPrimitive.Content.displayName
52 |
53 | export { Tabs, TabsList, TabsTrigger, TabsContent }
54 |
--------------------------------------------------------------------------------
/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Cross2Icon } from "@radix-ui/react-icons"
3 | import * as ToastPrimitives from "@radix-ui/react-toast"
4 | import { cva, type VariantProps } from "class-variance-authority"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24 |
25 | const toastVariants = cva(
26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
27 | {
28 | variants: {
29 | variant: {
30 | default: "border bg-background",
31 | destructive:
32 | "destructive group border-destructive bg-destructive text-destructive-foreground",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | },
38 | }
39 | )
40 |
41 | const Toast = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef &
44 | VariantProps
45 | >(({ className, variant, ...props }, ref) => {
46 | return (
47 |
52 | )
53 | })
54 | Toast.displayName = ToastPrimitives.Root.displayName
55 |
56 | const ToastAction = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
68 | ))
69 | ToastAction.displayName = ToastPrimitives.Action.displayName
70 |
71 | const ToastClose = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >(({ className, ...props }, ref) => (
75 |
84 |
85 |
86 | ))
87 | ToastClose.displayName = ToastPrimitives.Close.displayName
88 |
89 | const ToastTitle = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => (
93 |
98 | ))
99 | ToastTitle.displayName = ToastPrimitives.Title.displayName
100 |
101 | const ToastDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | ToastDescription.displayName = ToastPrimitives.Description.displayName
112 |
113 | type ToastProps = React.ComponentPropsWithoutRef
114 |
115 | type ToastActionElement = React.ReactElement
116 |
117 | export {
118 | type ToastProps,
119 | type ToastActionElement,
120 | ToastProvider,
121 | ToastViewport,
122 | Toast,
123 | ToastTitle,
124 | ToastDescription,
125 | ToastClose,
126 | ToastAction,
127 | }
128 |
--------------------------------------------------------------------------------
/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast";
11 | import { useToast } from "@/components/ui/use-toast";
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast();
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title} }
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | );
31 | })}
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = TooltipPrimitive.Root
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | const TooltipContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, sideOffset = 4, ...props }, ref) => (
16 |
25 | ))
26 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
27 |
28 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
29 |
--------------------------------------------------------------------------------
/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | // Inspired by react-hot-toast library
2 | import * as React from "react"
3 |
4 | import type {
5 | ToastActionElement,
6 | ToastProps,
7 | } from "@/components/ui/toast"
8 |
9 | const TOAST_LIMIT = 1
10 | const TOAST_REMOVE_DELAY = 1000000
11 |
12 | type ToasterToast = ToastProps & {
13 | id: string
14 | title?: React.ReactNode
15 | description?: React.ReactNode
16 | action?: ToastActionElement
17 | }
18 |
19 | const actionTypes = {
20 | ADD_TOAST: "ADD_TOAST",
21 | UPDATE_TOAST: "UPDATE_TOAST",
22 | DISMISS_TOAST: "DISMISS_TOAST",
23 | REMOVE_TOAST: "REMOVE_TOAST",
24 | } as const
25 |
26 | let count = 0
27 |
28 | function genId() {
29 | count = (count + 1) % Number.MAX_VALUE
30 | return count.toString()
31 | }
32 |
33 | type ActionType = typeof actionTypes
34 |
35 | type Action =
36 | | {
37 | type: ActionType["ADD_TOAST"]
38 | toast: ToasterToast
39 | }
40 | | {
41 | type: ActionType["UPDATE_TOAST"]
42 | toast: Partial
43 | }
44 | | {
45 | type: ActionType["DISMISS_TOAST"]
46 | toastId?: ToasterToast["id"]
47 | }
48 | | {
49 | type: ActionType["REMOVE_TOAST"]
50 | toastId?: ToasterToast["id"]
51 | }
52 |
53 | interface State {
54 | toasts: ToasterToast[]
55 | }
56 |
57 | const toastTimeouts = new Map>()
58 |
59 | const addToRemoveQueue = (toastId: string) => {
60 | if (toastTimeouts.has(toastId)) {
61 | return
62 | }
63 |
64 | const timeout = setTimeout(() => {
65 | toastTimeouts.delete(toastId)
66 | dispatch({
67 | type: "REMOVE_TOAST",
68 | toastId: toastId,
69 | })
70 | }, TOAST_REMOVE_DELAY)
71 |
72 | toastTimeouts.set(toastId, timeout)
73 | }
74 |
75 | export const reducer = (state: State, action: Action): State => {
76 | switch (action.type) {
77 | case "ADD_TOAST":
78 | return {
79 | ...state,
80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
81 | }
82 |
83 | case "UPDATE_TOAST":
84 | return {
85 | ...state,
86 | toasts: state.toasts.map((t) =>
87 | t.id === action.toast.id ? { ...t, ...action.toast } : t
88 | ),
89 | }
90 |
91 | case "DISMISS_TOAST": {
92 | const { toastId } = action
93 |
94 | // ! Side effects ! - This could be extracted into a dismissToast() action,
95 | // but I'll keep it here for simplicity
96 | if (toastId) {
97 | addToRemoveQueue(toastId)
98 | } else {
99 | state.toasts.forEach((toast) => {
100 | addToRemoveQueue(toast.id)
101 | })
102 | }
103 |
104 | return {
105 | ...state,
106 | toasts: state.toasts.map((t) =>
107 | t.id === toastId || toastId === undefined
108 | ? {
109 | ...t,
110 | open: false,
111 | }
112 | : t
113 | ),
114 | }
115 | }
116 | case "REMOVE_TOAST":
117 | if (action.toastId === undefined) {
118 | return {
119 | ...state,
120 | toasts: [],
121 | }
122 | }
123 | return {
124 | ...state,
125 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
126 | }
127 | }
128 | }
129 |
130 | const listeners: Array<(state: State) => void> = []
131 |
132 | let memoryState: State = { toasts: [] }
133 |
134 | function dispatch(action: Action) {
135 | memoryState = reducer(memoryState, action)
136 | listeners.forEach((listener) => {
137 | listener(memoryState)
138 | })
139 | }
140 |
141 | type Toast = Omit
142 |
143 | function toast({ ...props }: Toast) {
144 | const id = genId()
145 |
146 | const update = (props: ToasterToast) =>
147 | dispatch({
148 | type: "UPDATE_TOAST",
149 | toast: { ...props, id },
150 | })
151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
152 |
153 | dispatch({
154 | type: "ADD_TOAST",
155 | toast: {
156 | ...props,
157 | id,
158 | open: true,
159 | onOpenChange: (open) => {
160 | if (!open) dismiss()
161 | },
162 | },
163 | })
164 |
165 | return {
166 | id: id,
167 | dismiss,
168 | update,
169 | }
170 | }
171 |
172 | function useToast() {
173 | const [state, setState] = React.useState(memoryState)
174 |
175 | React.useEffect(() => {
176 | listeners.push(setState)
177 | return () => {
178 | const index = listeners.indexOf(setState)
179 | if (index > -1) {
180 | listeners.splice(index, 1)
181 | }
182 | }
183 | }, [state])
184 |
185 | return {
186 | ...state,
187 | toast,
188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
189 | }
190 | }
191 |
192 | export { useToast, toast }
193 |
--------------------------------------------------------------------------------
/components/usage/all-variants.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Accordion,
5 | AccordionContent,
6 | AccordionItem,
7 | AccordionTrigger,
8 | } from "@/components/ui/accordion";
9 | import { DEFINITIONS_VARIANTS } from "@/variants/definitions";
10 |
11 | import React from "react";
12 | import { ScrollArea } from "@/components/ui/scroll-area";
13 | import { Button } from "@/components/ui/button";
14 | import { CheckIcon, ClipboardIcon } from "@radix-ui/react-icons";
15 | import { useToast } from "@/components/ui/use-toast";
16 |
17 | export default function AllVariants() {
18 | const { toast } = useToast();
19 |
20 | const [copied, setCopied] = React.useState(false);
21 |
22 | React.useEffect(() => {
23 | if (copied) {
24 | const timeout = setTimeout(() => {
25 | setCopied(false);
26 | }, 2000);
27 | return () => clearTimeout(timeout);
28 | }
29 | }, [copied]);
30 |
31 | return (
32 |
33 |
34 | Variant Definitions
35 |
36 |
37 | {/* button that is absolute in the top right corner of the scrollarea */}
38 | {
43 | setCopied(true);
44 | window.navigator.clipboard.writeText(DEFINITIONS_VARIANTS);
45 | toast({
46 | title: "Copied variant definitions to clipboard",
47 | description: "Just paste it in your code!",
48 | });
49 | }}
50 | >
51 | {/* */}
52 | {copied ? (
53 |
54 | ) : (
55 |
56 | )}
57 |
58 |
59 |
60 | {DEFINITIONS_VARIANTS}
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/components/usage/call-to-action.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { Button } from "../ui/button";
3 |
4 | export default function CallToAction() {
5 | return (
6 |
7 |
8 |
9 |
10 | Discover beautiful variants for your next project now!
11 |
12 |
13 | Thank you for using Variant Vault. I hope you enjoy it.
14 |
15 |
16 |
17 | Get Started
18 |
19 |
20 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/components/usage/copy.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import {
5 | DropdownMenu,
6 | DropdownMenuContent,
7 | DropdownMenuGroup,
8 | DropdownMenuItem,
9 | DropdownMenuTrigger,
10 | } from "@/components/ui/dropdown-menu";
11 | import { Button } from "@/components/ui/button";
12 | import { CheckIcon, ClipboardIcon } from "@radix-ui/react-icons";
13 | import { useToast } from "@/components/ui/use-toast";
14 |
15 | export default function Copy() {
16 | const { toast } = useToast();
17 |
18 | const [copied, setCopied] = React.useState(false);
19 |
20 | React.useEffect(() => {
21 | if (copied) {
22 | const timeout = setTimeout(() => {
23 | setCopied(false);
24 | }, 2000);
25 | return () => clearTimeout(timeout);
26 | }
27 | }, [copied]);
28 | return (
29 |
30 |
31 |
36 | {/* */}
37 | {copied ? (
38 |
39 | ) : (
40 |
41 | )}
42 |
43 |
44 |
45 |
46 | {
48 | setCopied(true);
49 | window.navigator.clipboard.writeText("npm install framer-motion");
50 | toast({
51 | title: "Copied npm command to clipboard",
52 | description: "npm install framer-motion",
53 | });
54 | }}
55 | >
56 | npm
57 |
58 | {
60 | setCopied(true);
61 | window.navigator.clipboard.writeText("yarn add framer-motion");
62 | toast({
63 | title: "Copied yarn command to clipboard",
64 | description: "yarn add framer-motion",
65 | });
66 | }}
67 | >
68 | yarn
69 |
70 | {
72 | setCopied(true);
73 | window.navigator.clipboard.writeText("pnpm i framer-motion");
74 | toast({
75 | title: "Copied pnpm command to clipboard",
76 | description: "pnpm i framer-motion",
77 | });
78 | }}
79 | >
80 | pnpm
81 |
82 |
83 |
84 |
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/components/usage/intro.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import Copy from "./copy";
5 | import { motion, useInView } from "framer-motion";
6 | import AllVariants from "./all-variants";
7 |
8 | export default function Intro() {
9 | const ref = React.useRef(null);
10 | const isInView = useInView(ref);
11 |
12 | const FADE_DOWN_ANIMATION_VARIANTS = {
13 | hidden: { opacity: 0, y: -10 },
14 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
15 | };
16 | return (
17 |
18 |
32 |
36 | Variant Vault Usage Guide
37 |
38 |
42 | Using Variant Vault is easy. All you need to do is install Framer
43 | Motion and copy and paste the code into your project.{" "}
44 |
45 |
46 |
50 |
51 |
52 |
53 | Step 1: Install Framer Motion
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | yarn add framer-motion
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
76 |
80 |
81 | Here are all the variant definitions in case you want to make a
82 | constants file.
83 |
84 |
85 |
86 |
87 |
88 |
89 | {/*
90 | */}
91 |
92 |
95 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@motionone/utils": "^10.15.1",
13 | "@radix-ui/react-accordion": "^1.1.2",
14 | "@radix-ui/react-avatar": "^1.0.3",
15 | "@radix-ui/react-dialog": "^1.0.4",
16 | "@radix-ui/react-dropdown-menu": "^2.0.5",
17 | "@radix-ui/react-hover-card": "^1.0.6",
18 | "@radix-ui/react-icons": "^1.3.0",
19 | "@radix-ui/react-scroll-area": "^1.0.4",
20 | "@radix-ui/react-select": "^1.2.2",
21 | "@radix-ui/react-slot": "^1.0.2",
22 | "@radix-ui/react-tabs": "^1.0.4",
23 | "@radix-ui/react-toast": "^1.1.4",
24 | "@radix-ui/react-tooltip": "^1.0.6",
25 | "@types/node": "20.3.1",
26 | "@types/react": "18.2.13",
27 | "@types/react-dom": "18.2.6",
28 | "autoprefixer": "10.4.14",
29 | "class-variance-authority": "^0.6.0",
30 | "clsx": "^1.2.1",
31 | "cmdk": "^0.2.0",
32 | "eslint": "8.43.0",
33 | "eslint-config-next": "13.4.7",
34 | "framer-motion": "^10.12.16",
35 | "next": "13.4.7",
36 | "next-themes": "^0.2.1",
37 | "postcss": "8.4.24",
38 | "react": "18.2.0",
39 | "react-dom": "18.2.0",
40 | "tailwind-merge": "^1.13.2",
41 | "tailwindcss": "3.3.2",
42 | "tailwindcss-animate": "^1.0.6",
43 | "typescript": "5.1.3"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/landing.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChrisAbdo/MotionVariants/d0413146d159dcc4968d7dd68ab963e3c6c8360e/public/landing.jpeg
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './components/**/*.{ts,tsx}',
6 | './app/**/*.{ts,tsx}',
7 | './variants/**/*.{ts,tsx}',
8 | ],
9 | theme: {
10 | container: {
11 | center: true,
12 | padding: "2rem",
13 | screens: {
14 | "2xl": "1400px",
15 | },
16 | },
17 | extend: {
18 | colors: {
19 | border: "hsl(var(--border))",
20 | input: "hsl(var(--input))",
21 | ring: "hsl(var(--ring))",
22 | background: "hsl(var(--background))",
23 | foreground: "hsl(var(--foreground))",
24 | primary: {
25 | DEFAULT: "hsl(var(--primary))",
26 | foreground: "hsl(var(--primary-foreground))",
27 | },
28 | secondary: {
29 | DEFAULT: "hsl(var(--secondary))",
30 | foreground: "hsl(var(--secondary-foreground))",
31 | },
32 | destructive: {
33 | DEFAULT: "hsl(var(--destructive))",
34 | foreground: "hsl(var(--destructive-foreground))",
35 | },
36 | muted: {
37 | DEFAULT: "hsl(var(--muted))",
38 | foreground: "hsl(var(--muted-foreground))",
39 | },
40 | accent: {
41 | DEFAULT: "hsl(var(--accent))",
42 | foreground: "hsl(var(--accent-foreground))",
43 | },
44 | popover: {
45 | DEFAULT: "hsl(var(--popover))",
46 | foreground: "hsl(var(--popover-foreground))",
47 | },
48 | card: {
49 | DEFAULT: "hsl(var(--card))",
50 | foreground: "hsl(var(--card-foreground))",
51 | },
52 | },
53 | borderRadius: {
54 | lg: "var(--radius)",
55 | md: "calc(var(--radius) - 2px)",
56 | sm: "calc(var(--radius) - 4px)",
57 | },
58 | keyframes: {
59 | "accordion-down": {
60 | from: { height: 0 },
61 | to: { height: "var(--radix-accordion-content-height)" },
62 | },
63 | "accordion-up": {
64 | from: { height: "var(--radix-accordion-content-height)" },
65 | to: { height: 0 },
66 | },
67 | },
68 | animation: {
69 | "accordion-down": "accordion-down 0.2s ease-out",
70 | "accordion-up": "accordion-up 0.2s ease-out",
71 | },
72 | },
73 | },
74 | plugins: [require("tailwindcss-animate")],
75 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/variants/code-snippets/index.ts:
--------------------------------------------------------------------------------
1 | export const FADE_DOWN_CODE = `import { motion } from "framer-motion";
2 |
3 | export function FadeDown() {
4 | const FADE_DOWN_ANIMATION_VARIANTS = {
5 | hidden: { opacity: 0, y: -10 },
6 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
7 | };
8 | return (
9 |
22 |
26 | Fade Down
27 |
28 |
32 | Animation Preview
33 |
34 |
38 | If you're seeing this, thank you for trying my project out! - C.J.A
39 |
40 |
41 | );
42 | }
43 | `;
44 |
45 | export const FADE_UP_CODE = `import { motion } from "framer-motion";
46 |
47 | export function FadeUpStagger() {
48 | const FADE_UP_ANIMATION_VARIANTS = {
49 | hidden: { opacity: 0, y: 10 },
50 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
51 | };
52 | return (
53 |
66 |
70 | Fade Up
71 |
72 |
76 | Animation Preview
77 |
78 |
82 |
83 |
84 |
85 | );
86 | }
87 | `;
88 |
89 | export const MULTIDIRECTION_SLIDE_CODE = `import { motion } from "framer-motion";
90 |
91 | export function MultiDirectionSlide() {
92 | const MULTIDIRECTION_SLIDE_VARIANTS = {
93 | hidden: { opacity: 0, x: "-25vw" },
94 | visible: { opacity: 1, x: 0 },
95 | right: { opacity: 0, x: "25vw" },
96 | };
97 | return (
98 |
99 |
106 | Multi Direction
107 |
108 |
109 |
116 | Slide
117 |
118 |
119 | );
120 | }
121 | `;
122 |
123 | export const STAGGERED_FADE_IN_CODE = `
124 | import React from "react";
125 | import { motion } from "framer-motion";
126 |
127 | export default function StaggeredFade() {
128 | const sentence = "Staggered Fade In";
129 | const wordVariants = {
130 | hidden: { opacity: 0 },
131 | visible: (i) => ({ y: 0, opacity: 1, transition: { delay: i * 0.1 } }),
132 | };
133 | const words = sentence.split(" ");
134 | return (
135 |
140 | {words.map((word, i) => (
141 |
142 | {word}{" "}
143 |
144 | ))}
145 |
146 | );
147 | }
148 | `;
149 |
150 | export const STAGGERED_PULL_UP_CODE = `import { motion } from "framer-motion";
151 |
152 | export function LetterPullUp() {
153 | const words = "Staggered Letter Pull Up";
154 | const letters = words.split("");
155 |
156 | const pullupVariant = {
157 | initial: { y: 100, opacity: 0 },
158 | animate: (i: any) => ({
159 | y: 0,
160 | opacity: 1,
161 | transition: {
162 | delay: i * 0.05, // Delay each letter's animation by 0.05 seconds
163 | },
164 | }),
165 | };
166 |
167 | return (
168 |
169 | {letters.map((letter, i) => (
170 |
178 | {letter === " " ? : letter}
179 |
180 | ))}
181 |
182 | );
183 | }
184 | `;
185 |
186 | export const RIPPLE_PULL_UP_CODE = `import { motion } from "framer-motion";
187 |
188 | export function WordPullUp() {
189 | const container = {
190 | hidden: { opacity: 0 },
191 | show: {
192 | opacity: 1,
193 | transition: {
194 | staggerChildren: 0.2,
195 | },
196 | },
197 | };
198 |
199 | const item = {
200 | hidden: { y: 20, opacity: 0 },
201 | show: { y: 0, opacity: 1 },
202 | };
203 |
204 | const words = "Word Pull Up";
205 | return (
206 |
212 | {words.split(" ").map((word, i) => (
213 |
218 | {word === "" ? : word}
219 |
220 | ))}
221 |
222 | );
223 | }
224 | `;
225 |
226 | export const VELOCITY_CODE = `// this comes from the framer-motion docs, just ported to twcss
227 | import { wrap } from "@motionone/utils";
228 | import {
229 | motion,
230 | AnimatePresence,
231 | useScroll,
232 | useSpring,
233 | useTransform,
234 | useMotionValue,
235 | useVelocity,
236 | useAnimationFrame,
237 | } from "framer-motion";
238 |
239 | interface ParallaxProps {
240 | children: string;
241 | baseVelocity: number;
242 | }
243 |
244 | export function VelocityScroll() {
245 | function ParallaxText({ children, baseVelocity = 100 }: ParallaxProps) {
246 | const baseX = useMotionValue(0);
247 | const { scrollY } = useScroll();
248 | const scrollVelocity = useVelocity(scrollY);
249 | const smoothVelocity = useSpring(scrollVelocity, {
250 | damping: 50,
251 | stiffness: 400,
252 | });
253 | const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], {
254 | clamp: false,
255 | });
256 |
257 | /**
258 | * This is a magic wrapping for the length of the text - you
259 | * have to replace for wrapping that works for you or dynamically
260 | * calculate
261 | */
262 | const x = useTransform(baseX, (v) => $ {wrap(-20, -45, v)}%);
263 |
264 | const directionFactor = React.useRef(1);
265 | useAnimationFrame((t, delta) => {
266 | let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
267 |
268 | /**
269 | * This is what changes the direction of the scroll once we
270 | * switch scrolling directions.
271 | */
272 | if (velocityFactor.get() < 0) {
273 | directionFactor.current = -1;
274 | } else if (velocityFactor.get() > 0) {
275 | directionFactor.current = 1;
276 | }
277 |
278 | moveBy += directionFactor.current * moveBy * velocityFactor.get();
279 |
280 | baseX.set(baseX.get() + moveBy);
281 | });
282 |
283 | /**
284 | * The number of times to repeat the child text should be dynamically calculated
285 | * based on the size of the text and viewport. Likewise, the x motion value is
286 | * currently wrapped between -20 and -45% - this 25% is derived from the fact
287 | * we have four children (100% / 4). This would also want deriving from the
288 | * dynamically generated number of children.
289 | */
290 | return (
291 |
292 |
296 | {children}
297 | {children}
298 | {children}
299 | {children}
300 |
301 |
302 | );
303 | }
304 | return (
305 |
306 | Variant Vault
307 | Variant Vault
308 |
309 | );
310 | }
311 | `;
312 |
313 | export const ROTATE_WORDS_CODE = `import { AnimatePresence, motion } from "framer-motion";
314 | export default function RotateText() {
315 | const words = ["ROTATE", "BETWEEN", "TEXT"];
316 | const [index, setIndex] = React.useState(0);
317 |
318 | React.useEffect(() => {
319 | const interval = setInterval(() => {
320 | setIndex((prevIndex) => (prevIndex + 1) % words.length);
321 | }, 3000);
322 |
323 | // Clean up interval on unmount
324 | return () => clearInterval(interval);
325 | }, []);
326 | return (
327 |
328 |
336 | {words[index]}
337 |
338 |
339 | );
340 | }
341 | `;
342 |
343 | export const TYPING_EFFECT_CODE = `import { motion } from "framer-motion";
344 |
345 | export function TypingEffect() {
346 | const text = "Typing Effect";
347 | const [displayedText, setDisplayedText] = React.useState("");
348 | const [i, setI] = React.useState(0);
349 |
350 | React.useEffect(() => {
351 | const typingEffect = setInterval(() => {
352 | if (i < text.length) {
353 | setDisplayedText((prevState) => prevState + text.charAt(i));
354 | setI(i + 1);
355 | } else {
356 | clearInterval(typingEffect);
357 | }
358 | }, 200);
359 |
360 | return () => {
361 | clearInterval(typingEffect);
362 | };
363 | }, [i]);
364 |
365 | return (
366 |
367 | {displayedText ? displayedText : "Typing Effect"}
368 |
369 | );
370 | }
371 | `;
372 |
373 | export const SEPARATE_CODE = `import { motion } from "framer-motion";
374 |
375 | export function SeparateAway() {
376 | const separate = {
377 | hidden: { opacity: 0, y: 0 },
378 | visible: (custom: number) => ({
379 | opacity: 1,
380 | y: custom * 5,
381 | transition: { duration: 1.5 },
382 | }),
383 | };
384 | return (
385 |
386 |
393 | Separate
394 |
395 |
402 | Away
403 |
404 |
405 | );
406 | }
407 | `;
408 |
409 | export const GRADUAL_SPACING_CODE = `import { AnimatePresence, motion } from "framer-motion";
410 |
411 | export function GradualSpacing() {
412 | const text = "Gradual Spacing";
413 | const gradual = {
414 | hidden: { opacity: 0, x: -20 },
415 | visible: { opacity: 1, x: 0 },
416 | };
417 | return (
418 |
419 |
420 | {text.split("").map((char, i) => (
421 |
430 | {char === " " ? : char}
431 |
432 | ))}
433 |
434 |
435 | );
436 | }
437 | `;
438 |
439 | export const BLUR_IN_CODE = `import { motion } from "framer-motion";
440 |
441 | export function BlurIn() {
442 | const variants1 = {
443 | hidden: { filter: "blur(10px)", opacity: 0 },
444 | visible: { filter: "blur(0px)", opacity: 1 },
445 | };
446 | return (
447 |
454 | Blur In
455 |
456 | );
457 | }
458 | `;
459 |
460 | export const SLIGHT_FLIP_CODE = `import { AnimatePresence, motion } from "framer-motion";
461 |
462 | export function SlightFlip() {
463 | const word = "Slight Flip Text";
464 | const variants1 = {
465 | hidden: { rotateX: -90, opacity: 0 },
466 | visible: { rotateX: 0, opacity: 1 },
467 | };
468 | return (
469 |
470 |
471 | {word.split("").map((char, i) => (
472 |
482 | {char}
483 |
484 | ))}
485 |
486 |
487 | );
488 | }
489 | `;
490 |
491 | export const WAVY_TEXT_CODE = `import { motion, AnimatePresence } from "framer-motion";
492 |
493 | export function WavyText() {
494 | const word = "Wavy Text";
495 | const variants1 = {
496 | hidden: { y: 10 },
497 | visible: { y: -10 },
498 | };
499 | return (
500 |
501 |
502 | {word.split("").map((char, i) => (
503 |
512 | {char}
513 |
514 | ))}
515 |
516 |
517 | );
518 | }
519 | `;
520 |
--------------------------------------------------------------------------------
/variants/definitions/index.ts:
--------------------------------------------------------------------------------
1 | export const DEFINITIONS_VARIANTS = `export const FADE_UP_ANIMATION_VARIANTS = {
2 | hidden: { opacity: 0, y: 10 },
3 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
4 | };
5 |
6 | export const MULTIDIRECTION_SLIDE_VARIANTS = {
7 | hidden: { opacity: 0, x: "-25vw" },
8 | visible: { opacity: 1, x: 0 },
9 | right: { opacity: 0, x: "25vw" },
10 | };
11 |
12 | export const wordVariants = {
13 | hidden: { opacity: 0 },
14 | visible: (i: any) => ({ y: 0, opacity: 1, transition: { delay: i * 0.1 } }),
15 | };
16 | export const pullupVariant = {
17 | initial: { y: 100, opacity: 0 },
18 | animate: (i: any) => ({
19 | y: 0,
20 | opacity: 1,
21 | transition: {
22 | delay: i * 0.05, // Delay each letter's animation by 0.05 seconds
23 | },
24 | }),
25 | };
26 | `;
27 |
--------------------------------------------------------------------------------
/variants/variant-previews/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { HeartFilledIcon } from "@radix-ui/react-icons";
3 | import { wrap } from "@motionone/utils";
4 | import {
5 | motion,
6 | AnimatePresence,
7 | useScroll,
8 | useSpring,
9 | useTransform,
10 | useMotionValue,
11 | useVelocity,
12 | useAnimationFrame,
13 | } from "framer-motion";
14 |
15 | interface ParallaxProps {
16 | children: string;
17 | baseVelocity: number;
18 | }
19 |
20 | export function FadeDownStagger({ key }: { key: number }) {
21 | const FADE_DOWN_ANIMATION_VARIANTS = {
22 | hidden: { opacity: 0, y: -10 },
23 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
24 | };
25 | return (
26 |
40 |
44 | Fade Down
45 |
46 |
50 | Animation Preview
51 |
52 |
56 | If you're seeing this, thank you for trying my project out! - C.J.A
57 |
58 |
59 | );
60 | }
61 |
62 | export function FadeUpStagger({ key }: { key: number }) {
63 | const FADE_UP_ANIMATION_VARIANTS = {
64 | hidden: { opacity: 0, y: 10 },
65 | show: { opacity: 1, y: 0, transition: { type: "spring" } },
66 | };
67 | return (
68 |
82 |
86 | Fade Up
87 |
88 |
92 | Animation Preview
93 |
94 |
98 |
99 |
100 |
101 | );
102 | }
103 |
104 | export function MultiDirectionSlide({ key }: { key: number }) {
105 | const MULTIDIRECTION_SLIDE_VARIANTS = {
106 | hidden: { opacity: 0, x: "-25vw" },
107 | visible: { opacity: 1, x: 0 },
108 | right: { opacity: 0, x: "25vw" },
109 | };
110 | return (
111 |
112 |
119 | Multi Direction
120 |
121 |
122 |
129 | Slide
130 |
131 |
132 | );
133 | }
134 |
135 | export function StaggeredFadeIn() {
136 | const sentence = "Staggered Fade In";
137 | const words = sentence.split(" ");
138 |
139 | const wordVariants = {
140 | hidden: { opacity: 0 },
141 | visible: (i: any) => ({ y: 0, opacity: 1, transition: { delay: i * 0.1 } }),
142 | };
143 | return (
144 |
149 | {words.map((word, i) => (
150 |
151 | {word}{" "}
152 |
153 | ))}
154 |
155 | );
156 | }
157 |
158 | export function LetterPullUp() {
159 | const words = "Staggered Letter Pull Up";
160 | const letters = words.split("");
161 |
162 | const pullupVariant = {
163 | initial: { y: 100, opacity: 0 },
164 | animate: (i: any) => ({
165 | y: 0,
166 | opacity: 1,
167 | transition: {
168 | delay: i * 0.05, // Delay each letter's animation by 0.05 seconds
169 | },
170 | }),
171 | };
172 |
173 | return (
174 |
175 | {letters.map((letter, i) => (
176 |
184 | {letter === " " ? : letter}
185 |
186 | ))}
187 |
188 | );
189 | }
190 |
191 | export function WordPullUp() {
192 | const container = {
193 | hidden: { opacity: 0 },
194 | show: {
195 | opacity: 1,
196 | transition: {
197 | staggerChildren: 0.2,
198 | },
199 | },
200 | };
201 |
202 | const item = {
203 | hidden: { y: 20, opacity: 0 },
204 | show: { y: 0, opacity: 1 },
205 | };
206 |
207 | const words = "Word Pull Up";
208 | return (
209 |
215 | {words.split(" ").map((word, i) => (
216 |
221 | {word === "" ? : word}
222 |
223 | ))}
224 |
225 | );
226 | }
227 |
228 | export function VelocityScroll() {
229 | function ParallaxText({ children, baseVelocity = 100 }: ParallaxProps) {
230 | const baseX = useMotionValue(0);
231 | const { scrollY } = useScroll();
232 | const scrollVelocity = useVelocity(scrollY);
233 | const smoothVelocity = useSpring(scrollVelocity, {
234 | damping: 50,
235 | stiffness: 400,
236 | });
237 | const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], {
238 | clamp: false,
239 | });
240 |
241 | /**
242 | * This is a magic wrapping for the length of the text - you
243 | * have to replace for wrapping that works for you or dynamically
244 | * calculate
245 | */
246 | const x = useTransform(baseX, (v) => `${wrap(-20, -45, v)}%`);
247 |
248 | const directionFactor = React.useRef(1);
249 | useAnimationFrame((t, delta) => {
250 | let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
251 |
252 | /**
253 | * This is what changes the direction of the scroll once we
254 | * switch scrolling directions.
255 | */
256 | if (velocityFactor.get() < 0) {
257 | directionFactor.current = -1;
258 | } else if (velocityFactor.get() > 0) {
259 | directionFactor.current = 1;
260 | }
261 |
262 | moveBy += directionFactor.current * moveBy * velocityFactor.get();
263 |
264 | baseX.set(baseX.get() + moveBy);
265 | });
266 |
267 | /**
268 | * The number of times to repeat the child text should be dynamically calculated
269 | * based on the size of the text and viewport. Likewise, the x motion value is
270 | * currently wrapped between -20 and -45% - this 25% is derived from the fact
271 | * we have four children (100% / 4). This would also want deriving from the
272 | * dynamically generated number of children.
273 | */
274 | return (
275 |
276 |
280 | {children}
281 | {children}
282 | {children}
283 | {children}
284 |
285 |
286 | );
287 | }
288 | return (
289 |
290 | Variant Vault
291 | Variant Vault
292 |
293 | );
294 | }
295 |
296 | export function RotateText() {
297 | const words = ["ROTATE", "BETWEEN", "TEXT"];
298 | const [index, setIndex] = React.useState(0);
299 |
300 | React.useEffect(() => {
301 | const interval = setInterval(() => {
302 | setIndex((prevIndex) => (prevIndex + 1) % words.length);
303 | }, 3000);
304 |
305 | // Clean up interval on unmount
306 | return () => clearInterval(interval);
307 | }, []);
308 | return (
309 |
310 |
318 | {words[index]}
319 |
320 |
321 | );
322 | }
323 |
324 | export function TypingEffect() {
325 | const text = "Typing Effect";
326 | const [displayedText, setDisplayedText] = React.useState("");
327 | const [i, setI] = React.useState(0);
328 |
329 | React.useEffect(() => {
330 | const typingEffect = setInterval(() => {
331 | if (i < text.length) {
332 | setDisplayedText((prevState) => prevState + text.charAt(i));
333 | setI(i + 1);
334 | } else {
335 | clearInterval(typingEffect);
336 | }
337 | }, 200);
338 |
339 | return () => {
340 | clearInterval(typingEffect);
341 | };
342 | }, [i]);
343 |
344 | return (
345 |
346 | {displayedText ? displayedText : "Typing Effect"}
347 |
348 | );
349 | }
350 |
351 | export function SeparateAway() {
352 | const variants1 = {
353 | hidden: { opacity: 0, y: 0 },
354 | visible: (custom: number) => ({
355 | opacity: 1,
356 | y: custom * 5,
357 | transition: { duration: 1.5 },
358 | }),
359 | };
360 | return (
361 |
362 |
369 | Separate
370 |
371 |
378 | Away
379 |
380 |
381 | );
382 | }
383 |
384 | export function GradualSpacing() {
385 | const text = "Gradual Spacing";
386 | const gradual = {
387 | hidden: { opacity: 0, x: -20 },
388 | visible: { opacity: 1, x: 0 },
389 | };
390 | return (
391 |
392 |
393 | {text.split("").map((char, i) => (
394 |
403 | {char === " " ? : char}
404 |
405 | ))}
406 |
407 |
408 | );
409 | }
410 |
411 | export function BlurIn() {
412 | const variants1 = {
413 | hidden: { filter: "blur(10px)", opacity: 0 },
414 | visible: { filter: "blur(0px)", opacity: 1 },
415 | };
416 | return (
417 |
424 | Blur In
425 |
426 | );
427 | }
428 |
429 | export function SlightFlip() {
430 | const word = "Slight Flip Text";
431 | const variants1 = {
432 | hidden: { rotateX: -90, opacity: 0 },
433 | visible: { rotateX: 0, opacity: 1 },
434 | };
435 | return (
436 |
437 |
438 | {word.split("").map((char, i) => (
439 |
449 | {char}
450 |
451 | ))}
452 |
453 |
454 | );
455 | }
456 |
457 | export function WavyText() {
458 | const word = "Wavy Text";
459 | const variants1 = {
460 | hidden: { y: 10 },
461 | visible: { y: -10 },
462 | };
463 | return (
464 |
465 |
466 | {word.split("").map((char, i) => (
467 |
476 | {char}
477 |
478 | ))}
479 |
480 |
481 | );
482 | }
483 |
--------------------------------------------------------------------------------