mirror of
https://github.com/moumen-soliman/uitripled
synced 2026-04-21 13:37:20 +00:00
V1/release 16 Dec 2025 (#4)
* v1 prepration * Fix baseui tooltip components * Update native-liquid-button.tsx * Resumes fix * Add uitripled CLI package and update install tabs Introduces a new uitripled CLI package for installing animated UI components, including CLI source, documentation, and publish script. Updates the AnimationDetailPage to feature install instructions for both shadcn and uitripled, replacing the previous npx/yarn/pnpm tabs with shadcn/uitripled options and corresponding copy-to-clipboard functionality. * update package release * Update package.json * Hotfix
This commit is contained in:
parent
e23ae85528
commit
9ee2f7530a
1050 changed files with 72511 additions and 6638 deletions
10
.eslintrc
Normal file
10
.eslintrc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"plugins": ["unused-imports"],
|
||||
"rules": {
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
||||
]
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,6 +9,7 @@
|
|||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
.npmrc
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@
|
|||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
"endOfLine": "lf",
|
||||
"plugins": ["prettier-plugin-organize-imports"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import JSZip from "jszip";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
type PageFile = {
|
||||
path: string;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { readFileSync, existsSync } from "fs";
|
||||
import { join } from "path";
|
||||
import registryIndex from "@/registry.json";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { join } from "path";
|
||||
|
||||
type RegistryItem = {
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { BuilderCanvas } from "@/components/builder-canvas";
|
||||
import { BuilderCodeView } from "@/components/builder-code-view";
|
||||
import { BuilderSidebar } from "@/components/builder-sidebar";
|
||||
import { BuilderHeader } from "@/components/builder/builder-header";
|
||||
import { DragOverlay } from "@/components/builder/drag-overlay";
|
||||
import { InstructionsBanner } from "@/components/builder/instructions-banner";
|
||||
import { LoadProjectDialog } from "@/components/builder/load-project-dialog";
|
||||
import { PageTabs } from "@/components/builder/page-tabs";
|
||||
import { TextEditingBanner } from "@/components/builder/text-editing-banner";
|
||||
import {
|
||||
createPage,
|
||||
extractSavedPages,
|
||||
generateUniqueSlug,
|
||||
} from "@/lib/builder-utils";
|
||||
import { componentsRegistry } from "@/lib/components-registry";
|
||||
import type {
|
||||
BuilderComponent,
|
||||
BuilderProjectPage,
|
||||
SavedProject,
|
||||
} from "@/types/builder";
|
||||
import {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
|
|
@ -10,26 +29,7 @@ import {
|
|||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import { arrayMove } from "@dnd-kit/sortable";
|
||||
import { BuilderSidebar } from "@/components/builder-sidebar";
|
||||
import { BuilderCanvas } from "@/components/builder-canvas";
|
||||
import { BuilderCodeView } from "@/components/builder-code-view";
|
||||
import { componentsRegistry } from "@/lib/components-registry";
|
||||
import {
|
||||
createPage,
|
||||
generateUniqueSlug,
|
||||
extractSavedPages,
|
||||
} from "@/lib/builder-utils";
|
||||
import type {
|
||||
BuilderComponent,
|
||||
BuilderProjectPage,
|
||||
SavedProject,
|
||||
} from "@/types/builder";
|
||||
import { BuilderHeader } from "@/components/builder/builder-header";
|
||||
import { InstructionsBanner } from "@/components/builder/instructions-banner";
|
||||
import { TextEditingBanner } from "@/components/builder/text-editing-banner";
|
||||
import { PageTabs } from "@/components/builder/page-tabs";
|
||||
import { LoadProjectDialog } from "@/components/builder/load-project-dialog";
|
||||
import { DragOverlay } from "@/components/builder/drag-overlay";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
export default function BuilderPage() {
|
||||
const [pages, setPages] = useState<BuilderProjectPage[]>(() => {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
import Link from "next/link";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import AnimationDetailPageClient from "./AnimationDetailPage.client";
|
||||
import { createMetadata } from "@/lib/seo";
|
||||
import {
|
||||
getComponentById,
|
||||
componentsRegistry,
|
||||
getComponentById,
|
||||
loadComponentCode,
|
||||
} from "@/lib/components-registry";
|
||||
import { Component } from "@/types";
|
||||
import { createMetadata } from "@/lib/seo";
|
||||
import { notFound } from "next/navigation";
|
||||
import AnimationDetailPageClient from "./AnimationDetailPage.client";
|
||||
|
||||
type PageParams = {
|
||||
params: Promise<{
|
||||
|
|
@ -22,7 +21,11 @@ export function generateStaticParams() {
|
|||
|
||||
export const dynamicParams = true;
|
||||
|
||||
export async function generateMetadata({ params }: PageParams) {
|
||||
import { Metadata } from "next";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: PageParams): Promise<Metadata> {
|
||||
const { id } = await params;
|
||||
const component = getComponentById(id);
|
||||
|
||||
|
|
@ -84,12 +87,126 @@ export default async function AnimationDetailPage({ params }: PageParams) {
|
|||
);
|
||||
}
|
||||
|
||||
// Load both baseui and shadcnui code for native components
|
||||
let baseuiCode: string | undefined;
|
||||
let shadcnuiCode: string | undefined;
|
||||
let carbonCode: string | undefined;
|
||||
|
||||
let baseuiDemoCode: string | undefined;
|
||||
let shadcnuiDemoCode: string | undefined;
|
||||
let carbonDemoCode: string | undefined;
|
||||
|
||||
if (component.category === "native") {
|
||||
const baseuiPath = `@/components/native/baseui/${component.id}-baseui.tsx`;
|
||||
const shadcnuiPath = `@/components/native/shadcnui/${component.id}-shadcnui.tsx`;
|
||||
const carbonPath = `@/components/native/carbon/${component.id}-carbon.tsx`;
|
||||
|
||||
// Demo paths
|
||||
const baseuiDemoPath = `@/components/native/baseui/demo/${component.id}-demo.tsx`;
|
||||
const shadcnuiDemoPath = `@/components/native/shadcnui/demo/${component.id}-demo.tsx`;
|
||||
const carbonDemoPath = `@/components/native/carbon/demo/${component.id}-demo.tsx`;
|
||||
|
||||
try {
|
||||
baseuiCode = await loadComponentCode({
|
||||
...component,
|
||||
codePath: baseuiPath,
|
||||
});
|
||||
} catch (error) {
|
||||
// Baseui version doesn't exist, that's okay
|
||||
}
|
||||
|
||||
try {
|
||||
baseuiDemoCode = await loadComponentCode({
|
||||
...component,
|
||||
codePath: baseuiDemoPath,
|
||||
});
|
||||
} catch (error) {
|
||||
// Baseui demo doesn't exist, that's okay
|
||||
}
|
||||
|
||||
try {
|
||||
shadcnuiCode = await loadComponentCode({
|
||||
...component,
|
||||
codePath: shadcnuiPath,
|
||||
});
|
||||
} catch (error) {
|
||||
// Shadcnui version doesn't exist, that's okay
|
||||
}
|
||||
|
||||
try {
|
||||
shadcnuiDemoCode = await loadComponentCode({
|
||||
...component,
|
||||
codePath: shadcnuiDemoPath,
|
||||
});
|
||||
} catch (error) {
|
||||
// Shadcnui demo doesn't exist, that's okay
|
||||
}
|
||||
|
||||
try {
|
||||
carbonCode = await loadComponentCode({
|
||||
...component,
|
||||
codePath: carbonPath,
|
||||
});
|
||||
} catch (error) {
|
||||
// Carbon version doesn't exist, that's okay
|
||||
}
|
||||
|
||||
try {
|
||||
carbonDemoCode = await loadComponentCode({
|
||||
...component,
|
||||
codePath: carbonDemoPath,
|
||||
});
|
||||
} catch (error) {
|
||||
// Carbon demo doesn't exist, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
// Load baseui code for non-native components if available
|
||||
if (
|
||||
component.category !== "native" &&
|
||||
component.availableIn &&
|
||||
component.availableIn.includes("baseui")
|
||||
) {
|
||||
// Try different paths based on component category
|
||||
const possiblePaths = [
|
||||
`@/components/sections/baseui/${component.id}-baseui.tsx`,
|
||||
`@/components/components/resumes/baseui/${component.id}-baseui.tsx`,
|
||||
`@/components/components/cards/baseui/${component.id}-baseui.tsx`,
|
||||
];
|
||||
|
||||
for (const baseuiPath of possiblePaths) {
|
||||
try {
|
||||
const loadedBaseuiCode = await loadComponentCode({
|
||||
...component,
|
||||
codePath: baseuiPath,
|
||||
});
|
||||
if (loadedBaseuiCode) {
|
||||
baseuiCode = loadedBaseuiCode;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
// Path doesn't exist, try next one
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure shadcnuiCode is set to the default code if not already set
|
||||
if (!shadcnuiCode) {
|
||||
shadcnuiCode = code;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimationDetailPageClient
|
||||
code={code}
|
||||
relatedComponents={relatedComponents}
|
||||
variantCodes={variantCodes}
|
||||
baseId={component.id}
|
||||
baseuiCode={baseuiCode}
|
||||
shadcnuiCode={shadcnuiCode}
|
||||
carbonCode={carbonCode}
|
||||
baseuiDemoCode={baseuiDemoCode}
|
||||
shadcnuiDemoCode={shadcnuiDemoCode}
|
||||
carbonDemoCode={carbonDemoCode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useMemo, useEffect, useCallback, Suspense } from "react";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight, Menu, X } from "lucide-react";
|
||||
import { useQueryState, parseAsString } from "nuqs";
|
||||
import {
|
||||
getComponentById,
|
||||
componentsRegistry,
|
||||
} from "@/lib/components-registry";
|
||||
import { AnimationsSidebar } from "@/components/animation-sidebar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogTrigger,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
componentsRegistry,
|
||||
getComponentById,
|
||||
} from "@/lib/components-registry";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight, Menu } from "lucide-react";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import { parseAsString, useQueryState } from "nuqs";
|
||||
import { Suspense, useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
function ComponentsLayoutContent({ children }: { children: React.ReactNode }) {
|
||||
const params = useParams();
|
||||
|
|
|
|||
|
|
@ -4,13 +4,23 @@ import { createMetadata } from "@/lib/seo";
|
|||
export const metadata = createMetadata({
|
||||
title: "Component Library",
|
||||
description:
|
||||
"Browse 70+ production-ready motion components built with Framer Motion, shadcn/ui, and Tailwind CSS.",
|
||||
"Browse 70+ production-ready motion components built with Framer Motion, shadcn/ui, baseui, and Tailwind CSS.",
|
||||
path: "/components",
|
||||
keywords: [
|
||||
"React components",
|
||||
"motion components",
|
||||
"baseui components",
|
||||
"shadcn components",
|
||||
"tailwind css",
|
||||
"component library",
|
||||
"Framer Motion UI",
|
||||
"UI TripleD",
|
||||
"tripled",
|
||||
"UI components",
|
||||
"motion components",
|
||||
"React components",
|
||||
"Next.js components",
|
||||
"Framer Motion",
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import { CodeOutput } from "@/components/grid/code-output";
|
||||
import { GridPreview } from "@/components/grid/grid-preview";
|
||||
import { PresetsPanel } from "@/components/grid/presets-panel";
|
||||
import { SettingsPanel } from "@/components/grid/settings-panel";
|
||||
import {
|
||||
generateGridCode,
|
||||
getCellKey,
|
||||
initializeCells,
|
||||
type GridCell,
|
||||
} from "@/lib/grid-utils";
|
||||
import { SettingsPanel } from "@/components/grid/settings-panel";
|
||||
import { PresetsPanel } from "@/components/grid/presets-panel";
|
||||
import { CodeOutput } from "@/components/grid/code-output";
|
||||
import { GridPreview } from "@/components/grid/grid-preview";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
// Animation variants
|
||||
const containerVariants: Variants = {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { Footer } from "@/components/footer";
|
||||
import { Header } from "@/components/header";
|
||||
import { THEME_STORAGE_KEY, ThemeProvider } from "@/components/theme-provider";
|
||||
import { UILibraryProvider } from "@/components/ui-library-provider";
|
||||
import { baseMetadata, siteConfig } from "@/lib/seo";
|
||||
import type { Viewport } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import Script from "next/script";
|
||||
import "./globals.css";
|
||||
import { Header } from "@/components/header";
|
||||
import { Footer } from "@/components/footer";
|
||||
import { ThemeProvider, THEME_STORAGE_KEY } from "@/components/theme-provider";
|
||||
import { baseMetadata, siteConfig } from "@/lib/seo";
|
||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
|
|
@ -116,11 +117,13 @@ export default function RootLayout({
|
|||
async
|
||||
/>
|
||||
<ThemeProvider>
|
||||
<NuqsAdapter>
|
||||
<Header />
|
||||
<main className="min-h-screen">{children}</main>
|
||||
<Footer />
|
||||
</NuqsAdapter>
|
||||
<UILibraryProvider>
|
||||
<NuqsAdapter>
|
||||
<Header />
|
||||
<main className="min-h-screen">{children}</main>
|
||||
<Footer />
|
||||
</NuqsAdapter>
|
||||
</UILibraryProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
21
app/page.tsx
21
app/page.tsx
|
|
@ -2,16 +2,27 @@ import HomePageContent from "@/components/home-page-content";
|
|||
import { createMetadata, siteConfig } from "@/lib/seo";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "UI Components, Blocks & Templates",
|
||||
title: "UI Components, Blocks & Pages",
|
||||
description: siteConfig.description,
|
||||
path: "/",
|
||||
keywords: [
|
||||
"UI components",
|
||||
"baseui components",
|
||||
"shadcn components",
|
||||
"tailwind css",
|
||||
"motion components",
|
||||
"React components",
|
||||
"Next.js components",
|
||||
"Framer Motion",
|
||||
"shadcn/ui",
|
||||
"Tailwind CSS",
|
||||
"landing page templates",
|
||||
"UI library",
|
||||
"interactive UI",
|
||||
"UI TripleD",
|
||||
"tripled",
|
||||
"UI components",
|
||||
"motion components",
|
||||
"React UI library",
|
||||
"Next.js animations",
|
||||
"Framer Motion templates",
|
||||
"shadcn ui components",
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import { ComponentType, useEffect, useRef, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowLeft, Code, ExternalLink, Loader2 } from "lucide-react";
|
||||
import { CodeBlock } from "@/components/code-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { CodeBlock } from "@/components/code-block";
|
||||
import { componentsRegistry } from "@/lib/components-registry";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowLeft, Code, ExternalLink, Loader2 } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { ComponentType, useEffect, useRef, useState } from "react";
|
||||
|
||||
type SavedProjectComponent = {
|
||||
id?: string;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import PreviewProjectPageClient from "./PreviewProjectPage.client";
|
||||
import { createMetadata } from "@/lib/seo";
|
||||
import PreviewProjectPageClient from "./PreviewProjectPage.client";
|
||||
|
||||
type PreviewPageProps = {
|
||||
params: Promise<{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { captureRegistryEvent } from "@wandry/analytics-sdk";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { MetadataRoute } from "next";
|
||||
import { siteConfig } from "@/lib/seo";
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { MetadataRoute } from "next";
|
||||
import { sitemapEntries } from "@/lib/seo";
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const lastModified = new Date();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Component, categoryNames } from "@/types";
|
||||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { Component, categoryNames } from "@/types";
|
||||
import { useState } from "react";
|
||||
|
||||
type AnimationCardProps = {
|
||||
animation: Component;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Search, ChevronDown, ChevronRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useUILibrary } from "@/components/ui-library-provider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { componentsRegistry } from "@/lib/components-registry";
|
||||
import { Component, ComponentCategory, categoryNames } from "@/types";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ChevronDown, ChevronRight, Search } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
type AnimationsSidebarProps = {
|
||||
selectedComponent: Component | null;
|
||||
|
|
@ -22,6 +23,7 @@ export function AnimationsSidebar({
|
|||
useLinks = false,
|
||||
target,
|
||||
}: AnimationsSidebarProps) {
|
||||
const { selectedLibrary } = useUILibrary();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [expandedCategories, setExpandedCategories] = useState<
|
||||
Set<ComponentCategory | "all">
|
||||
|
|
@ -92,6 +94,24 @@ export function AnimationsSidebar({
|
|||
// First filter by display property (only show animations where display !== false)
|
||||
let filtered = componentsRegistry.filter((anim) => anim.display !== false);
|
||||
|
||||
// Filter by selected UI library
|
||||
// If availableIn is not specified, component defaults to shadcnui only
|
||||
// Carbon = pure React, accessible from both shadcnui and baseui
|
||||
filtered = filtered.filter((anim) => {
|
||||
const availableLibraries = anim.availableIn || ["shadcnui"];
|
||||
|
||||
// If component has "carbon" (pure React), it's compatible with shadcnui and baseui
|
||||
if (availableLibraries.includes("carbon")) {
|
||||
return (
|
||||
selectedLibrary === "shadcnui" ||
|
||||
selectedLibrary === "baseui" ||
|
||||
selectedLibrary === "carbon"
|
||||
);
|
||||
}
|
||||
|
||||
return availableLibraries.includes(selectedLibrary);
|
||||
});
|
||||
|
||||
if (searchQuery) {
|
||||
const lowerQuery = searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(
|
||||
|
|
@ -103,7 +123,7 @@ export function AnimationsSidebar({
|
|||
}
|
||||
|
||||
return filtered;
|
||||
}, [searchQuery]);
|
||||
}, [searchQuery, selectedLibrary]);
|
||||
|
||||
const animationsByCategory = useMemo(() => {
|
||||
const grouped: Record<ComponentCategory | "all", Component[]> = {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useId, useMemo } from "react";
|
||||
import { motion, useReducedMotion, type Transition } from "framer-motion";
|
||||
import { User } from "lucide-react";
|
||||
import { useId, useMemo } from "react";
|
||||
|
||||
type Avatar = {
|
||||
id: number;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import type { BuilderComponent } from "@/types/builder";
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import {
|
||||
SortableContext,
|
||||
|
|
@ -7,10 +8,9 @@ import {
|
|||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { X } from "lucide-react";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import type { BuilderComponent } from "@/types/builder";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
type CanvasComponentProps = {
|
||||
component: BuilderComponent;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Copy,
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Save,
|
||||
Eye,
|
||||
Rocket,
|
||||
Download,
|
||||
Loader2,
|
||||
} from "lucide-react";
|
||||
import { CodeBlock } from "./code-block";
|
||||
import type { BuilderComponent, BuilderProjectPage } from "@/types/builder";
|
||||
import { mergeComponentImports } from "@/lib/merge-imports";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -26,8 +11,22 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { mergeComponentImports } from "@/lib/merge-imports";
|
||||
import type { BuilderComponent, BuilderProjectPage } from "@/types/builder";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Copy,
|
||||
Download,
|
||||
Eye,
|
||||
Loader2,
|
||||
Save,
|
||||
} from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { CodeBlock } from "./code-block";
|
||||
|
||||
type BuilderCodeViewProps = {
|
||||
pages: BuilderProjectPage[];
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import { useDraggable } from "@dnd-kit/core";
|
||||
import { motion } from "framer-motion";
|
||||
import { Search } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { componentsRegistry } from "@/lib/components-registry";
|
||||
import { categoryNames } from "@/types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { categoryNames } from "@/types";
|
||||
|
||||
type ComponentItem = (typeof componentsRegistry)[number];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { BuilderSidebar } from "@/components/builder-sidebar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -7,8 +8,7 @@ import {
|
|||
DialogContent,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { BuilderSidebar } from "@/components/builder-sidebar";
|
||||
import { FolderOpen, Menu, Type, X } from "lucide-react";
|
||||
import { FolderOpen, Menu, X } from "lucide-react";
|
||||
|
||||
type BuilderHeaderProps = {
|
||||
mobileSidebarOpen: boolean;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Info } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Info } from "lucide-react";
|
||||
|
||||
type InstructionsBannerProps = {
|
||||
show: boolean;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
import type { SavedProject } from "@/types/builder";
|
||||
import { extractSavedPages } from "@/lib/builder-utils";
|
||||
import type { SavedProject } from "@/types/builder";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
|
||||
type LoadProjectDialogProps = {
|
||||
open: boolean;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Edit3, Plus, Trash2 } from "lucide-react";
|
||||
import type { BuilderProjectPage } from "@/types/builder";
|
||||
import { Edit3, Plus, Trash2 } from "lucide-react";
|
||||
|
||||
type PageTabsProps = {
|
||||
pages: BuilderProjectPage[];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
type TextEditingBannerProps = {
|
||||
show: boolean;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { useState, type ComponentType } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { useState, type ComponentType } from "react";
|
||||
import type { SyntaxHighlighterProps } from "react-syntax-highlighter";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
|
||||
const PrismSyntaxHighlighter =
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Palette, Check } from "lucide-react";
|
||||
import { useTheme } from "@/components/theme-provider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -12,7 +11,8 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTheme } from "@/components/theme-provider";
|
||||
import { Check, Palette } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type ColorScheme = {
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useId, useMemo, useState } from "react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { Check, ChevronDown, LogOut } from "lucide-react";
|
||||
import { useId, useMemo, useState } from "react";
|
||||
|
||||
type Account = {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import { ExternalLink, Quote, Code, Image as ImageIcon } from "lucide-react";
|
||||
import { Code, ExternalLink, Quote } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import type React from "react";
|
||||
|
||||
// ============================================================================
|
||||
// TYPES & INTERFACES
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
const cards = [
|
||||
{ title: "Card 1", description: "First card" },
|
||||
{ title: "Card 2", description: "Second card" },
|
||||
{ title: "Card 3", description: "Third card" },
|
||||
];
|
||||
|
||||
export function AnimatedCardStackBaseUI() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center px-8 py-16">
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0 -z-10">
|
||||
<motion.div
|
||||
className="absolute left-1/2 top-10 h-56 w-56 -translate-x-1/2 rounded-full bg-primary/20 blur-[140px]"
|
||||
animate={
|
||||
shouldReduceMotion
|
||||
? undefined
|
||||
: { opacity: [0.25, 0.45, 0.25], scale: [0.9, 1.05, 0.95] }
|
||||
}
|
||||
transition={
|
||||
shouldReduceMotion
|
||||
? undefined
|
||||
: { duration: 11, repeat: Infinity, ease: "easeInOut" }
|
||||
}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute bottom-8 right-12 h-48 w-48 rounded-full bg-emerald-400/25 blur-[150px]"
|
||||
animate={
|
||||
shouldReduceMotion
|
||||
? undefined
|
||||
: { opacity: [0.18, 0.35, 0.18], rotate: [0, 12, 0] }
|
||||
}
|
||||
transition={
|
||||
shouldReduceMotion
|
||||
? undefined
|
||||
: { duration: 13, repeat: Infinity, ease: "linear" }
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{cards.map((card, index) => {
|
||||
const baseScale = 1 - index * 0.04;
|
||||
const baseOffset = index * 18;
|
||||
const hoverMotion = shouldReduceMotion
|
||||
? undefined
|
||||
: { scale: baseScale + 0.06, y: -20 };
|
||||
return (
|
||||
<motion.div
|
||||
key={card.title}
|
||||
initial={{ scale: baseScale, y: baseOffset, opacity: 0 }}
|
||||
animate={{ scale: baseScale, y: baseOffset, opacity: 1 }}
|
||||
whileHover={hoverMotion}
|
||||
whileFocus={hoverMotion}
|
||||
transition={{
|
||||
type: shouldReduceMotion ? "tween" : "spring",
|
||||
stiffness: 260,
|
||||
damping: 26,
|
||||
delay: index * 0.08,
|
||||
}}
|
||||
className="absolute w-64 rounded-3xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
||||
style={{ zIndex: cards.length - index }}
|
||||
tabIndex={0}
|
||||
aria-label={`${card.title}: ${card.description}. Hover or focus to expand this card.`}
|
||||
role="group"
|
||||
>
|
||||
{/* Card replacement */}
|
||||
<div className="rounded-3xl border border-border/60 bg-card/80 shadow-[0_20px_70px_-40px_rgba(15,23,42,0.7)] backdrop-blur-xl transition-shadow duration-300 group-hover:shadow-[0_26px_90px_-45px_rgba(15,23,42,0.8)]">
|
||||
{/* CardHeader replacement */}
|
||||
<div className="flex flex-col space-y-1.5 p-6">
|
||||
{/* CardTitle replacement */}
|
||||
<h3 className="text-base font-semibold text-foreground">
|
||||
{card.title}
|
||||
</h3>
|
||||
{/* CardDescription replacement */}
|
||||
<p className="text-xs uppercase tracking-[0.32em] text-muted-foreground/80">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
{/* CardContent replacement */}
|
||||
<div className="p-6 pt-0">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Hover or focus to surface this panel and bring it to the
|
||||
front.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
200
components/components/cards/baseui/credit-card-baseui.tsx
Normal file
200
components/components/cards/baseui/credit-card-baseui.tsx
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
motion,
|
||||
useMotionValue,
|
||||
useReducedMotion,
|
||||
useSpring,
|
||||
useTransform,
|
||||
} from "framer-motion";
|
||||
import { CreditCard as CreditCardIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
interface CreditCardBaseUIProps {
|
||||
cardNumber?: string;
|
||||
cardholderName?: string;
|
||||
expiryDate?: string;
|
||||
cvv?: string;
|
||||
}
|
||||
|
||||
const gradients = {
|
||||
front:
|
||||
"linear-gradient(145deg, hsl(var(--primary) / 0.45), hsl(var(--primary) / 0.18))",
|
||||
back: "linear-gradient(145deg, hsl(var(--primary) / 0.45), hsl(var(--primary) / 0.18))",
|
||||
};
|
||||
|
||||
const overlayLights = [
|
||||
"radial-gradient(circle at 20% 30%, hsl(var(--foreground) / 0.12), transparent 55%)",
|
||||
"radial-gradient(circle at 80% 20%, hsl(var(--primary) / 0.22), transparent 60%)",
|
||||
"radial-gradient(circle at 50% 80%, hsl(var(--accent) / 0.28), transparent 65%)",
|
||||
];
|
||||
|
||||
export function CreditCardBaseUI({
|
||||
cardNumber = "4532 1234 5678 9010",
|
||||
cardholderName = "JORDAN PARK",
|
||||
expiryDate = "09/27",
|
||||
cvv = "123",
|
||||
}: CreditCardBaseUIProps) {
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
|
||||
const x = useMotionValue(0);
|
||||
const y = useMotionValue(0);
|
||||
|
||||
const rotateX = useSpring(useTransform(y, [-0.5, 0.5], [6, -6]), {
|
||||
stiffness: 280,
|
||||
damping: 28,
|
||||
});
|
||||
const rotateY = useSpring(useTransform(x, [-0.5, 0.5], [-6, 6]), {
|
||||
stiffness: 280,
|
||||
damping: 28,
|
||||
});
|
||||
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const overlayGradient = useMemo(() => overlayLights.join(","), []);
|
||||
|
||||
const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (shouldReduceMotion) return;
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
const mouseX = event.clientX - rect.left;
|
||||
const mouseY = event.clientY - rect.top;
|
||||
const xPct = mouseX / width - 0.5;
|
||||
const yPct = mouseY / height - 0.5;
|
||||
x.set(xPct);
|
||||
y.set(yPct);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
x.set(0);
|
||||
y.set(0);
|
||||
};
|
||||
|
||||
const formatCardNumber = (number: string) => {
|
||||
return number.replace(/(.{4})/g, "$1 ").trim();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8 perspective-1000">
|
||||
<motion.div
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={() => setIsFlipped(!isFlipped)}
|
||||
className="relative h-[240px] w-[380px] cursor-pointer"
|
||||
style={{
|
||||
rotateX,
|
||||
rotateY,
|
||||
transformStyle: "preserve-3d",
|
||||
}}
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.015 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 26 }}
|
||||
>
|
||||
<div className="absolute inset-0 rounded-[1.5rem] bg-foreground/10 blur-2xl" />
|
||||
{/* Front */}
|
||||
<motion.div
|
||||
className="absolute inset-0 flex flex-col justify-between rounded-[1.5rem] p-6 shadow-[0_22px_55px_-30px_rgba(15,23,42,0.75)]"
|
||||
style={{
|
||||
background: gradients.front,
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
}}
|
||||
animate={{ rotateY: isFlipped ? 180 : 0 }}
|
||||
transition={{ duration: 0.55, ease: "easeInOut" }}
|
||||
>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 rounded-[1.5rem]"
|
||||
style={{
|
||||
background: overlayGradient,
|
||||
opacity: shouldReduceMotion ? 0.5 : 1,
|
||||
}}
|
||||
/>
|
||||
<div className="relative flex items-start justify-between">
|
||||
<div className="relative">
|
||||
<div className="flex h-12 w-14 items-center justify-center rounded-lg border border-border/60 bg-foreground/15 backdrop-blur">
|
||||
<div className="h-8 w-10 rounded-md border border-border/40 bg-background/60" />
|
||||
</div>
|
||||
<div className="absolute -bottom-1 left-0 right-0 h-1 rounded-full bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative space-y-2">
|
||||
<span className="text-[10px] uppercase tracking-[0.42em] text-muted-foreground/70">
|
||||
Card Number
|
||||
</span>
|
||||
<p className="font-mono text-2xl tracking-[0.4em] text-foreground/90">
|
||||
{formatCardNumber(cardNumber)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative flex items-end justify-between">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] uppercase tracking-[0.36em] text-muted-foreground/70">
|
||||
Cardholder
|
||||
</span>
|
||||
<p className="text-sm font-semibold uppercase tracking-[0.32em] text-foreground/85">
|
||||
{cardholderName}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1 text-right">
|
||||
<span className="text-[10px] uppercase tracking-[0.36em] text-muted-foreground/70">
|
||||
Expires
|
||||
</span>
|
||||
<p className="text-sm font-semibold text-foreground/85">
|
||||
{expiryDate}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Back */}
|
||||
<motion.div
|
||||
className="absolute inset-0 flex flex-col justify-between rounded-[1.5rem] p-6 shadow-[0_22px_55px_-30px_rgba(15,23,42,0.75)]"
|
||||
style={{
|
||||
background: gradients.back,
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
rotateY: 180,
|
||||
}}
|
||||
animate={{ rotateY: isFlipped ? 0 : 180 }}
|
||||
transition={{ duration: 0.55, ease: "easeInOut" }}
|
||||
>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 rounded-[1.5rem]"
|
||||
style={{
|
||||
background: overlayGradient,
|
||||
opacity: shouldReduceMotion ? 0.5 : 0.9,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute left-0 right-0 top-8 h-12 rounded-sm bg-foreground/10"
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
<div className="relative mt-16 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] uppercase tracking-[0.32em] text-muted-foreground/70">
|
||||
CVV
|
||||
</span>
|
||||
<span className="flex min-w-[80px] items-center justify-center rounded-md border border-border/50 bg-muted px-3 py-1 font-mono text-lg font-semibold text-foreground shadow-[0_6px_18px_-12px_rgba(15,23,42,0.6)]">
|
||||
{cvv}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[10px] uppercase tracking-[0.32em] text-muted-foreground/60">
|
||||
Contact support if you notice unauthorized activity.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative flex items-center justify-end">
|
||||
<CreditCardIcon
|
||||
className="h-6 w-6 text-muted-foreground/70"
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
457
components/components/cards/baseui/detail-task-baseui.tsx
Normal file
457
components/components/cards/baseui/detail-task-baseui.tsx
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton } from "@/components/native/baseui/native-button-baseui";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
Bold,
|
||||
ChevronDown,
|
||||
Italic,
|
||||
List,
|
||||
ListOrdered,
|
||||
Plus,
|
||||
RotateCcw,
|
||||
Save,
|
||||
Underline,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
type Priority = "high" | "medium" | "low";
|
||||
|
||||
type TeamMember = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
initials: string;
|
||||
accent: string;
|
||||
};
|
||||
|
||||
const allMembers: TeamMember[] = [
|
||||
{
|
||||
id: "sophia",
|
||||
name: "Sophia Williams",
|
||||
role: "Product Designer",
|
||||
initials: "SW",
|
||||
accent: "ring-foreground text-foreground",
|
||||
},
|
||||
{
|
||||
id: "liam",
|
||||
name: "Liam Johnson",
|
||||
role: "Design Manager",
|
||||
initials: "LJ",
|
||||
accent: "ring-foreground text-foreground",
|
||||
},
|
||||
{
|
||||
id: "olivia",
|
||||
name: "Olivia Smith",
|
||||
role: "UX Researcher",
|
||||
initials: "OS",
|
||||
accent: "ring-foreground text-foreground",
|
||||
},
|
||||
{
|
||||
id: "mia",
|
||||
name: "Mia Chen",
|
||||
role: "Product Owner",
|
||||
initials: "MC",
|
||||
accent: "ring-foreground text-foreground",
|
||||
},
|
||||
{
|
||||
id: "ethan",
|
||||
name: "Ethan Davis",
|
||||
role: "UI Engineer",
|
||||
initials: "ED",
|
||||
accent: "ring-foreground text-foreground",
|
||||
},
|
||||
];
|
||||
|
||||
const priorityMap: Record<
|
||||
Priority,
|
||||
{ label: string; badge: string; dot: string; description: string }
|
||||
> = {
|
||||
high: {
|
||||
label: "High",
|
||||
badge:
|
||||
"border border-destructive/40 bg-destructive/20 text-destructive dark:text-red-400",
|
||||
dot: "bg-destructive",
|
||||
description: "Requires immediate focus and dedicated resources",
|
||||
},
|
||||
medium: {
|
||||
label: "Medium",
|
||||
badge:
|
||||
"border border-amber-500/30 bg-amber-500/20 text-amber-600 dark:text-amber-400",
|
||||
dot: "bg-amber-500",
|
||||
description: "Important but not blocking other work",
|
||||
},
|
||||
low: {
|
||||
label: "Low",
|
||||
badge:
|
||||
"border border-emerald-500/30 bg-emerald-500/20 text-emerald-600 dark:text-emerald-400",
|
||||
dot: "bg-emerald-500",
|
||||
description: "Nice-to-have improvements to schedule later",
|
||||
},
|
||||
};
|
||||
|
||||
const defaultDescription =
|
||||
"The goal is to update the current design system with the latest components and styles. This includes reviewing existing elements, identifying areas for improvement, and implementing changes to ensure consistency and usability across all platforms.";
|
||||
const maxDescriptionLength = 200;
|
||||
|
||||
export function DetailTaskCardBaseUI() {
|
||||
const [title, setTitle] = useState("Edit Design System");
|
||||
const [priority, setPriority] = useState<Priority>("high");
|
||||
const [assignees, setAssignees] = useState<TeamMember[]>(
|
||||
allMembers.slice(0, 3)
|
||||
);
|
||||
const [description, setDescription] = useState(defaultDescription);
|
||||
const [reminderEnabled, setReminderEnabled] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isSaved, setIsSaved] = useState(false);
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const remainingCharacters = maxDescriptionLength - description.length;
|
||||
|
||||
const availableMembers = useMemo(
|
||||
() =>
|
||||
allMembers.filter(
|
||||
(member) => !assignees.some((assigned) => assigned.id === member.id)
|
||||
),
|
||||
[assignees]
|
||||
);
|
||||
|
||||
const handleRemoveAssignee = (id: string) => {
|
||||
setAssignees((prev) => prev.filter((member) => member.id !== id));
|
||||
};
|
||||
|
||||
const handleAddPerson = () => {
|
||||
if (availableMembers.length === 0) return;
|
||||
const [nextMember] = availableMembers;
|
||||
setAssignees((prev) => [...prev, nextMember]);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setTitle("Edit Design System");
|
||||
setPriority("high");
|
||||
setAssignees(allMembers.slice(0, 3));
|
||||
setDescription(defaultDescription);
|
||||
setReminderEnabled(true);
|
||||
setIsSaved(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (isSaving) return;
|
||||
setIsSaving(true);
|
||||
setIsSaved(false);
|
||||
setTimeout(() => {
|
||||
setIsSaving(false);
|
||||
setIsSaved(true);
|
||||
setTimeout(() => setIsSaved(false), 2000);
|
||||
}, 900);
|
||||
};
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const toolbarIcons = [Bold, Italic, Underline, List, ListOrdered];
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
{/* Card replacement */}
|
||||
<div className="group relative w-full overflow-hidden rounded-2xl border border-border/40 bg-background/60 text-foreground backdrop-blur transition-all hover:border-border/60 hover:shadow-lg">
|
||||
<div className="pointer-events-none absolute inset-0 bg-gradient-to-br from-foreground/[0.04] via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 -z-10" />
|
||||
|
||||
{/* CardHeader replacement */}
|
||||
<div className="relative flex flex-col gap-3 border-b border-border/40 bg-background/40 px-6 py-6">
|
||||
{/* Badge replacement */}
|
||||
<span className="w-fit rounded-full bg-primary/15 px-3 py-1 text-[0.65rem] font-medium uppercase tracking-[0.25em] text-primary transition-colors hover:bg-primary hover:text-primary-foreground">
|
||||
Task Manager
|
||||
</span>
|
||||
{/* CardTitle replacement */}
|
||||
<h3 className="text-sm font-semibold uppercase tracking-[0.25em] text-foreground">
|
||||
Detail Task Overview
|
||||
</h3>
|
||||
{/* CardDescription replacement */}
|
||||
<p className="text-sm text-foreground/70">
|
||||
Keep your task aligned with team priorities and deliverables.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* CardContent replacement */}
|
||||
<div className="space-y-10 px-6 py-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35, ease: "easeOut" }}
|
||||
className="grid gap-6 md:grid-cols-2"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<label
|
||||
htmlFor="task-title"
|
||||
className="text-xs font-semibold uppercase tracking-[0.2em] text-foreground/60"
|
||||
>
|
||||
Title Task
|
||||
</label>
|
||||
{/* Input replacement */}
|
||||
<input
|
||||
id="task-title"
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.target.value)}
|
||||
className="flex h-10 w-full rounded-xl border border-border/40 bg-background/40 px-3 py-2 text-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:border-border/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
aria-describedby="task-title-description"
|
||||
/>
|
||||
<p
|
||||
id="task-title-description"
|
||||
className="text-xs text-foreground/60"
|
||||
>
|
||||
Keep it short and goal oriented.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.2em] text-foreground/60">
|
||||
Priority
|
||||
</span>
|
||||
{/* DropdownMenu replacement */}
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
className="flex w-full items-center justify-between gap-3 rounded-xl border border-border/40 bg-background/40 px-3 py-2 text-sm font-medium text-foreground transition-all hover:border-border/60 hover:bg-background/60"
|
||||
>
|
||||
<span className="flex items-center gap-3">
|
||||
<span
|
||||
className={`h-2.5 w-2.5 rounded-full ${priorityMap[priority].dot}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{priorityMap[priority].label}</span>
|
||||
</span>
|
||||
<ChevronDown
|
||||
className="h-4 w-4 text-foreground/60"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
{dropdownOpen && (
|
||||
<div className="absolute right-0 z-50 mt-2 w-44 rounded-xl border border-border/40 bg-background/70 p-1 backdrop-blur shadow-lg">
|
||||
{(Object.keys(priorityMap) as Priority[]).map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
onClick={() => {
|
||||
setPriority(option);
|
||||
setDropdownOpen(false);
|
||||
}}
|
||||
className="flex w-full items-center justify-between gap-2 rounded-lg px-2 py-1.5 text-sm text-foreground/80 transition-colors hover:bg-background/60 hover:text-foreground"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<span
|
||||
className={`h-2.5 w-2.5 rounded-full ${priorityMap[option].dot}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{priorityMap[option].label}
|
||||
</span>
|
||||
{priority === option ? (
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-[0.65rem] font-medium uppercase tracking-[0.15em] ${priorityMap[option].badge}`}
|
||||
>
|
||||
Selected
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-foreground/60">
|
||||
{priorityMap[priority].description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.2em] text-foreground/60">
|
||||
Assign Task To
|
||||
</span>
|
||||
<span className="rounded-full border border-border/40 bg-background/50 px-3 py-1 text-[0.65rem] font-medium uppercase tracking-[0.25em] text-foreground/70 backdrop-blur transition-colors hover:border-border/60 hover:bg-background/70 hover:text-foreground">
|
||||
Team
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<AnimatePresence>
|
||||
{assignees.map((member) => (
|
||||
<motion.div
|
||||
layout
|
||||
key={member.id}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.9, y: -8 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="flex items-center gap-3 rounded-xl border border-border/40 bg-background/40 px-3 py-2 backdrop-blur transition-colors hover:border-border/60"
|
||||
>
|
||||
<span
|
||||
className={`flex h-8 w-8 items-center justify-center rounded-full bg-background/70 text-xs font-semibold tracking-tight ring-1 ring-border/40 ring-offset-2 ring-offset-background ${member.accent}`}
|
||||
>
|
||||
{member.initials}
|
||||
</span>
|
||||
<div className="flex flex-col text-left">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{member.name}
|
||||
</span>
|
||||
<span className="text-xs text-foreground/60">
|
||||
{member.role}
|
||||
</span>
|
||||
</div>
|
||||
{/* Button replacement */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveAssignee(member.id)}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-lg text-foreground/60 transition-colors hover:bg-background/60 hover:text-foreground"
|
||||
aria-label={`Remove ${member.name} from this task`}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddPerson}
|
||||
disabled={availableMembers.length === 0}
|
||||
className="flex items-center gap-2 rounded-xl border border-dashed border-border/50 bg-transparent px-3 py-2 text-sm text-foreground/80 transition-all hover:border-border/70 hover:bg-background/40 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add another person
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.2em] text-foreground/60">
|
||||
Description
|
||||
</span>
|
||||
<span className="text-xs text-foreground/60">
|
||||
{Math.max(0, remainingCharacters)} / {maxDescriptionLength}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-border/40 bg-background/40 backdrop-blur">
|
||||
<div className="flex items-center gap-1 border-b border-border/40 px-3 py-2 text-foreground/60">
|
||||
{toolbarIcons.map((Icon, index) => (
|
||||
<button
|
||||
key={Icon.displayName ?? index}
|
||||
type="button"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-foreground/60 transition-colors hover:bg-background/60 hover:text-foreground"
|
||||
aria-label={`Formatting option ${Icon.displayName ?? index + 1}`}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* Textarea replacement */}
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(event) =>
|
||||
setDescription(
|
||||
event.target.value.slice(0, maxDescriptionLength)
|
||||
)
|
||||
}
|
||||
className="h-32 w-full resize-none border-0 bg-transparent px-3 py-3 text-sm text-foreground/80 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
aria-label="Task description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 rounded-2xl border border-border/40 bg-background/40 px-4 py-4 backdrop-blur md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<motion.button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-label="Toggle reminder task"
|
||||
aria-checked={reminderEnabled}
|
||||
onClick={() => setReminderEnabled((prev) => !prev)}
|
||||
className={`relative flex h-6 w-12 items-center rounded-full border border-border/50 transition-all ${
|
||||
reminderEnabled ? "bg-primary/20" : "bg-background/60"
|
||||
}`}
|
||||
>
|
||||
<motion.span
|
||||
layout
|
||||
className="absolute left-1 top-1 h-4 w-4 rounded-full bg-primary shadow-lg"
|
||||
animate={{ x: reminderEnabled ? 22 : 0 }}
|
||||
transition={{ type: "spring", stiffness: 400, damping: 32 }}
|
||||
/>
|
||||
</motion.button>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Reminder Task
|
||||
</p>
|
||||
<p className="text-xs text-foreground/60">
|
||||
{reminderEnabled
|
||||
? "We will notify the assignees 24 hours before the due date."
|
||||
: "Enable reminders to keep everyone on track."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="rounded-full border border-border/40 bg-background/60 px-3 py-1 text-[0.65rem] font-medium uppercase tracking-[0.25em] text-foreground/70 backdrop-blur transition-colors hover:border-border/60 hover:bg-background/70 hover:text-foreground">
|
||||
Sprint Q4
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CardFooter replacement */}
|
||||
<div className="flex flex-col gap-4 border-t border-border/40 bg-background/50 px-6 py-5 backdrop-blur sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-2 text-sm text-foreground/60">
|
||||
<RotateCcw className="h-4 w-4" aria-hidden="true" />
|
||||
<span>Need to start over?</span>
|
||||
<button
|
||||
type="button"
|
||||
className="px-0 text-sm text-foreground underline-offset-4 hover:text-primary hover:underline"
|
||||
onClick={handleReset}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<AnimatePresence mode="popLayout" initial={false}>
|
||||
{isSaved ? (
|
||||
<motion.span
|
||||
key="saved"
|
||||
initial={{ opacity: 0, y: 6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -6 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="text-sm text-emerald-600 dark:text-emerald-400"
|
||||
>
|
||||
Saved!
|
||||
</motion.span>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
<NativeButton
|
||||
variant="default"
|
||||
size="default"
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
loading={isSaving}
|
||||
>
|
||||
<Save className="h-4 w-4" aria-hidden="true" />
|
||||
{isSaving ? "Saving..." : "Save Changes"}
|
||||
</NativeButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton } from "@/components/native/baseui/native-button-baseui";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { Check, ShieldCheck, Sparkles, Star, Truck } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
type Plan = {
|
||||
id: string;
|
||||
title: string;
|
||||
price: string;
|
||||
description: string;
|
||||
promise: string;
|
||||
};
|
||||
|
||||
const plans: Plan[] = [
|
||||
{
|
||||
id: "essential",
|
||||
title: "Essential",
|
||||
price: "$48.00",
|
||||
description:
|
||||
"Everyday comfort with signature cushioning and durable materials.",
|
||||
promise: "Ships in 2 business days • 30-day returns",
|
||||
},
|
||||
{
|
||||
id: "plus",
|
||||
title: "Plus",
|
||||
price: "$68.00",
|
||||
description:
|
||||
"Upgraded foam footbed, recycled laces, and breathable knit upper.",
|
||||
promise: "Ships next business day • 45-day returns",
|
||||
},
|
||||
{
|
||||
id: "premium",
|
||||
title: "Premium",
|
||||
price: "$92.00",
|
||||
description:
|
||||
"Adaptive arch support and antimicrobial lining for long days on your feet.",
|
||||
promise: "Priority fulfillment • 60-day returns",
|
||||
},
|
||||
];
|
||||
|
||||
const highlights = [
|
||||
{
|
||||
icon: Sparkles,
|
||||
label: "Best Seller",
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
label: "2-year warranty",
|
||||
},
|
||||
];
|
||||
|
||||
export function EcommerceHighlightCardBaseUI() {
|
||||
const [activePlan, setActivePlan] = useState<Plan>(plans[1]);
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? false : { opacity: 0, y: 24 }}
|
||||
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
|
||||
transition={
|
||||
shouldReduceMotion ? undefined : { duration: 0.45, ease: "easeOut" }
|
||||
}
|
||||
className="relative z-10"
|
||||
>
|
||||
{/* Card replacement */}
|
||||
<div className="group relative overflow-hidden rounded-[28px] border border-border/40 bg-background text-foreground">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 bg-gradient-to-br from-foreground/[0.04] via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 -z-10"
|
||||
/>
|
||||
|
||||
{/* CardHeader replacement */}
|
||||
<div className="relative flex flex-col space-y-1.5 p-6 pb-0">
|
||||
{/* Badge replacement */}
|
||||
<span className="w-fit rounded-full bg-primary/15 px-3 py-1 text-[0.65rem] font-medium uppercase tracking-[0.25em] text-primary transition-colors hover:bg-primary hover:text-primary-foreground">
|
||||
Nimbus Collection
|
||||
</span>
|
||||
<div className="space-y-2">
|
||||
{/* CardTitle replacement */}
|
||||
<h3 className="text-2xl font-semibold tracking-tight">
|
||||
Nimbus Pace Runner
|
||||
</h3>
|
||||
{/* CardDescription replacement */}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Lightweight performance kicks engineered for all-day comfort and
|
||||
momentum.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<div className="flex items-center gap-1 text-primary">
|
||||
{[0, 1, 2, 3, 4].map((star) => (
|
||||
<Star
|
||||
key={star}
|
||||
className="h-4 w-4 fill-current"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-muted-foreground">4.9 • 1,240 reviews</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CardContent replacement */}
|
||||
<div className="relative space-y-6 p-6 pt-6">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{highlights.map(({ icon: Icon, label }) => (
|
||||
<span
|
||||
key={label}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-border/70 bg-muted/60 px-3 py-1 text-xs font-medium text-muted-foreground backdrop-blur"
|
||||
>
|
||||
<Icon
|
||||
className="h-3.5 w-3.5 text-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
||||
Choose your bundle
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{plans.map((plan) => {
|
||||
const isActive = plan.id === activePlan.id;
|
||||
return (
|
||||
/* Button replacement */
|
||||
<button
|
||||
key={plan.id}
|
||||
type="button"
|
||||
onClick={() => setActivePlan(plan)}
|
||||
aria-pressed={isActive}
|
||||
className={`inline-flex items-center justify-center rounded-full border border-border/60 px-4 py-2 text-sm font-medium transition-all ${
|
||||
isActive
|
||||
? "border-primary/40 bg-primary text-primary-foreground shadow-lg"
|
||||
: "bg-background/80 text-foreground hover:border-primary/30 hover:bg-muted"
|
||||
}`}
|
||||
>
|
||||
{plan.title}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activePlan.id}
|
||||
initial={shouldReduceMotion ? false : { opacity: 0, y: 12 }}
|
||||
animate={
|
||||
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
|
||||
}
|
||||
exit={
|
||||
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: -10 }
|
||||
}
|
||||
transition={
|
||||
shouldReduceMotion
|
||||
? undefined
|
||||
: {
|
||||
duration: 0.32,
|
||||
ease: "easeOut",
|
||||
opacity: { duration: 0.25 },
|
||||
}
|
||||
}
|
||||
className="space-y-3 rounded-2xl border border-border/60 bg-card/60 p-4 shadow-sm backdrop-blur"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
{activePlan.title}
|
||||
</p>
|
||||
<p className="text-base font-semibold text-foreground">
|
||||
{activePlan.price}
|
||||
</p>
|
||||
</div>
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-primary/15 px-3 py-1 text-xs font-medium uppercase tracking-[0.2em] text-primary">
|
||||
<Check className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
Selected
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed text-muted-foreground">
|
||||
{activePlan.description}
|
||||
</p>
|
||||
<p className="text-xs font-medium text-foreground">
|
||||
{activePlan.promise}
|
||||
</p>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Separator replacement */}
|
||||
<div className="h-px w-full bg-border/60" />
|
||||
|
||||
<div className="flex flex-col gap-3 rounded-2xl border border-border/60 bg-muted/50 p-4 text-sm text-muted-foreground backdrop-blur-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Truck className="h-4 w-4 text-primary" aria-hidden="true" />
|
||||
<span>Free express shipping on orders over $100</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ShieldCheck
|
||||
className="h-4 w-4 text-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Extended warranty included with every bundle</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CardFooter replacement */}
|
||||
<div className="relative flex flex-col gap-4 border-t border-border/50 bg-muted/40 px-8 py-6 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="text-sm">
|
||||
<p className="font-medium text-foreground">
|
||||
Arrives before Friday
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Checkout before 2PM local time
|
||||
</p>
|
||||
</div>
|
||||
<NativeButton
|
||||
variant="default"
|
||||
size="lg"
|
||||
glow
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Add to bag • {activePlan.price}
|
||||
</NativeButton>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
130
components/components/cards/baseui/glass-blog-card-baseui.tsx
Normal file
130
components/components/cards/baseui/glass-blog-card-baseui.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton } from "@/components/native/baseui/native-button-baseui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Avatar } from "@base-ui/react/avatar";
|
||||
import { motion } from "framer-motion";
|
||||
import { BookOpen, Clock } from "lucide-react";
|
||||
|
||||
interface GlassBlogCardBaseUIProps {
|
||||
title?: string;
|
||||
excerpt?: string;
|
||||
image?: string;
|
||||
author?: {
|
||||
name: string;
|
||||
avatar: string;
|
||||
};
|
||||
date?: string;
|
||||
readTime?: string;
|
||||
tags?: string[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const defaultPost = {
|
||||
title: "The Future of UI Design",
|
||||
excerpt:
|
||||
"Exploring the latest trends in glassmorphism, 3D elements, and micro-interactions.",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=800&q=80",
|
||||
author: {
|
||||
name: "Moumen Soliman",
|
||||
avatar: "https://iimydr2b8o.ufs.sh/f/Zqn6AViLMoTtoUjLg4dAryGEidskK72wBCQA6DNcZH4Xh5b8",
|
||||
},
|
||||
date: "Dec 2, 2025",
|
||||
readTime: "5 min read",
|
||||
tags: ["Design", "UI/UX"],
|
||||
};
|
||||
|
||||
const getInitials = (name: string) =>
|
||||
name
|
||||
.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
|
||||
export function GlassBlogCardBaseUI({
|
||||
title = defaultPost.title,
|
||||
excerpt = defaultPost.excerpt,
|
||||
image = defaultPost.image,
|
||||
author = defaultPost.author,
|
||||
date = defaultPost.date,
|
||||
readTime = defaultPost.readTime,
|
||||
tags = defaultPost.tags,
|
||||
className,
|
||||
}: GlassBlogCardBaseUIProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className={cn("w-full max-w-[400px]", className)}
|
||||
>
|
||||
<div className="group relative h-full overflow-hidden rounded-2xl border border-border/50 bg-card/30 backdrop-blur-md transition-all duration-300 hover:border-primary/50 hover:shadow-xl hover:shadow-primary/10">
|
||||
{/* Image Section */}
|
||||
<div className="relative aspect-[16/9] overflow-hidden">
|
||||
<motion.img
|
||||
src={image}
|
||||
alt={title}
|
||||
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background/80 to-transparent opacity-60 transition-opacity duration-300 group-hover:opacity-40" />
|
||||
|
||||
<div className="absolute bottom-3 left-3 flex gap-2">
|
||||
{tags?.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="inline-flex items-center rounded-md bg-secondary/80 px-2.5 py-0.5 text-xs font-medium text-secondary-foreground backdrop-blur-sm hover:bg-secondary"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Hover Overlay Action */}
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/20 backdrop-blur-[2px] opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<NativeButton variant="default" size="default" glow>
|
||||
<BookOpen className="h-4 w-4" />
|
||||
Read Article
|
||||
</NativeButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Section */}
|
||||
<div className="flex flex-col gap-4 p-5">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-semibold leading-tight tracking-tight text-foreground transition-colors group-hover:text-primary">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="line-clamp-2 text-sm text-muted-foreground">
|
||||
{excerpt}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between border-t border-border/50 pt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{/* BaseUI Avatar */}
|
||||
<Avatar.Root className="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-full border border-border/50">
|
||||
<Avatar.Image src={author.avatar} alt={author.name} />
|
||||
<Avatar.Fallback className="flex h-full w-full items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground">
|
||||
{getInitials(author.name)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div className="flex flex-col text-xs">
|
||||
<span className="font-medium text-foreground">
|
||||
{author.name}
|
||||
</span>
|
||||
<span className="text-muted-foreground">{date}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Clock className="h-3 w-3" />
|
||||
<span>{readTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton } from "@/components/native/baseui/native-button-baseui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { Calendar, CreditCard, Lock } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface GlassCheckoutCardBaseUIProps {
|
||||
amount?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function GlassCheckoutCardBaseUI({
|
||||
amount = 85.8,
|
||||
className,
|
||||
}: GlassCheckoutCardBaseUIProps) {
|
||||
const [paymentMethod, setPaymentMethod] = useState("card");
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className={cn("w-full max-w-[400px]", className)}
|
||||
>
|
||||
{/* Card replacement */}
|
||||
<div className="group relative overflow-hidden rounded-2xl border border-border/50 bg-card/30 backdrop-blur-md transition-all duration-300 hover:border-primary/50 hover:shadow-xl hover:shadow-primary/10">
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
Payment Details
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Complete your purchase securely
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Payment Methods */}
|
||||
<div className="mb-6 grid grid-cols-3 gap-2">
|
||||
{["card", "paypal", "apple"].map((method) => (
|
||||
<button
|
||||
key={method}
|
||||
onClick={() => setPaymentMethod(method)}
|
||||
className={cn(
|
||||
"flex h-12 items-center justify-center rounded-lg border border-border/50 bg-background/50 transition-all hover:bg-background/80",
|
||||
paymentMethod === method &&
|
||||
"border-primary bg-primary/10 text-primary"
|
||||
)}
|
||||
>
|
||||
{method === "card" && <CreditCard className="h-5 w-5" />}
|
||||
{method === "paypal" && (
|
||||
<span className="font-bold italic">Pay</span>
|
||||
)}
|
||||
{method === "apple" && (
|
||||
<span className="font-semibold">Pay</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
{/* Label replacement */}
|
||||
<label
|
||||
htmlFor="cardNumber"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Card Number
|
||||
</label>
|
||||
<div className="relative">
|
||||
{/* Input replacement */}
|
||||
<input
|
||||
id="cardNumber"
|
||||
type="text"
|
||||
placeholder="0000 0000 0000 0000"
|
||||
className="flex h-10 w-full rounded-md border border-border/50 bg-background/50 px-3 py-2 pl-10 text-sm backdrop-blur-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus:border-primary/50 focus:bg-background/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<CreditCard className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="expiry"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Expiry Date
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="expiry"
|
||||
type="text"
|
||||
placeholder="MM/YY"
|
||||
className="flex h-10 w-full rounded-md border border-border/50 bg-background/50 px-3 py-2 pl-10 text-sm backdrop-blur-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus:border-primary/50 focus:bg-background/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<Calendar className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="cvc"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
CVC
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="cvc"
|
||||
type="text"
|
||||
placeholder="123"
|
||||
className="flex h-10 w-full rounded-md border border-border/50 bg-background/50 px-3 py-2 pl-10 text-sm backdrop-blur-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus:border-primary/50 focus:bg-background/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<Lock className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Cardholder Name
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="John Doe"
|
||||
className="flex h-10 w-full rounded-md border border-border/50 bg-background/50 px-3 py-2 text-sm backdrop-blur-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus:border-primary/50 focus:bg-background/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NativeButton
|
||||
variant="default"
|
||||
size="lg"
|
||||
glow
|
||||
className="mt-6 w-full"
|
||||
>
|
||||
Pay ${amount.toFixed(2)}
|
||||
</NativeButton>
|
||||
|
||||
<p className="mt-4 text-center text-xs text-muted-foreground">
|
||||
<Lock className="inline-block h-3 w-3 mr-1" />
|
||||
Payments are secure and encrypted
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton } from "@/components/native/baseui/native-button-baseui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight, CreditCard, ShoppingBag } from "lucide-react";
|
||||
|
||||
interface OrderItem {
|
||||
id: string;
|
||||
name: string;
|
||||
price: number;
|
||||
image: string;
|
||||
quantity: number;
|
||||
variant?: string;
|
||||
}
|
||||
|
||||
interface GlassOrderSummaryBaseUIProps {
|
||||
items?: OrderItem[];
|
||||
subtotal?: number;
|
||||
tax?: number;
|
||||
shipping?: number;
|
||||
total?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const defaultItems: OrderItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Premium Icon Pack",
|
||||
price: 29.0,
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1611162617474-5b21e879e113?w=100&q=80",
|
||||
quantity: 1,
|
||||
variant: "Pro License",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "UI Design Kit",
|
||||
price: 49.0,
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1586717791821-3f44a5638d48?w=100&q=80",
|
||||
quantity: 1,
|
||||
variant: "Dark Mode",
|
||||
},
|
||||
];
|
||||
|
||||
export function GlassOrderSummaryBaseUI({
|
||||
items = defaultItems,
|
||||
subtotal = 78.0,
|
||||
tax = 7.8,
|
||||
shipping = 0,
|
||||
total = 85.8,
|
||||
className,
|
||||
}: GlassOrderSummaryBaseUIProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className={cn("w-full max-w-[400px]", className)}
|
||||
>
|
||||
{/* Card replacement */}
|
||||
<div className="group relative overflow-hidden rounded-2xl border border-border/50 bg-card/30 backdrop-blur-md transition-all duration-300 hover:border-primary/50 hover:shadow-xl hover:shadow-primary/10">
|
||||
<div className="p-6">
|
||||
<div className="mb-6 flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<ShoppingBag className="h-4 w-4" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
Order Summary
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{items.map((item) => (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="flex items-center gap-4 rounded-lg p-2 transition-colors hover:bg-background/40"
|
||||
>
|
||||
<div className="h-12 w-12 overflow-hidden rounded-md border border-border/50 bg-background/50">
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.name}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-medium text-foreground">
|
||||
{item.name}
|
||||
</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{item.variant}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
${item.price.toFixed(2)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
x{item.quantity}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Separator replacement */}
|
||||
<div className="my-6 h-px w-full bg-border/50" />
|
||||
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>Subtotal</span>
|
||||
<span>${subtotal.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>Tax</span>
|
||||
<span>${tax.toFixed(2)}</span>
|
||||
</div>
|
||||
{shipping > 0 && (
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>Shipping</span>
|
||||
<span>${shipping.toFixed(2)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 flex justify-between text-base font-semibold text-foreground">
|
||||
<span>Total</span>
|
||||
<span>${total.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NativeButton
|
||||
variant="default"
|
||||
size="lg"
|
||||
glow
|
||||
className="mt-6 w-full"
|
||||
>
|
||||
<CreditCard className="h-4 w-4" />
|
||||
Pay Now
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</NativeButton>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
142
components/components/cards/baseui/glass-wallet-card-baseui.tsx
Normal file
142
components/components/cards/baseui/glass-wallet-card-baseui.tsx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
ArrowDownLeft,
|
||||
ArrowUpRight,
|
||||
CreditCard,
|
||||
TrendingUp,
|
||||
Wallet,
|
||||
} from "lucide-react";
|
||||
|
||||
interface GlassWalletCardBaseUIProps {
|
||||
balance?: string;
|
||||
currency?: string;
|
||||
address?: string;
|
||||
trend?: string;
|
||||
trendUp?: boolean;
|
||||
cardHolder?: string;
|
||||
expiry?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const defaultWallet = {
|
||||
balance: "12,345.67",
|
||||
currency: "ETH",
|
||||
address: "0x71C...9A23",
|
||||
trend: "+5.2%",
|
||||
trendUp: true,
|
||||
cardHolder: "Alex Morgan",
|
||||
expiry: "12/28",
|
||||
};
|
||||
|
||||
export function GlassWalletCardBaseUI({
|
||||
balance = defaultWallet.balance,
|
||||
currency = defaultWallet.currency,
|
||||
address = defaultWallet.address,
|
||||
trend = defaultWallet.trend,
|
||||
trendUp = defaultWallet.trendUp,
|
||||
cardHolder = defaultWallet.cardHolder,
|
||||
expiry = defaultWallet.expiry,
|
||||
className,
|
||||
}: GlassWalletCardBaseUIProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className={cn("w-full max-w-[400px]", className)}
|
||||
>
|
||||
{/* Card replacement */}
|
||||
<div className="group relative h-56 overflow-hidden rounded-2xl border border-border/50 bg-gradient-to-br from-card/80 via-card/40 to-card/20 backdrop-blur-md transition-all duration-300 hover:border-primary/50 hover:shadow-xl hover:shadow-primary/10">
|
||||
{/* Abstract Background Shapes */}
|
||||
<div className="absolute -right-16 -top-16 h-48 w-48 rounded-full bg-primary/10 blur-3xl transition-all duration-500 group-hover:bg-primary/20" />
|
||||
<div className="absolute -bottom-16 -left-16 h-48 w-48 rounded-full bg-secondary/10 blur-3xl transition-all duration-500 group-hover:bg-secondary/20" />
|
||||
|
||||
<div className="relative flex h-full flex-col justify-between p-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary backdrop-blur-sm">
|
||||
<Wallet className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground">
|
||||
Total Balance
|
||||
</p>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<h3 className="text-2xl font-bold tracking-tight text-foreground">
|
||||
${balance}
|
||||
</h3>
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
{currency}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Badge replacement */}
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-full border border-border/50 bg-background/50 px-2.5 py-0.5 text-xs font-medium backdrop-blur-sm",
|
||||
trendUp ? "text-green-500" : "text-red-500"
|
||||
)}
|
||||
>
|
||||
<TrendingUp className="mr-1 h-3 w-3" />
|
||||
{trend}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Card Details */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<CreditCard className="h-4 w-4" />
|
||||
<span>•••• •••• •••• 4242</span>
|
||||
</div>
|
||||
<span className="font-mono text-xs text-muted-foreground">
|
||||
{expiry}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{cardHolder}
|
||||
</span>
|
||||
<span className="rounded-full bg-secondary/50 px-3 py-1 font-mono text-xs text-secondary-foreground backdrop-blur-sm">
|
||||
{address}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hover Actions Overlay */}
|
||||
<div className="absolute inset-0 flex items-center justify-center gap-6 bg-background/60 backdrop-blur-[2px] opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex flex-col items-center gap-2"
|
||||
>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/25">
|
||||
<ArrowUpRight className="h-6 w-6" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-foreground">Send</span>
|
||||
</motion.button>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex flex-col items-center gap-2"
|
||||
>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-secondary text-secondary-foreground shadow-lg">
|
||||
<ArrowDownLeft className="h-6 w-6" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
Receive
|
||||
</span>
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
40
components/components/cards/baseui/hover-expand-baseui.tsx
Normal file
40
components/components/cards/baseui/hover-expand-baseui.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
export function HoverExpandCardBaseUI() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<motion.div
|
||||
whileHover={shouldReduceMotion ? undefined : { y: -10, scale: 1.015 }}
|
||||
whileFocus={shouldReduceMotion ? undefined : { y: -10, scale: 1.015 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 26 }}
|
||||
className="group rounded-3xl border border-border/60 bg-card/80 p-6 backdrop-blur-xl transition-shadow duration-300 hover:shadow-[0_28px_90px_-40px_rgba(15,23,42,0.75)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
||||
tabIndex={0}
|
||||
role="group"
|
||||
aria-label="Hover expand card demonstrating glassmorphic elevation."
|
||||
>
|
||||
<div className="relative mb-4 h-40 overflow-hidden rounded-2xl border border-border/60 bg-gradient-to-br from-foreground/10 via-background/40 to-transparent">
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className="absolute inset-0 bg-[radial-gradient(circle_at_30%_20%,rgba(255,255,255,0.4),transparent_55%),radial-gradient(circle_at_80%_0%,rgba(79,70,229,0.45),transparent_60%)] transition-transform duration-500"
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.05 }}
|
||||
/>
|
||||
</div>
|
||||
{/* Badge replacement */}
|
||||
<span className="mb-3 inline-flex w-fit rounded-full border border-border/60 bg-white/5 px-3 py-1 text-[10px] uppercase tracking-[0.3em] text-muted-foreground">
|
||||
Feature
|
||||
</span>
|
||||
<h3 className="mb-2 text-xl font-semibold text-foreground">
|
||||
Beautiful Card
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-muted-foreground">
|
||||
Hover or focus to gently lift and expand the surface. Animations stay
|
||||
calm but responsive, matching the glassmorphic system.
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
102
components/components/cards/baseui/project-card-baseui.tsx
Normal file
102
components/components/cards/baseui/project-card-baseui.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { ExternalLink, Github } from "lucide-react";
|
||||
|
||||
interface ProjectCardBaseUIProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
image?: string;
|
||||
links?: {
|
||||
demo?: string;
|
||||
github?: string;
|
||||
};
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const defaultProject = {
|
||||
title: "E-Commerce Platform",
|
||||
description:
|
||||
"Full-stack online store with payment integration and inventory management",
|
||||
tags: ["React", "Node.js", "PostgreSQL", "Stripe"],
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1661956602116-aa6865609028?w=800&q=80",
|
||||
links: { demo: "#", github: "#" },
|
||||
};
|
||||
|
||||
export function ProjectCardBaseUI({
|
||||
title = defaultProject.title,
|
||||
description = defaultProject.description,
|
||||
tags = defaultProject.tags,
|
||||
image = defaultProject.image,
|
||||
links = defaultProject.links,
|
||||
className,
|
||||
}: ProjectCardBaseUIProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className={cn("w-full max-w-[400px]", className)}
|
||||
>
|
||||
{/* Card replacement */}
|
||||
<div className="group relative h-full overflow-hidden rounded-2xl border border-border/50 bg-card/50 backdrop-blur-sm transition-all duration-300 hover:border-primary/50 hover:shadow-xl hover:shadow-primary/10">
|
||||
<div className="relative aspect-video overflow-hidden">
|
||||
<motion.img
|
||||
src={image}
|
||||
alt={title}
|
||||
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background/90 via-background/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
||||
|
||||
<div className="absolute inset-0 flex items-center justify-center gap-4 opacity-0 transition-all duration-300 group-hover:opacity-100">
|
||||
{links?.demo && (
|
||||
<motion.a
|
||||
href={links.demo}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/25 backdrop-blur-md"
|
||||
title="View Demo"
|
||||
>
|
||||
<ExternalLink className="h-5 w-5" />
|
||||
</motion.a>
|
||||
)}
|
||||
{links?.github && (
|
||||
<motion.a
|
||||
href={links.github}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-secondary text-secondary-foreground shadow-lg backdrop-blur-md"
|
||||
title="View Code"
|
||||
>
|
||||
<Github className="h-5 w-5" />
|
||||
</motion.a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5">
|
||||
<h3 className="mb-2 text-xl font-semibold tracking-tight text-foreground transition-colors group-hover:text-primary">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mb-4 line-clamp-2 text-sm text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{tags?.map((tag, index) => (
|
||||
/* Badge replacement */
|
||||
<span
|
||||
key={index}
|
||||
className="inline-flex items-center rounded-md bg-secondary/50 px-2 py-0.5 text-xs font-normal text-secondary-foreground hover:bg-secondary"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { useId, useMemo, useState } from "react";
|
||||
import {
|
||||
AnimatePresence,
|
||||
motion,
|
||||
|
|
@ -8,6 +7,7 @@ import {
|
|||
type Variants,
|
||||
} from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { useId, useMemo, useState } from "react";
|
||||
|
||||
interface Slide {
|
||||
id: number;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
|
@ -8,6 +7,7 @@ import {
|
|||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
const cards = [
|
||||
{ title: "Card 1", description: "First card" },
|
||||
|
|
@ -3,13 +3,12 @@
|
|||
import {
|
||||
motion,
|
||||
useMotionValue,
|
||||
useReducedMotion,
|
||||
useSpring,
|
||||
useTransform,
|
||||
useReducedMotion,
|
||||
} from "framer-motion";
|
||||
import { useState, useMemo } from "react";
|
||||
import { CreditCard as CreditCardIcon } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useMemo, useState } from "react";
|
||||
interface CreditCardProps {
|
||||
cardNumber?: string;
|
||||
cardholderName?: string;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
|
@ -10,16 +10,15 @@ import {
|
|||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
Bold,
|
||||
ChevronDown,
|
||||
|
|
@ -32,6 +31,7 @@ import {
|
|||
Underline,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
type Priority = "high" | "medium" | "low";
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
|
@ -10,10 +10,10 @@ import {
|
|||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { Check, ShieldCheck, Sparkles, Star, Truck } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
type Plan = {
|
||||
id: string;
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Clock, ArrowRight, BookOpen } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { BookOpen, Clock } from "lucide-react";
|
||||
|
||||
interface GlassBlogCardProps {
|
||||
title?: string;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { CreditCard, Lock, Calendar } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { Calendar, CreditCard, Lock } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface GlassCheckoutCardProps {
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { ShoppingBag, CreditCard, ArrowRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight, CreditCard, ShoppingBag } from "lucide-react";
|
||||
|
||||
interface OrderItem {
|
||||
id: string;
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
ArrowDownLeft,
|
||||
Wallet,
|
||||
ArrowUpRight,
|
||||
CreditCard,
|
||||
TrendingUp,
|
||||
Wallet,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface GlassWalletCardProps {
|
||||
balance?: string;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
export function HoverExpandCard() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ExternalLink, Github } from "lucide-react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { ExternalLink, Github } from "lucide-react";
|
||||
|
||||
interface ProjectCardProps {
|
||||
title?: string;
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
ChevronDown,
|
||||
|
|
@ -16,6 +15,7 @@ import {
|
|||
Star,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
type DropdownType = "share" | "quick" | "history" | "magic" | "model" | null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Send, User, Bot, Loader2 } from "lucide-react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Bot, Loader2, Send, User } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
type Message = {
|
||||
id: number;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useId, useState } from "react";
|
||||
import { AnimatePresence, motion, type Variants } from "framer-motion";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -11,16 +9,18 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatePresence, motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
MessageSquare,
|
||||
X,
|
||||
Send,
|
||||
Sparkles,
|
||||
Zap,
|
||||
Brain,
|
||||
Code,
|
||||
MessageSquare,
|
||||
Send,
|
||||
Sparkles,
|
||||
X,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useCallback, useId, useState } from "react";
|
||||
|
||||
interface Agent {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { FormEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import {
|
||||
CheckCheck,
|
||||
MoreVertical,
|
||||
|
|
@ -17,6 +16,7 @@ import {
|
|||
Send,
|
||||
Video,
|
||||
} from "lucide-react";
|
||||
import { FormEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
type Message = {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import {
|
||||
MessageCircle,
|
||||
Heart,
|
||||
Share2,
|
||||
MoreHorizontal,
|
||||
CornerDownRight,
|
||||
Send,
|
||||
Smile,
|
||||
Image as ImageIcon,
|
||||
Paperclip,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
CornerDownRight,
|
||||
Heart,
|
||||
Image as ImageIcon,
|
||||
MessageCircle,
|
||||
MoreHorizontal,
|
||||
Paperclip,
|
||||
Send,
|
||||
Share2,
|
||||
Smile,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
"use client";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
AlertTriangle,
|
||||
Award,
|
||||
BookOpen,
|
||||
CheckCircle2,
|
||||
ChevronRight,
|
||||
Clock,
|
||||
Download,
|
||||
FileText,
|
||||
Languages,
|
||||
Lock,
|
||||
Play,
|
||||
PlayCircle,
|
||||
Share2,
|
||||
Star,
|
||||
Timer,
|
||||
Users,
|
||||
Video,
|
||||
CheckCircle2,
|
||||
Lock,
|
||||
FileText,
|
||||
PlayCircle,
|
||||
Timer,
|
||||
AlertTriangle,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// ============================================================================
|
||||
// TYPES & INTERFACES
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Check } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const planFeatures = [
|
||||
"Unlimited projects",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { FormEvent, useState } from "react";
|
||||
|
||||
export function GlassForgotPasswordCard() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Avatar } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Avatar } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { UploadCloud } from "lucide-react";
|
||||
import { FormEvent, useState } from "react";
|
||||
|
||||
export function GlassProfileSettingsCard() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { FormEvent } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Github, Twitter, Chrome } from "lucide-react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Chrome, Github, Twitter } from "lucide-react";
|
||||
import { FormEvent } from "react";
|
||||
|
||||
const socialProviders = [
|
||||
{ name: "Google", icon: Chrome },
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Github, Twitter, Chrome } from "lucide-react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Chrome, Github, Twitter } from "lucide-react";
|
||||
import { FormEvent, useState } from "react";
|
||||
|
||||
const socialProviders = [
|
||||
{ name: "Google", icon: Chrome },
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { useMemo, useState, FormEvent } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { FormEvent, useMemo, useState } from "react";
|
||||
|
||||
const CODE_LENGTH = 6;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
export function FloatingLabelInput() {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,57 +1,43 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DndContext,
|
||||
DragOverlay,
|
||||
closestCorners,
|
||||
defaultDropAnimationSideEffects,
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
DragOverEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
DropAnimation,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragStartEvent,
|
||||
DragOverEvent,
|
||||
DragEndEvent,
|
||||
defaultDropAnimationSideEffects,
|
||||
DropAnimation,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
horizontalListSortingStrategy,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
useSortable,
|
||||
horizontalListSortingStrategy,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import {
|
||||
Plus,
|
||||
MoreHorizontal,
|
||||
Calendar,
|
||||
MessageSquare,
|
||||
Paperclip,
|
||||
GripVertical,
|
||||
Trash2,
|
||||
Search,
|
||||
Filter,
|
||||
LayoutGrid,
|
||||
List,
|
||||
Clock,
|
||||
CheckCircle2,
|
||||
AlertCircle,
|
||||
Filter,
|
||||
MessageSquare,
|
||||
MoreHorizontal,
|
||||
Paperclip,
|
||||
Plus,
|
||||
Search,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
// --- Types ---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
|
||||
const checklistItems = [
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Reorder } from "framer-motion";
|
||||
import { GripVertical } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const initialItems = [
|
||||
{ id: 1, text: "First item" },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import {
|
||||
Newspaper,
|
||||
Clock,
|
||||
ArrowRight,
|
||||
TrendingUp,
|
||||
MessageSquare,
|
||||
Share2,
|
||||
Bookmark,
|
||||
MoreHorizontal,
|
||||
Search,
|
||||
Filter,
|
||||
} from "lucide-react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
ArrowRight,
|
||||
Bookmark,
|
||||
Clock,
|
||||
Filter,
|
||||
MessageSquare,
|
||||
MoreHorizontal,
|
||||
Newspaper,
|
||||
Search,
|
||||
Share2,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import {
|
||||
AlertCircle,
|
||||
|
|
@ -11,9 +13,7 @@ import {
|
|||
LucideIcon,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
type NotificationType = "success" | "error" | "warning" | "info";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
MapPin,
|
||||
Link as LinkIcon,
|
||||
Calendar,
|
||||
MoreHorizontal,
|
||||
UserPlus,
|
||||
} from "lucide-react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Calendar,
|
||||
Link as LinkIcon,
|
||||
MapPin,
|
||||
MoreHorizontal,
|
||||
UserPlus,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export function ProfilePage() {
|
||||
const [isFollowing, setIsFollowing] = useState(false);
|
||||
|
|
|
|||
284
components/components/resumes/baseui/minimal-resume-baseui.tsx
Normal file
284
components/components/resumes/baseui/minimal-resume-baseui.tsx
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton as Button } from "@/components/native/baseui/native-button-baseui";
|
||||
import { Avatar } from "@base-ui/react/avatar";
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Github,
|
||||
Globe,
|
||||
Mail,
|
||||
MapPin,
|
||||
Twitter,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
const RESUME_DATA = {
|
||||
profile: {
|
||||
name: "Jordan Lee",
|
||||
role: "Product Engineer",
|
||||
avatarGradient:
|
||||
"from-gray-100 to-gray-200 dark:from-zinc-800 dark:to-zinc-700",
|
||||
socials: [
|
||||
{ name: "GitHub", icon: Github, url: "#" },
|
||||
{ name: "Twitter", icon: Twitter, url: "#" },
|
||||
{ name: "Email", icon: Mail, url: "#" },
|
||||
],
|
||||
},
|
||||
contact: {
|
||||
website: "jordan.dev",
|
||||
location: "San Francisco, CA",
|
||||
},
|
||||
stack: [
|
||||
"Next.js",
|
||||
"React",
|
||||
"TypeScript",
|
||||
"Node.js",
|
||||
"PostgreSQL",
|
||||
"Tailwind",
|
||||
],
|
||||
education: {
|
||||
degree: "BS Computer Science",
|
||||
school: "University of Technology",
|
||||
period: "2016 - 2020",
|
||||
},
|
||||
about: {
|
||||
text: "Product engineer focused on building accessible, pixel-perfect user interfaces. Currently designing systems at",
|
||||
company: "Vercel",
|
||||
suffix: ". Passionate about web performance and developer experience.",
|
||||
},
|
||||
experience: [
|
||||
{
|
||||
role: "Senior Frontend Engineer",
|
||||
company: "Vercel",
|
||||
period: "2022 - Present",
|
||||
description: [
|
||||
"Led the migration to Next.js App Router, improving load times by 35%.",
|
||||
"Architected the internal component library used by 50+ engineers.",
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "Software Engineer",
|
||||
company: "Stripe",
|
||||
period: "2020 - 2022",
|
||||
description: [
|
||||
"Built the new checkout experience, increasing conversion by 12%.",
|
||||
"Implemented automated accessibility testing pipelines.",
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
title: "Geist UI",
|
||||
desc: "React component library inspired by Vercel's design system.",
|
||||
stars: "4.2k",
|
||||
},
|
||||
{
|
||||
title: "Next.js Conf",
|
||||
desc: "Interactive conference platform built with Next.js and WebGL.",
|
||||
stars: "2.1k",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function Separator({ className }: { className?: string }) {
|
||||
return (
|
||||
<div className={`w-full h-[1px] ${className}`} role="separator" />
|
||||
);
|
||||
}
|
||||
|
||||
export function MinimalResumeBaseUI() {
|
||||
const container: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.05,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const item: Variants = {
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.4, ease: "easeOut" } },
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="bg-white dark:bg-zinc-950 border border-gray-200 dark:border-zinc-800 rounded-lg overflow-hidden transition-colors duration-300"
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 min-h-[800px]">
|
||||
{/* Sidebar */}
|
||||
<div className="md:col-span-4 bg-gray-50/50 dark:bg-zinc-900/50 border-r border-gray-200 dark:border-zinc-800 p-8 flex flex-col gap-8 transition-colors duration-300">
|
||||
{/* Profile Header */}
|
||||
<motion.div variants={item} className="space-y-4">
|
||||
<Avatar.Root
|
||||
className={`flex h-16 w-16 shrink-0 overflow-hidden rounded-full border border-gray-200 dark:border-zinc-700 bg-gradient-to-tr ${RESUME_DATA.profile.avatarGradient}`}
|
||||
>
|
||||
{/* Using Fallback/div as there is no image but we want to use the primitive */}
|
||||
<Avatar.Fallback className="flex h-full w-full items-center justify-center rounded-full bg-transparent" />
|
||||
</Avatar.Root>
|
||||
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold tracking-tight text-gray-900 dark:text-zinc-100">
|
||||
{RESUME_DATA.profile.name}
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-zinc-400 font-mono mt-1">
|
||||
{RESUME_DATA.profile.role}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{RESUME_DATA.profile.socials.map((social, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-md border-gray-200 dark:border-zinc-800 hover:border-gray-300 dark:hover:border-zinc-700 hover:bg-white dark:hover:bg-zinc-800 text-gray-500 dark:text-zinc-400 hover:text-black dark:hover:text-zinc-100 transition-all p-0"
|
||||
>
|
||||
<social.icon className="h-4 w-4" />
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator className="bg-gray-200 dark:bg-zinc-800" />
|
||||
|
||||
{/* Contact */}
|
||||
<motion.div variants={item} className="space-y-3">
|
||||
<h2 className="text-xs font-mono font-medium uppercase tracking-wider text-gray-500 dark:text-zinc-500">
|
||||
Contact
|
||||
</h2>
|
||||
<div className="space-y-2 text-sm">
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center gap-2 text-gray-600 dark:text-zinc-400 hover:text-black dark:hover:text-zinc-200 transition-colors group"
|
||||
>
|
||||
<Globe className="h-3.5 w-3.5 text-gray-400 dark:text-zinc-500 group-hover:text-gray-600 dark:group-hover:text-zinc-300" />
|
||||
<span>{RESUME_DATA.contact.website}</span>
|
||||
</a>
|
||||
<div className="flex items-center gap-2 text-gray-600 dark:text-zinc-400">
|
||||
<MapPin className="h-3.5 w-3.5 text-gray-400 dark:text-zinc-500" />
|
||||
<span>{RESUME_DATA.contact.location}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Skills */}
|
||||
<motion.div variants={item} className="space-y-3">
|
||||
<h2 className="text-xs font-mono font-medium uppercase tracking-wider text-gray-500 dark:text-zinc-500">
|
||||
Stack
|
||||
</h2>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{RESUME_DATA.stack.map((skill) => (
|
||||
<span
|
||||
key={skill}
|
||||
className="px-2 py-1 bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded text-[11px] font-medium text-gray-600 dark:text-zinc-400"
|
||||
>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Education */}
|
||||
<motion.div variants={item} className="space-y-3">
|
||||
<h2 className="text-xs font-mono font-medium uppercase tracking-wider text-gray-500 dark:text-zinc-500">
|
||||
Education
|
||||
</h2>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900 dark:text-zinc-100 text-sm">
|
||||
{RESUME_DATA.education.degree}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-zinc-400 mt-0.5">
|
||||
{RESUME_DATA.education.school}
|
||||
</p>
|
||||
<p className="text-[10px] text-gray-400 dark:text-zinc-500 mt-1 font-mono">
|
||||
{RESUME_DATA.education.period}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="md:col-span-8 p-8 md:p-10 space-y-10 bg-white dark:bg-zinc-950 transition-colors duration-300">
|
||||
{/* About */}
|
||||
<motion.div variants={item} className="space-y-3">
|
||||
<h2 className="text-xs font-mono font-medium uppercase tracking-wider text-gray-500 dark:text-zinc-500">
|
||||
About
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-zinc-400 leading-relaxed max-w-2xl">
|
||||
{RESUME_DATA.about.text}{" "}
|
||||
<span className="font-medium text-gray-900 dark:text-zinc-100">
|
||||
{RESUME_DATA.about.company}
|
||||
</span>
|
||||
{RESUME_DATA.about.suffix}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Experience */}
|
||||
<motion.div variants={item} className="space-y-6">
|
||||
<h2 className="text-xs font-mono font-medium uppercase tracking-wider text-gray-500 dark:text-zinc-500">
|
||||
Experience
|
||||
</h2>
|
||||
|
||||
<div className="space-y-8">
|
||||
{RESUME_DATA.experience.map((job, index) => (
|
||||
<div key={index} className="group">
|
||||
<div className="flex justify-between items-baseline mb-1">
|
||||
<h3 className="font-medium text-gray-900 dark:text-zinc-100 text-sm">
|
||||
{job.role}
|
||||
</h3>
|
||||
<span className="text-xs font-mono text-gray-400 dark:text-zinc-500">
|
||||
{job.period}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-zinc-400 mb-3">
|
||||
{job.company}
|
||||
</p>
|
||||
<ul className="text-sm text-gray-600 dark:text-zinc-400 space-y-1.5 list-disc list-outside ml-3 marker:text-gray-300 dark:marker:text-zinc-700">
|
||||
{job.description.map((desc, i) => (
|
||||
<li key={i}>{desc}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Projects */}
|
||||
<motion.div variants={item} className="space-y-4">
|
||||
<h2 className="text-xs font-mono font-medium uppercase tracking-wider text-gray-500 dark:text-zinc-500">
|
||||
Projects
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{RESUME_DATA.projects.map((project, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="group p-3 rounded-md border border-gray-200 dark:border-zinc-800 hover:border-gray-300 dark:hover:border-zinc-700 hover:bg-gray-50/50 dark:hover:bg-zinc-900/50 transition-all cursor-pointer"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-1.5">
|
||||
<h3 className="font-medium text-sm text-gray-900 dark:text-zinc-100">
|
||||
{project.title}
|
||||
</h3>
|
||||
<ArrowUpRight className="h-3.5 w-3.5 text-gray-400 dark:text-zinc-500 group-hover:text-gray-600 dark:group-hover:text-zinc-300 transition-colors" />
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-zinc-400 line-clamp-2 mb-2">
|
||||
{project.desc}
|
||||
</p>
|
||||
<div className="flex items-center gap-1 text-[10px] text-gray-400 dark:text-zinc-500 font-mono">
|
||||
<span>★ {project.stars}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton as Button } from "@/components/native/baseui/native-button-baseui";
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import { Download, Globe, Linkedin, Mail, MapPin, Phone } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
const RESUME_DATA = {
|
||||
personalInfo: {
|
||||
name: "Alex Morgan",
|
||||
title: "Senior Software Engineer",
|
||||
summary:
|
||||
"Results-oriented Senior Software Engineer with over 8 years of experience in designing, developing, and deploying scalable web applications. Proven track record of leadership, mentoring junior developers, and driving technical innovation. Expert in full-stack development with a focus on React, Node.js, and cloud architecture.",
|
||||
email: "alex.morgan@example.com",
|
||||
phone: "(555) 123-4567",
|
||||
location: "New York, NY",
|
||||
linkedin: "linkedin.com/in/alexmorgan",
|
||||
website: "alexmorgan.dev",
|
||||
},
|
||||
experience: [
|
||||
{
|
||||
company: "TechCorp Solutions",
|
||||
role: "Senior Software Engineer",
|
||||
period: "2020 - Present",
|
||||
description: [
|
||||
"Architected and led the development of a microservices-based e-commerce platform, handling over 100k daily active users.",
|
||||
"Reduced server costs by 40% through optimization of AWS infrastructure and implementation of serverless functions.",
|
||||
"Mentored a team of 5 junior developers, conducting code reviews and facilitating technical workshops.",
|
||||
],
|
||||
},
|
||||
{
|
||||
company: "Innovate Inc.",
|
||||
role: "Software Engineer",
|
||||
period: "2017 - 2020",
|
||||
description: [
|
||||
"Developed key features for the company's flagship SaaS product using React and Redux.",
|
||||
"Implemented a real-time notification system using WebSockets, improving user engagement by 25%.",
|
||||
"Collaborated with product managers and designers to define requirements and deliver high-quality user experiences.",
|
||||
],
|
||||
},
|
||||
],
|
||||
skills: {
|
||||
languages:
|
||||
"JavaScript (ES6+), TypeScript, Python, React, Next.js, Node.js, Express, Django",
|
||||
tools:
|
||||
"AWS (EC2, Lambda, S3), Docker, Kubernetes, Git, CI/CD (GitHub Actions), PostgreSQL, MongoDB",
|
||||
},
|
||||
education: {
|
||||
school: "University of Technology",
|
||||
degree: "Bachelor of Science in Computer Science",
|
||||
period: "2013 - 2017",
|
||||
},
|
||||
certifications: [
|
||||
"AWS Certified Solutions Architect – Associate",
|
||||
"Meta Front-End Developer Professional Certificate",
|
||||
],
|
||||
};
|
||||
|
||||
function Separator({ className }: { className?: string }) {
|
||||
return (
|
||||
<div className={`w-full h-[1px] bg-border ${className}`} role="separator" />
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfessionalResumeBaseUI() {
|
||||
const fadeIn: Variants = {
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" } },
|
||||
};
|
||||
|
||||
const staggerContainer: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<motion.div
|
||||
variants={staggerContainer}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="p-8 md:p-12 space-y-8"
|
||||
>
|
||||
{/* Header */}
|
||||
<motion.div variants={fadeIn} className="text-center space-y-4">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-foreground tracking-tight">
|
||||
{RESUME_DATA.personalInfo.name}
|
||||
</h1>
|
||||
<p className="text-lg text-muted-foreground font-sans uppercase tracking-widest text-sm">
|
||||
{RESUME_DATA.personalInfo.title}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4 text-sm text-muted-foreground font-sans pt-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Mail className="h-4 w-4" />
|
||||
<span>{RESUME_DATA.personalInfo.email}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Phone className="h-4 w-4" />
|
||||
<span>{RESUME_DATA.personalInfo.phone}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<MapPin className="h-4 w-4" />
|
||||
<span>{RESUME_DATA.personalInfo.location}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Linkedin className="h-4 w-4" />
|
||||
<span>{RESUME_DATA.personalInfo.linkedin}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Globe className="h-4 w-4" />
|
||||
<span>{RESUME_DATA.personalInfo.website}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Summary */}
|
||||
<motion.div variants={fadeIn} className="space-y-3">
|
||||
<h2 className="text-xl font-bold text-foreground border-b-2 border-foreground pb-1 inline-block">
|
||||
Professional Summary
|
||||
</h2>
|
||||
<p className="text-card-foreground leading-relaxed font-sans text-sm md:text-base">
|
||||
{RESUME_DATA.personalInfo.summary}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Experience */}
|
||||
<motion.div variants={fadeIn} className="space-y-6">
|
||||
<h2 className="text-xl font-bold text-foreground border-b-2 border-foreground pb-1 inline-block">
|
||||
Experience
|
||||
</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
{RESUME_DATA.experience.map((job, index) => (
|
||||
<div key={index}>
|
||||
<div className="flex justify-between items-baseline mb-1">
|
||||
<h3 className="text-lg font-bold text-foreground">
|
||||
{job.company}
|
||||
</h3>
|
||||
<span className="text-sm font-sans text-muted-foreground">
|
||||
{job.period}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-card-foreground font-medium italic mb-2">
|
||||
{job.role}
|
||||
</p>
|
||||
<ul className="list-disc list-outside ml-5 space-y-1 text-card-foreground font-sans text-sm">
|
||||
{job.description.map((item, i) => (
|
||||
<li key={i}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Skills */}
|
||||
<motion.div variants={fadeIn} className="space-y-3">
|
||||
<h2 className="text-xl font-bold text-foreground border-b-2 border-foreground pb-1 inline-block">
|
||||
Technical Skills
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 font-sans text-sm">
|
||||
<div>
|
||||
<span className="font-bold text-foreground block mb-1">
|
||||
Languages & Frameworks:
|
||||
</span>
|
||||
<p className="text-card-foreground">
|
||||
{RESUME_DATA.skills.languages}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-foreground block mb-1">
|
||||
Tools & Platforms:
|
||||
</span>
|
||||
<p className="text-card-foreground">{RESUME_DATA.skills.tools}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Education */}
|
||||
<motion.div variants={fadeIn} className="space-y-3">
|
||||
<h2 className="text-xl font-bold text-foreground border-b-2 border-foreground pb-1 inline-block">
|
||||
Education
|
||||
</h2>
|
||||
<div>
|
||||
<div className="flex justify-between items-baseline">
|
||||
<h3 className="text-lg font-bold text-foreground">
|
||||
{RESUME_DATA.education.school}
|
||||
</h3>
|
||||
<span className="text-sm font-sans text-muted-foreground">
|
||||
{RESUME_DATA.education.period}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-card-foreground font-sans">
|
||||
{RESUME_DATA.education.degree}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Certifications */}
|
||||
<motion.div variants={fadeIn} className="space-y-3">
|
||||
<h2 className="text-xl font-bold text-foreground border-b-2 border-foreground pb-1 inline-block">
|
||||
Certifications
|
||||
</h2>
|
||||
<ul className="list-disc list-outside ml-5 space-y-1 text-card-foreground font-sans text-sm">
|
||||
{RESUME_DATA.certifications.map((cert, index) => (
|
||||
<li key={index}>{cert}</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
|
||||
<div className="pt-8 flex justify-center">
|
||||
<Button className="gap-2">
|
||||
<Download className="h-4 w-4" />
|
||||
Download PDF
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
437
components/components/resumes/baseui/resume-card-baseui.tsx
Normal file
437
components/components/resumes/baseui/resume-card-baseui.tsx
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton as Button } from "@/components/native/baseui/native-button-baseui";
|
||||
import { Avatar } from "@base-ui/react/avatar";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Download,
|
||||
Github,
|
||||
Globe,
|
||||
Linkedin,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
const AVATAR_IMAGE_URL =
|
||||
"https://iimydr2b8o.ufs.sh/f/Zqn6AViLMoTtoUjLg4dAryGEidskK72wBCQA6DNcZH4Xh5b8";
|
||||
|
||||
const RESUME_DATA = {
|
||||
personalInfo: {
|
||||
name: "John Doe",
|
||||
title: "Senior Full Stack Developer",
|
||||
summary:
|
||||
"Passionate developer with 8+ years of experience building scalable web applications. Specializing in React, Node.js, and cloud architecture.",
|
||||
location: "San Francisco, CA",
|
||||
email: "john.doe@example.com",
|
||||
phone: "+1 (555) 123-4567",
|
||||
avatarUrl: AVATAR_IMAGE_URL,
|
||||
initials: "JD",
|
||||
socials: [
|
||||
{ name: "GitHub", icon: Github, url: "#" },
|
||||
{ name: "LinkedIn", icon: Linkedin, url: "#" },
|
||||
{ name: "Portfolio", icon: Globe, url: "#" },
|
||||
],
|
||||
},
|
||||
experience: [
|
||||
{
|
||||
role: "Senior Full Stack Developer",
|
||||
company: "Tech Corp Inc.",
|
||||
period: "2021 - Present",
|
||||
location: "San Francisco, CA",
|
||||
achievements: [
|
||||
"Led the frontend team in rebuilding the core product using Next.js 14 and React, improving performance by 40%",
|
||||
"Architected and implemented a microservices-based backend using Node.js and GraphQL",
|
||||
"Mentored 5 junior developers and conducted weekly code review sessions",
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "Full Stack Developer",
|
||||
company: "StartUp Ltd.",
|
||||
period: "2019 - 2021",
|
||||
location: "Remote",
|
||||
achievements: [
|
||||
"Developed and maintained multiple client-facing applications with 99.9% uptime",
|
||||
"Implemented CI/CD pipelines using GitHub Actions, reducing deployment time by 60%",
|
||||
"Built a real-time analytics dashboard using React and WebSockets",
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "Junior Developer",
|
||||
company: "Digital Agency",
|
||||
period: "2017 - 2019",
|
||||
location: "New York, NY",
|
||||
achievements: [
|
||||
"Contributed to 20+ client projects using React, Vue.js, and WordPress",
|
||||
"Optimized web applications for SEO and accessibility (WCAG 2.1 AA)",
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
name: "E-commerce Platform",
|
||||
tech: ["Next.js", "Stripe", "Prisma"],
|
||||
desc: "A full-featured online store with real-time inventory management and payment processing.",
|
||||
stars: "2.3k",
|
||||
},
|
||||
{
|
||||
name: "Task Management App",
|
||||
tech: ["React", "Firebase", "Tailwind"],
|
||||
desc: "Collaborative task manager with drag-and-drop interface and real-time updates.",
|
||||
stars: "1.8k",
|
||||
},
|
||||
{
|
||||
name: "Analytics Dashboard",
|
||||
tech: ["Vue.js", "D3.js", "Node.js"],
|
||||
desc: "Real-time data visualization platform with custom chart components.",
|
||||
stars: "1.2k",
|
||||
},
|
||||
{
|
||||
name: "Social Media API",
|
||||
tech: ["GraphQL", "PostgreSQL", "Redis"],
|
||||
desc: "Scalable REST and GraphQL API serving 100k+ daily requests.",
|
||||
stars: "890",
|
||||
},
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
category: "Frontend",
|
||||
skills: ["React", "Next.js", "TypeScript", "Tailwind CSS", "Vue.js"],
|
||||
},
|
||||
{
|
||||
category: "Backend",
|
||||
skills: ["Node.js", "Express", "GraphQL", "PostgreSQL", "MongoDB"],
|
||||
},
|
||||
{
|
||||
category: "DevOps & Tools",
|
||||
skills: ["Docker", "AWS", "CI/CD", "Git", "Vercel"],
|
||||
},
|
||||
],
|
||||
education: [
|
||||
{
|
||||
degree: "BS Computer Science",
|
||||
institution: "University of Technology",
|
||||
period: "2015 - 2019",
|
||||
extra: "GPA: 3.8/4.0",
|
||||
},
|
||||
{
|
||||
degree: "Full Stack Web Development",
|
||||
institution: "Code Academy Bootcamp",
|
||||
period: "2019",
|
||||
extra: "Certificate",
|
||||
},
|
||||
{
|
||||
degree: "AWS Certified Solutions Architect",
|
||||
institution: "Amazon Web Services",
|
||||
period: "2022",
|
||||
extra: null,
|
||||
},
|
||||
],
|
||||
languages: [
|
||||
{ lang: "English", level: "Native", percentage: 100 },
|
||||
{ lang: "Spanish", level: "Professional", percentage: 75 },
|
||||
{ lang: "French", level: "Intermediate", percentage: 50 },
|
||||
{ lang: "Mandarin", level: "Basic", percentage: 30 },
|
||||
],
|
||||
};
|
||||
|
||||
function Separator({ className }: { className?: string }) {
|
||||
return (
|
||||
<div className={`w-full h-[1px] bg-border ${className}`} role="separator" />
|
||||
);
|
||||
}
|
||||
|
||||
function Badge({
|
||||
children,
|
||||
variant = "default",
|
||||
className = "",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
variant?: "default" | "secondary" | "outline";
|
||||
className?: string;
|
||||
}) {
|
||||
const baseStyles =
|
||||
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors";
|
||||
const variantStyles = {
|
||||
default: "bg-primary text-primary-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground",
|
||||
outline: "border border-input bg-background text-foreground",
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`${baseStyles} ${variantStyles[variant]} ${className}`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function ResumeCardBaseUI() {
|
||||
const container = {
|
||||
hidden: { opacity: 0 },
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.08,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const item = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
show: { opacity: 1, y: 0 },
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto p-6 md:p-8 bg-background">
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="space-y-12"
|
||||
>
|
||||
{/* Header Section */}
|
||||
<motion.div variants={item} className="space-y-6">
|
||||
<div className="flex flex-col md:flex-row gap-6 items-center md:items-start">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Avatar.Root className="w-24 h-24 md:w-28 md:h-28 relative flex shrink-0 overflow-hidden rounded-full">
|
||||
<Avatar.Image
|
||||
src={RESUME_DATA.personalInfo.avatarUrl}
|
||||
className="aspect-square h-full w-full object-cover"
|
||||
/>
|
||||
<Avatar.Fallback className="flex h-full w-full items-center justify-center rounded-full bg-muted text-lg font-semibold">
|
||||
{RESUME_DATA.personalInfo.initials}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex-1 text-center md:text-left space-y-3">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-foreground tracking-tight">
|
||||
{RESUME_DATA.personalInfo.name}
|
||||
</h1>
|
||||
<p className="text-lg text-muted-foreground font-medium mt-1">
|
||||
{RESUME_DATA.personalInfo.title}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground max-w-2xl leading-relaxed">
|
||||
{RESUME_DATA.personalInfo.summary}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center md:justify-start gap-x-4 gap-y-2 text-sm text-muted-foreground pt-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>{RESUME_DATA.personalInfo.location}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Mail className="w-4 h-4" />
|
||||
<span>{RESUME_DATA.personalInfo.email}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Phone className="w-4 h-4" />
|
||||
<span>{RESUME_DATA.personalInfo.phone}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center md:justify-start gap-2 pt-2">
|
||||
{RESUME_DATA.personalInfo.socials.map((social, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 h-9"
|
||||
>
|
||||
<social.icon className="w-4 h-4" />
|
||||
{social.name}
|
||||
</Button>
|
||||
))}
|
||||
<Button size="sm" className="gap-2 h-9">
|
||||
<Download className="w-4 h-4" />
|
||||
Download PDF
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Experience */}
|
||||
<motion.div variants={item} className="space-y-6">
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||||
Experience
|
||||
</h2>
|
||||
|
||||
<div className="space-y-8">
|
||||
{RESUME_DATA.experience.map((job, index) => (
|
||||
<div key={index} className="space-y-3">
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-2">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
{job.role}
|
||||
</h3>
|
||||
<p className="font-medium">{job.company}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{job.location}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="secondary" className="w-fit">
|
||||
{job.period}
|
||||
</Badge>
|
||||
</div>
|
||||
<ul className="space-y-1.5 text-sm text-muted-foreground">
|
||||
{job.achievements.map((achievement, i) => (
|
||||
<li key={i} className="flex gap-2">
|
||||
<span className="text-accent mt-0.5">•</span>
|
||||
<span>{achievement}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{index < RESUME_DATA.experience.length - 1 && (
|
||||
<Separator className="mt-6" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Projects */}
|
||||
<motion.div variants={item} className="space-y-6">
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||||
Featured Projects
|
||||
</h2>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-6">
|
||||
{RESUME_DATA.projects.map((project, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
whileHover={{ y: -4 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="p-5 rounded-lg bg-accent/5 hover:bg-accent/10 transition-colors cursor-pointer space-y-3"
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<h3 className="font-semibold text-foreground">
|
||||
{project.name}
|
||||
</h3>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Star className="w-3 h-3 fill-accent text-accent" />
|
||||
<span>{project.stars}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{project.desc}</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{project.tech.map((tech) => (
|
||||
<Badge key={tech} variant="outline" className="text-xs">
|
||||
{tech}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Skills */}
|
||||
<motion.div variants={item} className="space-y-6">
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2">Skills</h2>
|
||||
|
||||
<div className="space-y-5">
|
||||
{RESUME_DATA.skills.map((group, index) => (
|
||||
<div key={index}>
|
||||
<h4 className="text-sm font-semibold text-muted-foreground mb-3">
|
||||
{group.category}
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{group.skills.map((skill) => (
|
||||
<Badge
|
||||
key={skill}
|
||||
variant="secondary"
|
||||
className="hover:bg-accent hover:text-accent-foreground transition-colors cursor-default"
|
||||
>
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Education */}
|
||||
<motion.div variants={item} className="space-y-6">
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||||
Education
|
||||
</h2>
|
||||
|
||||
<div className="space-y-5">
|
||||
{RESUME_DATA.education.map((edu, index) => (
|
||||
<div key={index}>
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-2">
|
||||
<div>
|
||||
<h3 className="font-semibold text-foreground">
|
||||
{edu.degree}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{edu.institution}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{edu.period}
|
||||
</span>
|
||||
</div>
|
||||
{edu.extra && (
|
||||
<Badge variant="outline" className="mt-2 text-xs">
|
||||
{edu.extra}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Languages */}
|
||||
<motion.div variants={item} className="space-y-6">
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||||
Languages
|
||||
</h2>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-6">
|
||||
{RESUME_DATA.languages.map((language, index) => (
|
||||
<div key={index} className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{language.lang}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{language.level}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-accent/20 h-2 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${language.percentage}%` }}
|
||||
transition={{ duration: 1, delay: 0.5 + index * 0.1 }}
|
||||
className="bg-accent h-full rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
536
components/components/resumes/baseui/standard-resume-baseui.tsx
Normal file
536
components/components/resumes/baseui/standard-resume-baseui.tsx
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
"use client";
|
||||
|
||||
import { NativeButton as Button } from "@/components/native/baseui/native-button-baseui";
|
||||
import { Avatar } from "@base-ui/react/avatar";
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import {
|
||||
Briefcase,
|
||||
Building2,
|
||||
Code2,
|
||||
Download,
|
||||
ExternalLink,
|
||||
Globe,
|
||||
GraduationCap,
|
||||
Languages,
|
||||
Linkedin,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
const AVATAR_IMAGE_URL =
|
||||
"https://iimydr2b8o.ufs.sh/f/Zqn6AViLMoTtoUjLg4dAryGEidskK72wBCQA6DNcZH4Xh5b8";
|
||||
|
||||
const RESUME_DATA = {
|
||||
personalInfo: {
|
||||
name: "Jordan Davis",
|
||||
title: "Senior Full Stack Engineer",
|
||||
summary:
|
||||
"Passionate developer with 5+ years of experience building scalable web applications. Dedicated to writing clean, efficient code and creating intuitive user experiences that delight users.",
|
||||
email: "jordan.davis@example.com",
|
||||
phone: "(555) 987-6543",
|
||||
location: "San Francisco, CA",
|
||||
avatarUrl: AVATAR_IMAGE_URL,
|
||||
initials: "JD",
|
||||
},
|
||||
experience: [
|
||||
{
|
||||
role: "Senior Frontend Engineer",
|
||||
company: "TechFlow Systems",
|
||||
period: "2021 - Present",
|
||||
description: [
|
||||
"Spearheaded the migration of legacy codebase to Next.js, improving load times by 40%.",
|
||||
"Implemented a comprehensive design system using Tailwind CSS and Storybook, reducing development time by 25%.",
|
||||
"Mentored junior developers and established code quality standards through rigorous code reviews.",
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "Software Developer",
|
||||
company: "Creative Solutions Inc.",
|
||||
period: "2018 - 2021",
|
||||
description: [
|
||||
"Developed and maintained client-facing web applications using React and Redux.",
|
||||
"Collaborated with UX designers to implement responsive and accessible interfaces compliant with WCAG 2.1.",
|
||||
"Integrated third-party APIs for payment processing and data visualization.",
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
title: "E-commerce Dashboard",
|
||||
type: "Open Source",
|
||||
description:
|
||||
"A comprehensive analytics dashboard for online retailers featuring real-time data visualization, inventory management, and sales forecasting.",
|
||||
tech: ["React", "D3.js", "Node.js"],
|
||||
},
|
||||
{
|
||||
title: "Task Management App",
|
||||
type: "SaaS",
|
||||
description:
|
||||
"Collaborative project management tool with real-time updates, team chat functionality, and automated workflow triggers.",
|
||||
tech: ["Vue.js", "Firebase", "Tailwind"],
|
||||
},
|
||||
],
|
||||
education: [
|
||||
{
|
||||
degree: "Master of Computer Science",
|
||||
school: "Stanford University",
|
||||
year: "2016 - 2018",
|
||||
},
|
||||
{
|
||||
degree: "BS in Software Engineering",
|
||||
school: "MIT",
|
||||
year: "2012 - 2016",
|
||||
},
|
||||
],
|
||||
skills: {
|
||||
frontend: ["React", "Next.js", "TypeScript", "Tailwind", "Framer Motion"],
|
||||
backend: ["Node.js", "PostgreSQL", "GraphQL", "Redis"],
|
||||
tools: ["Git", "Docker", "AWS", "Figma"],
|
||||
},
|
||||
languages: [
|
||||
{ name: "English", level: "Native" },
|
||||
{ name: "Spanish", level: "Fluent" },
|
||||
{ name: "French", level: "Intermediate" },
|
||||
],
|
||||
};
|
||||
|
||||
function Separator({ className }: { className?: string }) {
|
||||
return (
|
||||
<div className={`w-full h-[1px] bg-border/60 ${className}`} role="separator" />
|
||||
);
|
||||
}
|
||||
|
||||
function Badge({
|
||||
children,
|
||||
variant = "default",
|
||||
className = "",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
variant?: "default" | "secondary" | "outline";
|
||||
className?: string;
|
||||
}) {
|
||||
const baseStyles =
|
||||
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors";
|
||||
const variantStyles = {
|
||||
default: "bg-primary text-primary-foreground shadow-sm",
|
||||
secondary: "bg-secondary text-secondary-foreground",
|
||||
outline: "border border-input bg-background text-foreground",
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`${baseStyles} ${variantStyles[variant]} ${className}`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function Card({
|
||||
children,
|
||||
className = "",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`rounded-lg border bg-card text-card-foreground ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CardContent({
|
||||
children,
|
||||
className = "",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={`p-6 ${className}`}>{children}</div>;
|
||||
}
|
||||
|
||||
export function StandardResumeBaseUI() {
|
||||
const containerVariants: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.1,
|
||||
delayChildren: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.5, ease: "easeOut" },
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="bg-card text-card-foreground rounded-2xl shadow-xl border border-border/50 overflow-hidden"
|
||||
>
|
||||
{/* Header Section with Gradient */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="relative bg-gradient-to-br from-primary/5 via-muted/50 to-background p-8 md:p-12 border-b border-border/60"
|
||||
>
|
||||
<div className="relative z-10 flex flex-col md:flex-row gap-8 items-center md:items-start text-center md:text-left">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
transition={{ type: "spring", stiffness: 300 }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="absolute inset-0 rounded-full bg-primary/20 blur-xl transform scale-110" />
|
||||
<Avatar.Root className="h-32 w-32 md:h-40 md:w-40 border-4 border-background shadow-2xl relative flex shrink-0 overflow-hidden rounded-full">
|
||||
<Avatar.Image
|
||||
src={RESUME_DATA.personalInfo.avatarUrl}
|
||||
className="aspect-square h-full w-full object-cover"
|
||||
/>
|
||||
<Avatar.Fallback className="flex h-full w-full items-center justify-center rounded-full bg-primary/10 text-primary text-4xl font-bold">
|
||||
{RESUME_DATA.personalInfo.initials}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex-1 space-y-4">
|
||||
<div>
|
||||
<h1 className="text-4xl md:text-5xl font-extrabold tracking-tight text-foreground mb-2">
|
||||
{RESUME_DATA.personalInfo.name}
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl text-primary font-medium tracking-wide">
|
||||
{RESUME_DATA.personalInfo.title}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground max-w-2xl mx-auto md:mx-0 leading-relaxed text-base md:text-lg">
|
||||
{RESUME_DATA.personalInfo.summary}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center md:justify-start gap-3 pt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 h-9 rounded-full bg-background/50 backdrop-blur-sm hover:bg-primary/10 hover:text-primary hover:border-primary/50 transition-all duration-300"
|
||||
>
|
||||
<Mail className="h-3.5 w-3.5" />
|
||||
<span>{RESUME_DATA.personalInfo.email}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 h-9 rounded-full bg-background/50 backdrop-blur-sm hover:bg-primary/10 hover:text-primary hover:border-primary/50 transition-all duration-300"
|
||||
>
|
||||
<Phone className="h-3.5 w-3.5" />
|
||||
<span>{RESUME_DATA.personalInfo.phone}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 h-9 rounded-full bg-background/50 backdrop-blur-sm hover:bg-primary/10 hover:text-primary hover:border-primary/50 transition-all duration-300"
|
||||
>
|
||||
<MapPin className="h-3.5 w-3.5" />
|
||||
<span>{RESUME_DATA.personalInfo.location}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 min-w-[160px]">
|
||||
<Button className="w-full gap-2 shadow-lg shadow-primary/20 hover:shadow-primary/40 transition-all duration-300 rounded-full">
|
||||
<Download className="h-4 w-4" />
|
||||
Download CV
|
||||
</Button>
|
||||
<div className="flex gap-2 justify-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded-full transition-all duration-300"
|
||||
>
|
||||
<Linkedin className="h-5 w-5" />
|
||||
<span className="sr-only">LinkedIn</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded-full transition-all duration-300"
|
||||
>
|
||||
<Globe className="h-5 w-5" />
|
||||
<span className="sr-only">Website</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-0 lg:divide-x divide-border/60">
|
||||
{/* Main Content Column */}
|
||||
<div className="lg:col-span-2 p-8 md:p-10 space-y-10">
|
||||
{/* Experience Section */}
|
||||
<motion.section
|
||||
variants={itemVariants}
|
||||
aria-labelledby="experience-heading"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl text-primary shadow-sm">
|
||||
<Briefcase className="h-5 w-5" />
|
||||
</div>
|
||||
<h2
|
||||
id="experience-heading"
|
||||
className="text-2xl font-bold text-foreground tracking-tight"
|
||||
>
|
||||
Work Experience
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8 relative pl-2">
|
||||
{RESUME_DATA.experience.map((job, index) => (
|
||||
<div key={index} className="relative pl-8 group">
|
||||
<div className="absolute left-0 top-1.5 h-5 w-5 rounded-full border-4 border-background bg-primary shadow-md group-hover:scale-125 transition-transform duration-300" />
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-baseline mb-2">
|
||||
<h3 className="font-bold text-lg text-foreground group-hover:text-primary transition-colors">
|
||||
{job.role}
|
||||
</h3>
|
||||
<Badge variant="secondary" className="font-medium w-fit">
|
||||
{job.period}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-primary font-semibold text-sm mb-3">
|
||||
<Building2 className="h-3.5 w-3.5" />
|
||||
<span>{job.company}</span>
|
||||
</div>
|
||||
<ul className="list-disc list-outside ml-4 space-y-2 text-sm text-muted-foreground/90 leading-relaxed">
|
||||
{job.description.map((item, i) => (
|
||||
<li
|
||||
key={i}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.replace(
|
||||
/(Next\.js|Tailwind CSS|React)/g,
|
||||
'<span class="font-medium text-foreground">$1</span>'
|
||||
),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
<Separator className="bg-border/60" />
|
||||
|
||||
{/* Projects Section */}
|
||||
<motion.section
|
||||
variants={itemVariants}
|
||||
aria-labelledby="projects-heading"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl text-primary shadow-sm">
|
||||
<Code2 className="h-5 w-5" />
|
||||
</div>
|
||||
<h2
|
||||
id="projects-heading"
|
||||
className="text-2xl font-bold text-foreground tracking-tight"
|
||||
>
|
||||
Featured Projects
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5">
|
||||
{RESUME_DATA.projects.map((project, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
whileHover={{ y: -4 }}
|
||||
transition={{ type: "spring", stiffness: 300 }}
|
||||
>
|
||||
<Card className="bg-gradient-to-br from-card to-muted/20 border-border/50 hover:border-primary/50 hover:shadow-lg transition-all duration-300">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-bold text-lg">
|
||||
{project.title}
|
||||
</h3>
|
||||
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
<Badge
|
||||
variant={
|
||||
project.type === "Open Source"
|
||||
? "default"
|
||||
: "outline"
|
||||
}
|
||||
className="text-[10px] uppercase tracking-wider font-bold shadow-sm"
|
||||
>
|
||||
{project.type}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4 leading-relaxed">
|
||||
{project.description}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tech.map((tech, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
variant="secondary"
|
||||
className="text-[10px] bg-primary/5 text-primary border-primary/10"
|
||||
>
|
||||
{tech}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.section>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Column */}
|
||||
<div className="bg-muted/10 p-8 md:p-10 space-y-10 h-full">
|
||||
{/* Education */}
|
||||
<motion.section
|
||||
variants={itemVariants}
|
||||
aria-labelledby="education-heading"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<GraduationCap className="h-5 w-5 text-primary" />
|
||||
<h2
|
||||
id="education-heading"
|
||||
className="text-lg font-bold text-foreground tracking-tight uppercase"
|
||||
>
|
||||
Education
|
||||
</h2>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{RESUME_DATA.education.map((edu, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative pl-4 border-l-2 border-primary/20"
|
||||
>
|
||||
<h3 className="font-bold text-base">{edu.degree}</h3>
|
||||
<div className="text-sm text-primary font-medium mb-1">
|
||||
{edu.school}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground font-medium uppercase tracking-wider">
|
||||
{edu.year}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
<Separator className="bg-border/60" />
|
||||
|
||||
{/* Skills */}
|
||||
<motion.section
|
||||
variants={itemVariants}
|
||||
aria-labelledby="skills-heading"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<Code2 className="h-5 w-5 text-primary" />
|
||||
<h2
|
||||
id="skills-heading"
|
||||
className="text-lg font-bold text-foreground tracking-tight uppercase"
|
||||
>
|
||||
Skills
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary" />{" "}
|
||||
Frontend
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{RESUME_DATA.skills.frontend.map((skill, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
className="bg-background hover:bg-primary hover:text-primary-foreground text-foreground border-border transition-colors duration-300"
|
||||
>
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary" />{" "}
|
||||
Backend
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{RESUME_DATA.skills.backend.map((skill, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
className="bg-background hover:bg-primary hover:text-primary-foreground text-foreground border-border transition-colors duration-300"
|
||||
>
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary" />{" "}
|
||||
Tools
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{RESUME_DATA.skills.tools.map((skill, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
className="bg-background hover:bg-primary hover:text-primary-foreground text-foreground border-border transition-colors duration-300"
|
||||
>
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
<Separator className="bg-border/60" />
|
||||
|
||||
{/* Languages */}
|
||||
<motion.section
|
||||
variants={itemVariants}
|
||||
aria-labelledby="languages-heading"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<Languages className="h-5 w-5 text-primary" />
|
||||
<h2
|
||||
id="languages-heading"
|
||||
className="text-lg font-bold text-foreground tracking-tight uppercase"
|
||||
>
|
||||
Languages
|
||||
</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{RESUME_DATA.languages.map((lang, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex justify-between items-center p-2 rounded-lg bg-background/50 border border-border/50"
|
||||
>
|
||||
<span className="font-medium">{lang.name}</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{lang.level}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.section>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Github,
|
||||
Twitter,
|
||||
Mail,
|
||||
Link as LinkIcon,
|
||||
MapPin,
|
||||
Globe,
|
||||
Mail,
|
||||
MapPin,
|
||||
Twitter,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const RESUME_DATA = {
|
||||
profile: {
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import { Mail, Phone, MapPin, Linkedin, Globe, Download } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import { Download, Globe, Linkedin, Mail, MapPin, Phone } from "lucide-react";
|
||||
|
||||
const RESUME_DATA = {
|
||||
personalInfo: {
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
"use client";
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Download,
|
||||
Mail,
|
||||
Phone,
|
||||
MapPin,
|
||||
Github,
|
||||
Linkedin,
|
||||
Globe,
|
||||
Linkedin,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const RESUME_DATA = {
|
||||
personalInfo: {
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import {
|
||||
Mail,
|
||||
Phone,
|
||||
MapPin,
|
||||
Linkedin,
|
||||
Globe,
|
||||
Briefcase,
|
||||
Building2,
|
||||
Code2,
|
||||
Download,
|
||||
ExternalLink,
|
||||
Building2,
|
||||
Briefcase,
|
||||
Globe,
|
||||
GraduationCap,
|
||||
Code2,
|
||||
Languages,
|
||||
Linkedin,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
|
||||
const RESUME_DATA = {
|
||||
personalInfo: {
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { motion, useMotionValue, animate } from "framer-motion";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { animate, motion, useMotionValue } from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight, Clock } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface CardData {
|
||||
id: number;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { motion, useMotionValue, useTransform, animate } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { animate, motion, useMotionValue, useTransform } from "framer-motion";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface SliderProps {
|
||||
min?: number;
|
||||
|
|
|
|||
|
|
@ -2,34 +2,34 @@
|
|||
|
||||
import type React from "react";
|
||||
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
Download,
|
||||
Settings,
|
||||
BarChart3,
|
||||
Activity,
|
||||
Users,
|
||||
TrendingUp,
|
||||
Clock,
|
||||
Menu,
|
||||
BarChart3,
|
||||
ChevronRight,
|
||||
TrendingDown,
|
||||
Clock,
|
||||
DollarSign,
|
||||
Download,
|
||||
Menu,
|
||||
Percent,
|
||||
Settings,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
Users,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
|
||||
// ============================================================================
|
||||
// TYPES & INTERFACES
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
DollarSign,
|
||||
BarChart3,
|
||||
ChevronRight,
|
||||
Building2,
|
||||
Activity,
|
||||
} from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Activity,
|
||||
BarChart3,
|
||||
Building2,
|
||||
ChevronRight,
|
||||
DollarSign,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Stock {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
||||
export function AnimatedTabs() {
|
||||
const tabs = ["Account", "Password", "Settings"];
|
||||
|
|
|
|||
|
|
@ -2,18 +2,9 @@
|
|||
|
||||
import type React from "react";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
} from "recharts";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
CalendarDays,
|
||||
Cloud,
|
||||
|
|
@ -27,10 +18,18 @@ import {
|
|||
Sun,
|
||||
Sunrise,
|
||||
Sunset,
|
||||
Thermometer,
|
||||
Umbrella,
|
||||
Wind,
|
||||
} from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
} from "recharts";
|
||||
|
||||
type WeatherCondition = "sunny" | "rain" | "cloudy" | "storm";
|
||||
|
||||
|
|
@ -688,8 +687,8 @@ export function WeatherDashboard(): React.ReactElement {
|
|||
color: "hsl(var(--foreground))",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
formatter={(value: number, key: string) => [
|
||||
value.toString() + "°",
|
||||
formatter={(value: any, key: any) => [
|
||||
value?.toString() + "°",
|
||||
key === "feelsLike" ? "Feels like" : "Temperature",
|
||||
]}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
type ChartView = "monthly" | "yearly";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { motion, useMotionValue, useTransform, animate } from "framer-motion";
|
||||
import { useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { animate, motion, useMotionValue, useTransform } from "framer-motion";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function AnimatedProgress() {
|
||||
const progress = useMotionValue(0);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const items = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { GithubIcon } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Check } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export function AnimatedCheckbox() {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
const options = ["Daily", "Weekly", "Monthly", "Never"];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { ChevronDown, Check } from "lucide-react";
|
||||
|
||||
const options = ["React", "Vue", "Angular", "Svelte", "Next.js"];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
const daysInMonth = Array.from({ length: 28 }, (_, i) => i + 1);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
Check,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
User,
|
||||
MapPin,
|
||||
Settings,
|
||||
FileCheck,
|
||||
} from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
Check,
|
||||
FileCheck,
|
||||
MapPin,
|
||||
Settings,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
// ============================================================================
|
||||
// ANIMATION VARIANTS
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { use, useMemo, Suspense } from "react";
|
||||
import { Star } from "lucide-react";
|
||||
import { Suspense, use, useMemo } from "react";
|
||||
|
||||
const GITHUB_REPO = "moumen-soliman/uitripled";
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue