会社で使えるようにTailWindCSSのヘッダーメニューコンポーネントを羅列していきます。
ヘッダーの詳細メニューでHeadless UIを使用しているので、インストールしてから使用してください。
ヘッダーメニュー(ノーマル)
※画像をクリックで拡大できます。
.tsx
'use client';
import React, { useState } from 'react';
import { Home, Users, FolderClosed, Calendar, FileText, BarChart2, Bell, Menu, X } from 'lucide-react';
const navigation = [
{ name: 'Dashboard', href: '#', icon: Home, current: true },
{ name: 'Team', href: '#', icon: Users, current: false },
{ name: 'Projects', href: '#', icon: FolderClosed, current: false },
{ name: 'Calendar', href: '#', icon: Calendar, current: false },
{ name: 'Documents', href: '#', icon: FileText, current: false },
{ name: 'Reports', href: '#', icon: BarChart2, current: false },
];
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
}
const DashboardNavigation: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<nav className="bg-gray-800">
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div className="relative flex h-16 items-center justify-between">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button*/}
<button
type="button"
className="relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
onClick={() => setIsOpen(!isOpen)}
>
<span className="absolute -inset-0.5" />
<span className="sr-only">Open main menu</span>
{isOpen ? (
<X className="block h-6 w-6" aria-hidden="true" />
) : (
<Menu className="block h-6 w-6" aria-hidden="true" />
)}
</button>
</div>
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex flex-shrink-0 items-center">
<img
alt=""
src="https://tailwindui.com/plus/img/logos/mark.svg?color=indigo&shade=600"
className="h-8 w-8 rounded-full"
/>
</div>
<div className="hidden sm:ml-6 sm:block">
<div className="flex space-x-4">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
'rounded-md px-3 py-2 text-sm font-medium'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
))}
</div>
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<button
type="button"
className="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
>
<span className="absolute -inset-1.5" />
<span className="sr-only">View notifications</span>
<Bell className="h-6 w-6" aria-hidden="true" />
</button>
{/* Profile dropdown */}
<div className="relative ml-3">
<div>
<button
type="button"
className="relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
id="user-menu-button"
aria-expanded="false"
aria-haspopup="true"
>
<span className="absolute -inset-1.5" />
<span className="sr-only">Open user menu</span>
<img
alt=""
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
className="h-8 w-8 rounded-full"
/>
</button>
</div>
</div>
</div>
</div>
</div>
{/* Mobile menu, show/hide based on menu state. */}
<div className={`sm:hidden ${isOpen ? 'block' : 'hidden'}`}>
<div className="space-y-1 px-2 pb-3 pt-2">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
'block rounded-md px-3 py-2 text-base font-medium'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
))}
</div>
</div>
</nav>
);
};
export default DashboardNavigation;
ヘッダーメニュー(トグルあり)
※画像をクリックで拡大できます。
.tsx
"use client";
import React, { useState } from 'react';
import { Menu } from '@headlessui/react';
import { ShoppingCart, Search, User, ChevronDown } from 'lucide-react';
const ProductivityPage = () => {
return (
<div className="min-h-screen bg-white">
<header className="border-b">
<nav className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center space-x-8">
<div className="text-blue-600 text-3xl">~</div>
<DropdownMenu label="Women" items={["Featured", "Categories", "Collection", "Brands"]} />
<a href="#" className="text-gray-600 hover:text-gray-900">Men</a>
<a href="#" className="text-gray-600 hover:text-gray-900">Company</a>
<a href="#" className="text-gray-600 hover:text-gray-900">Stores</a>
</div>
<div className="flex items-center space-x-4">
<User className="text-gray-400" />
</div>
</nav>
</header>
<main>
<div className="container mx-auto px-4 py-8">
<div className="flex">
<div className="w-1/2 pr-8">
<h1 className="text-5xl font-bold mb-4">Focus on what matters</h1>
<p className="text-xl text-gray-600 mb-6">
All the charts, datepickers, and notifications in the world can't beat checking off some items on a paper card.
</p>
<button className="bg-indigo-600 text-white px-6 py-3 rounded-md hover:bg-indigo-700 transition duration-300">
Shop Productivity
</button>
</div>
<div className="w-1/2">
<img
src="/api/placeholder/600/400"
alt="Productivity tools on a desk"
className="w-full h-auto rounded-lg shadow-lg"
/>
</div>
</div>
</div>
</main>
</div>
);
};
const DropdownMenu = ({ label, items }) => {
return (
<Menu as="div" className="relative inline-block text-left">
{({ open }) => (
<>
<Menu.Button className="flex items-center space-x-2 text-gray-600 hover:text-gray-900">
<span>{label}</span>
<ChevronDown className={`w-5 h-5 transition-transform duration-200 ${open ? 'transform rotate-180' : ''}`} />
</Menu.Button>
<Menu.Items className="absolute left-0 mt-2 w-56 origin-top-left bg-white border border-gray-200 rounded-md shadow-lg">
{items.map((item, index) => (
<Menu.Item key={index}>
{({ active }) => (
<a
href="#"
className={`block px-4 py-2 text-sm ${active ? 'bg-gray-100' : ''}`}
>
{item}
</a>
)}
</Menu.Item>
))}
</Menu.Items>
</>
)}
</Menu>
);
};
export default ProductivityPage;
ヘッダーメニュー(メニュー多い時用)
※画像をクリックで拡大できます。
.tsx
"use client"
import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { Button } from "@/components/ui/button"
interface User {
id: string
name: string
title: string
email: string
role: string
}
const initialUsers: User[] = [
{ id: "1", name: "Lindsay Walton", title: "Front-end Developer", email: "lindsay.walton@example.com", role: "Member" },
{ id: "2", name: "Courtney Henry", title: "Designer", email: "courtney.henry@example.com", role: "Admin" },
{ id: "3", name: "Tom Cook", title: "Director of Product", email: "tom.cook@example.com", role: "Member" },
{ id: "4", name: "Whitney Francis", title: "Copywriter", email: "whitney.francis@example.com", role: "Admin" },
{ id: "5", name: "Leonard Krasner", title: "Senior Designer", email: "leonard.krasner@example.com", role: "Owner" },
{ id: "6", name: "Floyd Miles", title: "Principal Designer", email: "floyd.miles@example.com", role: "Member" },
]
export default function Component() {
const [users, setUsers] = useState<User[]>(initialUsers)
const [selectedUsers, setSelectedUsers] = useState<string[]>([])
const handleSelectUser = (userId: string) => {
setSelectedUsers(prev =>
prev.includes(userId) ? prev.filter(id => id !== userId) : [...prev, userId]
)
}
const handleSelectAll = () => {
setSelectedUsers(selectedUsers.length === users.length ? [] : users.map(user => user.id))
}
const handleBulkEdit = () => {
console.log("Bulk edit for:", selectedUsers)
}
const handleDeleteAll = () => {
console.log("Delete all users")
}
const handleAddUser = () => {
console.log("Add new user")
}
return (
<div className="p-8">
<div className="sm:flex sm:items-center">
<div className="sm:flex-auto">
<h1 className="text-2xl font-semibold text-gray-900">Users</h1>
<p className="mt-2 text-sm text-gray-700">
A list of all the users in your account including their name, title, email and role.
</p>
</div>
<div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
<Button onClick={handleAddUser}>Add user</Button>
</div>
</div>
<div className="mt-8 flex flex-col">
<div className="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-gray-50">
<tr>
<th scope="col" className="relative w-12 px-6 sm:w-16 sm:px-8">
<Checkbox
checked={selectedUsers.length === users.length}
onCheckedChange={handleSelectAll}
aria-label="Select all users"
/>
</th>
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
Name
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Title
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Email
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Role
</th>
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{users.map((user) => (
<tr key={user.id} className={selectedUsers.includes(user.id) ? "bg-indigo-50" : undefined}>
<td className="relative w-12 px-6 sm:w-16 sm:px-8">
<Checkbox
checked={selectedUsers.includes(user.id)}
onCheckedChange={() => handleSelectUser(user.id)}
aria-label={`Select ${user.name}`}
/>
</td>
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
{user.name}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{user.title}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{user.email}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{user.role}</td>
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<Button variant="link" className="text-indigo-600 hover:text-indigo-900">
Edit<span className="sr-only">, {user.name}</span>
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
{selectedUsers.length > 0 && (
<div className="mt-4 flex space-x-4">
<Button onClick={handleBulkEdit}>Bulk edit</Button>
<Button variant="destructive" onClick={handleDeleteAll}>Delete all</Button>
</div>
)}
</div>
)
}
サイドバーメニュー(PCのみ)
※画像をクリックで拡大できます。
.tsx
'use client';
import React from 'react';
import { Home, Users, FolderClosed, Calendar, FileText, BarChart2 } from 'lucide-react';
const navigation = [
{ name: 'Dashboard', icon: Home, href: '#', current: true },
{ name: 'Team', icon: Users, href: '#', current: false },
{ name: 'Projects', icon: FolderClosed, href: '#', current: false },
{ name: 'Calendar', icon: Calendar, href: '#', current: false },
{ name: 'Documents', icon: FileText, href: '#', current: false },
{ name: 'Reports', icon: BarChart2, href: '#', current: false },
];
const teams = [
{ id: 'heroicons', name: 'Heroicons', initial: 'H' },
{ id: 'tailwind', name: 'Tailwind Labs', initial: 'T' },
{ id: 'workcation', name: 'Workcation', initial: 'W' },
];
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
}
const Sidebar = () => {
return (
<div className="flex min-h-screen">
<div className="fixed flex w-64 flex-col h-full">
<div className="flex h-full flex-1 flex-col bg-indigo-600">
<div className="flex flex-1 flex-col overflow-y-auto pt-5 pb-4">
<div className="flex flex-shrink-0 items-center px-4">
{/* Replace with your logo */}
<img
alt="Your Company"
src="https://tailwindui.com/plus/img/logos/mark.svg?color=indigo&shade=500"
className="h-8 w-auto"
/>
</div>
<nav className="mt-5 flex-1 space-y-1 px-2">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current ? 'bg-indigo-700 text-white' : 'text-indigo-100 hover:bg-indigo-500',
'group flex items-center px-2 py-2 text-sm font-medium rounded-md'
)}
>
<item.icon className="mr-3 h-6 w-6 flex-shrink-0 text-indigo-300" aria-hidden="true" />
{item.name}
</a>
))}
</nav>
</div>
<div className="flex flex-shrink-0 bg-indigo-700 p-4">
<div className="group block w-full flex-shrink-0">
<div className="flex items-center">
<div>
<img
alt=""
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
className="h-8 w-8 rounded-full"
/>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-white">Tom Cook</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="flex flex-1 flex-col ml-64">
<main className="flex-1">
<div className="py-6">
<div className="mx-auto max-w-7xl px-4 sm:px-6 md:px-8">
<h1 className="text-2xl font-semibold text-gray-900">Dashboard</h1>
</div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 md:px-8">
{/* Replace with your content */}
<div className="py-4">
<div className="h-96 rounded-lg border-4 border-dashed border-gray-200" />
</div>
{/* /End replace */}
</div>
</div>
<div className="py-6">
<div className="mx-auto max-w-7xl px-4 sm:px-6 md:px-8">
<h1 className="text-2xl font-semibold text-gray-900">Dashboard</h1>
</div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 md:px-8">
{/* Replace with your content */}
<div className="py-4">
<div className="h-96 rounded-lg border-4 border-dashed border-gray-200" />
</div>
{/* /End replace */}
</div>
</div>
</main>
</div>
</div>
);
};
export default Sidebar;