会社で使えるようにTailWindCSSのコンポーネントを羅列していきます。
ユーザー一覧コンポーネント
※画像をクリックで拡大できます。
.tsx
// pages/index.js
import Head from 'next/head'
import UserList from '../components/UserList'
export default function Home() {
return (
<div className="bg-gray-100">
<Head>
<title>ユーザーリスト</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<UserList />
</div>
</div>
)
}
// components/UserList.js
import { useState } from 'react'
import UserListHeader from './UserListHeader'
import UserListTable from './UserListTable'
export default function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: '山田 花子', title: 'フロントエンド開発者', email: 'hanako.yamada@example.com', role: 'メンバー' },
{ id: 2, name: '佐藤 太郎', title: 'デザイナー', email: 'taro.sato@example.com', role: '管理者' },
])
const addUser = () => {
// ユーザー追加のロジックをここに実装
}
return (
<>
<UserListHeader onAddUser={addUser} />
<UserListTable users={users} />
</>
)
}
// components/UserListHeader.js
export default function UserListHeader({ onAddUser }) {
return (
<div className="sm:flex sm:items-center">
<div className="sm:flex-auto">
<h1 className="text-base font-semibold leading-6 text-gray-900">ユーザー</h1>
<p className="mt-2 text-sm text-gray-700">
アカウント内のすべてのユーザーの名前、役職、メール、役割を含むリストです。
</p>
</div>
<div className="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
<button
type="button"
onClick={onAddUser}
className="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
ユーザー追加
</button>
</div>
</div>
)
}
// components/UserListTable.js
import { useState, useRef, useEffect } from 'react'
export default function UserListTable({ users }) {
const [activeMenu, setActiveMenu] = useState(null)
const menuRef = useRef()
useEffect(() => {
const handleClickOutside = (event) => {
if (menuRef.current && !menuRef.current.contains(event.target)) {
setActiveMenu(null)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [])
const toggleActions = (userId) => {
setActiveMenu(activeMenu === userId ? null : userId)
}
return (
<div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<table className="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">名前</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">役職</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">メール</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">役割</th>
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-0">
<span className="sr-only">操作</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{users.map((user) => (
<tr key={user.id}>
<td className="whitespace-nowrap py-5 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">{user.name}</td>
<td className="whitespace-nowrap px-3 py-5 text-sm text-gray-500">{user.title}</td>
<td className="whitespace-nowrap px-3 py-5 text-sm text-gray-500">{user.email}</td>
<td className="whitespace-nowrap px-3 py-5 text-sm text-gray-500">{user.role}</td>
<td className="relative whitespace-nowrap py-5 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
<button onClick={() => toggleActions(user.id)} className="text-indigo-600 hover:text-indigo-900">
編集
</button>
{activeMenu === user.id && (
<div ref={menuRef} className="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5">
<div className="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<a href="#" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">編集</a>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">削除</a>
</div>
</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)
}
// pages/_app.js
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
// styles/globals.css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
ユーザー一覧(チェックボックスあり)
.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>
)
}
インボイス用
※画像をクリックで拡大できます。
.tsx
import { Button } from "@/components/ui/button"
export default function Component() {
return (
<div className="p-4 sm:p-6 bg-white">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6">
<h1 className="text-2xl font-bold mb-2 sm:mb-0">Invoice</h1>
<Button className="bg-indigo-600 hover:bg-indigo-700 text-white w-full sm:w-auto">Print</Button>
</div>
<p className="text-gray-600 mb-8">
For work completed from August 1, 2022 to August 31, 2022.
</p>
<div className="overflow-x-auto">
<table className="w-full mb-8">
<thead className="hidden sm:table-header-group">
<tr className="border-b">
<th className="text-left py-2">Project</th>
<th className="text-right py-2">Hours</th>
<th className="text-right py-2">Rate</th>
<th className="text-right py-2">Price</th>
</tr>
</thead>
<tbody>
{[
{ name: "Logo redesign", description: "New logo and digital asset playbook.", hours: 20, rate: 100, price: 2000 },
{ name: "Website redesign", description: "Design and program new company website.", hours: 52, rate: 100, price: 5200 },
{ name: "Business cards", description: "Design and production of 3.5\" x 2.0\" business cards.", hours: 12, rate: 100, price: 1200 },
{ name: "T-shirt design", description: "Three t-shirt design concepts.", hours: 4, rate: 100, price: 400 },
].map((item, index) => (
<tr key={index} className="border-b sm:border-none">
<td className="py-4 sm:py-2">
<div className="font-medium">{item.name}</div>
<div className="text-sm text-gray-500">{item.description}</div>
<div className="sm:hidden mt-2">
<div className="flex justify-between">
<span>Hours:</span>
<span>{item.hours.toFixed(1)}</span>
</div>
<div className="flex justify-between">
<span>Rate:</span>
<span>${item.rate.toFixed(2)}</span>
</div>
<div className="flex justify-between font-medium">
<span>Price:</span>
<span>${item.price.toFixed(2)}</span>
</div>
</div>
</td>
<td className="text-right py-2 hidden sm:table-cell">{item.hours.toFixed(1)}</td>
<td className="text-right py-2 hidden sm:table-cell">${item.rate.toFixed(2)}</td>
<td className="text-right py-2 hidden sm:table-cell">${item.price.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="flex justify-end">
<div className="w-full sm:w-64">
<div className="flex justify-between mb-2">
<span>Subtotal</span>
<span>$8,800.00</span>
</div>
<div className="flex justify-between mb-2">
<span>Tax</span>
<span>$1,760.00</span>
</div>
<div className="flex justify-between font-bold text-lg">
<span>Total</span>
<span>$10,560.00</span>
</div>
</div>
</div>
</div>
)
}