Загрузка данных
Да, это очень сильно меняет дело! Теперь у нас есть точные CSS-классы и готовая верстка. Чтобы приложение выглядело в точности как задумано (со всеми стилями, галочками и цветами), нам нужно интегрировать эти классы в наши React-компоненты.
Кроме того, в твоем CSS есть классы состояний (например, .done для прочитанной книги и .checked для галочки), которые мы будем динамически добавлять через JavaScript.
Стили из index.css можешь целиком скопировать в файл src/index.css (или App.css), чтобы они применились ко всему проекту.
Вот как теперь должны выглядеть твои компоненты с правильной версткой:
1. src/App.jsx
Вставляем шапку (бренд "Shelf") и передаем состояние вниз.
import { useState } from 'react'
import ShelfScreen from './pages/ShelfScreen/ShelfScreen'
import './index.css' // Подключаем твои стили
function App() {
const [books, setBooks] = useState([
{ id: 1, title: 'Клара и Солнце', author: 'Кадзуо Исигуро', read: true }
])
const [showOnlyUnread, setShowOnlyUnread] = useState(false)
const handleAddBook = (title) => {
const newBook = {
id: Date.now(),
title,
author: 'Неизвестный автор',
read: false
}
setBooks(prev => [...prev, newBook])
}
const handleToggleRead = (id) => {
setBooks(prev => prev.map(book =>
book.id === id ? { ...book, read: !book.read } : book
))
}
const handleDeleteBook = (id) => {
setBooks(prev => prev.filter(book => book.id !== id))
}
return (
<div className="app">
<div className="app-header">
<div className="brand">
<div className="brand-mark">S</div>
<div className="brand-name">Shelf</div>
</div>
</div>
<ShelfScreen
books={books}
showOnlyUnread={showOnlyUnread}
setShowOnlyUnread={setShowOnlyUnread}
onAddBook={handleAddBook}
onToggleRead={handleToggleRead}
onDeleteBook={handleDeleteBook}
/>
</div>
)
}
export default App
2. src/pages/ShelfScreen/ShelfScreen.jsx
Оборачиваем все в <section className="screen active">.
import BookForm from '../../components/BookForm/BookForm'
import BookList from '../../components/BookList/BookList'
export default function ShelfScreen({
books,
showOnlyUnread,
setShowOnlyUnread,
onAddBook,
onToggleRead,
onDeleteBook
}) {
const filteredBooks = showOnlyUnread
? books.filter(book => !book.read)
: books;
return (
<section className="screen active" id="screen-shelf">
<p className="greeting">Добрый вечер</p>
<BookForm onAdd={onAddBook} />
<div className="list-toolbar">
<span className="toolbar-title">Книги</span>
<div className="filter-chip">
<input
type="checkbox"
id="filterCheckbox"
checked={showOnlyUnread}
onChange={() => setShowOnlyUnread(prev => !prev)}
/>
<label htmlFor="filterCheckbox">
<span className="dot" />
Только непрочитанные
</label>
</div>
</div>
<BookList
books={filteredBooks}
onToggleRead={onToggleRead}
onDeleteBook={onDeleteBook}
/>
</section>
)
}
3. src/components/BookForm/BookForm.jsx
Заменим div className="add-book-row" на form, чтобы книга добавлялась не только по клику, но и по нажатию Enter.
import { useState } from 'react'
export default function BookForm({ onAdd }) {
const [title, setTitle] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
const trimmedTitle = title.trim()
if (trimmedTitle) {
onAdd(trimmedTitle)
setTitle('')
}
}
return (
<form className="add-book-row" onSubmit={handleSubmit}>
<input
className="input"
placeholder="Название книги..."
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<button type="submit" className="btn">
Добавить на полку
</button>
</form>
)
}
4. src/components/BookList/BookList.jsx
Рендерит список. Если книг нет, показывает стилизованное сообщение.
import BookItem from '../BookItem/BookItem'
export default function BookList({ books, onToggleRead, onDeleteBook }) {
if (books.length === 0) {
return <div className="empty-note">Полка пуста</div>
}
return (
<div className="book-list">
{books.map(book => (
<BookItem
key={book.id}
book={book}
onToggle={onToggleRead}
onDelete={onDeleteBook}
/>
))}
</div>
)
}
5. src/components/BookItem/BookItem.jsx
Здесь добавляем динамические классы: если книга прочитана, к названию добавляется класс done, а к кнопке-галочке — класс checked.
export default function BookItem({ book, onToggle, onDelete }) {
// Цвета из твоего дизайна
const colors = ['#4f6b52', '#8a3a3a', '#c9a227', '#6f6353', '#2c231c']
const coverColor = colors[book.id % colors.length]
// Берем первую букву названия для обложки
const firstLetter = book.title ? book.title.charAt(0).toUpperCase() : '?'
return (
<div className="book-row">
<div className="book-cover" style={{ background: coverColor }}>
{firstLetter}
</div>
<div className="book-info">
{/* Если книга прочитана, добавляем класс 'done' */}
<p className={`book-title ${book.read ? 'done' : ''}`}>
{book.title}
</p>
<div className="book-author">{book.author}</div>
</div>
{/* Если книга прочитана, добавляем класс 'checked' */}
<div
className={`read-check ${book.read ? 'checked' : ''}`}
onClick={() => onToggle(book.id)}
>
<span className="check-circle">✓</span>
<span className="read-label">Прочитано</span>
</div>
<button
className="delete-btn"
title="Убрать с полки"
onClick={() => onDelete(book.id)}
>
✕
</button>
</div>
)
}