feat: integrate library design with shadcn components and improved contrast
- Integrated library-layouts.tsx design into main page.tsx - Replaced all custom styled elements with shadcn/ui components: * Button component with variants (outline, ghost, default) and sizes (sm, icon, icon-sm) * Input component for search field * Card and CardContent for image dropzone - Removed all inline styles in favor of Tailwind utility classes - Implemented proper shadcn semantic color tokens: * bg-background/text-foreground for main content * bg-muted/text-muted-foreground for secondary elements * Maintains warm creative palette with orange/amber accents - Improved accessibility with high contrast ratios - Added interactive features: * Library view with masonry grid layout * Collections view with grid layout * Expandable image search dropzone * View switching between Library and Collections - Follows shadcn best practices with no custom CSS variables - Supports automatic light/dark mode through shadcn tokens
This commit is contained in:
parent
2172a1c043
commit
11815fc119
@ -1,103 +1,307 @@
|
||||
import Image from "next/image";
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Search, Grid, List, User, Plus, Image, Upload } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
const [activeView, setActiveView] = useState('library');
|
||||
const [showDropzone, setShowDropzone] = useState(false);
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
// Mock data for library items
|
||||
const mockItems = [
|
||||
{ id: 1, height: 200, width: 'normal' },
|
||||
{ id: 2, height: 280, width: 'wide' },
|
||||
{ id: 3, height: 160, width: 'normal' },
|
||||
{ id: 4, height: 240, width: 'normal' },
|
||||
{ id: 5, height: 200, width: 'normal' },
|
||||
{ id: 6, height: 300, width: 'wide' },
|
||||
{ id: 7, height: 180, width: 'normal' },
|
||||
{ id: 8, height: 220, width: 'normal' },
|
||||
{ id: 9, height: 260, width: 'wide' },
|
||||
{ id: 10, height: 190, width: 'normal' },
|
||||
{ id: 11, height: 240, width: 'normal' },
|
||||
{ id: 12, height: 200, width: 'wide' },
|
||||
{ id: 13, height: 220, width: 'normal' },
|
||||
{ id: 14, height: 180, width: 'normal' },
|
||||
{ id: 15, height: 260, width: 'normal' },
|
||||
{ id: 16, height: 200, width: 'wide' },
|
||||
{ id: 17, height: 240, width: 'normal' },
|
||||
{ id: 18, height: 180, width: 'normal' },
|
||||
];
|
||||
|
||||
// Mock data for collections
|
||||
const mockCollections = [
|
||||
{ id: 1, name: 'Brand Identity', itemCount: 47 },
|
||||
{ id: 2, name: 'UI Inspiration', itemCount: 132 },
|
||||
{ id: 3, name: 'Typography', itemCount: 28 },
|
||||
{ id: 4, name: 'Color Palettes', itemCount: 64 },
|
||||
{ id: 5, name: 'Illustration', itemCount: 89 },
|
||||
{ id: 6, name: 'Photography', itemCount: 156 },
|
||||
{ id: 7, name: 'Web Design', itemCount: 93 },
|
||||
{ id: 8, name: 'Motion Graphics', itemCount: 41 },
|
||||
];
|
||||
|
||||
const LibraryView = () => (
|
||||
<>
|
||||
{/* Compact Horizontal Filters Toolbar */}
|
||||
<div className="px-8 py-3 flex items-center gap-2 border-b">
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<Grid size={14} />
|
||||
Filter
|
||||
</Button>
|
||||
|
||||
<Button size="sm" className="gap-2 font-medium bg-orange-500 hover:bg-orange-600 text-white">
|
||||
All Items
|
||||
<span className="text-xs opacity-70">▼</span>
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
Source
|
||||
<span className="text-xs opacity-70">▼</span>
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
Media type
|
||||
<span className="text-xs opacity-70">▼</span>
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
Date added
|
||||
<span className="text-xs opacity-70">▼</span>
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
Tags
|
||||
<span className="text-xs opacity-70">▼</span>
|
||||
</Button>
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
1,427 items
|
||||
</span>
|
||||
|
||||
<Button variant="outline" size="icon-sm">
|
||||
<Grid size={16} />
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" size="icon-sm">
|
||||
<List size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Main Content - Tightly Packed Masonry Grid */}
|
||||
<main className="flex-1 overflow-auto bg-background">
|
||||
<div className="columns-5 gap-0.5">
|
||||
{mockItems.map((item, index) => {
|
||||
const colorClasses = [
|
||||
'bg-orange-500',
|
||||
'bg-amber-400',
|
||||
'bg-stone-700',
|
||||
'bg-orange-600'
|
||||
];
|
||||
const itemColorClass = colorClasses[index % colorClasses.length];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`mb-0.5 break-inside-avoid group cursor-pointer relative overflow-hidden transition-all hover:opacity-90 ${itemColorClass}`}
|
||||
style={{ height: `${item.height}px` }}
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/60 transition-all flex items-center justify-center opacity-0 group-hover:opacity-100">
|
||||
<Button
|
||||
size="sm"
|
||||
className="hover:scale-105 transition-transform bg-foreground text-background hover:bg-foreground/90"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
View Details
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
</>
|
||||
);
|
||||
|
||||
const CollectionsView = () => (
|
||||
<>
|
||||
{/* Collections Toolbar */}
|
||||
<div className="px-8 py-4 flex items-center justify-between border-b">
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-xl font-bold">Your Collections</h2>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{mockCollections.length} collections
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button size="sm" className="gap-2 font-medium bg-orange-500 hover:bg-orange-600 text-white">
|
||||
<Plus size={16} />
|
||||
New Collection
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Collections Grid */}
|
||||
<main className="flex-1 overflow-auto bg-background">
|
||||
<div className="grid grid-cols-4 gap-0.5">
|
||||
{mockCollections.map((collection, idx) => {
|
||||
const colorClasses = [
|
||||
['bg-orange-500', 'bg-amber-400', 'bg-orange-600', 'bg-orange-500'],
|
||||
['bg-amber-400', 'bg-orange-500', 'bg-amber-400', 'bg-orange-600'],
|
||||
['bg-orange-600', 'bg-orange-500', 'bg-amber-400', 'bg-orange-500'],
|
||||
['bg-orange-500', 'bg-orange-600', 'bg-amber-400', 'bg-orange-500']
|
||||
];
|
||||
const previewColors = colorClasses[idx % colorClasses.length];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={collection.id}
|
||||
className="group cursor-pointer relative"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
{/* Collection Preview Grid */}
|
||||
<div className="aspect-square overflow-hidden transition-all hover:opacity-90 bg-muted">
|
||||
<div className="grid grid-cols-2 grid-rows-2 h-full gap-0">
|
||||
{previewColors.map((colorClass, colorIdx) => (
|
||||
<div
|
||||
key={colorIdx}
|
||||
className={colorClass}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Hover Overlay with Info */}
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/70 transition-all flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 p-4">
|
||||
<h3 className="font-bold text-base mb-2 text-center text-foreground">
|
||||
{collection.name}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2 text-sm mb-4 text-muted-foreground">
|
||||
<Image size={14} />
|
||||
<span>{collection.itemCount} items</span>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
className="hover:scale-105 transition-transform bg-foreground text-background hover:bg-foreground/90"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
Open Collection
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Create New Collection Card */}
|
||||
<div className="aspect-square cursor-pointer transition-all hover:opacity-80 flex flex-col items-center justify-center gap-3 bg-muted">
|
||||
<div className="w-12 h-12 flex items-center justify-center bg-orange-500 text-white">
|
||||
<Plus size={24} />
|
||||
</div>
|
||||
<span className="font-medium text-sm text-muted-foreground">
|
||||
Create Collection
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen flex flex-col bg-background text-foreground">
|
||||
{/* Header */}
|
||||
<header className="border-b">
|
||||
<div className="px-8 py-6 flex items-center justify-between">
|
||||
<div className="text-2xl font-bold tracking-tight">INSPIRATION</div>
|
||||
|
||||
{/* Prominent Central Search with Image Toggle */}
|
||||
<div className="flex-1 max-w-2xl mx-12 flex gap-3">
|
||||
<div className="relative flex-1">
|
||||
<Search
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||
size={20}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search by idea, concept, or visual..."
|
||||
className="w-full py-4 pl-12 pr-4 text-base h-auto border-0 bg-muted"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setShowDropzone(!showDropzone)}
|
||||
className={`h-auto py-4 px-4 ${
|
||||
showDropzone
|
||||
? 'bg-orange-500 text-white hover:bg-orange-600 border-orange-500'
|
||||
: 'bg-muted hover:bg-muted/80'
|
||||
}`}
|
||||
title="Search by image"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
<Image size={20} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<nav className="flex items-center gap-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setActiveView('library')}
|
||||
className={`font-medium ${
|
||||
activeView === 'library' ? 'text-foreground' : 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
Library
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setActiveView('collections')}
|
||||
className={`font-medium ${
|
||||
activeView === 'collections' ? 'text-foreground' : 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
Collections
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
||||
<User size={22} />
|
||||
</Button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Expandable Image Search Dropzone */}
|
||||
{showDropzone && (
|
||||
<div className="px-8 pb-6">
|
||||
<Card className="p-8 text-center cursor-pointer hover:opacity-80 transition-all border-0 shadow-none bg-muted">
|
||||
<CardContent className="p-0">
|
||||
<Upload size={32} className="text-orange-500 mx-auto mb-3" />
|
||||
<p className="font-medium mb-2">
|
||||
Drop your image to search
|
||||
</p>
|
||||
<p className="text-sm mb-4 text-muted-foreground">
|
||||
Drag and drop, or click to browse
|
||||
</p>
|
||||
<div className="flex gap-3 justify-center">
|
||||
<Button
|
||||
size="sm"
|
||||
className="font-medium bg-orange-500 hover:bg-orange-600 text-white"
|
||||
>
|
||||
Choose File
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="font-medium"
|
||||
>
|
||||
Paste URL
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Active View */}
|
||||
{activeView === 'library' ? <LibraryView /> : <CollectionsView />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user