From 24b2b58c5742d3df95476d28509cb865f8c0ecb1 Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 16 Oct 2024 13:33:36 -0400 Subject: [PATCH] feat: expandable, popover-form --- apps/www/__registry__/index.tsx | 44 ++ apps/www/app/(app)/page.tsx | 162 +++--- apps/www/app/layout.tsx | 6 +- apps/www/components/background-guides.tsx | 228 ++++++++ apps/www/components/distorted-glass.tsx | 2 +- .../components/landing/featured-component.tsx | 14 +- apps/www/components/landing/plug-grid.tsx | 2 +- apps/www/components/landing/template-grid.tsx | 3 +- apps/www/config/docs.ts | 14 +- .../content/docs/components/expandable.mdx | 44 ++ .../content/docs/components/popover-form.mdx | 148 +++++ apps/www/lib/fonts.ts | 20 +- apps/www/public/registry/index.json | 20 + .../registry/styles/default/expandable.json | 13 + .../registry/styles/default/popover-form.json | 13 + .../default/example/expandable-demo.tsx | 497 +++++++++++++++++ .../default/example/popover-form-demo.tsx | 386 +++++++++++++ apps/www/registry/default/ui/expandable.tsx | 508 ++++++++++++++++++ apps/www/registry/default/ui/popover-form.tsx | 324 +++++++++++ apps/www/registry/examples.ts | 12 + apps/www/registry/ui.ts | 13 + apps/www/styles/globals.css | 9 + 22 files changed, 2381 insertions(+), 101 deletions(-) create mode 100644 apps/www/components/background-guides.tsx create mode 100644 apps/www/content/docs/components/expandable.mdx create mode 100644 apps/www/content/docs/components/popover-form.mdx create mode 100644 apps/www/public/registry/styles/default/expandable.json create mode 100644 apps/www/public/registry/styles/default/popover-form.json create mode 100644 apps/www/registry/default/example/expandable-demo.tsx create mode 100644 apps/www/registry/default/example/popover-form-demo.tsx create mode 100644 apps/www/registry/default/ui/expandable.tsx create mode 100644 apps/www/registry/default/ui/popover-form.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index a2ff350..8b02f40 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -291,6 +291,28 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "popover-form": { + name: "popover-form", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/popover-form")), + source: "", + files: ["registry/default/ui/popover-form.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "expandable": { + name: "expandable", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/expandable")), + source: "", + files: ["registry/default/ui/expandable.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "text-animate-demo": { name: "text-animate-demo", type: "components:example", @@ -577,6 +599,28 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "popover-form-demo": { + name: "popover-form-demo", + type: "components:example", + registryDependencies: ["popover-form"], + component: React.lazy(() => import("@/registry/default/example/popover-form-demo")), + source: "", + files: ["registry/default/example/popover-form-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "expandable-demo": { + name: "expandable-demo", + type: "components:example", + registryDependencies: ["expandable"], + component: React.lazy(() => import("@/registry/default/example/expandable-demo")), + source: "", + files: ["registry/default/example/expandable-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "authentication-01": { name: "authentication-01", type: "components:block", diff --git a/apps/www/app/(app)/page.tsx b/apps/www/app/(app)/page.tsx index 1ac70dc..d3f8020 100644 --- a/apps/www/app/(app)/page.tsx +++ b/apps/www/app/(app)/page.tsx @@ -30,7 +30,7 @@ import { GradientHeading } from "@/registry/default/ui/gradient-heading" export default function IndexPage() { return ( -
+
{/* */} @@ -48,95 +48,97 @@ export default function IndexPage() { */}
- -
- - - - - - - Components crafted for
Design - Engineers -
-
- - -
- Ready-to-use -
-
- components for your React apps. - - Shadcn compatible. -
- -
- Styled with tailwindcss. -
-
- Copy and paste. Open Source. Typed. -
-
-
-
- - - - Get Started - - - - GitHub - - - -
+ {/* */} +
+ + + + + + + Components crafted for
Design + Engineers +
+
-
-
- - {/* */} +
+ Ready-to-use +
+
+ components for your React apps. -
- + Shadcn compatible. +
+
- -
- + Styled with tailwindcss. +
+
-
+ Copy and paste. Open Source. Typed. +
+
+
-
-
-
- + + Get Started + + - {" "} - Component Preview - - -
+ + GitHub + +
-
-
+ + + +
+ {/*
*/} +
+ + {/* */} +
+ + {/*
*/} +
+ +
+ +
+ +
+
+
+
+
+ +
+ + {" "} + Component Preview + + +
+
+
+ {/* */}
) } diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx index ed1f0e6..a8781a8 100644 --- a/apps/www/app/layout.tsx +++ b/apps/www/app/layout.tsx @@ -10,6 +10,7 @@ import { Toaster as NewYorkToaster, } from "@/components/ui/toaster" import { Analytics } from "@/components/analytics" +import { AnimatedBackgroundGuides } from "@/components/background-guides" import { ThemeProvider } from "@/components/providers" import { TailwindIndicator } from "@/components/tailwind-indicator" import { ThemeSwitcher } from "@/components/theme-switcher" @@ -84,10 +85,11 @@ export default function RootLayout({ children }: RootLayoutProps) { +
-
+
{children}
diff --git a/apps/www/components/background-guides.tsx b/apps/www/components/background-guides.tsx new file mode 100644 index 0000000..5f53bb6 --- /dev/null +++ b/apps/www/components/background-guides.tsx @@ -0,0 +1,228 @@ +"use client" + +import React, { useCallback, useEffect, useMemo, useState } from "react" +import { AnimatePresence, motion } from "framer-motion" + +type AnimationDirection = "top-to-bottom" | "bottom-to-top" | "both" | "random" +type AnimationEasing = "linear" | "easeIn" | "easeOut" | "easeInOut" | "spring" + +interface AnimatedBackgroundGuidesProps { + columnCount?: number + className?: string + solidLines?: number[] + animated?: boolean + animationDuration?: number + animationDelay?: number + glowColor?: string + glowSize?: string + glowOpacity?: number + randomize?: boolean + randomInterval?: number + direction?: AnimationDirection + easing?: AnimationEasing + responsive?: boolean + minColumnWidth?: string + maxActiveColumns?: number + darkMode?: boolean +} + +const easingFunctions = { + linear: [0, 0, 1, 1], + easeIn: [0.42, 0, 1, 1], + easeOut: [0, 0, 0.58, 1], + easeInOut: [0.42, 0, 0.58, 1], + spring: [0.175, 0.885, 0.32, 1.275], +} + +export function AnimatedBackgroundGuides({ + columnCount = 4, + className = "", + solidLines = [], + animated = true, + animationDuration = 62, + animationDelay = 0.8, + glowColor = "hsl(var(--accent))", + // glowColor = "#D2F583", + glowSize = "10vh", + glowOpacity = 0.4, + randomize = true, + randomInterval = 9000, + direction = "both", + easing = "spring", + responsive = false, + minColumnWidth = "4rem", + maxActiveColumns = 3, + darkMode = false, +}: AnimatedBackgroundGuidesProps) { + const [windowWidth, setWindowWidth] = useState( + typeof window !== "undefined" ? window.innerWidth : 0 + ) + + const columns = useMemo(() => { + const count = responsive + ? Math.max(Math.floor(windowWidth / parseInt(minColumnWidth)), 1) + : columnCount + return [...Array(count)] + }, [columnCount, responsive, windowWidth, minColumnWidth]) + + const [activeColumns, setActiveColumns] = useState( + columns.map(() => true) + ) + + const getRandomColumns = useCallback(() => { + const newActiveColumns = columns.map(() => Math.random() < 0.5) + const activeCount = newActiveColumns.filter(Boolean).length + if (activeCount > maxActiveColumns) { + const indicesToDeactivate = newActiveColumns + .map((isActive, index) => (isActive ? index : -1)) + .filter((index) => index !== -1) + .sort(() => Math.random() - 0.5) + .slice(0, activeCount - maxActiveColumns) + indicesToDeactivate.forEach((index) => { + newActiveColumns[index] = false + }) + } + return newActiveColumns + }, [columns, maxActiveColumns]) + + useEffect(() => { + const handleResize = () => setWindowWidth(window.innerWidth) + if (typeof window !== "undefined") { + window.addEventListener("resize", handleResize) + return () => window.removeEventListener("resize", handleResize) + } + }, []) + + useEffect(() => { + setActiveColumns(columns.map(() => true)) + }, [columns]) + + useEffect(() => { + if (randomize && animated) { + const intervalId = setInterval(() => { + setActiveColumns(getRandomColumns()) + }, randomInterval) + return () => clearInterval(intervalId) + } else { + setActiveColumns(columns.map(() => true)) + } + }, [randomize, animated, randomInterval, getRandomColumns, columns]) + + const getAnimationVariants = useCallback(() => { + const variants = { + "top-to-bottom": { + initial: { top: "-100%" }, + animate: { top: "100%" }, + }, + "bottom-to-top": { + initial: { top: "100%" }, + animate: { top: "-100%" }, + }, + both: { + initial: { top: "100%" }, + animate: { top: ["-100%", "100%"] }, + }, + random: { + initial: () => ({ top: Math.random() < 0.5 ? "-100%" : "100%" }), + animate: () => ({ top: Math.random() < 0.5 ? "-100%" : "100%" }), + }, + } + return variants[direction] || variants["top-to-bottom"] + }, [direction]) + + const animationVariants = useMemo( + () => getAnimationVariants(), + [getAnimationVariants] + ) + + const lineColors = useMemo(() => { + return { + solid: darkMode ? "hsl(233 14% 13%)" : "hsl(233 14.1% 96.1%)", + dashed: darkMode ? "hsl(233 14% 20%)" : "hsl(233 14% 93%)", + } + }, [darkMode]) + + return ( + + ) +} diff --git a/apps/www/components/distorted-glass.tsx b/apps/www/components/distorted-glass.tsx index e879614..0665db1 100644 --- a/apps/www/components/distorted-glass.tsx +++ b/apps/www/components/distorted-glass.tsx @@ -3,7 +3,7 @@ export const DistortedGlass = () => { return ( <> -
+
diff --git a/apps/www/components/landing/featured-component.tsx b/apps/www/components/landing/featured-component.tsx index 3880be9..4727b26 100644 --- a/apps/www/components/landing/featured-component.tsx +++ b/apps/www/components/landing/featured-component.tsx @@ -1,6 +1,7 @@ import Link from "next/link" import { ArrowRight, SparklesIcon } from "lucide-react" +import { ContactFormExample } from "@/registry/default/example/popover-form-demo" import ShaderLensBlurDemo from "@/registry/default/example/shader-lens-blur-demo" import { GradientHeading } from "@/registry/default/ui/gradient-heading" import { LightBoard } from "@/registry/default/ui/lightboard" @@ -96,7 +97,8 @@ export function FeaturedComponent() { export function LatestComponent() { return ( -
+ //
+
- Blur Shader + Popover Form - A shader that blurs the edges of your content + Headless popover form animation.
Inspired by @emilkowalski_
-
- +
+
) diff --git a/apps/www/components/landing/plug-grid.tsx b/apps/www/components/landing/plug-grid.tsx index 82ae6e6..734ec87 100644 --- a/apps/www/components/landing/plug-grid.tsx +++ b/apps/www/components/landing/plug-grid.tsx @@ -54,7 +54,7 @@ export function PlugCardGrid() { > {/* */} - + {card.title} diff --git a/apps/www/components/landing/template-grid.tsx b/apps/www/components/landing/template-grid.tsx index 4e6be31..cb0f794 100644 --- a/apps/www/components/landing/template-grid.tsx +++ b/apps/www/components/landing/template-grid.tsx @@ -18,7 +18,8 @@ import { Badge } from "../ui/badge" export function TemplateGrid() { return ( -
+ //
+
+ +## References + + +

Inspiration

+ + + + Animations dot dev + + + +
+ +## Installation + + + + Manual + + + + Copy and paste the following code into your project. + + Update the import paths to match your project setup. + + + diff --git a/apps/www/content/docs/components/popover-form.mdx b/apps/www/content/docs/components/popover-form.mdx new file mode 100644 index 0000000..4e2d6e7 --- /dev/null +++ b/apps/www/content/docs/components/popover-form.mdx @@ -0,0 +1,148 @@ +--- +title: PopoverForm +description: A headless popover form animation component with customizable options. +component: true +links: +--- + + + +## References + + +

Inspiration

+ + + + Animations dot dev + + + +
+ +## Installation + + + + Manual + + + + Copy and paste the following code into your project. + + Update the import paths to match your project setup. + + + + +## Usage + +```tsx +import { + PopoverForm, + PopoverFormButton, + PopoverFormCutOutLeftIcon, + PopoverFormCutOutRightIcon, + PopoverFormSeparator, + PopoverFormSuccess, +} from "@/registry/default/ui/popover-form" +``` + +```typescriptreact +export default function PopoverFormFeedbackExample() { + const [formState, setFormState] = useState("idle") + const [open, setOpen] = useState(false) + const [feedback, setFeedback] = useState("") + + function submit() { + setFormState("loading") + setTimeout(() => { + setFormState("success") + }, 1500) + + setTimeout(() => { + setOpen(false) + setFormState("idle") + setFeedback("") + }, 3300) + } + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setOpen(false) + } + + if ( + (event.ctrlKey || event.metaKey) && + event.key === "Enter" && + open && + formState === "idle" + ) { + submit() + } + } + + window.addEventListener("keydown", handleKeyDown) + return () => window.removeEventListener("keydown", handleKeyDown) + }, [open, formState]) + + return ( +
+ { + e.preventDefault() + if (!feedback) return + submit() + }} + > +
+