global navigation bar
This commit is contained in:
		
							parent
							
								
									2cc0bc6fda
								
							
						
					
					
						commit
						eea0832a42
					
				
					 4 changed files with 277 additions and 2 deletions
				
			
		| 
						 | 
				
			
			@ -4,6 +4,7 @@ import CommentHeader from './CommentHeader';
 | 
			
		|||
import { Footer } from './Footer';
 | 
			
		||||
import { GalleryContent } from './Gallery';
 | 
			
		||||
import { GalleryTitleHeader } from './GalleryTitleHeader';
 | 
			
		||||
import { GlobalNavigationBar } from './Header';
 | 
			
		||||
import { GalleryTable, TableRowData } from './table';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -163,6 +164,7 @@ const comments: SubCommentData[] = [
 | 
			
		|||
function App() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <GlobalNavigationBar />
 | 
			
		||||
      <div className='relative w-[1450px] mx-auto'>
 | 
			
		||||
        <main
 | 
			
		||||
          className='w-[1160px] m-[20px_auto_0]'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										269
									
								
								src/Header.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								src/Header.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,269 @@
 | 
			
		|||
import React, { useState, useEffect, useRef } from 'react';
 | 
			
		||||
import { cn } from './util/cn';
 | 
			
		||||
 | 
			
		||||
// Define interfaces for the components
 | 
			
		||||
interface DropdownItem {
 | 
			
		||||
    name: string;
 | 
			
		||||
    href?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DropdownMenuProps {
 | 
			
		||||
    items: DropdownItem[];
 | 
			
		||||
    isVisible: boolean;
 | 
			
		||||
    widthClass: string;
 | 
			
		||||
    leftClass: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface NavItemWithDropdownProps {
 | 
			
		||||
    title: string;
 | 
			
		||||
    items: DropdownItem[];
 | 
			
		||||
    widthClass: string;
 | 
			
		||||
    leftClass: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TickerItem {
 | 
			
		||||
    id: number;
 | 
			
		||||
    text: string;
 | 
			
		||||
    href: string;
 | 
			
		||||
    highlight?: string;
 | 
			
		||||
    highlightClass: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helper component for dropdown menus
 | 
			
		||||
const DropdownMenu: React.FC<DropdownMenuProps> = ({ items, isVisible, widthClass, leftClass }) => (
 | 
			
		||||
    <div
 | 
			
		||||
        className={cn(
 | 
			
		||||
            'absolute top-full box-content',
 | 
			
		||||
            leftClass,
 | 
			
		||||
            widthClass,
 | 
			
		||||
            'pt-3 pr-[3px] pb-[10px] pl-[17px]',
 | 
			
		||||
            'bg-custom-blue-dark z-4002',
 | 
			
		||||
            isVisible ? 'block' : 'hidden',
 | 
			
		||||
            'transition-opacity duration-150 ease-in-out',
 | 
			
		||||
            isVisible ? 'opacity-100' : 'opacity-0'
 | 
			
		||||
        )}
 | 
			
		||||
    >
 | 
			
		||||
        <span className="absolute left-0 top-0 w-[15px] h-[15px] bg-sp-img bg-[-96px_0px] z-10"></span>
 | 
			
		||||
        <ul className="list-none">
 | 
			
		||||
            {items.map((item) => (
 | 
			
		||||
                <li key={item.name} className="relative">
 | 
			
		||||
                    <a
 | 
			
		||||
                        href={item.href || '#'}
 | 
			
		||||
                        className="block text-white text-xs font-normal leading-6 tracking-0.025em text-shadow-blue-dark hover:text-custom-yellow"
 | 
			
		||||
                    >
 | 
			
		||||
                        {item.name}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
            ))}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Helper component for Nav Items with Dropdowns
 | 
			
		||||
const NavItemWithDropdown: React.FC<NavItemWithDropdownProps> = ({ title, items, widthClass, leftClass }) => {
 | 
			
		||||
    const [isDropdownVisible, setIsDropdownVisible] = useState(false);
 | 
			
		||||
    const timeoutRef = useRef<number | null>(null);
 | 
			
		||||
 | 
			
		||||
    const showDropdown = () => {
 | 
			
		||||
        if (timeoutRef.current) clearTimeout(timeoutRef.current);
 | 
			
		||||
        setIsDropdownVisible(true);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const hideDropdown = () => {
 | 
			
		||||
        timeoutRef.current = setTimeout(() => {
 | 
			
		||||
            setIsDropdownVisible(false);
 | 
			
		||||
        }, 150);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <li
 | 
			
		||||
            className={cn(
 | 
			
		||||
                'relative float-left ml-5'
 | 
			
		||||
            )}
 | 
			
		||||
            onMouseEnter={showDropdown}
 | 
			
		||||
            onMouseLeave={hideDropdown}
 | 
			
		||||
        >
 | 
			
		||||
            <a href="#" className="text-white text-sm font-bold leading-11 tracking-0.025em text-shadow-blue-dark">
 | 
			
		||||
                {title}
 | 
			
		||||
            </a>
 | 
			
		||||
            {title === '갤러리' && <span className="block w-[77px] h-[12px] absolute left-0 bottom-0"></span>}
 | 
			
		||||
            <DropdownMenu
 | 
			
		||||
                items={items}
 | 
			
		||||
                isVisible={isDropdownVisible}
 | 
			
		||||
                widthClass={widthClass}
 | 
			
		||||
                leftClass={leftClass}
 | 
			
		||||
            />
 | 
			
		||||
        </li>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Helper component for More Button Dropdown
 | 
			
		||||
const MoreButtonDropdown: React.FC<Omit<NavItemWithDropdownProps, 'title'>> = ({ items, widthClass, leftClass }) => {
 | 
			
		||||
    const [isDropdownVisible, setIsDropdownVisible] = useState(false);
 | 
			
		||||
    const timeoutRef = useRef<number | null>(null);
 | 
			
		||||
 | 
			
		||||
    const showDropdown = () => {
 | 
			
		||||
        if (timeoutRef.current) clearTimeout(timeoutRef.current);
 | 
			
		||||
        setIsDropdownVisible(true);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const hideDropdown = () => {
 | 
			
		||||
        timeoutRef.current = setTimeout(() => {
 | 
			
		||||
            setIsDropdownVisible(false);
 | 
			
		||||
        }, 150);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <li
 | 
			
		||||
            className="relative float-left ml-5"
 | 
			
		||||
            onMouseEnter={showDropdown}
 | 
			
		||||
            onMouseLeave={hideDropdown}
 | 
			
		||||
        >
 | 
			
		||||
            <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                className="text-white text-sm font-bold leading-11 tracking-0.025em cursor-pointer align-middle bg-transparent border-none"
 | 
			
		||||
                aria-haspopup="true"
 | 
			
		||||
                aria-expanded={isDropdownVisible}
 | 
			
		||||
            >
 | 
			
		||||
                더보기
 | 
			
		||||
                <span className="sr-only">더보기</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <DropdownMenu
 | 
			
		||||
                items={items}
 | 
			
		||||
                isVisible={isDropdownVisible}
 | 
			
		||||
                widthClass={widthClass}
 | 
			
		||||
                leftClass={leftClass}
 | 
			
		||||
            />
 | 
			
		||||
        </li>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Main Ticker component
 | 
			
		||||
const InfoTicker: React.FC = () => {
 | 
			
		||||
    const tickerItems: TickerItem[] = [
 | 
			
		||||
        { id: 1, text: '디시 로터리 응모', href: '#', highlightClass: '' },
 | 
			
		||||
        { id: 2, text: '어제 %HIGHLIGHT%개 게시글 등록', href: '#', highlight: '911,981', highlightClass: 'text-custom-yellow-light' },
 | 
			
		||||
        { id: 3, text: '어제 %HIGHLIGHT%개 댓글 등록', href: '#', highlight: '2,527,905', highlightClass: 'text-custom-cyan' },
 | 
			
		||||
        { id: 4, text: '총 갤러리 수 %HIGHLIGHT%개', href: '#', highlight: '80,516', highlightClass: 'text-custom-pink' },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    const [currentItemIndex, setCurrentItemIndex] = useState(0);
 | 
			
		||||
    const [animating, setAnimating] = useState(false);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const intervalId = setInterval(() => {
 | 
			
		||||
            setAnimating(true);
 | 
			
		||||
 | 
			
		||||
            const animTimeout = setTimeout(() => {
 | 
			
		||||
                setAnimating(false);
 | 
			
		||||
                setCurrentItemIndex((prevIndex) => (prevIndex + 1) % tickerItems.length);
 | 
			
		||||
            }, 500); // Animation duration
 | 
			
		||||
 | 
			
		||||
            return () => clearTimeout(animTimeout);
 | 
			
		||||
        }, 3000);
 | 
			
		||||
 | 
			
		||||
        return () => clearInterval(intervalId);
 | 
			
		||||
    }, [tickerItems.length]);
 | 
			
		||||
 | 
			
		||||
    const currentItem = tickerItems[currentItemIndex];
 | 
			
		||||
    const nextItemIndex = (currentItemIndex + 1) % tickerItems.length;
 | 
			
		||||
    const nextItem = tickerItems[nextItemIndex];
 | 
			
		||||
 | 
			
		||||
    const renderTickerItem = (item: TickerItem) => {
 | 
			
		||||
        const parts = item.text.split('%HIGHLIGHT%');
 | 
			
		||||
        return (
 | 
			
		||||
            <a href={item.href} className="text-white text-sm">
 | 
			
		||||
                {parts[0]}
 | 
			
		||||
                {item.highlight && (
 | 
			
		||||
                    <em className={cn(`not-italic font-bold tracking-0.7px`, item.highlightClass)}>
 | 
			
		||||
                        {` ${item.highlight} `}
 | 
			
		||||
                    </em>
 | 
			
		||||
                )}
 | 
			
		||||
                {parts[1]}
 | 
			
		||||
            </a>
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="w-[306px] h-11 text-center text-sm text-white leading-11 overflow-hidden">
 | 
			
		||||
            <div className="relative w-full h-full box-border px-[6.5px]">
 | 
			
		||||
                <div className="h-11 flex items-center justify-center bg-custom-blue-hover" key={currentItem.id}>
 | 
			
		||||
                    {renderTickerItem(currentItem)}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div
 | 
			
		||||
                    className={cn(
 | 
			
		||||
                        "transition-transform duration-500 ease-in z-10",
 | 
			
		||||
                        animating ? "-translate-y-full" : ""
 | 
			
		||||
                    )}
 | 
			
		||||
                    key={nextItem.id}
 | 
			
		||||
                >
 | 
			
		||||
                    <div className="h-11 flex items-center justify-center bg-custom-blue-hover">
 | 
			
		||||
                        {renderTickerItem(nextItem)}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Main Navigation Bar Component
 | 
			
		||||
export function GlobalNavigationBar(): JSX.Element {
 | 
			
		||||
    const galleryItems: DropdownItem[] = [
 | 
			
		||||
        { name: '게임', href: '#' }, { name: '연예/방송', href: '#' }, { name: '스포츠', href: '#' },
 | 
			
		||||
        { name: '교육/금융/IT', href: '#' }, { name: '여행/음식/생물', href: '#' }, { name: '취미/생활', href: '#' },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    const moreItems: DropdownItem[] = [
 | 
			
		||||
        { name: '디시뉴스', href: '#' }, { name: '디시게임', href: '#' }, { name: '디시위키', href: '#' },
 | 
			
		||||
        { name: '이벤트', href: '#' }, { name: '디시콘', href: '#' },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={cn("bg-custom-blue-hover relative min-w-[1160px] border border-custom-blue-hover")}>
 | 
			
		||||
            <nav className="w-[1160px] h-11 mx-auto flex justify-between items-center">
 | 
			
		||||
                <h2 className="sr-only">GNB</h2>
 | 
			
		||||
 | 
			
		||||
                <ul className="list-none m-0 p-0 flex items-center">
 | 
			
		||||
                    <NavItemWithDropdown
 | 
			
		||||
                        title="갤러리"
 | 
			
		||||
                        items={galleryItems}
 | 
			
		||||
                        widthClass="w-[104px]"
 | 
			
		||||
                        leftClass="left-0"
 | 
			
		||||
                    />
 | 
			
		||||
                    <li className="relative float-left ml-5">
 | 
			
		||||
                        <a href="#" className="text-custom-yellow text-sm font-bold leading-11 tracking-0.025em text-shadow-blue-dark">
 | 
			
		||||
                            마이너갤
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li className="relative float-left ml-5">
 | 
			
		||||
                        <a href="#" className="text-white text-sm font-bold leading-11 tracking-0.025em text-shadow-blue-dark">
 | 
			
		||||
                            미니갤
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li className="relative float-left ml-5">
 | 
			
		||||
                        <a href="#" className="text-white text-sm font-bold leading-11 tracking-0.025em text-shadow-blue-dark">
 | 
			
		||||
                            인물갤
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li className="relative float-left ml-5">
 | 
			
		||||
                        <a href="#" className="text-white text-sm font-bold leading-11 tracking-0.025em text-shadow-blue-dark">
 | 
			
		||||
                            갤로그
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li className="relative float-left ml-5">
 | 
			
		||||
                        <a href="#" className="text-white text-sm font-bold leading-11 tracking-0.025em text-shadow-blue-dark">
 | 
			
		||||
                            디시트렌드
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <MoreButtonDropdown
 | 
			
		||||
                        items={moreItems}
 | 
			
		||||
                        widthClass="w-[67px]"
 | 
			
		||||
                        leftClass="left-[-7px]"
 | 
			
		||||
                    />
 | 
			
		||||
                </ul>
 | 
			
		||||
 | 
			
		||||
                <InfoTicker />
 | 
			
		||||
            </nav>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +19,10 @@
 | 
			
		|||
    --color-custom-dropdown-title-bg: rgb(243 247 255);
 | 
			
		||||
    --color-custom-dropdown-bg: rgb(243 243 243);
 | 
			
		||||
    --color-custom-dropdown-text: rgb(85 85 85);
 | 
			
		||||
    --color-custom-yellow: rgb(255 237 68);
 | 
			
		||||
    --color-custom-yellow-light: rgb(255 255 153);
 | 
			
		||||
    --color-custom-cyan: rgb(134 225 240);
 | 
			
		||||
    --color-custom-pink: rgb(255 187 238);
 | 
			
		||||
 | 
			
		||||
    --font-apple: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", arial, 굴림, Gulim, sans-serif;
 | 
			
		||||
    --font-apple-dotum: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", arial, Dotum, 돋움, sans-serif;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import { clsx } from "clsx";
 | 
			
		||||
import { ClassValue, clsx } from "clsx";
 | 
			
		||||
import { twMerge } from "tailwind-merge";
 | 
			
		||||
 | 
			
		||||
export function cn(...inputs: (string | undefined | false)[]) {
 | 
			
		||||
export function cn(...inputs: ClassValue[]) {
 | 
			
		||||
  return twMerge(clsx(inputs));
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue