Skip to content

Commit

Permalink
feat: create new detail page layout #25
Browse files Browse the repository at this point in the history
  • Loading branch information
thxforall committed Dec 22, 2024
1 parent 50e4453 commit 4c56e18
Show file tree
Hide file tree
Showing 48 changed files with 997 additions and 397 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Image from 'next/image';
import Link from 'next/link';
import { ContentSection } from '../common/content-section';
import { PickInfo } from '@/types/model';
import { PickInfo } from '@/types/model.d';

interface MainLeftGridProps {
pick: PickInfo;
Expand Down
Binary file removed app/artists/.page.tsx.swp
Binary file not shown.
4 changes: 4 additions & 0 deletions app/details/components/layouts/buttons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { SearchButton } from './search/search-button';
export { InfoButton } from './info/info-button';
export { ItemButton } from './item/item-button';
export type { BaseButtonProps, ItemButtonProps } from '@/types/button.d';
27 changes: 27 additions & 0 deletions app/details/components/layouts/buttons/info/info-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Image from 'next/image';
import { ItemButtonProps } from '@/types/button.d';

export function InfoButton({ item, className }: ItemButtonProps) {
return (
<div className={`relative ${className}`}>
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-white/80 border border-white/50 backdrop-blur-md px-3 py-1.5 rounded-full flex items-center gap-2 whitespace-nowrap">
{item.info.imageUrl && (
<div className="relative w-3.5 h-3.5 rounded-full overflow-hidden">
<Image
src={item.info.imageUrl}
alt={item.info.name}
fill
className="object-cover"
/>
</div>
)}
<span className="text-[10px] text-black/60 font-semibold">
{item.info.brands?.[0].replace(/_/g, ' ').toUpperCase()}
</span>
</div>
<div className="w-4 h-4 rounded-full border border-white/80 flex items-center justify-center group-hover:border-white/100 transition-colors duration-200">
<div className="w-2 h-2 bg-white/80 rounded-full group-hover:bg-white/100 transition-colors duration-200" />
</div>
</div>
);
}
27 changes: 27 additions & 0 deletions app/details/components/layouts/buttons/item/item-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ItemButtonProps } from '@/types/button.d';
import { SearchButton } from '../search/search-button';
import { InfoButton } from '../info/info-button';
import { ItemDetailPopup } from '../../popup/item-detail-popup';

interface ItemButtonContainerProps extends ItemButtonProps {
variant?: 'default' | 'search';
}

export function ItemButton({
item,
isActive,
variant = 'default',
position = 'right',
className,
}: ItemButtonContainerProps) {
return (
<div className={`relative ${className}`}>
{variant === 'search' ? (
<SearchButton />
) : (
<InfoButton item={item} isActive={isActive} />
)}
<ItemDetailPopup item={item} isVisible={isActive} position={position} />
</div>
);
}
16 changes: 16 additions & 0 deletions app/details/components/layouts/buttons/search/search-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SearchIcon from '@mui/icons-material/Search';
import AddIcon from '@mui/icons-material/Add';
import { BaseButtonProps } from '@/types/button.d';

export function SearchButton({ className }: BaseButtonProps) {
return (
<div className={`absolute top-5 flex flex-col items-center gap-1 ${className}`}>
<div className="bg-black/60 backdrop-blur-md px-3 py-1 rounded-full flex items-center gap-2 whitespace-nowrap">
<SearchIcon className="w-4 h-4 text-white/80" />
</div>
<div className="w-4 h-4 rounded-full bg-black/60 flex items-center justify-center transition-colors duration-200">
<AddIcon className="w-3 h-3 text-white/80" />
</div>
</div>
);
}
19 changes: 0 additions & 19 deletions app/details/components/layouts/header/featured-image-header.tsx

This file was deleted.

47 changes: 47 additions & 0 deletions app/details/components/layouts/header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Button } from '@/components/ui/Button';
import {
pretendardBold,
pretendardSemiBold,
pretendardRegular,
} from '@/lib/constants/fonts';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import Link from 'next/link';

interface HeaderProps {
title: string | null;
description: string | null;
}

export default function Header({ title, description }: HeaderProps) {
return (
<div className="flex flex-col items-center mb-16">
<h1
className={`${pretendardBold.className} text-2xl md:text-5xl text-white mb-6`}
title={title ?? undefined}
>
뉴진스 다니엘 코디
</h1>
<p
className={`${pretendardRegular.className} text-md md:text-lg text-white/50 max-w-[60%] line-clamp-1 mb-6`}
title={description ?? undefined}
>
{description}
</p>
{/* badge 컴포넌트 */}
<button
className={`w-fit m-2 p-2 text-sm
md:text-base cursor-pointer text-white hover:bg-white/10 transition-colors flex items-center gap-2 ${pretendardBold.className}`}
>
<Link
href="/"
className=" bg-white/20 rounded-full w-6 h-6 md:w-8 md:h-8 flex items-center justify-center text-sm md:text-base"
></Link>
<p
className={`${pretendardRegular.className} text-white/70 text-sm md:text-base`}
>
뉴진스_민지
</p>
</button>
</div>
);
}
28 changes: 28 additions & 0 deletions app/details/components/layouts/image-section/main-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactNode, memo } from 'react';
import Image from 'next/image';

interface MainImageProps {
imageUrl: string;
onTouch: () => void;
children: ReactNode;
}

function MainImageComponent({ imageUrl, onTouch, children }: MainImageProps) {
return (
<div className="relative aspect-[3/4]" onClick={onTouch}>
<Image
src={imageUrl}
alt="Featured fashion"
fill
className="object-cover"
priority
sizes="650px"
/>
<div className="absolute inset-0 z-10">
{children}
</div>
</div>
);
}

export const MainImage = memo(MainImageComponent);
58 changes: 58 additions & 0 deletions app/details/components/layouts/image-section/view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client';

import { useState, useCallback, memo } from 'react';
import { DetailPageState } from '@/types/model.d';
import { ImagePopup } from '../popup/popup';
import { ImageList } from '../list';
import { MainImage } from './main-image';

interface ImageViewProps {
detailPageState: DetailPageState;
imageUrl: string;
}

function ImageViewComponent({ detailPageState, imageUrl }: ImageViewProps) {
const [currentIndex, setCurrentIndex] = useState<number | null>(null);
const [isTouch, setIsTouch] = useState(false);
const [hoveredItem, setHoveredItem] = useState<number | null>(null);

const handleTouch = useCallback(() => {
setIsTouch(prev => !prev);
}, []);

const handleSetCurrentIndex = useCallback((index: number | null) => {
setCurrentIndex(index);
}, []);

const handleSetHoveredItem = useCallback((index: number | null) => {
setHoveredItem(index);
}, []);

return (
<section className="flex flex-row w-[1000px] mx-auto">
<div className="w-[650px]">
<MainImage
imageUrl={imageUrl}
onTouch={handleTouch}
>
<ImagePopup
detailPageState={detailPageState}
currentIndex={currentIndex}
isTouch={isTouch}
hoveredItem={hoveredItem}
setHoveredItem={handleSetHoveredItem}
/>
</MainImage>
</div>
<div className="w-[320px] ml-[30px]">
<ImageList
detailPageState={detailPageState}
currentIndex={currentIndex}
setCurrentIndex={handleSetCurrentIndex}
/>
</div>
</section>
);
}

export const ImageView = memo(ImageViewComponent);
40 changes: 40 additions & 0 deletions app/details/components/layouts/list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DetailPageState } from '@/types/model.d';
import { ListHeader } from './list-header';
import { ListProgress } from './list-progress';
import { ListItems } from './list-items';
import { createNoImageItem } from './no-image';
import { useState, useCallback } from 'react';

interface ImageListProps {
detailPageState: DetailPageState;
currentIndex: number | null;
setCurrentIndex: (index: number | null) => void;
}

export function ImageList({
detailPageState,
currentIndex,
setCurrentIndex,
}: ImageListProps) {
const [hoveredItem, setHoveredItem] = useState<number | null>(null);
const items = [...(detailPageState.itemList ?? []), createNoImageItem()];
const currentProgress = currentIndex !== null ? currentIndex + 1 : 0;

const handleHover = useCallback((index: number | null) => {
setHoveredItem(index);
}, []);

return (
<div className="flex flex-col h-full">
<ListHeader />
<ListItems
items={items}
currentIndex={currentIndex}
setCurrentIndex={setCurrentIndex}
hoveredItem={hoveredItem}
setHoveredItem={handleHover}
/>
<ListProgress total={items.length} current={currentProgress} />
</div>
);
}
40 changes: 40 additions & 0 deletions app/details/components/layouts/list/list-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState } from 'react';

type Category = 'ALL' | 'FASHION' | 'INTERIOR';

interface ListHeaderProps {
onCategoryChange?: (category: Category) => void;
}

export function ListHeader({ onCategoryChange }: ListHeaderProps) {
const [activeCategory, setActiveCategory] = useState<Category>('ALL');

const handleCategoryClick = (category: Category) => {
setActiveCategory(category);
onCategoryChange?.(category);
};

return (
<div className="flex flex-col mb-3">
<div className="flex items-center h-[40px] border-b border-white/10">
{(['ALL', 'FASHION', 'INTERIOR'] as Category[]).map((category) => (
<button
key={category}
onClick={() => handleCategoryClick(category)}
className={`px-4 h-full text-xs font-medium transition-colors relative
${
activeCategory === category
? 'text-white after:absolute after:bottom-[-1px] after:left-0 after:w-full after:h-[1px] after:bg-white'
: 'text-white/40 hover:text-white/60'
}`}
>
{category}
</button>
))}
</div>
<div className="mt-4">
<div className="text-sm font-bold text-white/60 uppercase">FASHION</div>
</div>
</div>
);
}
41 changes: 41 additions & 0 deletions app/details/components/layouts/list/list-item-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Image from 'next/image';
import { HoverItem } from '@/types/model.d';

interface ListItemImageProps {
item: HoverItem;
isActive: boolean;
}

export function ListItemImage({ item, isActive }: ListItemImageProps) {
const imageSize = 90;

if (!item.info.imageUrl) {
return (
<div
className="relative bg-white/10 flex items-center justify-center shrink-0"
style={{ width: imageSize, height: imageSize }}
>
<div className="text-xs text-white/40 uppercase">
{item.info.category}
</div>
</div>
);
}

return (
<div
className="relative bg-white/10 shrink-0"
style={{ width: imageSize, height: imageSize }}
>
<Image
src={item.info.imageUrl}
alt={item.info.name}
width={imageSize}
height={imageSize}
className="w-full h-full object-cover"
priority
unoptimized
/>
</div>
);
}
22 changes: 22 additions & 0 deletions app/details/components/layouts/list/list-item-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { HoverItem } from '@/types/model.d';

interface ListItemInfoProps {
item: HoverItem;
isActive: boolean;
}

export function ListItemInfo({ item, isActive }: ListItemInfoProps) {
return (
<div className="flex flex-col justify-center flex-1 min-w-0">
<div className="text-xs text-white/40 mb-1">
{item.info.category?.toUpperCase()}
</div>
<div className="text-xs text-white/60 mb-1">
{item.info.brands?.[0].replace(/_/g, ' ').toUpperCase()}
</div>
<div className="text-sm text-white truncate">
{item.info.name}
</div>
</div>
);
}
Loading

0 comments on commit 4c56e18

Please sign in to comment.