From 83008343636b23cbf096742ba52c8000b61b9b8f Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Thu, 3 Oct 2024 15:10:02 +0545 Subject: [PATCH 01/42] chore: use head_ref (#289) --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9e6fb9da..ceae0dcd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,7 +42,7 @@ jobs: NEXT_PUBLIC_POSTHOG_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_KEY }} NEXT_PUBLIC_POSTHOG_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} run: yarn build - - name: Deploy to Cloudflare Pages + - name: Deploy to Cloudflare Pages (Preview) uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -50,5 +50,5 @@ jobs: projectName: animata directory: out gitHubToken: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.event.pull_request.head.ref }} + branch: ${{ github.head_ref }} wranglerVersion: "3" From 5d87f434b4cfd3b2478ca53c45700831b1988e5a Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Thu, 3 Oct 2024 15:33:00 +0545 Subject: [PATCH 02/42] fix: "chore: use head_ref" (#290) --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ceae0dcd..9e6fb9da 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,7 +42,7 @@ jobs: NEXT_PUBLIC_POSTHOG_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_KEY }} NEXT_PUBLIC_POSTHOG_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} run: yarn build - - name: Deploy to Cloudflare Pages (Preview) + - name: Deploy to Cloudflare Pages uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -50,5 +50,5 @@ jobs: projectName: animata directory: out gitHubToken: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.head_ref }} + branch: ${{ github.event.pull_request.head.ref }} wranglerVersion: "3" From be6f3158023cb44f1495f90b8c4dbcb83b481c2c Mon Sep 17 00:00:00 2001 From: Vishal Kumar Date: Thu, 3 Oct 2024 15:24:40 +0530 Subject: [PATCH 03/42] feat: create animated timeline component --- animata/progress/animatedtimeline.stories.tsx | 68 ++++++ animata/progress/animatedtimeline.tsx | 215 ++++++++++++++++++ content/docs/progress/animatedtimeline.mdx | 38 ++++ 3 files changed, 321 insertions(+) create mode 100644 animata/progress/animatedtimeline.stories.tsx create mode 100644 animata/progress/animatedtimeline.tsx create mode 100644 content/docs/progress/animatedtimeline.mdx diff --git a/animata/progress/animatedtimeline.stories.tsx b/animata/progress/animatedtimeline.stories.tsx new file mode 100644 index 00000000..fea084af --- /dev/null +++ b/animata/progress/animatedtimeline.stories.tsx @@ -0,0 +1,68 @@ +import Animatedtimeline, { TimelineEvent } from "@/animata/progress/animatedtimeline"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Progress/Animatedtimeline", + component: Animatedtimeline, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const events = [ + { + id: "1", + title: "Event 1", + description: "This is the first event", + date: "2021-01-01", + }, + { + id: "2", + title: "Event 2", + description: "This is the second event", + date: "2021-02-01", + }, + { + id: "3", + title: "Event 3", + description: "This is the third event", + date: "2021-03-01", + }, +]; + +const customEventRender = (event: TimelineEvent) => ( +
+

{event.title}

+

{event.description}

+ {event.date} +
+); + +const onEventClick = () => null; + +export const Primary: Story = { + args: { + events: events, + title: "Timeline", + containerClassName: "", + timelineStyles: { + lineColor: "#d1d5db", + activeLineColor: "#22c55e", + dotColor: "#d1d5db", + activeDotColor: "#22c55e", + dotSize: "1.5rem", + titleColor: "inherit", + descriptionColor: "inherit", + dateColor: "inherit", + }, + customEventRender: customEventRender, + onEventHover: () => null, + onEventClick: onEventClick, + initialActiveIndex: -1, + }, +}; diff --git a/animata/progress/animatedtimeline.tsx b/animata/progress/animatedtimeline.tsx new file mode 100644 index 00000000..09c19c42 --- /dev/null +++ b/animata/progress/animatedtimeline.tsx @@ -0,0 +1,215 @@ +"use client"; + +import React, { useState } from "react"; +import { motion } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +export interface TimelineEvent { + id: string; + title: string; + description?: string; + date?: string; + [key: string]: unknown; // Allow additional custom fields +} + +interface TimelineItemProps { + event: TimelineEvent; + isActive: boolean; + isFirst: boolean; + isLast: boolean; + onHover: (index: number | null) => void; + index: number; + activeIndex: number | null; + styles: TimelineStyles; + customRender?: (event: TimelineEvent) => React.ReactNode; +} + +interface TimelineStyles { + lineColor: string; + activeLineColor: string; + dotColor: string; + activeDotColor: string; + dotSize: string; + titleColor: string; + descriptionColor: string; + dateColor: string; +} + +const TimelineItem: React.FC = ({ + event, + isActive, + isLast, + onHover, + index, + activeIndex, + styles, + customRender, +}) => { + const fillDelay = activeIndex !== null ? Math.max(0, (index - 1) * 0.1) : 0; + const fillDuration = activeIndex !== null ? Math.max(0.2, 0.5 - index * 0.1) : 0.5; + + return ( + onHover(index)} + onHoverEnd={() => onHover(null)} + initial={{ opacity: 0, y: 50 }} + animate={{ opacity: 1, y: 0 }} + transition={{ duration: 0.5 }} + > +
+
+ +
+ +
+
+ {customRender ? ( + customRender(event) + ) : ( + <> +

+ {event.title} +

+

{event.description}

+ + {event.date} + + + )} +
+
+ ); +}; + +interface AnimatedTimelineProps { + events: TimelineEvent[]; + className?: string; + styles?: Partial; + customEventRender?: (event: TimelineEvent) => React.ReactNode; + onEventHover?: (event: TimelineEvent | null) => void; + onEventClick?: (event: TimelineEvent) => void; + initialActiveIndex?: number; +} + +const defaultStyles: TimelineStyles = { + lineColor: "#d1d5db", + activeLineColor: "#22c55e", + dotColor: "#d1d5db", + activeDotColor: "#22c55e", + dotSize: "1.5rem", + titleColor: "inherit", + descriptionColor: "inherit", + dateColor: "inherit", +}; + +export function AnimatedTimeline({ + events, + className = "", + styles: customStyles = {}, + customEventRender, + onEventHover, + onEventClick, + initialActiveIndex, +}: AnimatedTimelineProps) { + const [activeIndex, setActiveIndex] = useState(initialActiveIndex ?? null); + const styles = { ...defaultStyles, ...customStyles }; + + const handleHover = (index: number | null) => { + setActiveIndex(index); + onEventHover?.(index !== null ? events[index] : null); + }; + + return ( +
+ {events.map((event, index) => ( +
onEventClick?.(event)}> + +
+ ))} +
+ ); +} + +interface AnimatedTimelinePageProps { + events?: TimelineEvent[]; + title?: string; + containerClassName?: string; + timelineStyles?: Partial; + customEventRender?: (events: TimelineEvent) => React.ReactNode; + onEventHover?: (events: TimelineEvent | null) => void; + onEventClick?: (events: TimelineEvent) => void; + initialActiveIndex?: number; +} + +export default function AnimatedTimelinePage({ + events, + title, + containerClassName, + timelineStyles, + customEventRender, + onEventHover, + onEventClick, + initialActiveIndex, +}: AnimatedTimelinePageProps) { + const DefaultEvents = [ + { id: "1", title: "Event 1", description: "Description 1", date: "2023-01-01" }, + { id: "2", title: "Event 2", description: "Description 2", date: "2023-02-01" }, + { id: "3", title: "Event 3", description: "Description 3", date: "2023-03-01" }, + ]; + const defaultTitle = "Timeline"; + + return ( +
+

{title || defaultTitle}

+ +
+ ); +} diff --git a/content/docs/progress/animatedtimeline.mdx b/content/docs/progress/animatedtimeline.mdx new file mode 100644 index 00000000..aa9a9930 --- /dev/null +++ b/content/docs/progress/animatedtimeline.mdx @@ -0,0 +1,38 @@ +--- +title: Animated Timeline +description: The Animated Timeline component is an interactive, visually appealing timeline that responds to user interaction. Built with Framer Motion and React, this component highlights key events or milestones in a vertical timeline structure.When a user hovers over a specific timeline item, the associated circular dot and all previous dots in the sequence turn green, indicating progression. The dot size also enlarges slightly, enhancing the focus on the current event. The component offers a sleek and smooth animation experience, perfect for showcasing chronological steps, milestones, or achievements in an engaging and user-friendly manner.This component is highly customizable, allowing easy modifications to the timeline content and styling, making it suitable for diverse applications such as resumes, project timelines, or product roadmaps. +author: VishalKumar03__ +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion lucide-react +``` + +Run the following command + +It will create a new file `animatedtimeline.tsx` inside the `components/animata/progress` directory. + +```bash +mkdir -p components/animata/progress && touch components/animata/progress/animatedtimeline.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```jsx file=/animata/progress/animatedtimeline.tsx + +``` + + + +## Credits + +Built by [Vishal Kumar](https://github.com/vishal-kumar3) From 53eb6a4298501c9599983c89fedf4262e9f5bf7a Mon Sep 17 00:00:00 2001 From: Prithwi Hegde <115262737+Prithwi32@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:51:02 +0530 Subject: [PATCH 04/42] feat(card): add subscribe card (#283) Co-authored-by: Hari Lamichhane --- animata/card/subscribe-card.stories.tsx | 19 +++++++++++++ animata/card/subscribe-card.tsx | 32 +++++++++++++++++++++ content/docs/card/subscribe-card.mdx | 38 +++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 animata/card/subscribe-card.stories.tsx create mode 100644 animata/card/subscribe-card.tsx create mode 100644 content/docs/card/subscribe-card.mdx diff --git a/animata/card/subscribe-card.stories.tsx b/animata/card/subscribe-card.stories.tsx new file mode 100644 index 00000000..ee2f016a --- /dev/null +++ b/animata/card/subscribe-card.stories.tsx @@ -0,0 +1,19 @@ +import SubscribeCard from "@/animata/card/subscribe-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Subscribe Card", + component: SubscribeCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: {}, +}; diff --git a/animata/card/subscribe-card.tsx b/animata/card/subscribe-card.tsx new file mode 100644 index 00000000..d8bb5d35 --- /dev/null +++ b/animata/card/subscribe-card.tsx @@ -0,0 +1,32 @@ +import { Check } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; + +interface SubscribeCardProps { + title?: string; + placeholder?: string; + buttonText?: string; +} + +export default function SubscribeCard({ + title = "Want to read the rest?", + placeholder = "justin@buttondown.email", + buttonText = "Subscribe for $5/mo", +}: SubscribeCardProps) { + return ( +
+

{title}

+
+ + +
+
+ ); +} diff --git a/content/docs/card/subscribe-card.mdx b/content/docs/card/subscribe-card.mdx new file mode 100644 index 00000000..575414c4 --- /dev/null +++ b/content/docs/card/subscribe-card.mdx @@ -0,0 +1,38 @@ +--- +title: Subscribe Card +description: When the cursor hovers over the card then the card skews and when hovers over the button the check mark appears. +author: PrithwiHegde +--- + + + +## Installation + + +Install dependencies + +```bash +npm install lucide-react +``` + +Run the following command + +It will create a new file `subscribe-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/subscribe-card.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/subscribe-card.tsx + +``` + + + +## Credits + +Built by [Prithwi Hegde](https://github.com/Prithwi32) From 6e99e7079ae6a3188abd50a8177e6bfc0cc495f8 Mon Sep 17 00:00:00 2001 From: Shooman Khatri <112597601+ShoomanKhatri@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:24:27 +0545 Subject: [PATCH 05/42] feat: implement integration pills (#277) --- animata/card/integration-pills.stories.tsx | 19 +++++ animata/card/integration-pills.tsx | 91 ++++++++++++++++++++++ content/docs/card/integration-pills.mdx | 33 ++++++++ 3 files changed, 143 insertions(+) create mode 100644 animata/card/integration-pills.stories.tsx create mode 100644 animata/card/integration-pills.tsx create mode 100644 content/docs/card/integration-pills.mdx diff --git a/animata/card/integration-pills.stories.tsx b/animata/card/integration-pills.stories.tsx new file mode 100644 index 00000000..8745fc66 --- /dev/null +++ b/animata/card/integration-pills.stories.tsx @@ -0,0 +1,19 @@ +import IntegrationPills from "@/animata/card/integration-pills"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Integration pills", + component: IntegrationPills, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: {}, +}; diff --git a/animata/card/integration-pills.tsx b/animata/card/integration-pills.tsx new file mode 100644 index 00000000..2f65fb95 --- /dev/null +++ b/animata/card/integration-pills.tsx @@ -0,0 +1,91 @@ +import React from "react"; + +import { cn } from "@/lib/utils"; + +const brands = [ + { + name: "Shopify", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "RSS", + className: "bg-gray-200 font-bold", + hoverClass: + "group-hover:border-blue-500 group-hover:text-blue-500 group-hover:bg-white group-hover:border-2", + }, + { + name: "Zapier", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "Slack", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "Webflow", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "Squarespace", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "Twitter", + className: "bg-gray-200 font-bold", + hoverClass: + "group-hover:border-green-500 group-hover:text-green-500 group-hover:bg-white group-hover:border-2", + }, + { + name: "TikTok", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "n8n", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "BuySellAds", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, + { + name: "Mastodon", + className: "bg-gray-200 font-bold", + hoverClass: + "group-hover:border-purple-500 group-hover:text-purple-500 group-hover:bg-white group-hover:border-2", + }, + { + name: "Gumroad", + className: "bg-gray-200", + hoverClass: "group-hover:scale-75 group-hover:text-gray-500", + }, +]; + +export default function IntegrationPills() { + return ( +
+ {/* Rectangular box around all cards */} +
+ {brands.map((brand, index) => ( +
+ {brand.name} +
+ ))} +
+
+ ); +} diff --git a/content/docs/card/integration-pills.mdx b/content/docs/card/integration-pills.mdx new file mode 100644 index 00000000..8001859c --- /dev/null +++ b/content/docs/card/integration-pills.mdx @@ -0,0 +1,33 @@ +--- +title: Integration pills +description: A component that displays a list of services it can integrate with. +author: shoomankhatri +--- + + + +## Installation + + + +Run the following command + +It will create a new file `integration-pills.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/integration-pills.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/integration-pills.tsx + +``` + + + +## Credits + +Built by [Suman Khatri](https://github.com/shoomankhatri) From 8be3ebd7727475ab1ab06d51cd3b8dd7d45a5230 Mon Sep 17 00:00:00 2001 From: Mohit ahlawat <65100859+mohitahlawat2001@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:12:42 +0530 Subject: [PATCH 06/42] feat: add survey card (#276) --- animata/card/survey-card.stories.tsx | 49 ++++++++++++++++ animata/card/survey-card.tsx | 88 ++++++++++++++++++++++++++++ content/docs/card/survey-card.mdx | 33 +++++++++++ 3 files changed, 170 insertions(+) create mode 100644 animata/card/survey-card.stories.tsx create mode 100644 animata/card/survey-card.tsx create mode 100644 content/docs/card/survey-card.mdx diff --git a/animata/card/survey-card.stories.tsx b/animata/card/survey-card.stories.tsx new file mode 100644 index 00000000..b5bb1727 --- /dev/null +++ b/animata/card/survey-card.stories.tsx @@ -0,0 +1,49 @@ +import SurveyCard from "@/animata/card/survey-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Survey Card", + component: SurveyCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + items: [ + { + vote: 50, + itemName: "Charmander", + }, + { + vote: 60, + itemName: "Pikachu", + }, + { + vote: 20, + itemName: "Squirtle", + }, + ], + width: 250, // Fixed width + surveyTitle: "Pokemon Survey ?", + }, + render: (args) => { + return ( + <> + Survey Card +
+ {/* Adjust width to be fixed and control growth by width */} +
+ +
+
+ + ); + }, +}; diff --git a/animata/card/survey-card.tsx b/animata/card/survey-card.tsx new file mode 100644 index 00000000..8e086a49 --- /dev/null +++ b/animata/card/survey-card.tsx @@ -0,0 +1,88 @@ +import { useEffect, useMemo, useRef, useState } from "react"; + +import { cn } from "@/lib/utils"; + +interface SurveyCardProps { + /** + * The items to display in the Survey. + * Each item should have a vote and itemName. + */ + items: { + vote: number; + itemName?: string; + }[]; + /** + * The width of the Survey. recommended to use with more than 250px + */ + width?: number; + /** + * The title of the Survey. + */ + surveyTitle?: string; +} + +export default function SurveyCard({ items, width: providedWidth, surveyTitle }: SurveyCardProps) { + const [{ width }, setSize] = useState({ + width: providedWidth ?? 250, + }); + // Calculate total votes and max votes using useMemo + const totalVotes = useMemo(() => items.reduce((acc, item) => acc + item.vote, 0), [items]); + const maxVote = useMemo(() => Math.max(...items.map((item) => item.vote)), [items]); + + const containerRef = useRef(null); + + useEffect(() => { + setSize({ + width: providedWidth ?? containerRef.current?.offsetWidth ?? 250, + }); + }, [providedWidth, items]); + + const [isParentHovered, setIsParentHovered] = useState(false); + + const handleParentMouseOver = () => { + setIsParentHovered(true); + }; + + const handleParentMouseOut = () => { + setIsParentHovered(false); + }; + + return ( +
+
+

{surveyTitle}

+
+ {items.map((item, index) => { + const clampedProgress = Math.max(0, item.vote); + // saving overflow over itemName using 8/12 + const barWidth = isParentHovered ? (clampedProgress / totalVotes) * 100 * (8 / 12) : 30; + return ( +
+
+
+ {item.itemName} +
+
+ ); + })} +
+ ); +} diff --git a/content/docs/card/survey-card.mdx b/content/docs/card/survey-card.mdx new file mode 100644 index 00000000..a5e6aa02 --- /dev/null +++ b/content/docs/card/survey-card.mdx @@ -0,0 +1,33 @@ +--- +title: Survey Card +description: showing result of survey on hover over card +labels: ["requires interaction", "hover"] +author: Mahlawat2001 +--- + + + +## Installation + + +Run the following command + +It will create a new file `survey-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/survey-card.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/survey-card.tsx + +``` + + + +## Credits + +Built by [Mohit Ahlawat](https://github.com/mohitahlawat2001) From ed13daa816877bd07f2ff4f233bc8265c3c2d9c9 Mon Sep 17 00:00:00 2001 From: Mansi Dhamne <129254413+mansidhamne@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:15:56 +0530 Subject: [PATCH 07/42] feat: add airbnb type image reveal animation (#275) --- animata/image/images-reveal.stories.tsx | 19 +++++++ animata/image/images-reveal.tsx | 74 +++++++++++++++++++++++++ content/docs/image/images-reveal.mdx | 39 +++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 animata/image/images-reveal.stories.tsx create mode 100644 animata/image/images-reveal.tsx create mode 100644 content/docs/image/images-reveal.mdx diff --git a/animata/image/images-reveal.stories.tsx b/animata/image/images-reveal.stories.tsx new file mode 100644 index 00000000..8ac7c65a --- /dev/null +++ b/animata/image/images-reveal.stories.tsx @@ -0,0 +1,19 @@ +import ImagesReveal from "@/animata/image/images-reveal"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Image/Images Reveal", + component: ImagesReveal, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: {}, +}; diff --git a/animata/image/images-reveal.tsx b/animata/image/images-reveal.tsx new file mode 100644 index 00000000..b414e4c5 --- /dev/null +++ b/animata/image/images-reveal.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { motion } from "framer-motion"; + +const cards = [ + { + src: "https://images.unsplash.com/photo-1727717768632-f4241a128f50?q=80&w=2889&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + angle: "8deg", + }, + { + src: "https://images.unsplash.com/photo-1727400068319-565c56633dc3?q=80&w=1911&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + angle: "-15deg", + }, + { + src: "https://images.unsplash.com/photo-1726551195764-f98a8e8a57c3?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + angle: "-5deg", + }, + { + src: "https://images.unsplash.com/photo-1727775805114-a87c6bcaf9db?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + angle: "10deg", + }, + { + src: "https://images.unsplash.com/photo-1614680108604-c23b65f7e7dc?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + angle: "-5deg", + }, +]; + +interface CustomProps { + index: number; + angle: string; +} + +const cardVariants = { + hidden: { opacity: 0, scale: 0.2 }, + visible: (custom: CustomProps) => ({ + opacity: 1, + scale: 1, + rotate: custom.angle, + transition: { + delay: custom.index * 0.1, + duration: 0.3, + type: "spring", + stiffness: 150, + damping: 20, + mass: 0.5, + }, + }), +}; + +export default function ImagesReveal() { + return ( +
+

Airbnb Image Reveal

+
+ {cards.map((card, i) => ( + + ))} +
+
+ ); +} diff --git a/content/docs/image/images-reveal.mdx b/content/docs/image/images-reveal.mdx new file mode 100644 index 00000000..b89f9801 --- /dev/null +++ b/content/docs/image/images-reveal.mdx @@ -0,0 +1,39 @@ +--- +title: Images Reveal +description: An image reveal animation +author: mansidhamne +labels: ["requires interaction", "hover"] +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `images-reveal.tsx` inside the `components/animata/image` directory. + +```bash +mkdir -p components/animata/image && touch components/animata/image/images-reveal.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/image/images-reveal.tsx + +``` + + + +## Credits + +Built by [mansidhamne](https://github.com/mansidhamne) From 8b1107626b6de3fa55c346fc62e54561122864a4 Mon Sep 17 00:00:00 2001 From: Prince Kumar Yadav <69517192+prince981620@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:19:49 +0530 Subject: [PATCH 08/42] feat: add score card component (#279) --- animata/card/score-card.stories.tsx | 35 ++++++++ animata/card/score-card.tsx | 121 ++++++++++++++++++++++++++++ content/docs/card/score-card.mdx | 39 +++++++++ 3 files changed, 195 insertions(+) create mode 100644 animata/card/score-card.stories.tsx create mode 100644 animata/card/score-card.tsx create mode 100644 content/docs/card/score-card.mdx diff --git a/animata/card/score-card.stories.tsx b/animata/card/score-card.stories.tsx new file mode 100644 index 00000000..234ae686 --- /dev/null +++ b/animata/card/score-card.stories.tsx @@ -0,0 +1,35 @@ +import Scorecard from "@/animata/card/score-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Score Card", + component: Scorecard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const homeTeam = { + name: "Brentford", + logo: "https://upload.wikimedia.org/wikipedia/en/2/2a/Brentford_FC_crest.svg", +}; + +const awayTeam = { + name: "Man. City", + logo: "https://upload.wikimedia.org/wikipedia/en/e/eb/Manchester_City_FC_badge.svg", +}; +export const Primary: Story = { + args: { + homeTeam: homeTeam, + awayTeam: awayTeam, + homeScore: 1, + awayScore: 2, + matchTime: "2nd Half - 70'", + scorer: "Foden", + }, +}; diff --git a/animata/card/score-card.tsx b/animata/card/score-card.tsx new file mode 100644 index 00000000..65b9d826 --- /dev/null +++ b/animata/card/score-card.tsx @@ -0,0 +1,121 @@ +import React, { useState } from "react"; +import { motion } from "framer-motion"; + +interface Team { + name: string; + logo: string; +} + +interface ScoreProps { + homeTeam: Team; + awayTeam: Team; + homeScore: number; + awayScore: number; + matchTime: string; + scorer: string; +} + +export default function LiveScoreWidget({ + homeTeam, + awayTeam, + homeScore, + awayScore, + matchTime, + scorer, +}: ScoreProps) { + const [scored, setScored] = useState(false); + const [awScore, setAwScore] = useState(awayScore); + const [popAnimation, setPopAnimation] = useState(false); + + return ( +
+ +
+ {/* Home Team */} +
+ {homeTeam.name} + {!scored && {homeTeam.name}} +
+ + {/* Score */} +
+ setPopAnimation(false)} + > + {homeScore}-{awScore} + +
{matchTime}
+
+ + {/* Away Team */} +
+ {awayTeam.name} + {!scored && {awayTeam.name}} +
+
+ {scored && ( + +
+
+ + {scorer} scores! +
+ now +
+
+ )} +
+ + +
+ ); +} + +function FootbalIcon() { + return ( + + + + + ); +} diff --git a/content/docs/card/score-card.mdx b/content/docs/card/score-card.mdx new file mode 100644 index 00000000..10b29ad1 --- /dev/null +++ b/content/docs/card/score-card.mdx @@ -0,0 +1,39 @@ +--- +title: Score Card +description: This Score-card component is designed to track and update scores for two teams in real-time with a simple animation showing which team scored. +author: m_jinprince +labels: ["requires interaction", "Click score button"] +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file called `score-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/score-card.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/score-card.tsx + +``` + + + +## Credits + +Built by [Prince Yadav](https://github.com/prince981620) From 15d71b9605729f9941cbc19eaaf46675f98e7944 Mon Sep 17 00:00:00 2001 From: Eshan Singh <32596297+R0X4R@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:51:44 +0530 Subject: [PATCH 09/42] feat: add dock component (#292) --- animata/container/animated-dock.stories.tsx | 88 +++++++++ animata/container/animated-dock.tsx | 186 ++++++++++++++++++++ content/docs/container/animated-dock.mdx | 38 ++++ 3 files changed, 312 insertions(+) create mode 100644 animata/container/animated-dock.stories.tsx create mode 100644 animata/container/animated-dock.tsx create mode 100644 content/docs/container/animated-dock.mdx diff --git a/animata/container/animated-dock.stories.tsx b/animata/container/animated-dock.stories.tsx new file mode 100644 index 00000000..705d1df4 --- /dev/null +++ b/animata/container/animated-dock.stories.tsx @@ -0,0 +1,88 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { Home, Search, Bell, User } from "lucide-react"; +import AnimatedDock from "@/animata/container/animated-dock"; + + +const meta = { + title: "Container/Animated Dock", + component: AnimatedDock, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + largeClassName: { control: "text" }, + smallClassName: { control: "text" }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + + +// Example contents for AnimatedDock +const dockItems = [ + { title: "Home", icon: , href: "/" }, + { title: "Search", icon: , href: "/search" }, + { title: "Notifications", icon: , href: "/notifications" }, + { title: "Profile", icon: , href: "/profile" }, +]; + + +// Primary story for AnimatedDock (default layout) +export const Primary: Story = { + args: { + items: dockItems, + largeClassName: "max-w-lg", + smallClassName: "w-full", + }, + render: (args) => ( +
+ +
+ ), +}; + + +// Story focused on the Small layout (for mobile view) +export const Small: Story = { + args: { + items: dockItems, + smallClassName: "w-full", + }, + render: (args) => ( +
+ +
+ ), +}; + + +// Story focused on the Large layout (for desktop view) +export const Large: Story = { + args: { + items: dockItems, + largeClassName: "max-w-lg", + }, + render: (args) => ( +
+ +
+ ), +}; + + +// Story showing both layouts at the same time (for comparison) +export const Multiple: Story = { + args: { + items: dockItems, + largeClassName: "max-w-lg", + smallClassName: "w-full", + }, + render: (args) => ( +
+ + +
+ ), +}; diff --git a/animata/container/animated-dock.tsx b/animata/container/animated-dock.tsx new file mode 100644 index 00000000..190815cc --- /dev/null +++ b/animata/container/animated-dock.tsx @@ -0,0 +1,186 @@ +import { cn } from "@/lib/utils"; // Import utility for conditional class names +import { + AnimatePresence, // Enables animation presence detection + MotionValue, // Type for motion values + motion, // Main component for animations + useMotionValue, // Hook to create a motion value + useSpring, // Hook to create smooth spring animations + useTransform, // Hook to transform motion values +} from "framer-motion"; +import Link from "next/link"; // Next.js Link component for navigation +import React, { useRef, useState } from "react"; // Importing React hooks +import { Menu, X } from "lucide-react"; // Importing icons from lucide-react + +// Interface for props accepted by the AnimatedDock component +interface AnimatedDockProps { + items: { title: string; icon: React.ReactNode; href: string }[]; // Array of menu items + largeClassName?: string; // Optional class name for large dock + smallClassName?: string; // Optional class name for small dock +} + +// Main AnimatedDock component that renders both LargeDock and SmallDock +export default function AnimatedDock({ items, largeClassName, smallClassName }: AnimatedDockProps) { + return ( + <> + {/* Render LargeDock for larger screens */} + + {/* Render SmallDock for smaller screens */} + + + ); +} + +// Component for the large dock, visible on larger screens +const LargeDock = ({ + items, + className, +}: { + items: { title: string; icon: React.ReactNode; href: string }[]; // Items to display + className?: string; // Optional class name +}) => { + const mouseXPosition = useMotionValue(Infinity); // Create a motion value for mouse X position + return ( + mouseXPosition.set(e.pageX)} // Update mouse X position on mouse move + onMouseLeave={() => mouseXPosition.set(Infinity)} // Reset on mouse leave + className={cn( + "mx-auto hidden h-16 items-end gap-4 rounded-2xl bg-white/10 px-4 pb-3 dark:bg-black/10 md:flex", // Large dock styles + className, + "border border-gray-200/30 backdrop-blur-sm dark:border-gray-800/30", + )} + > + {/* Render each dock icon */} + {items.map((item) => ( + + ))} + + ); +}; + +// Component for individual icons in the dock +function DockIcon({ + mouseX, + title, + icon, + href, +}: { + mouseX: MotionValue; // Motion value for mouse position + title: string; // Title of the icon + icon: React.ReactNode; // Icon component + href: string; // Link destination +}) { + const ref = useRef(null); // Ref for measuring distance from mouse + + // Calculate the distance from the mouse to the icon + const distanceFromMouse = useTransform(mouseX, (val) => { + const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; // Get icon bounds + return val - bounds.x - bounds.width / 2; // Calculate distance from center + }); + + // Transform properties for width and height based on mouse distance + const widthTransform = useTransform(distanceFromMouse, [-150, 0, 150], [40, 80, 40]); + const heightTransform = useTransform(distanceFromMouse, [-150, 0, 150], [40, 80, 40]); + + // Transform properties for icon size based on mouse distance + const iconWidthTransform = useTransform(distanceFromMouse, [-150, 0, 150], [20, 40, 20]); + const iconHeightTransform = useTransform(distanceFromMouse, [-150, 0, 150], [20, 40, 20]); + + // Spring animations for smooth transitions + const width = useSpring(widthTransform, { mass: 0.1, stiffness: 150, damping: 12 }); + const height = useSpring(heightTransform, { mass: 0.1, stiffness: 150, damping: 12 }); + const iconWidth = useSpring(iconWidthTransform, { mass: 0.1, stiffness: 150, damping: 12 }); + const iconHeight = useSpring(iconHeightTransform, { mass: 0.1, stiffness: 150, damping: 12 }); + + const [isHovered, setIsHovered] = useState(false); // State for hover effect + + return ( + + setIsHovered(true)} // Handle mouse enter + onMouseLeave={() => setIsHovered(false)} // Handle mouse leave + className="relative flex aspect-square items-center justify-center rounded-full bg-white/20 text-black shadow-lg backdrop-blur-md dark:bg-black/20 dark:text-white" + > + + {/* Tooltip that appears on hover */} + {isHovered && ( + + {title} {/* Tooltip text */} + + )} + + + {icon} {/* Render the icon */} + + + + ); +} + +// Component for the small dock, visible on smaller screens +const SmallDock = ({ + items, + className, +}: { + items: { title: string; icon: React.ReactNode; href: string }[]; // Items to display + className?: string; // Optional class name +}) => { + const [isOpen, setIsOpen] = useState(false); // State to manage open/close of the small dock + + return ( +
+ + {/* Render menu items when open */} + {isOpen && ( + + {items.map((item, index) => ( + + +
{item.icon}
{/* Render the icon */} + +
+ ))} +
+ )} +
+ {/* Button to toggle the small dock open/close */} + +
+ ); +}; diff --git a/content/docs/container/animated-dock.mdx b/content/docs/container/animated-dock.mdx new file mode 100644 index 00000000..a88eff89 --- /dev/null +++ b/content/docs/container/animated-dock.mdx @@ -0,0 +1,38 @@ +--- +title: Animated Dock +description: A sleek dock-style navigation bar, inspired by macOS, that combines glassmorphic design with functionality. With smooth animations and responsive icons, it enhances navigation for a modern web application. +author: R0X4R +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion lucide-react +``` + +Run the following command + +It will create a new file `animated-dock.tsx` inside the `components/animata/container` directory. + +```bash +mkdir -p components/animata/container && touch components/animata/container/animated-dock.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```jsx file=/animata/container/animated-dock.tsx + +``` + + + +## Credits + + Built by [Eshan Singh](https://github.com/R0X4R) with the help of [Framer Motion](https://www.framer.com/motion/). Inspired by [Build UI](https://buildui.com/recipes/magnified-dock) From c369eb8b60ec70da3d70a0d4b8b6a43dc132f3fb Mon Sep 17 00:00:00 2001 From: Vishal <145477397+Pikachu-345@users.noreply.github.com> Date: Fri, 4 Oct 2024 06:15:00 +0530 Subject: [PATCH 10/42] feat: add the Orbiting items 3D component (#243) Co-authored-by: Hari Lamichhane --- animata/list/orbiting-items-3-d.stories.tsx | 26 +++ animata/list/orbiting-items-3-d.tsx | 172 ++++++++++++++++++++ content/docs/list/orbiting-items-3-d.mdx | 60 +++++++ tailwind.config.ts | 5 + 4 files changed, 263 insertions(+) create mode 100644 animata/list/orbiting-items-3-d.stories.tsx create mode 100644 animata/list/orbiting-items-3-d.tsx create mode 100644 content/docs/list/orbiting-items-3-d.mdx diff --git a/animata/list/orbiting-items-3-d.stories.tsx b/animata/list/orbiting-items-3-d.stories.tsx new file mode 100644 index 00000000..d58c3afd --- /dev/null +++ b/animata/list/orbiting-items-3-d.stories.tsx @@ -0,0 +1,26 @@ +import OrbitingItems3D from "@/animata/list/orbiting-items-3-d"; +import { LucideIcons } from "@/animata/list/orbiting-items-3-d"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "List/Orbiting Items 3 D", + component: OrbitingItems3D, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + radiusX: 120, + radiusY: 30, + items: LucideIcons, + duration: 25, + tiltAngle: 360 - 30, + }, +}; diff --git a/animata/list/orbiting-items-3-d.tsx b/animata/list/orbiting-items-3-d.tsx new file mode 100644 index 00000000..6e086c87 --- /dev/null +++ b/animata/list/orbiting-items-3-d.tsx @@ -0,0 +1,172 @@ +import { useEffect, useState } from "react"; +import { Apple, BadgeCent, BadgeInfo, BadgeX, Banana, Bolt } from "lucide-react"; + +import { Icons } from "@/components/icons"; +import { cn } from "@/lib/utils"; + +export const CenterIcon = ( + +); +export const LucideIcons = [ + , + , + , + , + , + , +]; + +interface OrbitingItems3DProps { + /** + * The radius of the ellipse on X-axis in percentage, relative to the container. + */ + radiusX: number; + + /** + * The radius of the ellipse on Y-axis in percentage, relative to the container. + */ + radiusY: number; + + /** + * The angle at which ellipse is tilted to x-axis. + */ + tiltAngle: number; + + /** + * The time taken for the revolution around the center element. + */ + duration: number; + + /** + * The items to orbit around the center of the parent element. + */ + items: React.ReactNode[]; + + /** + * Class name for the background element. + */ + backgroundClassName?: string; + + /** + * Class name for the container element. + */ + containerClassName?: string; + + /** + * Additional classes for the item container. + */ + className?: string; +} + +export default function OrbitingItems3D({ + radiusX = 120, + radiusY = 30, + tiltAngle = 360 - 30, + duration = 25, + items = LucideIcons, + backgroundClassName, + containerClassName, + className, +}: OrbitingItems3DProps) { + // The OrbitingItems3D component creates an animated elliptical orbiting effect for a set of items around a central element. + // It allows for a visually dynamic layout, where items revolve around the center in a smooth, continuous motion, + // creating the illusion of 3D movement. The component provides a range of customizable options to control the orbit, + // including the size of the elliptical path, tilt angle, and animation duration. + + const CalculateItemStyle = ({ + index, + radiusX, + radiusY, + totalItems, + tiltAngle, + duration, + }: { + index: number; + radiusX: number; + radiusY: number; + totalItems: number; + tiltAngle: number; + duration: number; + }) => { + const angleStep = 360 / totalItems; + const [angle, setAngle] = useState(index * angleStep); + useEffect(() => { + const animation = setInterval(() => { + setAngle((prevAngle) => (prevAngle + 1) % 360); + }, duration); + + return () => clearInterval(animation); + }, [duration]); + // Calculate the current angle for the item on the orbit + + const radians = (angle * Math.PI) / 180; + + // X and Y positions before tilt + const x = radiusX * Math.cos(radians); + const y = radiusY * Math.sin(radians); + + // Apply the tilt using rotation matrix + const tiltRadians = (tiltAngle * Math.PI) / 180; + const xTilted = x * Math.cos(tiltRadians) - y * Math.sin(tiltRadians); + const yTilted = x * Math.sin(tiltRadians) + y * Math.cos(tiltRadians); + const zIndex = angle > 180 ? -1 : 1; + const scale = angle < 180 ? 1.2 : 1.0; + + return { + left: `${50 + xTilted}%`, + top: `${50 + yTilted}%`, + transform: `translate(-50%, -50%) scale(${scale})`, + zIndex: zIndex, + transition: "transform 0.8s ease-in-out", + }; + }; + + const reverse = cn("transition-transform ease-linear direction-reverse repeat-infinite"); + + return ( +
+
+
+ {CenterIcon} + {items.map((item, index) => { + return ( +
+
{item}
+
+ ); + })} +
+
+ ); +} diff --git a/content/docs/list/orbiting-items-3-d.mdx b/content/docs/list/orbiting-items-3-d.mdx new file mode 100644 index 00000000..f894503c --- /dev/null +++ b/content/docs/list/orbiting-items-3-d.mdx @@ -0,0 +1,60 @@ +--- +title: Orbiting Items 3D +description: List component with orbiting items. The items orbit around the center of an element in 3D Ellipse. +author: Pikachu-345 +--- + + + +## Installation + + +Install dependencies + +```bash +npm install lucide-react +``` + +Update `tailwind.config.js` + +Add the following to your tailwind.config.js file. + +```json +module.exports = { + theme: { + extend: { + keyframes: { + float: { + '0%, 100%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-40px)' }, + }, + }, + animation: { + float: 'float 3s ease-in-out infinite', + }, + } + } +} +``` + +Run the following command + +It will create a new file `orbiting-items-3-d.tsx` inside the `components/animata/list` directory. + +```bash +mkdir -p components/animata/list && touch components/animata/list/orbiting-items-3-d.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/list/orbiting-items-3-d.tsx + +``` + + + +## Credits + +Built by [Vishal](https://github.com/Pikachu-345) diff --git a/tailwind.config.ts b/tailwind.config.ts index 3c51c61f..d173bb31 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -198,6 +198,10 @@ const config = { "50%": { opacity: "1" }, "100%": { opacity: "0" }, }, + float: { + "0%, 100%": { transform: "translateY(0)" }, + "50%": { transform: "translateY(-40px)" }, + }, }, animation: { fill: "fill 1s forwards", @@ -214,6 +218,7 @@ const config = { meteor: "meteor var(--duration) var(--delay) ease-in-out infinite", trail: "trail var(--duration) linear infinite", led: "led 100ms ease-in-out", + float: "float 3s ease-in-out infinite", }, transitionTimingFunction: { slow: "cubic-bezier(.405, 0, .025, 1)", From 0a939a34ef9d885e964060b5c67496372543e2d5 Mon Sep 17 00:00:00 2001 From: Adriana Fruchter Date: Thu, 3 Oct 2024 20:50:37 -0400 Subject: [PATCH 11/42] feat: add interactive case study card (#245) --- animata/card/case-study-card.stories.tsx | 37 ++++++++ animata/card/case-study-card.tsx | 111 +++++++++++++++++++++++ content/docs/card/case-study-card.mdx | 33 +++++++ 3 files changed, 181 insertions(+) create mode 100644 animata/card/case-study-card.stories.tsx create mode 100644 animata/card/case-study-card.tsx create mode 100644 content/docs/card/case-study-card.mdx diff --git a/animata/card/case-study-card.stories.tsx b/animata/card/case-study-card.stories.tsx new file mode 100644 index 00000000..54df0431 --- /dev/null +++ b/animata/card/case-study-card.stories.tsx @@ -0,0 +1,37 @@ +import CaseStudyCard from "@/animata/card/case-study-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Case Study Card", + component: CaseStudyCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + title: + "How Delivery Hero streamlines marketing reports across all their brands with Clarisights", + category: "BOOKS", + logo: "https://plus.unsplash.com/premium_photo-1686593923007-218c4db786ca?q=80&w=3095&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + link: "https://github.com/codse/animata", + type: "content", + image: + "https://images.unsplash.com/photo-1675285410608-ddd6bb430b19?q=80&w=2938&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + }, +}; + +export const Secondary: Story = { + args: { + image: + "https://images.unsplash.com/photo-1675285410608-ddd6bb430b19?q=80&w=2938&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + link: "https://github.com/codse/animata", + type: "simple-image", + }, +}; diff --git a/animata/card/case-study-card.tsx b/animata/card/case-study-card.tsx new file mode 100644 index 00000000..f0c03814 --- /dev/null +++ b/animata/card/case-study-card.tsx @@ -0,0 +1,111 @@ +import React from "react"; + +import { cn } from "@/lib/utils"; + +interface CaseStudyCardProps extends React.HTMLAttributes { + title?: string; + category?: string; + image?: string; + logo?: string; + link?: string; + type?: "content" | "simple-image"; // Decides between text or image +} + +// ContentCard Component for rendering text + image +const ContentCard: React.FC = ({ title, category, image, logo }) => { + return ( +
+ {image &&
} + +
+ {category &&
{category}
} + + {title && ( +
+ {title} +
+ )} +
+ {logo && ( // Check if image exists + {title} + )} +
+ ); +}; + +// SimpleImageCard component for rendering only image +const SimpleImageCard: React.FC = ({ image }) => { + return ( +
+ ); +}; + +const HoverRevealSlip = ({ show }: { show: React.ReactNode }) => { + const common = "absolute flex w-full h-full [backface-visibility:hidden]"; + + return ( +
+ {/* Back cover - static */} +
+ + {/* Card container with slight book opening effect on hover */} +
+ {/* Front side of the card */} +
{show}
+
+ + {/* Sliding link/tab coming out from behind */} +
+
CLICK TO READ
+
+
+ ); +}; + +// Main CaseStudyCard Component +export default function CaseStudyCard({ + title, + category, + link, + image, + logo, + type, +}: CaseStudyCardProps) { + return ( + + ); +} diff --git a/content/docs/card/case-study-card.mdx b/content/docs/card/case-study-card.mdx new file mode 100644 index 00000000..c707f4c6 --- /dev/null +++ b/content/docs/card/case-study-card.mdx @@ -0,0 +1,33 @@ +--- +title: Case Study Card +description: An interactive case study card that expands on hover, revealing a hidden tab with a clickable link to read comments. +author: parankarj +--- + + + +## Installation + + + +Run the following command + +It will create a new file `case-study-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/case-study-card.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/case-study-card.tsx + +``` + + + +## Credits + +Built by [Adriana Fruchter](https://github.com/webdevNYC) From f20af3f49589608d60eb6b1e95638924a3de2648 Mon Sep 17 00:00:00 2001 From: Satyam Vyas Date: Fri, 4 Oct 2024 19:04:51 +0530 Subject: [PATCH 12/42] feat: add pricing component (#305) --- animata/section/pricing.stories.tsx | 56 +++++++ animata/section/pricing.tsx | 223 ++++++++++++++++++++++++++++ config/docs.ts | 4 + content/docs/section/pricing.mdx | 39 +++++ 4 files changed, 322 insertions(+) create mode 100644 animata/section/pricing.stories.tsx create mode 100644 animata/section/pricing.tsx create mode 100644 content/docs/section/pricing.mdx diff --git a/animata/section/pricing.stories.tsx b/animata/section/pricing.stories.tsx new file mode 100644 index 00000000..284ef767 --- /dev/null +++ b/animata/section/pricing.stories.tsx @@ -0,0 +1,56 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import Pricing from "./pricing"; + +const meta = { + title: "Section/Pricing", + component: Pricing, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + width: { + control: { type: "select" }, + options: ["sm", "md", "lg", "xl"], + }, + outerRadius: { + control: { type: "select" }, + options: ["normal", "rounded", "moreRounded"], + }, + padding: { + control: { type: "select" }, + options: ["small", "medium", "large"], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + plans: [ + { name: "Free", monthlyPrice: "$0.00", yearlyPrice: "$0.00" }, + { name: "Starter", monthlyPrice: "$9.99", yearlyPrice: "$99.99", popular: true }, + { name: "Pro", monthlyPrice: "$19.99", yearlyPrice: "$199.99" }, + ], + width: "md", + outerRadius: "rounded", + padding: "medium", + }, +}; + +export const CustomPlans: Story = { + args: { + plans: [ + { name: "Basic", monthlyPrice: "$4.99", yearlyPrice: "$49.99" }, + { name: "Standard", monthlyPrice: "$14.99", yearlyPrice: "$149.99", popular: true }, + { name: "Premium", monthlyPrice: "$24.99", yearlyPrice: "$249.99" }, + { name: "Enterprise", monthlyPrice: "$49.99", yearlyPrice: "$499.99" }, + ], + width: "xl", + outerRadius: "moreRounded", + padding: "large", + }, +}; diff --git a/animata/section/pricing.tsx b/animata/section/pricing.tsx new file mode 100644 index 00000000..692157a4 --- /dev/null +++ b/animata/section/pricing.tsx @@ -0,0 +1,223 @@ +import React, { useEffect, useRef, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +interface Plan { + name: string; + monthlyPrice: string; + yearlyPrice: string; + popular?: boolean; +} + +interface PricingProps { + plans: Plan[]; + onPlanSelect?: (plan: string) => void; + onCycleChange?: (cycle: "Monthly" | "Yearly") => void; + width?: "sm" | "md" | "lg" | "xl"; + outerRadius?: "normal" | "rounded" | "moreRounded"; + padding?: "small" | "medium" | "large"; +} + +const widthClasses = { + sm: "w-full sm:w-[300px]", + md: "w-full sm:w-[300px] md:w-[500px]", + lg: "w-full sm:w-[300px] md:w-[500px] lg:w-[768px]", + xl: "w-full sm:w-[300px] md:w-[500px] lg:w-[768px] xl:w-[1024px]", +}; + +const outerRadiusClasses = { + normal: "rounded-[16px]", + rounded: "rounded-[24px]", + moreRounded: "rounded-[32px]", +}; + +const paddingClasses = { + small: "p-2", + medium: "p-3", + large: "p-4", +}; + +const innerRadiusClasses = { + normal: "rounded-xl", + rounded: "rounded-2xl", + moreRounded: "rounded-3xl", +}; + +export default function Pricing({ + plans, + width = "lg", + outerRadius = "rounded", + padding = "medium", +}: PricingProps) { + const [selectedPlan, setSelectedPlan] = useState("Basic"); + const [billingCycle, setBillingCycle] = useState<"Monthly" | "Yearly">("Monthly"); + + const handlePlanSelect = (planName: string) => { + setSelectedPlan(planName); + }; + + const handleCycleChange = (cycle: "Monthly" | "Yearly") => { + setBillingCycle(cycle); + }; + + return ( +
+
+
+ +
+ {["Monthly", "Yearly"].map((cycle) => ( + { + e.stopPropagation(); + handleCycleChange(cycle as "Monthly" | "Yearly"); + }} + whileTap={{ scale: 0.95 }} + > + {cycle} + + ))} +
+
+
+ + {plans.map((plan) => ( + handlePlanSelect(plan.name)} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + layout + > + + {selectedPlan === plan.name && ( + + )} + +
+
+ {plan.name} + {plan.popular && ( + Popular + )} +
+ + {selectedPlan === plan.name && ( + + )} + +
+
+ +
+
+ ))} + + + Get Started + +
+ ); +} +interface AnimatedPriceProps { + monthlyPrice: string; + yearlyPrice: string; + billingCycle: "Monthly" | "Yearly"; +} + +function AnimatedPrice({ + monthlyPrice, + yearlyPrice, + billingCycle, +}: AnimatedPriceProps): React.JSX.Element { + const [price, setPrice] = useState(monthlyPrice); + const animationRef = useRef(null); + + useEffect(() => { + const targetPrice = billingCycle === "Monthly" ? monthlyPrice : yearlyPrice; + const startValue = parseFloat(price.replace(/[^0-9.-]+/g, "")); + const endValue = parseFloat(targetPrice.replace(/[^0-9.-]+/g, "")); + const duration = 50; // Animation duration in milliseconds + const startTime = Date.now(); + + const animatePrice = () => { + const elapsedTime = Date.now() - startTime; + const progress = Math.min(elapsedTime / duration, 1); + const currentValue = startValue + (endValue - startValue) * progress; + + setPrice(`$${currentValue.toFixed(2)}`); + + if (progress < 1) { + animationRef.current = requestAnimationFrame(animatePrice); + } else { + setPrice(targetPrice); + } + }; + + animatePrice(); + + return () => { + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, [price, billingCycle, monthlyPrice, yearlyPrice]); + + return ( +
+ {price} + /{billingCycle.toLowerCase().slice(0, -2)} +
+ ); +} diff --git a/config/docs.ts b/config/docs.ts index c24432fa..aea2b010 100644 --- a/config/docs.ts +++ b/config/docs.ts @@ -109,6 +109,10 @@ const sidebarNav: SidebarNavItem[] = [ title: "Card", items: createLinks("card"), }, + { + title: "Section", + items: createLinks("section"), + }, { title: "Icon", items: createLinks("icon"), diff --git a/content/docs/section/pricing.mdx b/content/docs/section/pricing.mdx new file mode 100644 index 00000000..20aa0532 --- /dev/null +++ b/content/docs/section/pricing.mdx @@ -0,0 +1,39 @@ +--- +title: Pricing +description: Pricing component that display the pricing options of various plans in an sleek and interactive way +author: SatyamVyas04 +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion lucide-react +``` + +Run the following command + +It will create a new file `pricing.tsx` inside the `components/animata/section` directory. + +```bash +mkdir -p components/animata/section && touch components/animata/section/pricing.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/section/pricing.tsx + +``` + + + +## Credits + +Built by [SatyamVyas04](https://github.com/SatyamVyas04) +Inspired by [Nitish Khagwal](https://x.com/nitishkmrk) From 03dc2807aeef0245ba5e985fb3dba51ff1d0ee60 Mon Sep 17 00:00:00 2001 From: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com> Date: Sat, 5 Oct 2024 07:18:35 +0530 Subject: [PATCH 13/42] fix: add notification with user info component (#312) --- animata/card/notify-user-info.stories.tsx | 26 +++++ animata/card/notify-user-info.tsx | 123 ++++++++++++++++++++++ content/docs/card/notify-user-info.mdx | 38 +++++++ 3 files changed, 187 insertions(+) create mode 100644 animata/card/notify-user-info.stories.tsx create mode 100644 animata/card/notify-user-info.tsx create mode 100644 content/docs/card/notify-user-info.mdx diff --git a/animata/card/notify-user-info.stories.tsx b/animata/card/notify-user-info.stories.tsx new file mode 100644 index 00000000..32894144 --- /dev/null +++ b/animata/card/notify-user-info.stories.tsx @@ -0,0 +1,26 @@ +import NotifyUserInfo from "@/animata/card/notify-user-info"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Notify User Info", + component: NotifyUserInfo, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + aiName: "Rostra AI", + userName: "Sandra", + paperTopic: "neural conditions", + doctorName: "DoctorLLM", + earnings: "$0.25c", + weekTotal: "$400", + }, +}; diff --git a/animata/card/notify-user-info.tsx b/animata/card/notify-user-info.tsx new file mode 100644 index 00000000..b3cd9a11 --- /dev/null +++ b/animata/card/notify-user-info.tsx @@ -0,0 +1,123 @@ +"use client"; +import React from "react"; +import { motion } from "framer-motion"; + +interface NotificationCardProps { + aiName?: string; + userName?: string; + paperTopic?: string; + doctorName?: string; + earnings?: string; + weekTotal?: string; +} + +export default function NotifyUserInfo({ + aiName = "AI name", + userName = "User", + paperTopic = "general topic", + doctorName = "AI Doctor", + earnings = "$0.20c", + weekTotal = "$400", +}: NotificationCardProps) { + return ( + +
+ {/* Animated Header */} + +
+
+ {aiName[0]} +
+
+ + {aiName} +
+ +
+ {/* Vertical Line */} + + + {/* Animated Content Wrapper */} + + {/* Animated Content */} + +

+ Hey {userName}, your paper on {paperTopic} was used by{" "} + + {doctorName} + {" "} + to give an assessment to a patient today. +

+
+ + {/* Earnings Notification */} + +
+

+ You've earned {earnings} because a new doctor LLM used your knowledge. This + week's total: {weekTotal}. +

+
+
+
+
+
+
+ ); +} diff --git a/content/docs/card/notify-user-info.mdx b/content/docs/card/notify-user-info.mdx new file mode 100644 index 00000000..a893727c --- /dev/null +++ b/content/docs/card/notify-user-info.mdx @@ -0,0 +1,38 @@ +--- +title: Notify User Info +description: This component is a notification animation based on user information. +author: MEbandhan +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `notify-user-info.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/notify-user-info.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/notify-user-info.tsx + +``` + + + +## Credits + +Built by [Bandhan Majumder](https://github.com/bandhan-majumder) From 1ae65c05ddb4baeff6baf2eaa8191c334a76d132 Mon Sep 17 00:00:00 2001 From: Sudha Shrestha Date: Sat, 5 Oct 2024 11:12:36 +0545 Subject: [PATCH 14/42] chore: update hacktoberfest issue template (#334) --- .github/ISSUE_TEMPLATE/new-component-ticket.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/new-component-ticket.md b/.github/ISSUE_TEMPLATE/new-component-ticket.md index bcee02b8..399259a1 100644 --- a/.github/ISSUE_TEMPLATE/new-component-ticket.md +++ b/.github/ISSUE_TEMPLATE/new-component-ticket.md @@ -22,6 +22,8 @@ Create the component as shown in the video attached below. ### Requirements 1. Create a new animated component that matches the provided design. 2. Add example usage of the component in the documentation or storybook. +3. **Add credits** for any resources, references, or assets used, as specified in the **Additional Resources** section. +
From 2e97190317ae95541bfe480f9e2f8790a752d59d Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sat, 5 Oct 2024 13:27:30 +0545 Subject: [PATCH 15/42] feat: update reward $ (#335) --- app/_landing/hero.tsx | 2 +- content/blog/hacktoberfest-2024.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/_landing/hero.tsx b/app/_landing/hero.tsx index 1004b5b4..f425e37e 100644 --- a/app/_landing/hero.tsx +++ b/app/_landing/hero.tsx @@ -39,7 +39,7 @@ export default function Hero() { }} /> - +
diff --git a/content/blog/hacktoberfest-2024.mdx b/content/blog/hacktoberfest-2024.mdx index 2db873a7..bb0737c6 100644 --- a/content/blog/hacktoberfest-2024.mdx +++ b/content/blog/hacktoberfest-2024.mdx @@ -43,7 +43,7 @@ Join our [Discord community](https://discord.gg/YfvqMf5MTE) to connect with othe ### Contribution Rewards -We’re offering a **$50 reward** to one lucky contributor based on a raffle system. +We’re offering a **$100 reward** to one lucky contributor based on a raffle system. ### Contribution Rules @@ -70,7 +70,7 @@ The winner will be selected randomly from all entries. All contributions must be submitted by **October 31, 11:59 PM UTC**. 6. **Payment Eligibility:** - To qualify for the **$50 reward**, you must be able to **receive international payments** via platforms like PayPal. + To qualify for the **$100 reward**, you must be able to **receive international payments** via platforms like PayPal. 7. **Others:** - The reward will be paid within 30 days of the contest end date. From 4220cf466543824c43118b9a1fdc8ea672437675 Mon Sep 17 00:00:00 2001 From: Vishal <145477397+Pikachu-345@users.noreply.github.com> Date: Sat, 5 Oct 2024 20:52:45 +0530 Subject: [PATCH 16/42] feat: add confirmation message component (#315) Co-authored-by: Hari Lamichhane --- .../confirmation-message.stories.tsx | 24 ++++ .../feature-cards/confirmation-message.tsx | 109 ++++++++++++++++++ config/docs.ts | 4 + .../feature-cards/confirmation-message.mdx | 38 ++++++ 4 files changed, 175 insertions(+) create mode 100644 animata/feature-cards/confirmation-message.stories.tsx create mode 100644 animata/feature-cards/confirmation-message.tsx create mode 100644 content/docs/feature-cards/confirmation-message.mdx diff --git a/animata/feature-cards/confirmation-message.stories.tsx b/animata/feature-cards/confirmation-message.stories.tsx new file mode 100644 index 00000000..9edda5e1 --- /dev/null +++ b/animata/feature-cards/confirmation-message.stories.tsx @@ -0,0 +1,24 @@ +import ConfirmationMessage from "@/animata/feature-cards/confirmation-message"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Feature Cards/Confirmation Message", + component: ConfirmationMessage, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + successMessage: "Process Successful", + labelName: "Animata", + labelMessage: `The Confirmation Message component is a sleek, animated UI element that displays a checkmark with a success message. + It expands to reveal a personalized detailed description of the process.`, + }, +}; diff --git a/animata/feature-cards/confirmation-message.tsx b/animata/feature-cards/confirmation-message.tsx new file mode 100644 index 00000000..d1860cb2 --- /dev/null +++ b/animata/feature-cards/confirmation-message.tsx @@ -0,0 +1,109 @@ +import { motion } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +interface ConfirmationMessageProps { + /** + * The message to appear in green box when the process will be successfully completed. + */ + successMessage: string; + + /** + * The name of the organization/bot who performs the operations. + */ + labelName: string; + + /** + * The brief about the process/text/output. + */ + labelMessage: string; + + /** + * Class name for the background element. + */ + backgroundClassName?: string; + + /** + * Class name for the container element. + */ + containerClassName?: string; +} + +export default function ConfirmationMessage({ + successMessage = "Process Successful", + labelName = "Animata", + labelMessage, + backgroundClassName, + containerClassName, +}: ConfirmationMessageProps) { + return ( +
+
+ + {/* Parent Container for message */} +
+
+ {/* Checkmark */} +
+ {" "} + {/* Adjusted size */} + ✓ +
+ + {/* Expanding green box with sliding text */} + + + {successMessage} + + +
+ + {/* Container to control height animation */} + + {/* Message box */} +
+
+ {labelName[0]} +
+
+

{labelName}

+ + {labelMessage.length > 200 ? labelMessage.slice(0, 199) + "..." : labelMessage} + +
+
+
+
+
+ ); +} diff --git a/config/docs.ts b/config/docs.ts index aea2b010..f6da719e 100644 --- a/config/docs.ts +++ b/config/docs.ts @@ -172,6 +172,10 @@ const sidebarNav: SidebarNavItem[] = [ href: "/docs/skeleton", items: createLinks("skeleton"), }, + { + title: "Feature cards", + items: createLinks("feature-cards"), + }, ] .filter((category) => Boolean(category.items?.length || category.label)) .sort((a, b) => { diff --git a/content/docs/feature-cards/confirmation-message.mdx b/content/docs/feature-cards/confirmation-message.mdx new file mode 100644 index 00000000..b8a18247 --- /dev/null +++ b/content/docs/feature-cards/confirmation-message.mdx @@ -0,0 +1,38 @@ +--- +title: Confirmation Message +description: An animated component which shows success message with custom name and description. +author: i_v1shal_ +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `confirmation-message.tsx` inside the `components/animata/feature-cards` directory. + +```bash +mkdir -p components/animata/feature-cards && touch components/animata/feature-cards/confirmation-message.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/feature-cards/confirmation-message.tsx + +``` + + + +## Credits + +Built by [Vishal](https://github.com/Pikachu-345) From 3df87122309dde5d06b586d535d2e09e80ef9acc Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sun, 6 Oct 2024 17:42:10 +0545 Subject: [PATCH 17/42] fix: remove branch (#340) --- .github/workflows/deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9e6fb9da..9d2a2edc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,8 +2,6 @@ name: Deploy Preview on: pull_request_target: - branches: - - main types: [opened, synchronize, reopened] jobs: From a2df04357656bc7008368322d2bb0d2e808b6450 Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sun, 6 Oct 2024 18:00:58 +0545 Subject: [PATCH 18/42] feat: get rid of brach arg (#341) --- .github/workflows/deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9d2a2edc..193a07a2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,5 +48,4 @@ jobs: projectName: animata directory: out gitHubToken: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.event.pull_request.head.ref }} wranglerVersion: "3" From 520f4f251aa1ee2fff4511a7e2ea4edce39fc41d Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sun, 6 Oct 2024 19:01:40 +0545 Subject: [PATCH 19/42] feat: bring back branch (#342) --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 193a07a2..98f70d73 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,4 +48,5 @@ jobs: projectName: animata directory: out gitHubToken: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.head_ref || github.ref_name }} wranglerVersion: "3" From 173f06b54ad16673463ebe7df008805a3d6118cf Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sun, 6 Oct 2024 19:26:26 +0545 Subject: [PATCH 20/42] fix: more attempt (#343) --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 98f70d73..a71380fd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,12 +41,12 @@ jobs: NEXT_PUBLIC_POSTHOG_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} run: yarn build - name: Deploy to Cloudflare Pages - uses: cloudflare/pages-action@v1 + uses: cloudflare/wrangler-action@v3.8.0 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} projectName: animata directory: out gitHubToken: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.head_ref || github.ref_name }} + branch: main wranglerVersion: "3" From 6fe771ee1b449f38c4eb0b02d6af6774962e1955 Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sun, 6 Oct 2024 19:36:04 +0545 Subject: [PATCH 21/42] fix: use pages action (#344) --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a71380fd..04e5a8d8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,7 +41,7 @@ jobs: NEXT_PUBLIC_POSTHOG_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} run: yarn build - name: Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@v3.8.0 + uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} From a24eebf88938c2d172ed51c2f3006f38cae2e148 Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sun, 6 Oct 2024 21:11:21 +0545 Subject: [PATCH 22/42] fix: update deployment flow (#345) --- .github/workflows/deploy.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 04e5a8d8..4e058188 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,6 +2,8 @@ name: Deploy Preview on: pull_request_target: + branches: + - main types: [opened, synchronize, reopened] jobs: @@ -41,12 +43,9 @@ jobs: NEXT_PUBLIC_POSTHOG_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} run: yarn build - name: Deploy to Cloudflare Pages - uses: cloudflare/pages-action@v1 + uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - projectName: animata - directory: out + command: pages deploy out --project-name=animata --branch=dev-${{ github.event.pull_request.head.ref }} gitHubToken: ${{ secrets.GITHUB_TOKEN }} - branch: main - wranglerVersion: "3" From 102076c8fadc4e3ab22b2c6e9c9b6a0ebc77c98a Mon Sep 17 00:00:00 2001 From: Sabin Shrestha <161845983+masabinhok@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:25:44 +0545 Subject: [PATCH 23/42] feat: add customizable speed dial with a tooltip #316 (#339) --- animata/fabs/speed-dial.stories.tsx | 57 +++++++++++++++ animata/fabs/speed-dial.tsx | 107 ++++++++++++++++++++++++++++ config/docs.ts | 4 ++ content/docs/fabs/speed-dial.mdx | 38 ++++++++++ 4 files changed, 206 insertions(+) create mode 100644 animata/fabs/speed-dial.stories.tsx create mode 100644 animata/fabs/speed-dial.tsx create mode 100644 content/docs/fabs/speed-dial.mdx diff --git a/animata/fabs/speed-dial.stories.tsx b/animata/fabs/speed-dial.stories.tsx new file mode 100644 index 00000000..dff05b85 --- /dev/null +++ b/animata/fabs/speed-dial.stories.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { Copy, Edit, Share2, Trash } from "lucide-react"; + +import SpeedDial from "@/animata/fabs/speed-dial"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Fabs/Speed Dial", + component: SpeedDial, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + direction: { + control: { type: "select" }, + options: ["up", "down", "left", "right"], + description: "Direction of the SpeedDial", + table: { + type: { summary: "string" }, + defaultValue: { summary: "right" }, + }, + }, + actionButtons: { + description: + "Array of action buttons to be displayed in the SpeedDial. Each button should have an icon, label, and action function.", + table: { + type: { summary: "Array of objects" }, + }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const actionButtons = [ + { icon: , label: "Copy", key: "copy", action: () => console.log("Copy clicked") }, + { icon: , label: "Edit", key: "edit", action: () => console.log("Edit clicked") }, + { icon: , label: "Share", key: "share", action: () => console.log("Share clicked") }, + { icon: , label: "Delete", key: "delete", action: () => console.log("Delete clicked") }, +]; + +// Primary story for SpeedDial +export const Primary: Story = { + args: { + direction: "right", + actionButtons: actionButtons, + }, +}; + +export const Secondary: Story = { + args: { + direction: "down", + actionButtons: actionButtons, + }, +}; diff --git a/animata/fabs/speed-dial.tsx b/animata/fabs/speed-dial.tsx new file mode 100644 index 00000000..fdd00a83 --- /dev/null +++ b/animata/fabs/speed-dial.tsx @@ -0,0 +1,107 @@ +"use client"; + +import React, { useState } from "react"; +import { Plus } from "lucide-react"; + +interface SpeedialProps { + direction: string; + actionButtons: Array<{ + icon: React.ReactNode; + label: string; + key: string; + action: () => void; + }>; +} + +interface TooltipProps { + text: string; + children: React.ReactNode; + direction: string; +} + +const Tooltip: React.FC = ({ text, children, direction }) => { + const [visible, setVisible] = useState(false); + + const showTooltip = () => setVisible(true); + const hideTooltip = () => setVisible(false); + + return ( +
+ {children} + {visible && ( +
+ {text} +
+ )} +
+ ); +}; + +export default function Speeddial({ direction, actionButtons }: SpeedialProps) { + const [isHovered, setIsHovered] = useState(false); + + const getAnimation = () => { + switch (direction) { + case "up": + return "origin-bottom flex-col order-0"; + case "down": + return "origin-top flex-col order-2"; + case "left": + return "origin-right order-0"; + case "right": + return "origin-left order-2"; + default: + return ""; + } + }; + + const handleMouseEnter = () => setIsHovered(true); + const handleMouseLeave = () => setIsHovered(false); + + const getGlassyClasses = () => { + return "backdrop-filter backdrop-blur-xl bg-white border border-white rounded-xl shadow-lg transition-all duration-300"; + }; + + //customize your action buttons here + + return ( +
+ + + {/* Speed Dial Actions */} +
+ {actionButtons.map((action, index) => ( + + + + ))} +
+
+ ); +} diff --git a/config/docs.ts b/config/docs.ts index f6da719e..cad6c766 100644 --- a/config/docs.ts +++ b/config/docs.ts @@ -176,6 +176,10 @@ const sidebarNav: SidebarNavItem[] = [ title: "Feature cards", items: createLinks("feature-cards"), }, + { + title: "Floating Action Buttons", + items: createLinks("fabs"), + }, ] .filter((category) => Boolean(category.items?.length || category.label)) .sort((a, b) => { diff --git a/content/docs/fabs/speed-dial.mdx b/content/docs/fabs/speed-dial.mdx new file mode 100644 index 00000000..fc1c3be9 --- /dev/null +++ b/content/docs/fabs/speed-dial.mdx @@ -0,0 +1,38 @@ +--- +title: Speed Dial +description: Speed Dial +author: masabinhok +--- + + + +## Installation + + +Install dependencies + +```bash +npm install lucide-react +``` + +Run the following command + +It will create a new file `speed-dial.tsx` inside the `components/animata/fabs` directory. + +```bash +mkdir -p components/animata/fabs && touch components/animata/fabs/speed-dial.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/fabs/speed-dial.tsx + +``` + + + +## Credits + +Built by [Sabin Shrestha](https://github.com/masabinhok) From 4ffc26d6d7a2453a73b87de7a51763f247d5c688 Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Mon, 7 Oct 2024 09:08:02 +0545 Subject: [PATCH 24/42] feat: comment link on PR (#349) --- .github/workflows/deploy.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4e058188..ace6ab08 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -43,9 +43,21 @@ jobs: NEXT_PUBLIC_POSTHOG_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} run: yarn build - name: Deploy to Cloudflare Pages + id: deploy uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: pages deploy out --project-name=animata --branch=dev-${{ github.event.pull_request.head.ref }} gitHubToken: ${{ secrets.GITHUB_TOKEN }} + - name: Comment Deployment URL + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `🚀 Preview deployed to: ${{ steps.deploy.outputs.deployment-url }}` + }) From 298f97416b7d93b30789a68f5461eec80a569408 Mon Sep 17 00:00:00 2001 From: Prince Kumar Yadav <69517192+prince981620@users.noreply.github.com> Date: Thu, 10 Oct 2024 05:20:01 +0530 Subject: [PATCH 25/42] feat: add reminder-scheduler component (#313) --- animata/card/reminder-scheduler.stories.tsx | 49 +++++++ animata/card/reminder-scheduler.tsx | 144 ++++++++++++++++++++ content/docs/card/reminder-scheduler.mdx | 54 ++++++++ 3 files changed, 247 insertions(+) create mode 100644 animata/card/reminder-scheduler.stories.tsx create mode 100644 animata/card/reminder-scheduler.tsx create mode 100644 content/docs/card/reminder-scheduler.mdx diff --git a/animata/card/reminder-scheduler.stories.tsx b/animata/card/reminder-scheduler.stories.tsx new file mode 100644 index 00000000..e67ce647 --- /dev/null +++ b/animata/card/reminder-scheduler.stories.tsx @@ -0,0 +1,49 @@ +import { useState } from "react"; + +import ReminderScheduler from "@/animata/card/reminder-scheduler"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Reminder Scheduler", + component: ReminderScheduler, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + isRepeating: { control: "boolean" }, + repeatInterval: { control: "text" }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => { + const [isRepeating, setIsRepeating] = useState(args.isRepeating); + const [repeatInterval, setRepeatInterval] = useState(args.repeatInterval); + + const toggleRepeating = () => { + setIsRepeating((prev) => !prev); + }; + + return ( + + ); + }, + args: { + isRepeating: true, + repeatInterval: "Weekly", + toggleRepeating: () => {}, + setRepeatInterval: () => {}, + daysOfWeek: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + }, +}; diff --git a/animata/card/reminder-scheduler.tsx b/animata/card/reminder-scheduler.tsx new file mode 100644 index 00000000..94aee3df --- /dev/null +++ b/animata/card/reminder-scheduler.tsx @@ -0,0 +1,144 @@ +import React, { useEffect, useState } from "react"; + +import { cn } from "@/lib/utils"; + +interface ReminderSchedulerProps { + isRepeating: boolean; + toggleRepeating: () => void; + repeatInterval: string; + setRepeatInterval: (interval: string) => void; + daysOfWeek: string[]; +} + +const ReminderScheduler: React.FC = ({ + isRepeating, + toggleRepeating, + repeatInterval, + setRepeatInterval, + daysOfWeek, +}) => { + const selectedDays = isRepeating ? new Set(["Th", "Fr", "Su"]) : new Set(["Mo", "We", "Sa"]); + return ( +
+ {/* Toggle Switch */} +
+ Is repeating + +
+ + {/* Repeat Interval Dropdown */} +
+ + +
+ + {/* Day Selection */} +
+
+ {daysOfWeek.map((day) => ( + + ))} +
+
+
+ ); +}; + +const Switch = ({ toggle, value }: { toggle: () => void; value: boolean }) => { + return ( + + ); +}; +// credit: author: harimanok_ , https://github.com/hari +interface SwapTextProps extends React.ComponentPropsWithoutRef<"div"> { + initialText: string; + finalText: string; + supportsHover?: boolean; + textClassName?: string; + initialTextClassName?: string; + finalTextClassName?: string; + disableClick?: boolean; + check?: boolean; +} + +function SwapText({ + initialText, + finalText, + className, + supportsHover = true, + textClassName, + initialTextClassName, + finalTextClassName, + disableClick, + check, + ...props +}: SwapTextProps) { + const [active, setActive] = useState(!check); + useEffect(() => { + let timeoutId: NodeJS.Timeout; + if (check) { + timeoutId = setTimeout(() => { + setActive((current) => !current); + }, 100); + } else { + timeoutId = setTimeout(() => { + setActive((current) => !current); + }, 100); + } + return () => { + clearTimeout(timeoutId); // clear the timeout when component unmounts + }; + }, [check]); + const common = "block transition-all duration-1000 ease-slow"; + const longWord = finalText.length > initialText.length ? finalText : null; + return ( +
+
!disableClick && setActive((current) => !current)} + > + + {initialText} + {Boolean(longWord?.length) && {longWord}} + + + {finalText} + +
+
+ ); +} +export default ReminderScheduler; diff --git a/content/docs/card/reminder-scheduler.mdx b/content/docs/card/reminder-scheduler.mdx new file mode 100644 index 00000000..f8bdb8e6 --- /dev/null +++ b/content/docs/card/reminder-scheduler.mdx @@ -0,0 +1,54 @@ +--- +title: Reminder Scheduler +description: a simple card to set up recurring reminders along with frequency and repetition. +author: m_jinprince +labels: ["requires interaction", "toggle switch"] +--- + + + +## Installation + + + +Update `tailwind.config.js` + +Add the following to your tailwind.config.js file. + +```js +theme: { + extend: { + colors: { + foreground: "hsl(var(--foreground))", + }, + transitionTimingFunction: { + slow: "cubic-bezier(.405, 0, .025, 1)", + "minor-spring": "cubic-bezier(0.18,0.89,0.82,1.04)", + } + }, + }, +``` + +Run the following command + +It will create a new file `reminder-scheduler.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/reminder-scheduler.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/reminder-scheduler.tsx + +``` + + + +## Credits + +Built by [Prince Yadav](https://github.com/prince981620) + +[hari](https://github.com/hari) From 2a15612593eacb405eca86596daf4038bda17217 Mon Sep 17 00:00:00 2001 From: Pavan kumar Date: Thu, 10 Oct 2024 05:24:50 +0530 Subject: [PATCH 26/42] feat: add webhook card (#338) --- .env.example | 6 --- animata/card/WebHooks-card.tsx | 53 ++++++++++++++++++++++++++ animata/card/webhooks-card.stories.tsx | 20 ++++++++++ content/docs/card/webhooks-card.mdx | 34 +++++++++++++++++ 4 files changed, 107 insertions(+), 6 deletions(-) delete mode 100644 .env.example create mode 100644 animata/card/WebHooks-card.tsx create mode 100644 animata/card/webhooks-card.stories.tsx create mode 100644 content/docs/card/webhooks-card.mdx diff --git a/.env.example b/.env.example deleted file mode 100644 index 4e66c7e6..00000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -NEXT_PUBLIC_APP_URL=http://localhost:3000 -NEXT_PUBLIC_STORYBOOK_URL=http://localhost:6006 -NEXT_PUBLIC_SUPABASE_URL= -NEXT_PUBLIC_SUPABASE_ANON_KEY= -NEXT_PUBLIC_POSTHOG_KEY= -NEXT_PUBLIC_POSTHOG_HOST= \ No newline at end of file diff --git a/animata/card/WebHooks-card.tsx b/animata/card/WebHooks-card.tsx new file mode 100644 index 00000000..317fe3d6 --- /dev/null +++ b/animata/card/WebHooks-card.tsx @@ -0,0 +1,53 @@ +import React, { useState } from "react"; +interface webHooksCardCommentProps { + leftBoxElem: string; + rightBoxElem: string; +} + +export const WebHooks = ({ leftBoxElem, rightBoxElem }: webHooksCardCommentProps) => { + const [isHovered, setIsHovered] = useState(false); + + return ( + <> +
+
+ {/* Left Box */} +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {leftBoxElem} +
+ + {/* Connecting Line */} +
+ + {/* Animated Ball */} +
+ + {/* Right Box */} +
+ {rightBoxElem} +
+
+
+ + ); +}; diff --git a/animata/card/webhooks-card.stories.tsx b/animata/card/webhooks-card.stories.tsx new file mode 100644 index 00000000..290d2127 --- /dev/null +++ b/animata/card/webhooks-card.stories.tsx @@ -0,0 +1,20 @@ +import { WebHooks } from "@/animata/card/WebHooks-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Web Hooks", + component: WebHooks, + parameters: {}, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + leftBoxElem: "Button Down ", + rightBoxElem: "Your app", + }, +}; diff --git a/content/docs/card/webhooks-card.mdx b/content/docs/card/webhooks-card.mdx new file mode 100644 index 00000000..a3e724f6 --- /dev/null +++ b/content/docs/card/webhooks-card.mdx @@ -0,0 +1,34 @@ +--- +title: Web Hooks +description: A card component with animated boxes, lines, and a ball. The component changes style on hover. +labels: ["requires interaction", "hover"] +author: Pavan kumar +--- + + + +## Installation + + + +Run the following command + +It will create a new file `WebHooks-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/WebHooks-card.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/WebHooks-card.tsx + +``` + + + +## Credits + +Built by [Pavan kumar](https://github.com/pavankumar07s) From a1d82975a1a9a34f1ba76336a4e6ec3cc976b3bc Mon Sep 17 00:00:00 2001 From: Anshuman Date: Thu, 10 Oct 2024 05:29:44 +0530 Subject: [PATCH 27/42] feat: add faq in accordion (#348) Co-authored-by: anshuman --- animata/accordion/faq.stories.tsx | 53 +++++++++++++ animata/accordion/faq.tsx | 126 ++++++++++++++++++++++++++++++ config/docs.ts | 4 + content/docs/accordion/faq.mdx | 38 +++++++++ 4 files changed, 221 insertions(+) create mode 100644 animata/accordion/faq.stories.tsx create mode 100644 animata/accordion/faq.tsx create mode 100644 content/docs/accordion/faq.mdx diff --git a/animata/accordion/faq.stories.tsx b/animata/accordion/faq.stories.tsx new file mode 100644 index 00000000..9d4d6d56 --- /dev/null +++ b/animata/accordion/faq.stories.tsx @@ -0,0 +1,53 @@ +import Faq from "@/animata/accordion/faq"; +import { Meta, StoryObj } from "@storybook/react"; + +const faqData = [ + { + id: 1, + question: "How late does the internet close?", + answer: "The internet doesn't close. It's available 24/7.", + icon: "❤️", + iconPosition: "right", + }, + { + id: 2, + question: "Do I need a license to browse this website?", + answer: "No, you don't need a license to browse this website.", + }, + { + id: 3, + question: "What flavour are the cookies?", + answer: "Our cookies are digital, not edible. They're used for website functionality.", + }, + { + id: 4, + question: "Can I get lost here?", + answer: "Yes, but we do have a return policy", + icon: "⭐", + iconPostion: "left", + }, + { + id: 5, + question: "What if I click the wrong button?", + answer: "Don't worry, you can always go back or refresh the page.", + }, +]; + +const meta = { + title: "Accordion/Faq", + component: Faq, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + data: faqData, + }, +}; diff --git a/animata/accordion/faq.tsx b/animata/accordion/faq.tsx new file mode 100644 index 00000000..845f1c23 --- /dev/null +++ b/animata/accordion/faq.tsx @@ -0,0 +1,126 @@ +"use client"; + +import React, { useState } from "react"; +import { motion } from "framer-motion"; + +import * as Accordion from "@radix-ui/react-accordion"; + +interface FAQItem { + id: number; + question: string; + answer: string; + icon?: string; + iconPosition?: string; +} + +interface FaqSectionProps { + data: FAQItem[]; +} + +export default function FaqSection({ data }: FaqSectionProps) { + const [openItem, setOpenItem] = useState(null); + + return ( +
+
Every day, 9:01 AM
+ + setOpenItem(value)} + > + {data.map((item) => ( + + + +
+ {item.icon && ( + + {item.icon} + + )} + {item.question} +
+ + + {openItem === item.id.toString() ? ( + + + + ) : ( + + + + )} + +
+
+ + +
+
+ {item.answer} +
+
+
+
+
+
+ ))} +
+
+ ); +} diff --git a/config/docs.ts b/config/docs.ts index cad6c766..034ca70b 100644 --- a/config/docs.ts +++ b/config/docs.ts @@ -105,6 +105,10 @@ const sidebarNav: SidebarNavItem[] = [ title: "Container", items: createLinks("container"), }, + { + title: "Accordion", + items: createLinks("accordion"), + }, { title: "Card", items: createLinks("card"), diff --git a/content/docs/accordion/faq.mdx b/content/docs/accordion/faq.mdx new file mode 100644 index 00000000..59051503 --- /dev/null +++ b/content/docs/accordion/faq.mdx @@ -0,0 +1,38 @@ +--- +title: Faq +description: its an faq accordion that looks like an chating interface with smooth animations +author: anshu_code +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion lucide-react +``` + +Run the following command + +It will create a new file `faq.tsx` inside the `components/animata/accordion` directory. + +```bash +mkdir -p components/animata/accordion && touch components/animata/accordion/faq.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/accordion/faq.tsx + +``` + + + +## Credits + +Built by [Anshuman](https://github.com/anshuman008) From 46ce9d9dfd2875b72c26f558024aa5fc9c52f207 Mon Sep 17 00:00:00 2001 From: Rudra Sankha Sinhamahapatra <101992909+Rudra-Sankha-Sinhamahapatra@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:28:15 +0530 Subject: [PATCH 28/42] feat: add fluid tabs component (#351) --- animata/card/fluid-tabs.stories.tsx | 19 +++++++ animata/card/fluid-tabs.tsx | 82 +++++++++++++++++++++++++++++ content/docs/card/fluid-tabs.mdx | 52 ++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 animata/card/fluid-tabs.stories.tsx create mode 100644 animata/card/fluid-tabs.tsx create mode 100644 content/docs/card/fluid-tabs.mdx diff --git a/animata/card/fluid-tabs.stories.tsx b/animata/card/fluid-tabs.stories.tsx new file mode 100644 index 00000000..6f814c98 --- /dev/null +++ b/animata/card/fluid-tabs.stories.tsx @@ -0,0 +1,19 @@ +import FluidTabs from "@/animata/card/fluid-tabs"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Tabs/Fluid Tabs", + component: FluidTabs, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: {}, +}; diff --git a/animata/card/fluid-tabs.tsx b/animata/card/fluid-tabs.tsx new file mode 100644 index 00000000..b4b03b99 --- /dev/null +++ b/animata/card/fluid-tabs.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { Inbox, Landmark, PieChart } from "lucide-react"; + +const tabs = [ + { + id: "accounts", + label: "Accounts", + icon: , + }, + { + id: "deposits", + label: "Deposits", + icon: , + }, + { + id: "funds", + label: "Funds", + icon: , + }, +]; + +export default function FluidTabs() { + const [activeTab, setActiveTab] = useState("funds"); + const [touchedTab, setTouchedTab] = useState(null); + const [prevActiveTab, setPrevActiveTab] = useState("funds"); + const timeoutRef = useRef(null); + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + const handleTabClick = (tabId: string) => { + setPrevActiveTab(activeTab); + setActiveTab(tabId); + setTouchedTab(tabId); + + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setTouchedTab(null); + }, 300); + }; + + const getTabIndex = (tabId: string) => tabs.findIndex((tab) => tab.id === tabId); + + return ( +
+
+ + + + {tabs.map((tab) => ( + handleTabClick(tab.id)} + > + {tab.icon} + {tab.label} + + ))} +
+
+ ); +} diff --git a/content/docs/card/fluid-tabs.mdx b/content/docs/card/fluid-tabs.mdx new file mode 100644 index 00000000..df593bb5 --- /dev/null +++ b/content/docs/card/fluid-tabs.mdx @@ -0,0 +1,52 @@ +--- +title: Fluid Tabs +description: The component is a sliding animation card +author: RudraSankha +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion lucide-react +``` + +Update `tailwind.config.js` + +Add the following to your tailwind.config.js file. + +```json +module.exports = { + theme: { + extend: { + } + } +} +``` + +Run the following command + +It will create a new file `fluid-tabs.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/fluid-tabs.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/fluid-tabs.tsx + +``` + + + +## Credits + +Built by [Rudra Sankha Sinhamahapatra](https://github.com/Rudra-Sankha-Sinhamahapatra) +Twitter Handle [Rudra Sankha](https://x.com/RudraSankha) From 85599271a56f99b7060a7071decd1b3d41202d9d Mon Sep 17 00:00:00 2001 From: Prince Kumar Yadav <69517192+prince981620@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:29:05 +0530 Subject: [PATCH 29/42] feat: add transaction list (#352) --- animata/list/transaction-list.stories.tsx | 86 ++++++++++ animata/list/transaction-list.tsx | 199 ++++++++++++++++++++++ content/docs/list/transaction-list.mdx | 39 +++++ 3 files changed, 324 insertions(+) create mode 100644 animata/list/transaction-list.stories.tsx create mode 100644 animata/list/transaction-list.tsx create mode 100644 content/docs/list/transaction-list.mdx diff --git a/animata/list/transaction-list.stories.tsx b/animata/list/transaction-list.stories.tsx new file mode 100644 index 00000000..600c85c8 --- /dev/null +++ b/animata/list/transaction-list.stories.tsx @@ -0,0 +1,86 @@ +import { ChefHat, Receipt, Signal } from "lucide-react"; + +import TransactionList from "@/animata/list/transaction-list"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "List/Transaction List", + component: TransactionList, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { transactions: { control: { type: "object" } } }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const transactions = [ + { + id: "67593", + name: "Netflix", + type: "Subscription", + amount: -6.99, + date: "September 26", + time: "12:01 am", + icon: , + paymentMethod: "Credit Card", + cardLastFour: "9342", + cardType: "visa", + }, + { + id: "67482", + name: "Verizon", + type: "Mobile Recharge", + amount: -4.05, + date: "September 24", + time: "05:18 pm", + icon: , + paymentMethod: "Credit Card", + cardLastFour: "2316", + cardType: "mastercard", + }, + { + id: "52363", + name: "Figma", + type: "Subscription", + amount: -15.0, + date: "September 15", + time: "01:11 pm", + icon: , + paymentMethod: "Credit Card", + cardLastFour: "9342", + cardType: "visa", + }, + { + id: "54635", + name: "Rive", + type: "Subscription", + amount: -32.0, + date: "September 16", + time: "02:11 pm", + icon: , + paymentMethod: "Credit Card", + cardLastFour: "9342", + cardType: "mastercard", + }, + { + id: "52342", + name: "Big Belly Burger", + type: "Restaurant", + amount: -12.05, + date: "September 12", + time: "09:06 pm", + icon: , + paymentMethod: "Credit Card", + cardLastFour: "2316", + cardType: "visa", + }, +]; + +export const Primary: Story = { + args: { + transactions: transactions, + }, +}; diff --git a/animata/list/transaction-list.tsx b/animata/list/transaction-list.tsx new file mode 100644 index 00000000..9d34f1f4 --- /dev/null +++ b/animata/list/transaction-list.tsx @@ -0,0 +1,199 @@ +import React, { useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { ArrowRight, CreditCard, X } from "lucide-react"; + +interface Transaction { + id: string; + name: string; + type: string; + amount: number; + date: string; + time: string; + icon: React.ReactNode; + paymentMethod: string; + cardLastFour: string; + cardType?: string; +} + +export default function TransactionList({ transactions }: { transactions: Transaction[] }) { + const [selectedTransaction, setSelectedTransaction] = useState(null); + + return ( +
+ + + {!selectedTransaction ? ( + +

Transactions

+
+ {transactions.map((transaction) => ( + setSelectedTransaction(transaction)} + > +
+ + {transaction.icon} + +
+ + {transaction.name} + + + {transaction.type} + +
+
+ + ${Math.abs(transaction.amount).toFixed(2)} + +
+ ))} +
+ + All Transactions + +
+ ) : ( + +
+ + + {selectedTransaction.icon} + + + +
+
+
+ + {selectedTransaction.name} + + + {selectedTransaction.type} + +
+ + ${Math.abs(selectedTransaction.amount).toFixed(2)} + +
+ +
+

#{selectedTransaction.id}

+

{selectedTransaction.date}

+

{selectedTransaction.time}

+
+
+

Paid Via {selectedTransaction.paymentMethod}

+
+ +

XXXX {selectedTransaction.cardLastFour}

+

+ {selectedTransaction.cardType === "visa" ? : } +

+
+
+
+
+ )} +
+
+
+ ); +} + +const MasterCardLogo = () => { + return ( + + + + + + + ); +}; + +const VisaLogo = () => { + return ( + + + + + + + + + + ); +}; diff --git a/content/docs/list/transaction-list.mdx b/content/docs/list/transaction-list.mdx new file mode 100644 index 00000000..b2550b85 --- /dev/null +++ b/content/docs/list/transaction-list.mdx @@ -0,0 +1,39 @@ +--- +title: Transaction List +description: A simple component to list all the recent transaction. +author: m_jinprince +labels: ["requires interaction", "Click any recent Transaction"] +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion lucide-react +``` + +Run the following command + +It will create a new file `transaction-list.tsx` inside the `components/animata/list` directory. + +```bash +mkdir -p components/animata/list && touch components/animata/list/transaction-list.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/list/transaction-list.tsx + +``` + + + +## Credits + +Built by [Prince Yadav](https://github.com/prince981620) From 85058d83eaf42e64990963985b1a86c117eaeb0d Mon Sep 17 00:00:00 2001 From: Vishal <145477397+Pikachu-345@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:40:45 +0530 Subject: [PATCH 30/42] feat) add the fund widget (#359) --- animata/widget/fund-widget.stories.tsx | 25 ++++ animata/widget/fund-widget.tsx | 180 +++++++++++++++++++++++++ content/docs/widget/fund-widget.mdx | 38 ++++++ 3 files changed, 243 insertions(+) create mode 100644 animata/widget/fund-widget.stories.tsx create mode 100644 animata/widget/fund-widget.tsx create mode 100644 content/docs/widget/fund-widget.mdx diff --git a/animata/widget/fund-widget.stories.tsx b/animata/widget/fund-widget.stories.tsx new file mode 100644 index 00000000..78785783 --- /dev/null +++ b/animata/widget/fund-widget.stories.tsx @@ -0,0 +1,25 @@ +import FundWidget from "@/animata/widget/fund-widget"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Widget/Fund Widget", + component: FundWidget, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + funds: [ + { value: "2.7Cr", change: 12, label: "Stocks" }, + { value: "3.5Cr", change: -8, label: "Funds" }, + { value: "1.2Cr", change: 6, label: "Deposits" }, + ], + }, +}; diff --git a/animata/widget/fund-widget.tsx b/animata/widget/fund-widget.tsx new file mode 100644 index 00000000..f64280ad --- /dev/null +++ b/animata/widget/fund-widget.tsx @@ -0,0 +1,180 @@ +import { useEffect, useState } from "react"; +import { AnimatePresence, motion, PanInfo } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +type Fund = { + value: string; + change: number; + label: string; +}; + +interface FundWidgetProps { + /** + * The array which contains all the funds with their value, changes, and label. + */ + funds: Fund[]; + + /** + * Class name for the background element. + */ + backgroundClassName?: string; + + /** + * Class name for the container element. + */ + containerClassName?: string; +} + +export default function FundWidget({ + funds = [ + { value: "2.7Cr", change: 12, label: "Stocks" }, + { value: "3.5Cr", change: -8, label: "Funds" }, + { value: "1.2Cr", change: 6, label: "Deposits" }, + ], + backgroundClassName, + containerClassName, +}: FundWidgetProps) { + const len = funds.length; + + const [[activeDiv, direction], setDirection] = useState([0, 0]); + const [dragDistance, setDragDistance] = useState(0); + + // Reset dragDistance after the drag ends to remove blur/rotate effects + useEffect(() => { + if (dragDistance !== 0) { + const timer = setTimeout(() => { + setDragDistance(0); + }, 500); + return () => clearTimeout(timer); + } + }, [activeDiv, dragDistance]); + + const sliderVariants = { + incoming: (direction: number) => ({ + y: direction > 0 ? "100%" : "-100%", + scale: 1.0, + opacity: 0, + }), + active: { y: 0, scale: 1, opacity: 1 }, + exit: (direction: number) => ({ + y: direction > 0 ? "100%" : "-100%", + scale: 1, + opacity: 0.2, + }), + }; + + const sliderTransition = { + duration: 0.5, + ease: [0.56, 0.03, 0.12, 1.04], + }; + + const swipeToAction = (direction: number) => { + const newDiv = activeDiv + direction; + if (newDiv < 0 || newDiv >= len) return; + + setDirection([newDiv, direction]); + }; + + const draghandler = (dragInfo: PanInfo) => { + const dragDistanceY = dragInfo.offset.y; + const swipeThreshold = 20; + + // Only swipe down if not at the first div (activeDiv !== 0) + if (dragDistanceY > swipeThreshold) { + swipeToAction(-1); + } + // Only swipe up if not at the last div (activeDiv !== len - 1) + else if (dragDistanceY < -swipeThreshold) { + swipeToAction(1); + } + + setDragDistance(0); + }; + + const skipToDiv = (divId: number) => { + let changeDirection = 1; + if (divId > activeDiv) { + changeDirection = 1; + } else if (divId < activeDiv) { + changeDirection = -1; + } + setDirection([divId, changeDirection]); + }; + + const blurValue = Math.min(Math.abs(dragDistance / 20), 10); + const rotateYValue = Math.min(dragDistance / 10, 15); + + return ( + <> +
+
+ +
+
+ draghandler(dragInfo)} + onDrag={(event, info) => setDragDistance(info.offset.y)} + style={{ + filter: `blur(${blurValue}px)`, + transform: `rotateY(${rotateYValue}deg)`, + }} + > +
+

{funds[activeDiv].value}

+ {funds[activeDiv].change < 0 ? ( +

+ {funds[activeDiv].change}% ↓ +

+ ) : ( +

+ {funds[activeDiv].change}% ↑ +

+ )} +

+ {funds[activeDiv].label} +

+
+
+
+ {funds.map((_, index) => { + return ( + skipToDiv(index)} + > + ); + })} +
+
+
+
+
+
+ + ); +} diff --git a/content/docs/widget/fund-widget.mdx b/content/docs/widget/fund-widget.mdx new file mode 100644 index 00000000..1eedc78c --- /dev/null +++ b/content/docs/widget/fund-widget.mdx @@ -0,0 +1,38 @@ +--- +title: Fund Widget +description: This component is a financial dashboard-like UI element for showing the current status of the funds. +author: i_v1shal_ +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `fund-widget.tsx` inside the `components/animata/widget` directory. + +```bash +mkdir -p components/animata/widget && touch components/animata/widget/fund-widget.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/widget/fund-widget.tsx + +``` + + + +## Credits + +Built by [Vishal](https://github.com/Pikachu-345) From f0323f5911df1b4cf3c344431c7c3956f5c5be07 Mon Sep 17 00:00:00 2001 From: Ayush Gupta <72538480+wolfofdalalst@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:05:03 +0530 Subject: [PATCH 31/42] feat: add sliding arrow button component (#369) --- animata/button/slide-arrow-button.stories.tsx | 22 +++++++++++ animata/button/slide-arrow-button.tsx | 33 ++++++++++++++++ content/docs/button/index.mdx | 9 +++++ content/docs/button/slide-arrow-button.mdx | 39 +++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 animata/button/slide-arrow-button.stories.tsx create mode 100644 animata/button/slide-arrow-button.tsx create mode 100644 content/docs/button/slide-arrow-button.mdx diff --git a/animata/button/slide-arrow-button.stories.tsx b/animata/button/slide-arrow-button.stories.tsx new file mode 100644 index 00000000..b5681a70 --- /dev/null +++ b/animata/button/slide-arrow-button.stories.tsx @@ -0,0 +1,22 @@ +import SlideArrowButton from "@/animata/button/slide-arrow-button"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Button/Slide Arrow Button", + component: SlideArrowButton, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + text: "Get Started", + primaryColor: "#6f3cff", + }, +}; diff --git a/animata/button/slide-arrow-button.tsx b/animata/button/slide-arrow-button.tsx new file mode 100644 index 00000000..5ccac720 --- /dev/null +++ b/animata/button/slide-arrow-button.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { ArrowRight } from "lucide-react"; + +interface SlideArrowButtonProps extends React.ButtonHTMLAttributes { + text?: string; + primaryColor?: string; +} + +export default function SlideArrowButton({ + text = "Get Started", + primaryColor = "#6f3cff", + className = "", + ...props +}: SlideArrowButtonProps) { + return ( + + ); +} diff --git a/content/docs/button/index.mdx b/content/docs/button/index.mdx index e52fbc2c..472a78e4 100644 --- a/content/docs/button/index.mdx +++ b/content/docs/button/index.mdx @@ -13,6 +13,7 @@ import ExternalLinkButton from "@/animata/button/external-link-button"; import AlgoliaButtonWhite from "@/animata/button/algolia-white-button"; import AlgoliaButtonBlue from "@/animata/button/algolia-blue-button"; import Duolingo from "@/animata/button/duolingo"; +import SlideArrowButton from "@/animata/button/slide-arrow-button"; @@ -100,4 +101,12 @@ import Duolingo from "@/animata/button/duolingo"; ```` + + + + ```tsx file=/animata/button/slide-arrow-button.tsx copyId="slide-arrow-button" + + ```` + + diff --git a/content/docs/button/slide-arrow-button.mdx b/content/docs/button/slide-arrow-button.mdx new file mode 100644 index 00000000..391ec2c8 --- /dev/null +++ b/content/docs/button/slide-arrow-button.mdx @@ -0,0 +1,39 @@ +--- +title: Slide Arrow Button +description: An arrow button which slides on hover +author: wolfofdalalst +labels: ["requires interaction", "hover"] +--- + + + +## Installation + + +Install dependencies + +```bash +npm install lucide-react +``` + +Run the following command + +It will create a new file `slide-arrow-button.tsx` inside the `components/animata/button` directory. + +```bash +mkdir -p components/animata/button && touch components/animata/button/slide-arrow-button.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/button/slide-arrow-button.tsx + +``` + + + +## Credits + +Built by [Ayush Gupta](https://github.com/wolfofdalalst) From 20b049dc56538e46f964982dab92226c1ac806a7 Mon Sep 17 00:00:00 2001 From: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:32:43 +0530 Subject: [PATCH 32/42] fix: add tools hover interaction icon component (#357) --- animata/icon/hover-interaction.stories.tsx | 22 ++++ animata/icon/hover-interaction.tsx | 133 +++++++++++++++++++++ content/docs/icon/hover-interaction.mdx | 49 ++++++++ 3 files changed, 204 insertions(+) create mode 100644 animata/icon/hover-interaction.stories.tsx create mode 100644 animata/icon/hover-interaction.tsx create mode 100644 content/docs/icon/hover-interaction.mdx diff --git a/animata/icon/hover-interaction.stories.tsx b/animata/icon/hover-interaction.stories.tsx new file mode 100644 index 00000000..9e7bbae2 --- /dev/null +++ b/animata/icon/hover-interaction.stories.tsx @@ -0,0 +1,22 @@ +import HoverInteraction from "@/animata/icon/hover-interaction"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Icon/Hover Interaction", + component: HoverInteraction, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + title: "instagram", + size: "4", + }, +}; diff --git a/animata/icon/hover-interaction.tsx b/animata/icon/hover-interaction.tsx new file mode 100644 index 00000000..8439489c --- /dev/null +++ b/animata/icon/hover-interaction.tsx @@ -0,0 +1,133 @@ +"use client"; + +import React, { ElementType, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; + +// default imports +import { + FigmaLogoIcon, + FramerLogoIcon, + GitHubLogoIcon, + InstagramLogoIcon, + LinkedInLogoIcon, + SquareIcon, + TwitterLogoIcon, +} from "@radix-ui/react-icons"; + +type IconSize = "1" | "2" | "3" | "4"; // source: https://www.radix-ui.com/themes/docs/components/icon-button + +interface IconHoverProps { + title: string; // default is Square + size?: IconSize; // default is 4 +} + +const sizeClasses: Record = { + "1": "w-6 h-6", + "2": "w-7 h-7", + "3": "w-8 h-8", + "4": "w-10 h-10", +}; + +const textSizeClasses: Record = { + "1": "text-sm", + "2": "text-base", + "3": "text-lg", + "4": "text-xl", +}; + +const getIconForTitle = (title: string) => { + const lowercaseTitle = title.toLowerCase().trim(); + const iconMap: { [key: string]: ElementType } = { + framer: FramerLogoIcon, + "twitter/x": TwitterLogoIcon, + instagram: InstagramLogoIcon, + linkedin: LinkedInLogoIcon, + github: GitHubLogoIcon, + figma: FigmaLogoIcon + }; + + // SquareIcon as default + return (iconMap[lowercaseTitle] as ElementType) || SquareIcon; +}; + +// twitter -> Twitter, Twitter -> Twitter, twitter/x -> Twitter/X, Twitter/x -> Twitter/X +const capitalizeWithSlash = (str: string) => { + return str + .split("/") + .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()) + .join("/"); +}; + +export default function HoverInteraction({ + // default values + title = "Square", + size = "4", +}: IconHoverProps) { + const [isHovered, setIsHovered] = useState(false); + const DynamicIcon = getIconForTitle(title); + + const sizeClass = sizeClasses[size]; + const textSizeClass = textSizeClasses[size]; + + const formattedTitle = capitalizeWithSlash(title); + + const logoVariants = { + hidden: { + opacity: 0, + y: 0, + scale: 0.5, + rotate: 100, + }, + visible: { + opacity: 1, + y: 13, + scale: 1, + rotate: 0, + transition: { + type: "spring", + stiffness: 100, + damping: 15, + duration: 0.3, + }, + }, + exit: { + opacity: 0, + y: 13, + scale: 0.5, + rotate: 100, + transition: { duration: 0.3 }, + }, + }; + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + {formattedTitle} + + + {isHovered && ( + + + + )} + + + ); +} diff --git a/content/docs/icon/hover-interaction.mdx b/content/docs/icon/hover-interaction.mdx new file mode 100644 index 00000000..c966abd9 --- /dev/null +++ b/content/docs/icon/hover-interaction.mdx @@ -0,0 +1,49 @@ +--- +title: Hover Interaction +description: This is a component which shows icon based on text hovered by user. By default it has alreay imported Twitter/x, Framer, Figma, Instagram, GitHub, LinkedIn, and it shows Square-Box icon by default it user tries to get other icons without importing. Importing steps are provided below. +author: MEbandhan +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion @radix-ui/react-icons +``` + +Run the following command + +It will create a new file `hover-interaction.tsx` inside the `components/animata/icon` directory. + +```bash +mkdir -p components/animata/icon && touch components/animata/icon/hover-interaction.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```jsx file=/animata/icon/hover-interaction.tsx + +``` + + + +Use the component with default/non-default interacitons + +defaults are mentioned in the description + +**Other than defaults** + +1. Go to the [radix-ui icon page](https://www.radix-ui.com/icons) +2. Search for the icon. +3. Import the icon in the copied code. If the icon name is Twitter Logo, the import will be TwitterLogoIcon +4. Add switch case for that logo in **lower case**. + +## Credits + +Built by [Bandhan Majumder](https://github.com/bandhan-majumder) From f74de401e7bb7f6881e679acca1a6d425c2cb7e4 Mon Sep 17 00:00:00 2001 From: Mohit ahlawat <65100859+mohitahlawat2001@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:12:20 +0530 Subject: [PATCH 33/42] feat: add music stack interaction widget (#314) --- .../ISSUE_TEMPLATE/new-component-ticket.md | 10 +- .../music-stack-interaction.stories.tsx | 53 ++++++++ animata/widget/music-stack-interaction.tsx | 126 ++++++++++++++++++ .../docs/widget/music-stack-interaction.mdx | 38 ++++++ 4 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 animata/widget/music-stack-interaction.stories.tsx create mode 100644 animata/widget/music-stack-interaction.tsx create mode 100644 content/docs/widget/music-stack-interaction.mdx diff --git a/.github/ISSUE_TEMPLATE/new-component-ticket.md b/.github/ISSUE_TEMPLATE/new-component-ticket.md index 399259a1..124d44f5 100644 --- a/.github/ISSUE_TEMPLATE/new-component-ticket.md +++ b/.github/ISSUE_TEMPLATE/new-component-ticket.md @@ -3,28 +3,28 @@ name: New Component Ticket [HACKTOBERFEST] about: Create a new animated component task for Hacktoberfest title: Component Name labels: hacktoberfest -assignees: '' - +assignees: "" --- ### Description + Create the component as shown in the video attached below. ### Animation Preview + - Screenshot/Video: [Upload file here] - > [!NOTE] > Please refer to the **Animata contributing guidelines** available [here](https://www.animata.design/docs/contributing) for rules on how to contribute to the project. Let us know in the comments if you’re working on this issue, and feel free to ask any questions! ### Requirements + 1. Create a new animated component that matches the provided design. 2. Add example usage of the component in the documentation or storybook. 3. **Add credits** for any resources, references, or assets used, as specified in the **Additional Resources** section. -

Guidelines & Best Practices

@@ -38,12 +38,14 @@ Create the component as shown in the video attached below. > [!IMPORTANT] > To ensure more contributors have the opportunity to participate, we kindly request that: +> > - Each contributor submits only **one pull request** related to this issue. > - If you're interested in working on this issue, please comment below. We will assign the issue on a **first-come, first-served basis**. --- ### Additional Resources + --- diff --git a/animata/widget/music-stack-interaction.stories.tsx b/animata/widget/music-stack-interaction.stories.tsx new file mode 100644 index 00000000..39319b3c --- /dev/null +++ b/animata/widget/music-stack-interaction.stories.tsx @@ -0,0 +1,53 @@ +import MusicStackInteraction from "@/animata/widget/music-stack-interaction"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Widget/Music Stack Interaction", + component: MusicStackInteraction, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + albums: [ + { + id: 1, + title: "The Dark Side of the Moon", + artist: "Pink Floyd", + cover: "https://images.unsplash.com/photo-1569424758782-cba94e6165fd", + }, + { + id: 2, + title: "Abbey Road", + artist: "The Beatles", + cover: "https://images.unsplash.com/photo-1516410529446-2c777cb7366d", + }, + { + id: 3, + title: "Thriller", + artist: "Michael Jackson", + cover: "https://images.unsplash.com/photo-1559406041-c7d2b2e98690", + }, + { + id: 4, + title: "The Wall", + artist: "Pink Floyd", + cover: "https://images.unsplash.com/photo-1528822234686-beae35cab346", + }, + ], + }, + render: (args) => { + return ( +
+ +
+ ); + }, +}; diff --git a/animata/widget/music-stack-interaction.tsx b/animata/widget/music-stack-interaction.tsx new file mode 100644 index 00000000..f32c6ab9 --- /dev/null +++ b/animata/widget/music-stack-interaction.tsx @@ -0,0 +1,126 @@ +import React, { useState } from "react"; +import { motion } from "framer-motion"; +import { Layers, LayoutGrid } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const carouselStyles = { + perspective: "1000px", + overflow: "hidden", +}; + +const carouselInnerStyles: React.CSSProperties = { + display: "flex", + transformStyle: "preserve-3d", + transition: "transform 2s", + justifyContent: "center", + alignItems: "center", + height: "100%", +}; + +const carouselItemStyles: React.CSSProperties = { + minWidth: "200px", + marginLeft: "-180px", + transform: "rotateY(15deg) translateZ(300px) translateX(-50px)", + backfaceVisibility: "hidden", + boxShadow: "0 4px 8px rgba(0, 0, 0, 0.2)", + transition: "transform 2s, box-shadow 2s", +}; + +const carouselItemFirstChildStyles: React.CSSProperties = { + minWidth: "200px", + marginLeft: "20px", + transform: "rotateY(5deg) translateZ(300px) translateX(0)", + backfaceVisibility: "hidden", + boxShadow: "0 4px 8px rgba(0, 0, 0, 0.2)", + transition: "transform 2s, box-shadow 2s", +}; + +interface albumsProps { + /* + * Array of album objects + */ + albums: { + id: number; + title: string; + artist: string; + cover: string; + }[]; +} + +export default function MusicStackInteraction({ albums }: albumsProps) { + const [isGridView, setIsGridView] = useState(true); + + const handleToggleView = () => { + setIsGridView(!isGridView); + }; + + return ( +
+ + + {albums.map((album, index) => ( +
+ + + + {album.title} + + + {album.artist} + + +
+ ))} +
+
+ + +
+
+ +
+
+ +
+
+
+
+ ); +} diff --git a/content/docs/widget/music-stack-interaction.mdx b/content/docs/widget/music-stack-interaction.mdx new file mode 100644 index 00000000..0f406e84 --- /dev/null +++ b/content/docs/widget/music-stack-interaction.mdx @@ -0,0 +1,38 @@ +--- +title: Music Stack Interaction +description: widget for Music stack and unstacking +author: Mahlawat2001 +--- + + + +## Installation + + +Install dependencies + +```bash +npm install lucide-react framer-motion +``` + +Run the following command + +It will create a new file `music-stack-interaction.tsx` inside the `components/animata/widget` directory. + +```bash +mkdir -p components/animata/widget && touch components/animata/widget/music-stack-interaction.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/widget/music-stack-interaction.tsx + +``` + + + +## Credits + +Built by [Mohit Ahlawat](https://github.com/mohitahlawat2001) From ecb313a553c3fb81201f24024ee9bf86bcd32096 Mon Sep 17 00:00:00 2001 From: Arjun Vijay Prakash <137415649+ArjunCodess@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:49:06 +0530 Subject: [PATCH 34/42] feat: implement team clock component (#368) --- animata/widget/team-clock.stories.tsx | 47 +++ animata/widget/team-clock.tsx | 469 ++++++++++++++++++++++++++ content/docs/widget/team-clock.mdx | 38 +++ 3 files changed, 554 insertions(+) create mode 100644 animata/widget/team-clock.stories.tsx create mode 100644 animata/widget/team-clock.tsx create mode 100644 content/docs/widget/team-clock.mdx diff --git a/animata/widget/team-clock.stories.tsx b/animata/widget/team-clock.stories.tsx new file mode 100644 index 00000000..1fe6feb4 --- /dev/null +++ b/animata/widget/team-clock.stories.tsx @@ -0,0 +1,47 @@ +import TeamClock from "@/animata/widget/team-clock"; +import { Meta, StoryObj } from "@storybook/react"; + +const testTeamClockProps = { + users: [ + { + name: "User 1", + city: "New York", + country: "USA", + timeDifference: "-5", + pfp: "https://avatar.vercel.sh/1", + }, + { + name: "User 2", + city: "London", + country: "UK", + timeDifference: "+5", + pfp: "https://avatar.vercel.sh/2", + }, + ], + clockSize: 250, + animationDuration: 0.3, + accentColor: "#34D399", + backgroundColor: "#ECFDF5", + textColor: "#065F46", + borderColor: "#D1FAE5", + hoverBackgroundColor: "#D1FAE5", + showSeconds: true, + use24HourFormat: false, +}; + +const meta = { + title: "Widget/Team Clock", + component: TeamClock, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: testTeamClockProps, +}; diff --git a/animata/widget/team-clock.tsx b/animata/widget/team-clock.tsx new file mode 100644 index 00000000..15d4e448 --- /dev/null +++ b/animata/widget/team-clock.tsx @@ -0,0 +1,469 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +interface TeamClockProps { + users: Array<{ + name: string; + city: string; + country: string; + timeDifference: string; + pfp: string; + }>; + clockSize: number; + animationDuration: number; + accentColor: string; + backgroundColor: string; + textColor: string; + borderColor: string; + hoverBackgroundColor: string; + showSeconds: boolean; + use24HourFormat: boolean; +} + +export default function TeamClock({ + users, + clockSize, + animationDuration, + accentColor = "#000", + backgroundColor = "#ffffff", + textColor = "#1f2937", + borderColor = "#e5e7eb", + hoverBackgroundColor = "#f3f4f6", + showSeconds = false, + use24HourFormat = false, +}: TeamClockProps) { + const [isExpanded, setIsExpanded] = useState(false); + const [angle, setAngle] = useState(0); + const [currentTime, setCurrentTime] = useState(new Date()); + const [isMobile, setIsMobile] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [hoveredUser, setHoveredUser] = useState(null); + + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < 768); + }; + + checkMobile(); + window.addEventListener("resize", checkMobile); + + return () => window.removeEventListener("resize", checkMobile); + }, []); + + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); + + return () => clearInterval(timer); + }, []); + + const handleToggle = () => { + setIsExpanded(!isExpanded); + }; + + const handleUserSelect = (userName: string, timeDifference: string) => { + if (selectedUser === userName) { + setSelectedUser(null); + setAngle(0); + } else { + setSelectedUser(userName); + setAngle(parseInt(timeDifference) * 30); + } + }; + + const handleUserHover = (userName: string | null, timeDifference: string | null) => { + if (userName && timeDifference) { + setHoveredUser(userName); + setAngle(parseInt(timeDifference) * 30); + } else { + setHoveredUser(null); + if (!selectedUser) { + setAngle(0); + } else { + const selectedUserData = users.find((user) => user.name === selectedUser); + if (selectedUserData) { + setAngle(parseInt(selectedUserData.timeDifference) * 30); + } + } + } + }; + + return ( + <> + +
+
+

Team

+ {!isMobile && ( + + )} +
+
+ +
+
+ {currentTime.toLocaleTimeString([], { + hour: use24HourFormat ? "2-digit" : "numeric", + minute: "2-digit", + second: showSeconds ? "2-digit" : undefined, + hour12: !use24HourFormat, + })} +
+
+ + {/* Add vertical dividing line */} + {isExpanded && !isMobile && ( +
+ )} + + + {(isExpanded || isMobile) && ( + + + {users.map((user, index) => ( + + ))} + + + )} + +
+ + ); +} + +interface ClockProps { + angle: number; + pressed: boolean; + size: number; + animationDuration: number; + accentColor: string; + textColor: string; + backgroundColor: string; +} + +function Clock({ + angle, + size, + animationDuration, + accentColor, + textColor, + backgroundColor, +}: ClockProps) { + const [time, setTime] = useState(null); + const gradientRef = useRef(null); + + useEffect(() => { + setTime(new Date()); + const interval = setInterval(() => { + setTime(new Date()); + }, 1000); + + return () => clearInterval(interval); + }, []); + + useEffect(() => { + const isClockwise = angle > 0; + if (gradientRef.current && time) { + const hours = time.getHours(); + const minutes = time.getMinutes(); + const hourDegrees = (hours % 12) * 30 + minutes * 0.5; + gradientRef.current.style.background = isClockwise + ? `conic-gradient(from ${hourDegrees}deg, rgba(0,200,0,0.5), rgba(0,200,0,0) ${angle}deg)` + : `conic-gradient(from ${ + hourDegrees + angle + }deg, rgba(200,0,0,0.3), rgba(200,0,0,0.0) ${-angle}deg)`; + } + }, [angle, time]); + + if (!time) { + return null; + } + + const hours = time.getHours(); + const minutes = time.getMinutes(); + const seconds = time.getSeconds(); + const hourDegrees = (hours % 12) * 30 + minutes * 0.5; + const minuteDegrees = minutes * 6; + const secondDegrees = seconds * 6; + + return ( +
+
+ {Array.from({ length: 12 }, (_, i) => ( +
+
+
+ ))} + + + + +
+
+
+ ); +} + +interface ListElementProp { + name: string; + city: string; + country: string; + timeDifference: string; + pfp: string; + onSelect: (name: string, timeDifference: string) => void; + onHover: (name: string | null, timeDifference: string | null) => void; + isSelected: boolean; + isHovered: boolean; + currentTime: Date; + animationDuration: number; + accentColor: string; + textColor: string; + hoverBackgroundColor: string; +} + +function ListElement(props: ListElementProp) { + const [isHovered, setIsHovered] = useState(false); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < 768); + }; + + checkMobile(); + window.addEventListener("resize", checkMobile); + + return () => window.removeEventListener("resize", checkMobile); + }, []); + + const handleEnter = () => { + if (!isMobile) { + setIsHovered(true); + props.onHover(props.name, props.timeDifference); + } + }; + + const handleLeave = () => { + if (!isMobile) { + setIsHovered(false); + props.onHover(null, null); + } + }; + + const handleClick = () => { + props.onSelect(props.name, props.timeDifference); + }; + + const localTime = useMemo(() => { + const hourDifference = parseInt(props.timeDifference); + const newTime = new Date(props.currentTime); + newTime.setHours(newTime.getHours() + hourDifference); + return newTime.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); + }, [props.currentTime, props.timeDifference]); + + return ( + + {props.name} +
+
+ {props.name} +
+ + {!props.isSelected && !isHovered && ( + + {localTime} + + )} + {(props.isSelected || isHovered) && ( + + {parseInt(props.timeDifference) === 0 + ? "+ 0 Hours" + : `${props.timeDifference} Hours`} + + )} + +
+
+
{`${props.city}, ${props.country}`}
+
+
+ ); +} + +type ToggleButtonProps = { + onClick: (isToggled: boolean) => void; + accentColor: string; + textColor: string; +}; + +function ToggleButton({ onClick, accentColor, textColor, ...props }: ToggleButtonProps) { + const [isToggled, setIsToggled] = useState(false); + + const handleClick = () => { + setIsToggled(!isToggled); + onClick(!isToggled); + }; + + return ( + + + {isToggled ? "Hide List" : "Show List"} + +
+ + +
+
+ ); +} diff --git a/content/docs/widget/team-clock.mdx b/content/docs/widget/team-clock.mdx new file mode 100644 index 00000000..c463c802 --- /dev/null +++ b/content/docs/widget/team-clock.mdx @@ -0,0 +1,38 @@ +--- +title: Team Clock +description: A customizable, animated clock displaying multiple time zones for remote teams. +author: arjuncodess +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `team-clock.tsx` inside the `components/animata/widget` directory. + +```bash +mkdir -p components/animata/widget && touch components/animata/widget/team-clock.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/widget/team-clock.tsx + +``` + + + +## Credits + +Built by [Arjun Vijay Prakash](https://github.com/arjuncodess). From 58a00e5fde4da7dcdc5998b100818b3e4e2e627d Mon Sep 17 00:00:00 2001 From: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:09:50 +0530 Subject: [PATCH 35/42] feat: add content scan component (#374) --- .../feature-cards/content-scan.stories.tsx | 34 +++ animata/feature-cards/content-scan.tsx | 249 ++++++++++++++++++ content/docs/feature-cards/content-scan.mdx | 54 ++++ tailwind.config.ts | 3 +- 4 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 animata/feature-cards/content-scan.stories.tsx create mode 100644 animata/feature-cards/content-scan.tsx create mode 100644 content/docs/feature-cards/content-scan.mdx diff --git a/animata/feature-cards/content-scan.stories.tsx b/animata/feature-cards/content-scan.stories.tsx new file mode 100644 index 00000000..97b5cdd8 --- /dev/null +++ b/animata/feature-cards/content-scan.stories.tsx @@ -0,0 +1,34 @@ +import ContentScan from "@/animata/feature-cards/content-scan"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Feature Cards/Content Scan", + component: ContentScan, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + content: + "Ten years ago there were only five private prisons in the country, with a population of 2,000 inmates; now, 30 there are 100, with 62,000 inmates. It is expected that by the coming decade, the number will hit 360,000 according to reports. The private contracting of prisoners for work fosters.", + highlightWords: [ + "Ten years ago", + "only five private prisons", + "now, 30", + "62,000", + "are 100", + "62\\,000 inmates", + "expected", + "coming decade", + ], + scanDuration: 4, + reverseDuration: 1, + }, +}; diff --git a/animata/feature-cards/content-scan.tsx b/animata/feature-cards/content-scan.tsx new file mode 100644 index 00000000..041cef70 --- /dev/null +++ b/animata/feature-cards/content-scan.tsx @@ -0,0 +1,249 @@ +import React, { useEffect, useRef, useState } from "react"; +import { motion, useAnimation } from "framer-motion"; + +interface ContentScannerProps { + content: string; + highlightWords: string[]; + scanDuration?: number; + reverseDuration?: number; +} + +const ContentScanner: React.FC = ({ + content, + highlightWords, + scanDuration = 3, + reverseDuration = 1, +}) => { + const [scanning, setScanning] = useState(false); + const [aiProbability, setAiProbability] = useState(0); + const containerRef = useRef(null); + const scannerRef = useRef(null); + const contentRef = useRef(null); + const scannerAnimation = useAnimation(); + const [highlightedWords, setHighlightedWords] = useState([]); + const [animationPhase, setAnimationPhase] = useState<"idle" | "forward" | "paused" | "reverse">( + "idle", + ); + + const startScanning = async () => { + if (scanning || !containerRef.current) return; + + setScanning(true); + setAiProbability(0); + setHighlightedWords([]); + setAnimationPhase("forward"); + + const containerWidth = containerRef.current.offsetWidth - 110; + + // Forward scan + await scannerAnimation.start({ + x: containerWidth, + transition: { duration: scanDuration, ease: "linear" }, + }); + + setAnimationPhase("paused"); + + // Pause + await new Promise((resolve) => setTimeout(resolve, 200)); + + setAnimationPhase("reverse"); + + // Backward scan + await scannerAnimation.start({ + x: "-87%", + transition: { duration: reverseDuration, ease: "linear" }, + }); + + setScanning(false); + setHighlightedWords([]); + setAnimationPhase("idle"); + }; + + useEffect(() => { + let interval: NodeJS.Timeout; + let pauseTimeout: NodeJS.Timeout; + + if (animationPhase === "forward") { + interval = setInterval( + () => { + setAiProbability((prev) => + Math.min(prev + 1, Math.floor(content.length / highlightWords.length)), + ); + }, + (scanDuration * 1000) / 55, + ); + } else if (animationPhase === "paused") { + //delay before starting reverse + pauseTimeout = setTimeout(() => { + setAnimationPhase("reverse"); + }, 200); + } else if (animationPhase === "reverse") { + interval = setInterval( + () => { + setAiProbability((prev) => Math.max(prev - 1, 0)); + }, + (reverseDuration * 1000) / 40, + ); + } + + return () => { + clearInterval(interval); + clearTimeout(pauseTimeout); + }; + }, [animationPhase, scanDuration, reverseDuration, content.length, highlightWords.length]); + + useEffect(() => { + if (scanning && scannerRef.current && contentRef.current) { + const updateHighlightedWords = () => { + const scannerRect = scannerRef.current!.getBoundingClientRect(); + const contentRect = contentRef.current!.getBoundingClientRect(); + const scannerRightEdge = scannerRect.right - contentRect.left; + + const newHighlightedWords = highlightWords.filter((phrase) => { + const phraseElements = contentRef.current!.querySelectorAll(`[data-phrase="${phrase}"]`); + return Array.from(phraseElements).some((element) => { + const elementRect = element.getBoundingClientRect(); + const elementRightEdge = elementRect.right - contentRect.left; + return elementRightEdge <= scannerRightEdge; + }); + }); + + setHighlightedWords(newHighlightedWords); + }; + + const animationFrame = requestAnimationFrame(function animate() { + updateHighlightedWords(); + if (scanning) { + requestAnimationFrame(animate); + } + }); + + return () => cancelAnimationFrame(animationFrame); + } + }, [scanning, highlightWords]); + + const highlightText = (text: string) => { + let result = text; + highlightWords.forEach((phrase) => { + const regex = new RegExp(`(${phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi"); + result = result.replace( + regex, + (match) => + `${match}`, + ); + }); + return result; + }; + + const renderAiProbability = (probability: number) => { + const digits = probability.toString().padStart(2, "0").split("").map(Number); + + const digitVariants = { + initial: { y: 0 }, + animate: { + y: [0, -30, 0], + transition: { + repeat: Infinity, + repeatType: "loop" as const, + duration: 1.5, + ease: "easeInOut", + }, + }, + }; + + return ( + <> +
+
+ {digits.map((digit, index) => ( + + {[digit, (digit + 1) % 10, (digit + 2) % 10].map((n, i) => ( + + {n} + + ))} + + ))} +
+
+ + ); + }; + + return ( +
+
+

Free AI Content Detector

+

Brand new content in seconds. Remove any form of plagiarism

+
+ + +
+ +
+
+
+
+ + + +
+
+ +
+
+
+ {aiProbability > 0 && renderAiProbability(Math.floor(aiProbability))} + % + AI Content Probability +
+
+
+ + +
+ ); +}; + +export default ContentScanner; diff --git a/content/docs/feature-cards/content-scan.mdx b/content/docs/feature-cards/content-scan.mdx new file mode 100644 index 00000000..550c2406 --- /dev/null +++ b/content/docs/feature-cards/content-scan.mdx @@ -0,0 +1,54 @@ +--- +title: Content Scan +description: A scanning component to highlight detected words to predict AI content probability +author: MEbandhan +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Update `tailwind.config.js` + +Add the following to your tailwind.config.js file. + +```json +module.exports = { + theme: { + extend: { + backgroundImage: { + "custom-gradient": "linear-gradient(to left, rgba(136,127,242,0.7) 0%, transparent 100%)", + }, + }, + } +} +``` + +Run the following command + +It will create a new file `content-scan.tsx` inside the `components/animata/feature-cards` directory. + +```bash +mkdir -p components/animata/feature-cards && touch components/animata/feature-cards/content-scan.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/feature-cards/content-scan.tsx + +``` + + + +## Credits + +Built by [Bandhan Majumder](https://github.com/bandhan-majumder) diff --git a/tailwind.config.ts b/tailwind.config.ts index d173bb31..930d8ab5 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -15,7 +15,8 @@ const config = { extend: { backgroundImage: { striped: - "repeating-linear-gradient(45deg, #3B3A3D, #3B3A3D 5px, transparent 5px, transparent 20px)", + "repeating-linear-gradient(45deg, #3B3A3D 0px, #3B3A3D 5px, transparent 5px, transparent 20px)", + "custom-gradient": "linear-gradient(to left, rgba(136,127,242,0.7) 0%, transparent 100%)", }, colors: { border: "hsl(var(--border))", From 0dd1f09c725c7c196acfff8db2aeff0eb2be8791 Mon Sep 17 00:00:00 2001 From: Abhinandan <93651229+AE-Hertz@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:23:01 +0530 Subject: [PATCH 36/42] feat: add notice card component (#356) --- animata/card/notice-card.stories.tsx | 35 ++++++++ animata/card/notice-card.tsx | 114 +++++++++++++++++++++++++++ content/docs/card/notice-card.mdx | 38 +++++++++ 3 files changed, 187 insertions(+) create mode 100644 animata/card/notice-card.stories.tsx create mode 100644 animata/card/notice-card.tsx create mode 100644 content/docs/card/notice-card.mdx diff --git a/animata/card/notice-card.stories.tsx b/animata/card/notice-card.stories.tsx new file mode 100644 index 00000000..fcb2b02d --- /dev/null +++ b/animata/card/notice-card.stories.tsx @@ -0,0 +1,35 @@ +import NoticeCard from "@/animata/card/notice-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Notice Card", + component: NoticeCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + acceptText: { control: "text" }, + title: { control: "text" }, + description: { control: "text" }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + acceptText: "Accept", + title: "To your attention!", + description: + "Due to severe weather conditions, we will be closed from 11th to 14th of January.", + }, + render: (args) => { + return ( + <> + + + ); + }, +}; diff --git a/animata/card/notice-card.tsx b/animata/card/notice-card.tsx new file mode 100644 index 00000000..92d798d7 --- /dev/null +++ b/animata/card/notice-card.tsx @@ -0,0 +1,114 @@ +import { useState } from "react"; +import { motion } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +interface NoticeCardProps { + acceptText?: string; + title?: string; + description?: string; +} + +export default function NoticeCard({ + acceptText = "Accept", + title = "To your attention!", + description = "Due to severe weather conditions, we will be closed from 11th to 14th of January.", +}: NoticeCardProps) { + const [isAccepted, setIsAccepted] = useState(false); + + const handleClick = () => { + setIsAccepted(!isAccepted); + }; + + const bgClass = isAccepted + ? "bg-green-300" + : "bg-gradient-to-r from-slate-50 via-slate-50 to-green-100"; + + return ( +
+ {/* Outer container with breathing scaling effect */} +
+ {/* Mid-level static container */} + + + {/* Stable inner content */} +
+
+ {/* Icon */} +
+ + + +
+ + {/* Title */} +

{title}

+ + {/* Description */} +

{description}

+ + {/* Toggle Button */} +
+ {/* Toggle Handle */} +
+ + {/* Accept Text */} + + + + + {acceptText} + +
+
+
+
+
+ ); +} diff --git a/content/docs/card/notice-card.mdx b/content/docs/card/notice-card.mdx new file mode 100644 index 00000000..b6607ad1 --- /dev/null +++ b/content/docs/card/notice-card.mdx @@ -0,0 +1,38 @@ +--- +title: Notice Card +description: A card component for displaying important notices with an accept toggle. +labels: ["requires interaction", "toggle"] +author: AE-Hertz +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `notice-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/notice-card.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```tsx file=/animata/card/notice-card.tsx +``` + + + +## Credits + +Built by [Abhinandan](https://github.com/AE-Hertz) From 06b9cfc985f4230b2a1b6261812686287809dc2e Mon Sep 17 00:00:00 2001 From: Prince Kumar Yadav <69517192+prince981620@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:24:30 +0530 Subject: [PATCH 37/42] feat: add notification card (#362) Co-authored-by: prince981620@gmail.com --- animata/card/notification-card.stories.tsx | 44 +++++++++++++++ animata/card/notification-card.tsx | 66 ++++++++++++++++++++++ content/docs/card/notification-card.mdx | 38 +++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 animata/card/notification-card.stories.tsx create mode 100644 animata/card/notification-card.tsx create mode 100644 content/docs/card/notification-card.mdx diff --git a/animata/card/notification-card.stories.tsx b/animata/card/notification-card.stories.tsx new file mode 100644 index 00000000..ebb90c07 --- /dev/null +++ b/animata/card/notification-card.stories.tsx @@ -0,0 +1,44 @@ +import NotificationCard from "@/animata/card/notification-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Notification Card", + component: NotificationCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; +const RosettaLogo: React.FC = () => ( + + + + R + + +); + +export const Primary: Story = { + args: { + title: "Rosetta AI", + message: "Your dataset on renewable energy efficiency has just been cited by Dr. A. Scott", + RosettaLogo, + userInfo: { + name: "Dr. A. Scott", + title: "Senior Researcher", + avatar: "https://avatars.githubusercontent.com/u/17984567?v=4", + }, + }, +}; diff --git a/animata/card/notification-card.tsx b/animata/card/notification-card.tsx new file mode 100644 index 00000000..d0237ae2 --- /dev/null +++ b/animata/card/notification-card.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { motion } from "framer-motion"; + +interface UserInfoProps { + name: string; + title: string; + avatar: string; +} + +interface NotificationCardProps { + title: string; + message: string; + userInfo: UserInfoProps; + RosettaLogo: React.FC; +} + +const NotificationCard: React.FC = ({ + title, + message, + userInfo, + RosettaLogo, +}) => { + return ( +
+ {/* Notification Section */} + +
+ +

{title}

+
+
+

{message}

+
+
+ + {/* User Info Section */} +
+ +
+ {userInfo.name} +
+

{userInfo.name}

+

{userInfo.title}

+
+
+
+
+
+ ); +}; + +export default NotificationCard; diff --git a/content/docs/card/notification-card.mdx b/content/docs/card/notification-card.mdx new file mode 100644 index 00000000..f333d1ea --- /dev/null +++ b/content/docs/card/notification-card.mdx @@ -0,0 +1,38 @@ +--- +title: Notification Card +description: Simple notification like componenet that beautiully expands to show its contents. +author: m_jinprince +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `notification-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/notification-card.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/notification-card.tsx + +``` + + + +## Credits + +Built by [Prince Yadav](https://github.com/prince981620) From ccf0ce58642c72b812b3da39513ea2d5343daabe Mon Sep 17 00:00:00 2001 From: Prince Kumar Yadav <69517192+prince981620@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:29:48 +0530 Subject: [PATCH 38/42] feat: add comment-reply card (#347) Co-authored-by: prince981620@gmail.com --- animata/card/comment-reply-card.stories.tsx | 29 +++ animata/card/comment-reply-card.tsx | 220 ++++++++++++++++++++ content/docs/card/comment-reply-card.mdx | 38 ++++ 3 files changed, 287 insertions(+) create mode 100644 animata/card/comment-reply-card.stories.tsx create mode 100644 animata/card/comment-reply-card.tsx create mode 100644 content/docs/card/comment-reply-card.mdx diff --git a/animata/card/comment-reply-card.stories.tsx b/animata/card/comment-reply-card.stories.tsx new file mode 100644 index 00000000..e2a1736b --- /dev/null +++ b/animata/card/comment-reply-card.stories.tsx @@ -0,0 +1,29 @@ +import CommentReplyCard from "@/animata/card/comment-reply-card"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Card/Comment Reply Card", + component: CommentReplyCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const initialComments = [ + { + id: 1, + user: "Mike", + text: ["Is it just me, or is the font size on this page designed for ants?"], + time: "13 hours ago", + avatarColor: "#e8824b", + }, +]; + +export const Primary: Story = { + args: { initialComments }, +}; diff --git a/animata/card/comment-reply-card.tsx b/animata/card/comment-reply-card.tsx new file mode 100644 index 00000000..815f682a --- /dev/null +++ b/animata/card/comment-reply-card.tsx @@ -0,0 +1,220 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { Check, X } from "lucide-react"; + +interface Comment { + id: number; + user: string; + text: string[]; + time: string; + avatarColor: string; +} + +const containerVariants = { + hidden: { height: "auto" }, + visible: { height: "auto", transition: { duration: 0.5, ease: "easeInOut" } }, +}; + +const commentVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { opacity: 1, y: 0, transition: { duration: 0.3 } }, + exit: { opacity: 0, y: -20, transition: { duration: 0.3 } }, +}; + +export default function CommentReplyCard({ initialComments }: { initialComments: Comment[] }) { + const [comments, setComments] = useState([...initialComments]); + const [newComment, setNewComment] = useState(""); + const [isAnimating, setIsAnimating] = useState(false); + const containerRef = useRef(null); + const inputRef = useRef(null); + + const handleAddComment = () => { + if (newComment.trim() === "") return; + setIsAnimating(true); + const newCommentData: Comment = { + id: comments.length + 1, + user: "Emily", + text: [newComment], + time: "now", + avatarColor: "#e84b9d", + }; + + setTimeout(() => { + setComments((prevComments) => [...prevComments, newCommentData]); + setNewComment(""); + setTimeout(() => { + setIsAnimating(false); + if (inputRef.current) { + inputRef.current.focus(); + } + }, 300); + }, 300); + }; + + useEffect(() => { + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + }, [comments]); + + return ( +
+
+
+
+ + Comment +
+
+ + +
+
+ + {/* Comment Section */} + + + + {comments.map((comment) => ( + +
+
+ + + +
+
+ {comment.user} + {comment.time} +
+
+
+ {comment.text.map((text, textIndex) => ( + + {text} + + ))} +
+
+ ))} +
+
+
+
+ + {/* Input Box */} + + {!isAnimating && ( + +
+
+ + + +
+ setNewComment(e.target.value)} + onKeyPress={(e) => { + if (e.key === "Enter") { + handleAddComment(); + } + }} + /> + +
+
+ )} +
+
+ ); +} + +const CommentIcon: React.FC = () => { + return ( + + + + ); +}; diff --git a/content/docs/card/comment-reply-card.mdx b/content/docs/card/comment-reply-card.mdx new file mode 100644 index 00000000..6807aa07 --- /dev/null +++ b/content/docs/card/comment-reply-card.mdx @@ -0,0 +1,38 @@ +--- +title: Comment Reply Card +description: This new React component allows users to submit comments through an input field. Once a comment is submitted, it dynamically appears at the top of the comment list. +author: m_jinprince +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion lucide-react +``` + +Run the following command + +It will create a new file `comment-reply-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/comment-reply-card.tsx +``` + +Paste the code{" "} + +Open the newly created file and paste the following code: + +```jsx file=/animata/card/comment-reply-card.tsx + +``` + + + +## Credits + +Built by [Prince Yadav](https://github.com/prince981620) From 816539dfb1d91e16d468323c9a13167d46d71bc7 Mon Sep 17 00:00:00 2001 From: Abhinandan <93651229+AE-Hertz@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:44:08 +0530 Subject: [PATCH 39/42] feat(button): add ripple button (#350) --- animata/button/ripple-button.stories.tsx | 24 +++++ animata/button/ripple-button.tsx | 127 +++++++++++++++++++++++ content/docs/button/ripple-button.mdx | 33 ++++++ 3 files changed, 184 insertions(+) create mode 100644 animata/button/ripple-button.stories.tsx create mode 100644 animata/button/ripple-button.tsx create mode 100644 content/docs/button/ripple-button.mdx diff --git a/animata/button/ripple-button.stories.tsx b/animata/button/ripple-button.stories.tsx new file mode 100644 index 00000000..42c1c430 --- /dev/null +++ b/animata/button/ripple-button.stories.tsx @@ -0,0 +1,24 @@ +import RippleButton from "@/animata/button/ripple-button"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Button/Ripple Button", + component: RippleButton, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + children: { control: "text" }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Homepage: Story = { + args: { + children: "Homepage", + }, +}; diff --git a/animata/button/ripple-button.tsx b/animata/button/ripple-button.tsx new file mode 100644 index 00000000..7f1493c4 --- /dev/null +++ b/animata/button/ripple-button.tsx @@ -0,0 +1,127 @@ +"use client"; +import { useCallback, useRef, useState } from "react"; + +interface RippleButtonProps extends React.ButtonHTMLAttributes { + children: React.ReactNode; +} + +export default function RippleButton({ children, ...props }: RippleButtonProps) { + const buttonRef = useRef(null); + const rippleRef = useRef(null); + const [isHovered, setIsHovered] = useState(false); + + const createRipple = useCallback( + (event: React.MouseEvent) => { + if (isHovered || !buttonRef.current || !rippleRef.current) return; + setIsHovered(true); + + const button = buttonRef.current; + const ripple = rippleRef.current; + const rect = button.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height) * 2; + const x = event.clientX - rect.left - size / 2; + const y = event.clientY - rect.top - size / 2; + + ripple.style.width = `${size}px`; + ripple.style.height = `${size}px`; + ripple.style.left = `${x}px`; + ripple.style.top = `${y}px`; + + ripple.classList.remove("ripple-leave"); + ripple.classList.add("ripple-enter"); + }, + [isHovered], + ); + + const removeRipple = useCallback((event: React.MouseEvent) => { + if (event.target !== event.currentTarget) return; + if (!buttonRef.current || !rippleRef.current) return; + setIsHovered(false); + + const button = buttonRef.current; + const ripple = rippleRef.current; + const rect = button.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height) * 2; + const x = event.clientX - rect.left - size / 2; + const y = event.clientY - rect.top - size / 2; + + ripple.style.left = `${x}px`; + ripple.style.top = `${y}px`; + + ripple.classList.remove("ripple-enter"); + ripple.classList.add("ripple-leave"); + + const handleAnimationEnd = () => { + if (ripple) { + ripple.classList.remove("ripple-leave"); + ripple.removeEventListener("animationend", handleAnimationEnd); + } + }; + + ripple.addEventListener("animationend", handleAnimationEnd); + }, []); + + const handleMouseMove = useCallback( + (event: React.MouseEvent) => { + if (!buttonRef.current || !rippleRef.current || !isHovered) return; + + const button = buttonRef.current; + const ripple = rippleRef.current; + const rect = button.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height) * 2; + const x = event.clientX - rect.left - size / 2; + const y = event.clientY - rect.top - size / 2; + + ripple.style.left = `${x}px`; + ripple.style.top = `${y}px`; + }, + [isHovered], + ); + + return ( + + ); +} diff --git a/content/docs/button/ripple-button.mdx b/content/docs/button/ripple-button.mdx new file mode 100644 index 00000000..0d4e44f6 --- /dev/null +++ b/content/docs/button/ripple-button.mdx @@ -0,0 +1,33 @@ +--- +title: Ripple Button +description: Button with ripple effect on mouse position. +labels: ["requires interaction", "hover"] +author: Abhi_Hertz +--- + + + +## Installation + + +Run the following command + +It will create a new file called `ripple-button.tsx` inside the `compoents/animata/button` directory. + +```bash +mkdir -p components/animata/button && touch components/animata/button/ripple-button.tsx +``` + +Paste the code + +Open the newly create file and paste the following code: + +```jsx file=/animata/button/ripple-button.tsx + +``` + + + +## Credits + + Built by [Abhinandan](hhttps://github.com/AE-Hertz/) From 1c0c1ddc9f0161dbf8dd9038c27ac35bd5b6ab0f Mon Sep 17 00:00:00 2001 From: Arjun Vijay Prakash <137415649+ArjunCodess@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:53:20 +0530 Subject: [PATCH 40/42] feat: implement flower menu component (#379) --- animata/list/flower-menu.stories.tsx | 45 +++++++ animata/list/flower-menu.tsx | 187 +++++++++++++++++++++++++++ content/docs/list/flower-menu.mdx | 33 +++++ 3 files changed, 265 insertions(+) create mode 100644 animata/list/flower-menu.stories.tsx create mode 100644 animata/list/flower-menu.tsx create mode 100644 content/docs/list/flower-menu.mdx diff --git a/animata/list/flower-menu.stories.tsx b/animata/list/flower-menu.stories.tsx new file mode 100644 index 00000000..ace2b1fc --- /dev/null +++ b/animata/list/flower-menu.stories.tsx @@ -0,0 +1,45 @@ +import { + Codepen, + Facebook, + Github, + Instagram, + Linkedin, + Twitch, + Twitter, + Youtube, +} from "lucide-react"; + +import FlowerMenu from "@/animata/list/flower-menu"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "List/Flower Menu", + component: FlowerMenu, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + menuItems: [ + { icon: Github, href: "https://github.com/" }, + { icon: Twitter, href: "https://twitter.com/" }, + { icon: Instagram, href: "https://instagram.com/" }, + { icon: Linkedin, href: "https://www.linkedin.com/" }, + { icon: Youtube, href: "https://www.youtube.com/" }, + { icon: Twitch, href: "https://www.twitch.tv/" }, + { icon: Facebook, href: "https://www.facebook.com/" }, + { icon: Codepen, href: "https://www.codepen.io/" }, + ], + iconColor: "#ffffff", + backgroundColor: "rgba(0, 0, 0)", + animationDuration: 700, + togglerSize: 40, + }, +}; diff --git a/animata/list/flower-menu.tsx b/animata/list/flower-menu.tsx new file mode 100644 index 00000000..865150d4 --- /dev/null +++ b/animata/list/flower-menu.tsx @@ -0,0 +1,187 @@ +import { useState } from "react"; +import Link from "next/link"; + +type MenuItem = { + icon: React.ComponentType>; + href: string; +}; + +type FlowerMenuProps = { + menuItems: MenuItem[]; + iconColor?: string; + backgroundColor?: string; + animationDuration?: number; + togglerSize?: number; +}; + +const MenuToggler = ({ + isOpen, + onChange, + backgroundColor, + iconColor, + animationDuration, + togglerSize, + iconSize, +}: { + isOpen: boolean; + onChange: () => void; + backgroundColor: string; + iconColor: string; + animationDuration: number; + togglerSize: number; + iconSize: number; +}) => { + const lineHeight = iconSize * 0.1; + const lineWidth = iconSize * 0.8; + const lineSpacing = iconSize * 0.25; + + return ( + <> + + + + ); +}; + +const MenuItem = ({ + item, + index, + isOpen, + iconColor, + backgroundColor, + animationDuration, + itemCount, + itemSize, + iconSize, +}: { + item: MenuItem; + index: number; + isOpen: boolean; + iconColor: string; + backgroundColor: string; + animationDuration: number; + itemCount: number; + itemSize: number; + iconSize: number; +}) => { + const Icon = item.icon; + return ( +
  • + + + +
  • + ); +}; + +export default function FlowerMenu({ + menuItems, + iconColor = "white", + backgroundColor = "rgba(255, 255, 255, 0.2)", + animationDuration = 500, + togglerSize = 40, +}: FlowerMenuProps) { + const [isOpen, setIsOpen] = useState(false); + const itemCount = menuItems.length; + const itemSize = togglerSize * 2; + const iconSize = Math.max(24, Math.floor(togglerSize * 0.6)); + + return ( + + ); +} diff --git a/content/docs/list/flower-menu.mdx b/content/docs/list/flower-menu.mdx new file mode 100644 index 00000000..c2077a5a --- /dev/null +++ b/content/docs/list/flower-menu.mdx @@ -0,0 +1,33 @@ +--- +title: Flower Menu +description: A circular flower menu with several icons and a central close button. +author: arjuncodess +--- + + + +## Installation + + + +Run the following command + +It will create a new file `flower-menu.tsx` inside the `components/animata/list` directory. + +```bash +mkdir -p components/animata/list && touch components/animata/list/flower-menu.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```jsx file=/animata/list/flower-menu.tsx + +``` + + + +## Credits + +Built by [Arjun Vijay Prakash](https://github.com/arjuncodess). From 1d40447aebf76e890feb4686b7daa16f1a7f08d8 Mon Sep 17 00:00:00 2001 From: Eshan Singh <32596297+R0X4R@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:41:39 +0530 Subject: [PATCH 41/42] feat: add animated follow button (#304) --- .../button/animated-follow-button.stories.tsx | 72 +++++++++++++++ animata/button/animated-follow-button.tsx | 87 +++++++++++++++++++ .../docs/button/animated-follow-button.mdx | 38 ++++++++ 3 files changed, 197 insertions(+) create mode 100644 animata/button/animated-follow-button.stories.tsx create mode 100644 animata/button/animated-follow-button.tsx create mode 100644 content/docs/button/animated-follow-button.mdx diff --git a/animata/button/animated-follow-button.stories.tsx b/animata/button/animated-follow-button.stories.tsx new file mode 100644 index 00000000..6c163f74 --- /dev/null +++ b/animata/button/animated-follow-button.stories.tsx @@ -0,0 +1,72 @@ +import AnimatedFollowButton from "@/animata/button/animated-follow-button"; +import { Meta, StoryObj } from "@storybook/react"; + +const meta: Meta = { + title: "Button/Animated Follow Button", + component: AnimatedFollowButton, + parameters: { layout: "centered" }, + argTypes: { + initialText: { control: "text" }, + changeText: { control: "text" }, + className: { control: "text" }, + changeTextClassName: { control: "text" }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + initialText: "Follow", + changeText: "Following!", + className: "h-16 bg-green-100 text-green-700 flex rounded-full items-center justify-center", + changeTextClassName: "h-16 bg-green-700 text-green-100 rounded-full text-white flex items-center justify-center", + }, + render: (args) => ( +
    + +
    + ), +}; + +export const DifferentAnimations: Story = { + args: {}, + render: () => { + const buttons = [ + { + initialText: "Default", + changeText: "Up To Down", + animationType: "up-to-down" as const, + color: "blue", + }, + { + initialText: "Click Me", + changeText: "Down To Up", + animationType: "down-to-up" as const, + color: "zinc", + }, + { + initialText: "Click Me", + changeText: "Zoom In", + animationType: "zoom-in" as const, + color: "red", + }, + ]; + + return ( +
    + {buttons.map(({ initialText, changeText, animationType, color }, idx) => ( + {initialText}} + changeText={{changeText}} + animationType={animationType} + /> + ))} +
    + ); + }, +}; diff --git a/animata/button/animated-follow-button.tsx b/animata/button/animated-follow-button.tsx new file mode 100644 index 00000000..8ee8c638 --- /dev/null +++ b/animata/button/animated-follow-button.tsx @@ -0,0 +1,87 @@ +"use client"; + +import React, { useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; + +interface AnimatedFollowButtonProps { + initialText: React.ReactElement | string; // Text or element displayed initially + changeText: React.ReactElement | string; // Text or element displayed after the button is clicked + className?: string; // ClassName prop for custom button styling + changeTextClassName?: string; // ClassName prop for custom styling of changeText + animationType?: "up-to-down" | "down-to-up" | "left-to-right" | "right-to-left" | "zoom-in" | "zoom-out"; // Prop to define animation type +} + +const AnimatedFollowButton: React.FC = ({ + initialText, + changeText, + className, + changeTextClassName, + animationType = "up-to-down", // Set default animation to "up-to-down" +}) => { + const [isClicked, setIsClicked] = useState(false); // Track button click state + + // Determine animation settings based on animationType prop + const getAnimation = () => { + switch (animationType) { + case "down-to-up": + return { initial: { y: 20 }, animate: { y: 0 }, exit: { y: 20 } }; // Down to up animation + case "left-to-right": + return { initial: { x: -20 }, animate: { x: 0 }, exit: { x: -20 } }; // Left to right animation + case "right-to-left": + return { initial: { x: 20 }, animate: { x: 0 }, exit: { x: 20 } }; // Right to left animation + case "zoom-in": + return { initial: { scale: 0.8 }, animate: { scale: 1 }, exit: { scale: 0.8 } }; // Zoom in animation + case "zoom-out": + return { initial: { scale: 1.2 }, animate: { scale: 1 }, exit: { scale: 1.2 } }; // Zoom out animation + case "up-to-down": + default: + return { initial: { y: -20 }, animate: { y: 0 }, exit: { y: -20 } }; // Default: Up to down animation + } + }; + + const animation = getAnimation(); // Get animation settings based on the selected type + + return ( + + {isClicked ? ( + // Button after being clicked + setIsClicked(false)} // On click, toggle button state + initial={{ opacity: 0 }} // Initial animation for opacity + animate={{ opacity: 1 }} // Animate to full opacity + exit={{ opacity: 0 }} // Exit animation for opacity + > + {/* Change text with defined animation */} + + {changeText} {/* Display the changeText */} + + + ) : ( + // Button before being clicked + setIsClicked(true)} // On click, toggle button state + initial={{ opacity: 0 }} // Initial animation for opacity + animate={{ opacity: 1 }} // Animate to full opacity + exit={{ opacity: 0 }} // Exit animation for opacity + > + {/* Initial text with defined animation */} + + {initialText} {/* Display the initialText */} + + + )} + + ); +}; + +export default AnimatedFollowButton; diff --git a/content/docs/button/animated-follow-button.mdx b/content/docs/button/animated-follow-button.mdx new file mode 100644 index 00000000..063df174 --- /dev/null +++ b/content/docs/button/animated-follow-button.mdx @@ -0,0 +1,38 @@ +--- +title: Animated Follow Button +description: The Animated Follow Button is an interactive UI component with customizable entrance animations and dynamic text changes on click, offering flexible styling options. Perfect for engaging user interactions like follow or subscribe actions. +author: R0X4R +--- + + + +## Installation + + +Install dependencies + +```bash +npm install framer-motion +``` + +Run the following command + +It will create a new file `animated-follow-button.tsx` inside the `components/animata/button` directory. + +```bash +mkdir -p components/animata/button && touch components/animata/button/animated-follow-button.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```jsx file=/animata/button/animated-follow-button.tsx + +``` + + + +## Credits + + Built by [Eshan Singh](https://github.com/R0X4R) with [Framer Motion](https://www.framer.com/motion/). From 6050738ba5bb46ad72cf7553bbe2e9e9efc7c6ef Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Mon, 25 Nov 2024 12:02:08 +0545 Subject: [PATCH 42/42] feat: remove announcement (#395) Co-authored-by: Vishal Kumar Co-authored-by: Prithwi Hegde <115262737+Prithwi32@users.noreply.github.com> Co-authored-by: Shooman Khatri <112597601+ShoomanKhatri@users.noreply.github.com> Co-authored-by: Mohit ahlawat <65100859+mohitahlawat2001@users.noreply.github.com> Co-authored-by: Mansi Dhamne <129254413+mansidhamne@users.noreply.github.com> Co-authored-by: Prince Kumar Yadav <69517192+prince981620@users.noreply.github.com> Co-authored-by: Eshan Singh <32596297+R0X4R@users.noreply.github.com> Co-authored-by: Vishal <145477397+Pikachu-345@users.noreply.github.com> Co-authored-by: Adriana Fruchter Co-authored-by: Satyam Vyas Co-authored-by: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com> Co-authored-by: Sudha Shrestha --- .github/ISSUE_TEMPLATE/new-component-ticket.md | 1 + app/_landing/hero.tsx | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/new-component-ticket.md b/.github/ISSUE_TEMPLATE/new-component-ticket.md index 124d44f5..42df9d9a 100644 --- a/.github/ISSUE_TEMPLATE/new-component-ticket.md +++ b/.github/ISSUE_TEMPLATE/new-component-ticket.md @@ -25,6 +25,7 @@ Create the component as shown in the video attached below. 2. Add example usage of the component in the documentation or storybook. 3. **Add credits** for any resources, references, or assets used, as specified in the **Additional Resources** section. +

    Guidelines & Best Practices

    diff --git a/app/_landing/hero.tsx b/app/_landing/hero.tsx index f425e37e..f9ced922 100644 --- a/app/_landing/hero.tsx +++ b/app/_landing/hero.tsx @@ -1,6 +1,5 @@ import Link from "next/link"; -import { Announcement } from "@/components/announcement"; import { Icons } from "@/components/icons"; import { PageHeaderDescription } from "@/components/page-header"; import { buttonVariants } from "@/components/ui/button"; @@ -38,9 +37,6 @@ export default function Hero() { radial-gradient(at 66% 84%, hsla(89,66%,79%,1) 0px, transparent 50%)`, }} /> - - -