ساختن یک برنامه لیست بسته بندی آماده تابستانی با React و TailwindCSS
سلام، مردم دوست داشتنی! 🌞
تابستان در ایالات متحده است، به این معنی که فصل تعطیلات آخر هفته، سفر به ساحل، یا شاید فقط یک روز استراحت در خانه وانمود کنید که در بالی هستید. فرقی نمیکند شما یک برنامهریز سختافزار باشید یا یک بستهبندی لحظه آخری، همه ما در تلاش برای فراموش کردن چیزی ضروری در سفر بودهایم، اینطور نیست؟
برای نجات همه ما از “اوه نه، من _____م را فراموش کردم!” در لحظه، من یک برنامه لیست بسته بندی با استفاده از React و TailwindCSS ساختم. شما می توانید آن را در اینجا بررسی کنید و یک نسخه آزمایشی زنده را اینجا ببینید. این پست شما را با نحوه ساختن آن آشنا می کند.
شروع کار با پروژه
من از Vite با قالب React برای داربست پروژه استفاده کردم. این کار به سادگی اجرای دستور زیر در ترمینال من بود:
npx create-vite my-app -- --template react
سپس من از TailwindCSS برای یک ظاهر طراحی استفاده کردم، زیرا، خوب، چه کسی فریم ورکهای CSS اولیه را دوست ندارد، درست میگویم؟
پیکربندی TailwindCSS
من پیکربندی پیش فرض Tailwind را سفارشی کردم (tailwind.config.js
) برای اضافه کردن چند رنگ و فونت اضافی که می خواستم در سراسر برنامه استفاده کنم. این رنگها و فونتها به برنامه کمک میکنند تا کمی شخصیت دریایی ⚓️ ببخشند، و چه کسی آن را دوست ندارد؟ در اینجا نگاهی به پیکربندی Tailwind من داریم:
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
whip: '#fdf0d5',
maroon: '#780000',
lava: '#c1121f',
navy: '#003049',
cerulean: '#669bbc',
},
fontFamily: {
body: ['"Karla"', 'sans-serif'],
heading: ['"Caprasimo"', 'cursive'],
},
},
},
plugins: [],
};
نکته حرفه ای: اضافه کردن خواص سفارشی به
extend
فیلد در بخش تم به شما امکان می دهد پیکربندی پیش فرض Tailwind را به جای جایگزین کردن کامل آن گسترش دهید.
یک ظاهر طراحی با TailwindCSS
در مرحله بعد، بیایید به نحوه استایل کردن برنامه بپردازیم. اکثر یک ظاهر طراحی شده در تعریف شده است index.css
و App.css
. من استفاده کردم @apply
دستورالعمل Tailwind برای اعمال چندین کلاس کاربردی در کلاس سفارشی خودم، و استفاده کردم @layer
تا اطمینان حاصل شود که این سبک ها در جای مناسب در فایل CSS نهایی گنجانده شده اند.
این index.css
فایل:
@import url('https://fonts.googleapis.com/css2?family=Caprasimo&family=Karla:wght@500;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
.list ul {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
@layer components {
.pill {
@apply bg-whip text-navy font-body px-8 py-3 text-lg font-bold border-none rounded-full cursor-pointer;
}
.pill-sm {
@apply px-6 py-2 mx-2 text-sm font-bold uppercase;
}
}
این App.css
فایل به سادگی شبکه را برای برنامه تعریف می کند:
.app {
grid-template-rows: auto auto 1fr auto;
}
حقیقت خنده دار: آیا میدانستید که با TailwindCSS میتوانید از آن استفاده کنید
@apply
دستورالعملی برای خشک کردن سبک های خود با استفاده مجدد از الگوهای کاربردی؟
ایجاد کامپوننت ها
برنامه ما به شش جزء اصلی تقسیم می شود: App
، Header
، Form
، PackingList
، Item
، و Stats
.
در اینجا خلاصه ای سریع آورده شده است:
-
App
: این جزء اصلی برنامه ما است. این جایی است که ما تمام وضعیت خود را مدیریت می کنیم و همه منطق برنامه خود را مدیریت می کنیم. -
Header
: فقط یک هدر ساده برای دادن عنوان به برنامه ما. -
Form
: فرم ورودی که در آن کاربران می توانند موارد را به لیست بسته بندی خود اضافه کنند. -
PackingList
: قلب برنامه ما که در آن همه موارد موجود در لیست بسته بندی را نمایش می دهیم. -
Item
: موارد منفرد در PackingList. هر مورد دارای چک باکس و دکمه حذف مخصوص به خود برای تعامل کاربر است. -
Stats
: مؤلفه ای برای نشان دادن آمار سرگرم کننده در مورد پیشرفت بسته بندی ما.
حال، بیایید نگاهی دقیق تر به هر جزء بیندازیم.
مؤلفه برنامه
import { useState } from 'react';
import Header from './components/Header';
import Form from './components/Form';
import PackingList from './components/PackingList';
import Stats from './components/Stats';
import './App.css';
function App() {
const [items, setItems] = useState([]);
const [description, setDescription] = useState('');
const [quantity, setQuantity] = useState(1);
function handleQuantityChange(e) {
setQuantity(e.target.value);
}
function handleDescriptionChange(e) {
setDescription(e.target.value);
}
function handleFormSubmit(e) {
e.preventDefault();
const newItem = {
id: items.length + 1,
inputOrder: items.length + 1,
quantity: quantity,
description: description,
packed: false,
};
setItems([...items, newItem]);
setQuantity(1);
setDescription('');
}
// Handle packed state of each item
function handlePackedChange(id) {
// Create a new array with the same items, but with the packed state of the selected item toggled
const newItems = items.map((item) => (item.id === id ? { ...item, packed: !item.packed } : item));
setItems(newItems);
}
function handleRemoveItem(id) {
const updatedItems = items.filter((item) => item.id !== id);
setItems(updatedItems);
}
function calculatePackedItems() {
return items.filter((item) => item.packed).length;
}
function calculatePercentagePacked() {
return Math.round((calculatePackedItems() / items.length) * 100);
}
function handleSort(e) {
const sortBy = e.target.value;
setItems((currentItems) => {
const sortedItems = [...currentItems];
if (sortBy === 'input') {
sortedItems.sort((a, b) => a.inputOrder - b.inputOrder);
return sortedItems;
} else if (sortBy === 'description') {
sortedItems.sort((a, b) => {
if (a.description.toLowerCase() < b.description.toLowerCase()) {
return -1;
} else if (a.description.toLowerCase() > b.description.toLowerCase()) {
return 1;
} else {
return 0;
}
});
return sortedItems;
} else if (sortBy === 'packed') {
sortedItems.sort((a, b) => {
if (a.packed < b.packed) {
return -1;
} else if (a.packed > b.packed) {
return 1;
} else {
return 0;
}
});
return sortedItems;
}
});
}
function handleClearList() {
setItems([]);
}
const numOfItems = items.length;
const numOfPacked = calculatePackedItems();
const percentPacked = numOfItems > 0 ? calculatePercentagePacked() : 0;
return (
<div className='app font-body text-navy grid w-full h-screen'>
<Header />
<Form
onFormSubmit={handleFormSubmit}
quantity={quantity}
description={description}
onDescriptionChange={handleDescriptionChange}
onQuantityChange={handleQuantityChange}
/>
<PackingList
items={items}
onPackedChange={handlePackedChange}
onRemoveItem={handleRemoveItem}
onClearList={handleClearList}
onSort={handleSort}
/>
<Stats numOfItems={numOfItems} numOfPacked={numOfPacked} percentPacked={percentPacked} />
</div>
);
}
export default App;
این جزء اصلی برنامه ما است. ما از React استفاده می کنیم useState
قلاب برای پیگیری موارد لیست و کمیت و توضیحات مورد فعلی. چندین عملکرد کمکی برای مدیریت لیست بسته بندی وجود دارد:
-
handleQuantityChange(e)
: این تابع هر زمان که کاربر فیلد ورودی کمیت را تغییر می دهد، مقدار کمیت را در حالت به روز می کند. -
handleDescriptionChange(e)
: این تابع هر زمان که کاربر فیلد ورودی توضیحات را تغییر می دهد، مقدار توضیحات را در حالت به روز می کند. -
handleFormSubmit(e)
: هنگامی که کاربر فرم را ارسال می کند، این تابع از بارگیری مجدد صفحه جلوگیری می کند، یک شی مورد جدید ایجاد می کند، این شی را به لیست موارد فعلی اضافه می کند و فیلدهای کمیت و توضیحات را برای ورودی بیشتر بازنشانی می کند. -
handlePackedChange(id)
: این تابع وضعیت بسته بندی یک آیتم خاص را هنگامی که کاربر چک باکس مربوطه را علامت زده یا علامت آن را بردارید تغییر می دهد. -
handleRemoveItem(id)
: اگر کاربر روی دکمه حذف روی یک مورد خاص کلیک کند، این تابع آن مورد را از لیست آیتم ها حذف می کند. -
calculatePackedItems()
: این تابع تعداد اقلام بسته بندی شده در لیست را محاسبه می کند. -
calculatePercentagePacked()
: این تابع درصد موارد بسته بندی شده را محاسبه می کند. -
handleSort(e)
: این تابع لیست اقلام را بر اساس ترجیح کاربر مرتب می کند: بر اساس ترتیب ورودی، توضیحات، یا وضعیت بسته بندی شده. -
handleClearList()
: وقتی کاربر روی دکمه “پاک کردن لیست” کلیک می کند، این عملکرد همه موارد را از لیست پاک می کند.
جزء سرصفحه
Header.jsx
ساده ترین اجزای ما است. این فقط یک استایل را برمی گرداند h1
تگ کنید، و هیچ ویژگی یا وضعیتی در کار نیست. آسان peasy! 🍋
export default function Header() {
return (
<h1 className='font-heading bg-cerulean py-6 text-6xl tracking-tight text-center uppercase'>
✈️ AFK Packing List ⌨️
</h1>
);
}
مؤلفه فرم
مؤلفه Form ورودی های کاربر را برای اقلام جدید و مقادیر آنها کنترل می کند. این مقدار و توضیحات فعلی را از مولفه App از طریق props می گیرد و از آنها برای پر کردن فیلدهای فرم از قبل استفاده می کند.
export default function Form({ onFormSubmit, quantity, description, onQuantityChange, onDescriptionChange }) {
return (
<form className='bg-lava py-7 flex items-center justify-center gap-3' onSubmit={onFormSubmit}>
<h3 className='mr-4 text-2xl'>what do you need for your trip?</h3>
<select value={quantity} onChange={onQuantityChange} id='quantity' className='pill focus:outline-lava'>
{[...Array(20)].map((_, i) => (
<option key={i} value={i + 1}>
{i + 1}
</option>
))}
</select>
<input
value={description}
onChange={onDescriptionChange}
id='description'
className='pill focus:outline-lava'
type='text'
placeholder='Item...'
/>
<button className='pill bg-cerulean focus:outline-navy uppercase' type='submit'>
Add
</button>
</form>
);
}
این جزء نیز از onFormSubmit
، onQuantityChange
، و onDescriptionChange
ابزارهایی برای رسیدگی به فرم ارسالی و به روز رسانی فیلدهای کمیت و توضیحات.
این select
ورودی برای کمیت یک ویژگی کوچک شسته و رفته است! 🎩 ورودی کمیت از روشهای آرایه و نقشه جاوا اسکریپت برای ایجاد یک منوی کشویی با 20 گزینه استفاده میکند و به کاربر امکان میدهد به راحتی مقدار یک مورد را انتخاب کند.
[...Array(20)].map((_, i) => (
<option key={i} value={i + 1}>
{i + 1}
</option>
))
- ابتدا با استفاده از سازنده Array و عملگر گسترش آرایه ES6 یک آرایه با 20 آیتم خالی ایجاد می کنیم.
- سپس، متد map() را فراخوانی می کنیم تا روی آرایه تکرار شود و یک آرایه جدید با تعداد گزینه هایی که نیاز داریم ایجاد کنیم.
- متد map() تابع callback را در هر آیتم آرایه فراخوانی می کند.
- مقدار آرگومان اول همیشه آیتم آرایه فعلی است.
- مقدار آرگومان دوم همیشه شاخص مورد فعلی است.
- ما از ایندکس برای ایجاد یک کلید منحصر به فرد برای هر گزینه استفاده می کنیم. هر آیتم همچنین دارای یک چک باکس و یک دکمه حذف است. چک باکس به کاربر اجازه می دهد تا وضعیت بسته بندی یک مورد را تغییر دهد و دکمه حذف به کاربر اجازه می دهد یک مورد را از لیست حذف کند.
جزء لیست بسته بندی
این جایی است که سحر و جادو اتفاق می افتد! ✨ مولفه PackingList آرایه ای از آیتم ها و نقشه ها را روی آنها می گیرد و برای هر کدام یک جزء آیتم جدید ایجاد می کند.
import Item from './Item';
export default function PackingList({ items, onPackedChange, onRemoveItem, onClearList, onSort }) {
return (
<section className='bg-navy text-whip list flex flex-col items-center justify-between gap-8 py-10'>
<ul className='grid content-start justify-center w-4/5 gap-3 overflow-scroll list-none'>
{items.map((item) => {
return (
<Item
key={item.id}
id={item.id}
description={item.description}
quantity={item.quantity}
packed={item.packed}
onPackedChange={onPackedChange}
onRemoveItem={onRemoveItem}
/>
);
})}
</ul>
<div>
<select onChange={onSort} className='pill pill-sm'>
<option value='input'>Sort by input order</option>
<option value='description'>Sort by description</option>
<option value='packed'>Sort by packed status</option>
</select>
<button onClick={onClearList} className='pill pill-sm'>
Clear List
</button>
</div>
</section>
);
}
علاوه بر این، این مؤلفه شامل کنترلهایی برای مرتبسازی فهرست و پاک کردن همه موارد است که باعث میشود فهرست بستهبندی ما نه تنها کاربردی، بلکه کاربرپسند باشد!
جزء آیتم
حالا بیایید نگاهی به مولفه Item بیندازیم. هر آیتم نشان دهنده یک آیتم جداگانه در لیست بسته بندی است و مسئول ارائه مقدار، توضیحات و وضعیت بسته بندی آن است.
export default function Item({ id, description, quantity, packed, onPackedChange, onRemoveItem }) {
return (
<>
<li className='flex items-center gap-3'>
<input
className='accent-cerulean w-5 h-5'
type='checkbox'
checked={packed}
onChange={() => onPackedChange(id)}
/>
<span style={packed ? { textDecoration: 'line-through' } : {}}>
{quantity} {description}
</span>
<button
className='bg-none p-2 text-lg translate-y-0.5 border-none cursor-pointer'
onClick={() => onRemoveItem(id)}
>
❌
</button>
</li>
</>
);
}
جزء آمار
آخرین اما نه کم اهمیت، جزء آمار. این جزء برخی از آمارهای مربوط به لیست بسته بندی ما را نشان می دهد، مانند تعداد کل اقلام، تعداد اقلامی که قبلاً بسته بندی شده اند و درصد اقلام بسته بندی شده.
export default function Stats({ numOfItems = 0, numOfPacked = 0, percentPacked = 0 }) {
return (
<footer className='bg-maroon text-whip py-8 text-lg font-bold text-center'>
<p>
You have {numOfItems} items on your list and you've already packed {numOfPacked} ({percentPacked}%)
</p>
</footer>
);
}
و شما آن را دارید – یک برنامه لیست بسته بندی آماده برای تابستان که با React و TailwindCSS ساخته شده است! امیدوارم از این نگاه کوچک به روند من لذت برده باشید، و امیدوارم الهام بخش شما برای ایجاد چیزی از خودتان باشد.
امیدوارم این راهنما به شما کمک کند تا بفهمید چگونه فهرست بسته بندی AFK را ایجاد کردم. اگر به ساختن نسخه خود یا مشارکت در نسخه من علاقه دارید، با خیال راحت مخزن را شبیه سازی کنید و آن را امتحان کنید! 🚀
تا دفعه بعد کدنویسی مبارک و سفر خوش! 🚀
بردن: ساختن این برنامه لیست بسته بندی نه تنها به سفرهای شما بدون استرس کمک می کند، بلکه روشی عملی و سرگرم کننده برای عملی کردن دانش React و TailwindCSS در اختیار شما قرار می دهد. چه پروژه های دیگری برای حل مشکلات روزمره ساخته اید؟ در نظرات زیر به اشتراک بگذارید!