From ffbe8795de8c62c1bc3a58ad769ec0dc8559a2ac Mon Sep 17 00:00:00 2001 From: Nathan Windisch Date: Thu, 13 Mar 2025 04:32:15 +0000 Subject: [PATCH] added animated links with icons.pqoqubbw.dev --- package.json | 2 + src/App.tsx | 1 + src/components/social-links.tsx | 42 ++++++++ src/components/ui/git-commit-vertical.tsx | 119 ++++++++++++++++++++++ src/components/ui/terminal.tsx | 105 +++++++++++++++++++ src/components/ui/tooltip.tsx | 59 +++++++++++ 6 files changed, 328 insertions(+) create mode 100644 src/components/social-links.tsx create mode 100644 src/components/ui/git-commit-vertical.tsx create mode 100644 src/components/ui/terminal.tsx create mode 100644 src/components/ui/tooltip.tsx diff --git a/package.json b/package.json index 8951ce5..9802529 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "dependencies": { "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.12", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.479.0", + "motion": "^12.5.0", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^3.0.2", diff --git a/src/App.tsx b/src/App.tsx index f347dcd..af4d45c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,7 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { GitSocialLink, SelfSocialLink } from "@/components/social-links"; import "@/App.css"; const uri = "https://login.microsoft.com/{tenant}/.well-known/openid-configuration"; diff --git a/src/components/social-links.tsx b/src/components/social-links.tsx new file mode 100644 index 0000000..aaf6caf --- /dev/null +++ b/src/components/social-links.tsx @@ -0,0 +1,42 @@ +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Button } from "@/components/ui/button"; +import { TerminalIcon } from "@/components/ui/terminal"; +import { GitCommitVerticalIcon } from "@/components/ui/git-commit-vertical"; + +export const SelfSocialLink = () => ( + + + + + + + created with 💜 by @wnd.sh (click!) + + + +); + +export const GitSocialLink = () => ( + + + + + + + GitHub (click!) + + + +); diff --git a/src/components/ui/git-commit-vertical.tsx b/src/components/ui/git-commit-vertical.tsx new file mode 100644 index 0000000..e30d958 --- /dev/null +++ b/src/components/ui/git-commit-vertical.tsx @@ -0,0 +1,119 @@ +'use client'; + +import type { Variants } from 'motion/react'; +import { motion, useAnimation } from 'motion/react'; +import type { HTMLAttributes } from 'react'; +import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; +import { cn } from '@/lib/utils'; + +export interface GitCommitVerticalIconHandle { + startAnimation: () => void; + stopAnimation: () => void; +} + +interface GitCommitVerticalIconProps extends HTMLAttributes { + size?: number; +} + +const variants: Variants = { + normal: { + pathLength: 1, + opacity: 1, + }, + animate: (custom: number) => ({ + pathLength: [0, 1], + opacity: [0, 1], + transition: { + delay: 0.15 * custom, + opacity: { delay: 0.1 * custom }, + }, + }), +}; + +const GitCommitVerticalIcon = forwardRef< + GitCommitVerticalIconHandle, + GitCommitVerticalIconProps +>(({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => { + const controls = useAnimation(); + const isControlledRef = useRef(false); + + useImperativeHandle(ref, () => { + isControlledRef.current = true; + + return { + startAnimation: () => controls.start('animate'), + stopAnimation: () => controls.start('normal'), + }; + }); + + const handleMouseEnter = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start('animate'); + } else { + onMouseEnter?.(e); + } + }, + [controls, onMouseEnter] + ); + + const handleMouseLeave = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start('normal'); + } else { + onMouseLeave?.(e); + } + }, + [controls, onMouseLeave] + ); + + return ( +
+ + + + + +
+ ); +}); + +GitCommitVerticalIcon.displayName = 'GitCommitVerticalIcon'; + +export { GitCommitVerticalIcon }; diff --git a/src/components/ui/terminal.tsx b/src/components/ui/terminal.tsx new file mode 100644 index 0000000..8c5f1fa --- /dev/null +++ b/src/components/ui/terminal.tsx @@ -0,0 +1,105 @@ +'use client'; + +import type { Variants } from 'motion/react'; +import { motion, useAnimation } from 'motion/react'; +import type { HTMLAttributes } from 'react'; +import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; +import { cn } from '@/lib/utils'; + +export interface TerminalIconHandle { + startAnimation: () => void; + stopAnimation: () => void; +} + +interface TerminalIconProps extends HTMLAttributes { + size?: number; +} + +const lineVariants: Variants = { + normal: { opacity: 1 }, + animate: { + opacity: [1, 0, 1], + transition: { + duration: 0.8, + repeat: Infinity, + ease: 'linear', + }, + }, +}; + +const TerminalIcon = forwardRef( + ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => { + const controls = useAnimation(); + const isControlledRef = useRef(false); + + useImperativeHandle(ref, () => { + isControlledRef.current = true; + + return { + startAnimation: () => controls.start('animate'), + stopAnimation: () => controls.start('normal'), + }; + }); + + const handleMouseEnter = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start('animate'); + } else { + onMouseEnter?.(e); + } + }, + [controls, onMouseEnter] + ); + + const handleMouseLeave = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start('normal'); + } else { + onMouseLeave?.(e); + } + }, + [controls, onMouseLeave] + ); + + return ( +
+ + + + +
+ ); + } +); + +TerminalIcon.displayName = 'TerminalIcon'; + +export { TerminalIcon }; diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..ee7ae86 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }