new ux start

This commit is contained in:
colinislit
2025-11-17 17:24:35 +01:00
parent 1592fdfac6
commit 1591e3271a
11 changed files with 3515 additions and 9 deletions

View File

@@ -0,0 +1,160 @@
import React from 'react';
import { cn } from "@/lib/utils";
import { motion } from 'framer-motion';
// Icon component for contact details
const InfoIcon = ({ type }: { type: 'website' | 'phone' | 'address' }) => {
const icons = {
website: (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" x2="22" y1="12" y2="12"></line>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
),
phone: (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
</svg>
),
address: (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
),
};
return <div className="mr-2 flex-shrink-0">{icons[type]}</div>;
};
// Prop types for the HeroSection component
interface HeroSectionProps extends React.HTMLAttributes<HTMLDivElement> {
logo?: {
url: string;
alt: string;
text?: string;
};
slogan?: string;
title: React.ReactNode;
subtitle: string;
callToAction: {
text: string;
href: string;
};
backgroundImage: string;
contactInfo: {
website: string;
phone: string;
address: string;
};
}
const HeroSection = React.forwardRef<HTMLDivElement, HeroSectionProps>(
({ className, logo, slogan, title, subtitle, callToAction, backgroundImage, contactInfo, ...props }, ref) => {
// Animation variants for the container to orchestrate children animations
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.2,
},
},
};
// Animation variants for individual text/UI elements
const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: {
duration: 0.5,
ease: "easeOut",
},
},
};
return (
<motion.section
ref={ref}
className={cn(
"relative flex w-full flex-col overflow-hidden bg-background text-foreground md:flex-row",
className
)}
initial="hidden"
animate="visible"
variants={containerVariants}
{...props}
>
{/* Left Side: Content */}
<div className="flex w-full flex-col justify-between p-8 md:w-1/2 md:p-12 lg:w-3/5 lg:p-16">
{/* Top Section: Logo & Main Content */}
<div>
<motion.header className="mb-12" variants={itemVariants}>
{logo && (
<div className="flex items-center">
<img src={logo.url} alt={logo.alt} className="mr-3 h-8" />
<div>
{logo.text && <p className="text-lg font-bold text-foreground">{logo.text}</p>}
{slogan && <p className="text-xs tracking-wider text-muted-foreground">{slogan}</p>}
</div>
</div>
)}
</motion.header>
<motion.main variants={containerVariants}>
<motion.h1 className="text-4xl font-bold leading-tight text-foreground md:text-5xl" variants={itemVariants}>
{title}
</motion.h1>
<motion.div className="my-6 h-1 w-20 bg-primary" variants={itemVariants}></motion.div>
<motion.p className="mb-8 max-w-md text-base text-muted-foreground" variants={itemVariants}>
{subtitle}
</motion.p>
<motion.a href={callToAction.href} className="text-lg font-bold tracking-widest text-primary transition-colors hover:text-primary/80" variants={itemVariants}>
{callToAction.text}
</motion.a>
</motion.main>
</div>
{/* Bottom Section: Footer Info */}
<motion.footer className="mt-12 w-full" variants={itemVariants}>
<div className="grid grid-cols-1 gap-6 text-xs text-muted-foreground sm:grid-cols-3">
<div className="flex items-center">
<InfoIcon type="website" />
<span>{contactInfo.website}</span>
</div>
<div className="flex items-center">
<InfoIcon type="phone" />
<span>{contactInfo.phone}</span>
</div>
<div className="flex items-center">
<InfoIcon type="address" />
<span>{contactInfo.address}</span>
</div>
</div>
</motion.footer>
</div>
{/* Right Side: Image with Clip Path Animation */}
<motion.div
className="w-full min-h-[300px] bg-cover bg-center md:w-1/2 md:min-h-full lg:w-2/5"
style={{
backgroundImage: `url(${backgroundImage})`,
}}
initial={{ clipPath: 'polygon(100% 0, 100% 0, 100% 100%, 100% 100%)' }}
animate={{ clipPath: 'polygon(25% 0, 100% 0, 100% 100%, 0% 100%)' }}
transition={{ duration: 1.2, ease: "circOut" }}
>
</motion.div>
</motion.section>
);
}
);
HeroSection.displayName = "HeroSection";
export { HeroSection };

View File

@@ -0,0 +1,300 @@
"use client";
import React, { useState, useEffect } from 'react';
import {
Home,
User,
Settings,
LogOut,
Menu,
X,
ChevronLeft,
ChevronRight,
BarChart3,
FileText,
Bell,
Search,
HelpCircle
} from 'lucide-react';
interface NavigationItem {
id: string;
name: string;
icon: React.ComponentType<{ className?: string }>;
href: string;
badge?: string;
}
interface SidebarProps {
className?: string;
}
// Updated navigation items - remove logout from here
const navigationItems: NavigationItem[] = [
{ id: "dashboard", name: "Dashboard", icon: Home, href: "/dashboard" },
{ id: "analytics", name: "Analytics", icon: BarChart3, href: "/analytics" },
{ id: "documents", name: "Documents", icon: FileText, href: "/documents", badge: "3" },
{ id: "notifications", name: "Notifications", icon: Bell, href: "/notifications", badge: "12" },
{ id: "profile", name: "Profile", icon: User, href: "/profile" },
{ id: "settings", name: "Settings", icon: Settings, href: "/settings" },
{ id: "help", name: "Help & Support", icon: HelpCircle, href: "/help" },
];
export function Sidebar({ className = "" }: SidebarProps) {
const [isOpen, setIsOpen] = useState(false);
const [isCollapsed, setIsCollapsed] = useState(false);
const [activeItem, setActiveItem] = useState("dashboard");
// Auto-open sidebar on desktop
useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 768) {
setIsOpen(true);
} else {
setIsOpen(false);
}
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const toggleSidebar = () => setIsOpen(!isOpen);
const toggleCollapse = () => setIsCollapsed(!isCollapsed);
const handleItemClick = (itemId: string) => {
setActiveItem(itemId);
if (window.innerWidth < 768) {
setIsOpen(false);
}
};
return (
<>
{/* Mobile hamburger button */}
<button
onClick={toggleSidebar}
className="fixed top-6 left-6 z-50 p-3 rounded-lg bg-white shadow-md border border-slate-100 md:hidden hover:bg-slate-50 transition-all duration-200"
aria-label="Toggle sidebar"
>
{isOpen ?
<X className="h-5 w-5 text-slate-600" /> :
<Menu className="h-5 w-5 text-slate-600" />
}
</button>
{/* Mobile overlay */}
{isOpen && (
<div
className="fixed inset-0 bg-black/40 backdrop-blur-sm z-30 md:hidden transition-opacity duration-300"
onClick={toggleSidebar}
/>
)}
{/* Sidebar */}
<div
className={`
fixed top-0 left-0 h-full bg-white border-r border-slate-200 z-40 transition-all duration-300 ease-in-out flex flex-col
${isOpen ? "translate-x-0" : "-translate-x-full"}
${isCollapsed ? "w-28" : "w-78"}
md:translate-x-0 md:static md:z-auto
${className}
`}
>
{/* Header with logo and collapse button */}
<div className="flex items-center justify-between p-5 border-b border-slate-200 bg-slate-50/60">
{!isCollapsed && (
<div className="flex items-center space-x-2.5">
<div className="w-9 h-9 bg-blue-600 rounded-lg flex items-center justify-center shadow-sm">
<span className="text-white font-bold text-base">A</span>
</div>
<div className="flex flex-col">
<span className="font-semibold text-slate-800 text-base">Acme Corp</span>
<span className="text-xs text-slate-500">Enterprise Dashboard</span>
</div>
</div>
)}
{isCollapsed && (
<div className="w-9 h-9 bg-blue-600 rounded-lg flex items-center justify-center mx-auto shadow-sm">
<span className="text-white font-bold text-base">A</span>
</div>
)}
{/* Desktop collapse button */}
<button
onClick={toggleCollapse}
className="hidden md:flex p-1.5 rounded-md hover:bg-slate-100 transition-all duration-200"
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
>
{isCollapsed ? (
<ChevronRight className="h-4 w-4 text-slate-500" />
) : (
<ChevronLeft className="h-4 w-4 text-slate-500" />
)}
</button>
</div>
{/* Search Bar */}
{!isCollapsed && (
<div className="px-4 py-3">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-3.5 w-3.5 text-slate-400" />
<input
type="text"
placeholder="Search..."
className="w-full pl-9 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-md text-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
/>
</div>
</div>
)}
{/* Navigation */}
<nav className="flex-1 px-3 py-2 overflow-y-auto">
<ul className="space-y-0.5">
{navigationItems.map((item) => {
const Icon = item.icon;
const isActive = activeItem === item.id;
return (
<li key={item.id}>
<button
onClick={() => handleItemClick(item.id)}
className={`
w-full flex items-center space-x-2.5 px-3 py-2.5 rounded-md text-left transition-all duration-200 group
${isActive
? "bg-blue-50 text-blue-700"
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900"
}
${isCollapsed ? "justify-center px-2" : ""}
`}
title={isCollapsed ? item.name : undefined}
>
<div className="flex items-center justify-center min-w-[24px]">
<Icon
className={`
h-4.5 w-4.5 flex-shrink-0
${isActive
? "text-blue-600"
: "text-slate-500 group-hover:text-slate-700"
}
`}
/>
</div>
{!isCollapsed && (
<div className="flex items-center justify-between w-full">
<span className={`text-sm ${isActive ? "font-medium" : "font-normal"}`}>{item.name}</span>
{item.badge && (
<span className={`
px-1.5 py-0.5 text-xs font-medium rounded-full
${isActive
? "bg-blue-100 text-blue-700"
: "bg-slate-100 text-slate-600"
}
`}>
{item.badge}
</span>
)}
</div>
)}
{/* Badge for collapsed state */}
{isCollapsed && item.badge && (
<div className="absolute top-1 right-1 w-4 h-4 flex items-center justify-center rounded-full bg-blue-100 border border-white">
<span className="text-[10px] font-medium text-blue-700">
{parseInt(item.badge) > 9 ? '9+' : item.badge}
</span>
</div>
)}
{/* Tooltip for collapsed state */}
{isCollapsed && (
<div className="absolute left-full ml-2 px-2 py-1 bg-slate-800 text-white text-xs rounded opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 whitespace-nowrap z-50">
{item.name}
{item.badge && (
<span className="ml-1.5 px-1 py-0.5 bg-slate-700 rounded-full text-[10px]">
{item.badge}
</span>
)}
<div className="absolute left-0 top-1/2 transform -translate-y-1/2 -translate-x-1 w-1.5 h-1.5 bg-slate-800 rotate-45" />
</div>
)}
</button>
</li>
);
})}
</ul>
</nav>
{/* Bottom section with profile and logout */}
<div className="mt-auto border-t border-slate-200">
{/* Profile Section */}
<div className={`border-b border-slate-200 bg-slate-50/30 ${isCollapsed ? 'py-3 px-2' : 'p-3'}`}>
{!isCollapsed ? (
<div className="flex items-center px-3 py-2 rounded-md bg-white hover:bg-slate-50 transition-colors duration-200">
<div className="w-8 h-8 bg-slate-200 rounded-full flex items-center justify-center">
<span className="text-slate-700 font-medium text-sm">JD</span>
</div>
<div className="flex-1 min-w-0 ml-2.5">
<p className="text-sm font-medium text-slate-800 truncate">John Doe</p>
<p className="text-xs text-slate-500 truncate">Senior Administrator</p>
</div>
<div className="w-2 h-2 bg-green-500 rounded-full ml-2" title="Online" />
</div>
) : (
<div className="flex justify-center">
<div className="relative">
<div className="w-9 h-9 bg-slate-200 rounded-full flex items-center justify-center">
<span className="text-slate-700 font-medium text-sm">JD</span>
</div>
<div className="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" />
</div>
</div>
)}
</div>
{/* Logout Button */}
<div className="p-3">
<button
onClick={() => handleItemClick("logout")}
className={`
w-full flex items-center rounded-md text-left transition-all duration-200 group
text-red-600 hover:bg-red-50 hover:text-red-700
${isCollapsed ? "justify-center p-2.5" : "space-x-2.5 px-3 py-2.5"}
`}
title={isCollapsed ? "Logout" : undefined}
>
<div className="flex items-center justify-center min-w-[24px]">
<LogOut className="h-4.5 w-4.5 flex-shrink-0 text-red-500 group-hover:text-red-600" />
</div>
{!isCollapsed && (
<span className="text-sm">Logout</span>
)}
{/* Tooltip for collapsed state */}
{isCollapsed && (
<div className="absolute left-full ml-2 px-2 py-1 bg-slate-800 text-white text-xs rounded opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 whitespace-nowrap z-50">
Logout
<div className="absolute left-0 top-1/2 transform -translate-y-1/2 -translate-x-1 w-1.5 h-1.5 bg-slate-800 rotate-45" />
</div>
)}
</button>
</div>
</div>
</div>
{/* Main Content Area */}
<div
className={`
transition-all duration-300 ease-in-out w-full
${isCollapsed ? "md:ml-20" : "md:ml-72"}
`}
>
{/* Your content remains the same */}
</div>
</>
);
}

View File

@@ -0,0 +1,90 @@
"use client";
import {
useMotionValueEvent,
useScroll,
useTransform,
motion,
} from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
interface TimelineEntry {
title: string;
content: React.ReactNode;
}
export const Timeline = ({ data }: { data: TimelineEntry[] }) => {
const ref = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [height, setHeight] = useState(0);
useEffect(() => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect();
setHeight(rect.height);
}
}, [ref]);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start 10%", "end 50%"],
});
const heightTransform = useTransform(scrollYProgress, [0, 1], [0, height]);
const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]);
return (
<div
className="w-full bg-white dark:bg-neutral-950 font-sans md:px-10"
ref={containerRef}
>
<div className="max-w-7xl mx-auto py-20 px-4 md:px-8 lg:px-10">
<h2 className="text-lg md:text-4xl mb-4 text-black dark:text-white max-w-4xl">
Changelog from my journey
</h2>
<p className="text-neutral-700 dark:text-neutral-300 text-sm md:text-base max-w-sm">
I&apos;ve been working on Aceternity for the past 2 years. Here&apos;s
a timeline of my journey.
</p>
</div>
<div ref={ref} className="relative max-w-7xl mx-auto pb-20">
{data.map((item, index) => (
<div
key={index}
className="flex justify-start pt-10 md:pt-40 md:gap-10"
>
<div className="sticky flex flex-col md:flex-row z-40 items-center top-40 self-start max-w-xs lg:max-w-sm md:w-full">
<div className="h-10 absolute left-3 md:left-3 w-10 rounded-full bg-white dark:bg-black flex items-center justify-center">
<div className="h-4 w-4 rounded-full bg-neutral-200 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 p-2" />
</div>
<h3 className="hidden md:block text-xl md:pl-20 md:text-5xl font-bold text-neutral-500 dark:text-neutral-500 ">
{item.title}
</h3>
</div>
<div className="relative pl-20 pr-4 md:pl-4 w-full">
<h3 className="md:hidden block text-2xl mb-4 text-left font-bold text-neutral-500 dark:text-neutral-500">
{item.title}
</h3>
{item.content}{" "}
</div>
</div>
))}
<div
style={{
height: height + "px",
}}
className="absolute md:left-8 left-8 top-0 overflow-hidden w-[2px] bg-[linear-gradient(to_bottom,var(--tw-gradient-stops))] from-transparent from-[0%] via-neutral-200 dark:via-neutral-700 to-transparent to-[99%] [mask-image:linear-gradient(to_bottom,transparent_0%,black_10%,black_90%,transparent_100%)] "
>
<motion.div
style={{
height: heightTransform,
opacity: opacityTransform,
}}
className="absolute inset-x-0 top-0 w-[2px] bg-gradient-to-t from-purple-500 via-blue-500 to-transparent from-[0%] via-[10%] rounded-full"
/>
</div>
</div>
</div>
);
};