React Icons는 다양한 아이콘을 쉽게 사용할 수 있게 해주는 라이브러리입니다. 설치 명령어는 다음과 같습니다:
npm install react-icons --save
src/components/Header.css
헤더의 기본 레이아웃을 설정하는 CSS 파일입니다.
.nav {
display: flex;
justify-content: space-between;
}
분석:
.nav
클래스는 Flexbox를 사용하여 자식 요소들을 수평으로 배치하고, justify-content: space-between
을 통해 양 끝에 요소들을 정렬합니다. 이를 통해 네비게이션 링크와 로고가 양쪽에 배치됩니다.src/components/Header.jsx
헤더 컴포넌트의 React 코드입니다. React Icons를 활용하여 아이콘을 추가하고, 반응형 메뉴를 구현합니다.
// import './Header.css';
import { Link, NavLink } from 'react-router-dom';
import {
FaHome,
FaInfoCircle,
FaEnvelope,
FaBars,
FaTimes,
} from 'react-icons/fa';
import { useState } from 'react';
function Header() {
const navItems = [
{ id: 'home', label: 'Home', icon: <FaHome />, to: '/' },
{ id: 'about', label: 'About', icon: <FaInfoCircle />, to: '/about' },
{ id: 'contact', label: 'Contact', icon: <FaEnvelope />, to: '/contact' },
];
const [isMenuOpen, setIsMenuOpen] = useState(false);
const toggleMenu = () => setIsMenuOpen(!isMenuOpen);
return (
<header className="sticky top-0 bg-gray-800 text-white px-4">
<div className="container mx-auto flex justify-between items-center h-14">
{/* 로고 */}
<div>
<Link to="/" className="text-xl font-bold">
Lean Canvas
</Link>
</div>
{/* 데스크탑 네비게이션 */}
<nav className="hidden md:flex space-x-4">
{navItems.map(item => (
<NavLink
key={item.id}
to={item.to}
className={({ isActive }) =>
isActive ? 'text-blue-700' : 'hover:text-gray-300'
}
>
{item.icon} {item.label}
</NavLink>
))}
</nav>
{/* 모바일 메뉴 토글 버튼 */}
<button className="md:hidden" onClick={toggleMenu}>
<FaBars />
</button>
{/* 데스크탑 버튼 */}
<button className="hidden md:block bg-blue-500 hover:bg-blue-600 text-white font-bold py-1.5 px-4 rounded transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
짐코딩 강의
</button>
</div>
{/* 모바일 메뉴 */}
<aside
className={`
fixed top-0 left-0 w-64 h-full bg-gray-800 z-50
${isMenuOpen ? 'translate-x-0' : '-translate-x-full'}
md:hidden transform transition-transform duration-300 ease-in-out
`}
>
<div className="flex justify-end p-4">
<button
className="text-white focus:outline-none"
aria-label="Close menu"
onClick={toggleMenu}
>
<FaTimes className="h-6 w-6" />
</button>
</div>
<nav className="flex flex-col space-y-4 p-4">
{navItems.map(item => (
<NavLink
key={item.id}
to={item.to}
className="hover:text-gray-300"
onClick={toggleMenu} // 메뉴 클릭 시 닫기
>
{item.icon} {item.label}
</NavLink>
))}
</nav>
</aside>
</header>
);
}
export default Header;
분석:
FaHome
, FaInfoCircle
, FaEnvelope
, FaBars
, FaTimes
아이콘을 임포트하여 네비게이션 링크와 메뉴 토글 버튼에 사용합니다.hidden md:flex
클래스를 사용하여 데스크탑에서는 네비게이션이 보이고, 모바일에서는 숨겨집니다. 반대로, 모바일 메뉴 토글 버튼은 md:hidden
클래스로 데스크탑에서는 숨겨집니다.useState
훅을 사용하여 모바일 메뉴의 열림/닫힘 상태를 관리합니다. toggleMenu
함수로 상태를 전환합니다.<NavLink>
는 현재 활성화된 링크에 text-blue-700
클래스를 적용하여 시각적으로 강조합니다.translate-x-0
과 -translate-x-full
클래스를 사용하여 모바일 메뉴가 슬라이드 인/아웃되도록 애니메이션을 적용합니다.aria-label
을 추가하여 스크린 리더 사용자에게 메뉴 토글 버튼의 목적을 명확히 전달합니다.헤더 UI 구현과 관련된 더 자세한 내용은 공식 React Router 문서와 React Icons 문서를 참고하세요.
src/pages/Home.jsx
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { FaSearch, FaList, FaTh } from 'react-icons/fa';
function Home() {
const [searchText, setSearchText] = useState('');
const [isGridView, setIsGridView] = useState(true);
const dummyData = [
{
id: 1,
title: '친환경 도시 농업 플랫폼',
lastModified: '2023-06-15',
category: '농업',
},
{
id: 2,
title: 'AI 기반 건강 관리 앱',
lastModified: '2023-06-10',
category: '헬스케어',
},
{
id: 3,
title: '온디맨드 물류 서비스',
lastModified: '2023-06-05',
category: '물류',
},
{
id: 4,
title: 'VR 가상 여행 서비스',
lastModified: '2023-06-01',
category: '여행',
},
];
const filteredData = dummyData.filter(item =>
item.title.toLowerCase().includes(searchText.toLowerCase()),
);
return (
<div className="container mx-auto px-4 py-16">
<div className="mb-6 flex flex-col sm:flex-row items-center justify-between">
<div className="relative w-full sm:w-64 mb-4 sm:mb-0">
<input
type="text"
placeholder="검색"
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
value={searchText}
onChange={e => setSearchText(e.target.value)}
aria-label="검색"
/>
<FaSearch className="absolute left-3 top-3 text-gray-400" />
</div>
<div className="flex space-x-2">
<button
onClick={() => setIsGridView(true)}
className={`p-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 ${isGridView ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
aria-label="Grid view"
>
<FaTh />
</button>
<button
onClick={() => setIsGridView(false)}
className={`p-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 ${!isGridView ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
aria-label="List view"
>
<FaList />
</button>
</div>
</div>
{filteredData.length === 0 ? (
<div className="text-center py-10">
<p className="text-xl text-gray-600">
{searchText ? '검색 결과가 없습니다' : '목록이 없습니다'}
</p>
</div>
) : (
<div
className={`grid gap-6 ${isGridView ? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3' : 'grid-cols-1'}`}
>
{filteredData.map(item => (
<Link
key={item.id}
className="bg-white rounded-lg shadow-md overflow-hidden transition-transform duration-300 hover:scale-105"
to={`/canvases/${item.id}`}
>
<div className="p-6">
<h2 className="text-2xl font-bold mb-2 text-gray-800">
{item.title}
</h2>
<p className="text-sm text-gray-600 mb-4">
최근 수정일: {item.lastModified}
</p>
<span className="inline-block px-3 py-1 text-sm font-semibold text-gray-700 bg-gray-200 rounded-full">
{item.category}
</span>
</div>
</Link>
))}
</div>
)}
</div>
);
}
export default Home;
검색 기능 추가 (useState
사용):
searchText
상태를 도입하여 사용자가 입력한 검색어를 관리합니다.input
필드의 value
와 onChange
핸들러를 통해 실시간으로 searchText
를 업데이트합니다.filteredData
는 dummyData
를 searchText
에 따라 필터링하여 검색 결과를 보여줍니다.그리드 및 리스트 보기 토글 (isGridView
상태):
isGridView
상태를 사용하여 그리드 보기와 리스트 보기 사이를 전환할 수 있습니다.FaTh
아이콘과 FaList
아이콘)을 통해 사용자에게 보기 방식을 선택할 수 있는 인터페이스를 제공합니다.isGridView
상태가 변경되며, 이에 따라 CSS 클래스가 동적으로 적용되어 레이아웃이 변경됩니다.더미 데이터 (dummyData
):
dummyData
배열로 정의하여 UI에 표시할 항목들을 관리합니다.id
, title
, lastModified
, category
속성을 포함합니다.조건부 렌더링:
filteredData
의 길이에 따라 "목록이 없습니다" 또는 "검색 결과가 없습니다" 메시지를 표시합니다.반응형 그리드 레이아웃:
grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
클래스를 통해 화면 크기에 따라 열 수가 변경됩니다.grid-cols-1
로 설정하여 모든 항목을 세로로 나열합니다.스타일링 및 애니메이션:
bg-white
, rounded-lg
, shadow-md
등의 클래스로 스타일링되어 카드 형태로 표시됩니다.transition-transform duration-300 hover:scale-105
클래스를 통해 호버 시 약간의 확대 효과를 줍니다.접근성:
aria-label
속성을 사용하여 버튼의 목적을 명확히 전달합니다.aria-label
을 추가하여 스크린 리더 사용자에게 필드의 용도를 설명합니다.