Created
June 11, 2025 12:40
-
-
Save sunmeat/b10add8c204b4ca33abe5f3643013106 to your computer and use it in GitHub Desktop.
reactagram v.0.1 - useParams, useNavigate, useLocation, запросы
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
App.jsx: | |
import { | |
BrowserRouter, | |
Routes, | |
Route, | |
NavLink, | |
Link, | |
Outlet, | |
Navigate, | |
useParams, | |
useNavigate, | |
useLocation | |
} from 'react-router'; | |
import './App.css'; | |
import {useState, useEffect, createContext, useContext, useMemo, useRef} from "react"; | |
const UsersContext = createContext(); | |
export function UsersProvider({children}) { | |
const [loading, setLoading] = useState(true); | |
const [error, setError] = useState(null); | |
const cachedUsersRef = useRef(null); | |
const [, forceUpdate] = useState(0); | |
useEffect(() => { | |
async function fetchUsers() { | |
try { | |
setLoading(true); | |
setError(null); | |
const response = await fetch('https://randomuser.me/api/?results=1000'); | |
// загрузка данных через API с useEffect | |
if (!response.ok) { | |
throw new Error('Ошибка загрузки пользователей'); | |
} | |
const data = await response.json(); | |
if (!cachedUsersRef.current) { | |
cachedUsersRef.current = data.results; | |
forceUpdate(n => n + 1); | |
} | |
} catch (err) { | |
setError(err.message); | |
} finally { | |
setLoading(false); | |
} | |
} | |
fetchUsers(); | |
}, []); | |
return ( | |
<UsersContext.Provider | |
value={{ | |
users: cachedUsersRef.current || [], | |
loading, | |
error, | |
}} | |
> | |
{children} | |
</UsersContext.Provider> | |
); | |
} | |
export function useUsers() { | |
return useContext(UsersContext); | |
} | |
function Home() { | |
const {users, loading: usersLoading, error: usersError} = useUsers(); | |
const [cachedUsers, setCachedUsers] = useState(null); | |
useEffect(() => { | |
if (!usersLoading && users?.length && !cachedUsers) { | |
setCachedUsers(users); | |
} | |
// загрузка данных через API с useEffect | |
}, [usersLoading, users, cachedUsers]); | |
if (!cachedUsers) { | |
if (usersError) { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title">Ошибка</h1> | |
<p className="about-text">{usersError}</p> | |
</div> | |
); | |
} | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title">Загрузка...</h1> | |
</div> | |
); | |
} | |
const getUserById = (id) => { | |
const index = id - 1; | |
return cachedUsers[index] ?? null; | |
}; | |
const user1 = getUserById(1); | |
const user2 = getUserById(2); | |
// url /users/1 показывает профиль конкретного пользователя | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title home-title">Лента</h1> | |
<div className="post"> | |
<div className="post-header"> | |
<div className="post-author-wrapper"> | |
<span className="post-avatar"></span> | |
<Link to="/users/1" className="post-author"> | |
{user1?.login.username ?? "Неизвестный"} | |
</Link> | |
</div> | |
<span className="post-time">1м назад</span> | |
</div> | |
<div className="post-content">Красивый закат в Одессе! #природа #море</div> | |
<div className="post-actions"> | |
<button className="action-button like">Лайк</button> | |
<Link to="/messages" className="action-button comment">Комментировать</Link> | |
</div> | |
</div> | |
<div className="post"> | |
<div className="post-header"> | |
<div className="post-author-wrapper"> | |
<span className="post-avatar"></span> | |
<Link to="/users/2" className="post-author"> | |
{user2?.login.username ?? "Неизвестный"} | |
</Link> | |
</div> | |
<span className="post-time">2м назад</span> | |
</div> | |
<div className="post-content">Вылазка на Говерлу! #путешествия #горы</div> | |
<div className="post-actions"> | |
<button className="action-button like">Лайк</button> | |
<Link to="/messages" className="action-button comment">Комментировать</Link> | |
</div> | |
</div> | |
<div className="link-group"> | |
<Link to="/profile" className="page-link">Перейти к профилю</Link> | |
<Link to="/search" className="page-link">Найти друзей</Link> | |
</div> | |
</div> | |
); | |
} | |
function UserProfile() { | |
const {id} = useParams(); // хук useParams получает параметры из URL | |
const {users, loading: usersLoading, error: usersError} = useUsers(); | |
const userId = parseInt(id, 10); | |
const user = useMemo(() => { | |
if (!Array.isArray(users) || users.length === 0) return null; | |
if (isNaN(userId) || userId < 1 || userId > users.length) return null; | |
return users[userId - 1]; | |
}, [userId, users]); | |
if (usersLoading) { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title">Загрузка...</h1> | |
</div> | |
); | |
} | |
if (usersError) { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title">Ошибка</h1> | |
<p className="about-text">{usersError}</p> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
</div> | |
</div> | |
); | |
} | |
if (!user) { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title">Пользователь не найден</h1> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
</div> | |
</div> | |
); | |
} | |
// {'url /users/:id показывает профиль конкретного пользователя'} | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title profile-title">Профиль пользователя #{id}</h1> | |
<div className="profile-info"> | |
<div className="profile-header"> | |
<div className="profile-avatar"> | |
<img | |
src={user.picture.large} | |
alt="Avatar" | |
style={{width: "100%", height: "100%", borderRadius: "50%"}} | |
/> | |
</div> | |
<div className="profile-details"> | |
<span className="profile-username">{user.login.username}</span> | |
<p>{user.name.first} {user.name.last}</p> | |
<p>{user.email}</p> | |
<p>{user.location.city}, {user.location.country}</p> | |
</div> | |
</div> | |
<div className="profile-stats"> | |
<span>{Math.floor(Math.random() * 100)} постов</span> | |
<span>{Math.floor(Math.random() * 1000)} подписчиков</span> | |
<span>{Math.floor(Math.random() * 500)} подписок</span> | |
</div> | |
</div> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
<Link to="/search" className="page-link">Поиск</Link> | |
</div> | |
</div> | |
); | |
} | |
function ProfilePosts() { | |
const [activePost, setActivePost] = useState(0); | |
const posts = [ | |
"Поехать согласилась только крыша…", | |
"Пойди приляг. Желательно на рельсы.", | |
"Контрольный выстрел мало что исправил…", | |
"Бежать за пивом помешали ноги.", | |
"Кругом такое!.. Хоть иди участвуй.", | |
"Сегодня дел полно! Во-первых, завтрак…" | |
]; | |
return ( | |
<div className="profile-content"> | |
<h2>Мои посты</h2> | |
{posts.map((post, index) => ( | |
<div | |
key={index} | |
className={`post ${activePost === index ? 'active' : ''}`} | |
onClick={() => setActivePost(index)} | |
> | |
{post} | |
</div> | |
))} | |
</div> | |
); | |
} | |
function ProfileSettings() { | |
return ( | |
<div className="profile-content"> | |
<h2>Настройки профиля</h2> | |
<h4>Тут какие-нибудь настройки профиля</h4> | |
</div> | |
); | |
} | |
function Profile() { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title profile-title">Мой профиль</h1> | |
<div className="profile-info"> | |
<div className="profile-header"> | |
<div className="profile-avatar"></div> | |
<div className="profile-details"> | |
<span className="profile-username">sunmeat</span> | |
<button className="profile-edit">Редактировать профиль</button> | |
</div> | |
</div> | |
<div className="profile-stats"> | |
<span>6 постов</span> | |
<span>1.2K подписчиков</span> | |
<span>350 подписок</span> | |
</div> | |
</div> | |
<div className="profile-nav"> | |
<NavLink to="posts" | |
className={({isActive}) => isActive ? "nav-link active" : "nav-link"}>Посты</NavLink> | |
<NavLink to="settings" | |
className={({isActive}) => isActive ? "nav-link active" : "nav-link"}>Настройки</NavLink> | |
</div> | |
<Outlet/> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
<Link to="/messages" className="page-link">Сообщения</Link> | |
<Link to="/search" className="page-link">Поиск</Link> | |
</div> | |
</div> | |
); | |
} | |
function MessagesInbox() { | |
// url /users/793 показывает профиль конкретного пользователя | |
return ( | |
<div className="messages-content"> | |
<h2>Входящие</h2> | |
<div className="message"> | |
<div className="message-sender-wrapper"> | |
<span className="message-avatar"></span> | |
<Link to="/users/215" className="message-sender">Вася</Link> | |
</div> | |
<span className="message-preview">Шо ты там?</span> | |
</div> | |
<div className="message"> | |
<div className="message-sender-wrapper"> | |
<span className="message-avatar"></span> | |
<Link to="/users/793" className="message-sender">Лена</Link> | |
</div> | |
<span className="message-preview">Ты помнишь, что у меня завтра важное?</span> | |
</div> | |
</div> | |
); | |
} | |
function MessagesSent() { | |
return ( | |
<div className="messages-content"> | |
<h2>Отправленные</h2> | |
<div className="message"> | |
<div className="message-sender-wrapper"> | |
<span className="message-avatar"></span> | |
<Link to="/profile" className="message-sender">sunmeat</Link> | |
</div> | |
<span className="message-preview">Да, помню, удачи!</span> | |
</div> | |
</div> | |
); | |
} | |
function Messages() { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title messages-title">Сообщения</h1> | |
<div className="messages-nav"> | |
<NavLink to="inbox" | |
className={({isActive}) => isActive ? "nav-link active" : "nav-link"}>Входящие</NavLink> | |
<NavLink to="sent" | |
className={({isActive}) => isActive ? "nav-link active" : "nav-link"}>Отправленные</NavLink> | |
</div> | |
<Outlet/> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
<Link to="/profile" className="page-link">Профиль</Link> | |
</div> | |
</div> | |
); | |
} | |
function Search() { | |
const [searchTerm, setSearchTerm] = useState(''); | |
const [filteredUsers, setFilteredUsers] = useState([]); | |
const [searchLoading, setSearchLoading] = useState(false); | |
const navigate = useNavigate(); // useNavigate позволяет программно переходить между страницами | |
const location = useLocation(); // useLocation предоставляет доступ к текущему URL и его частям | |
const {users, loading: usersLoading, error: usersError} = useUsers(); | |
const searchParams = new URLSearchParams(location.search); | |
// запросы позволяют обрабатывать дополнительные параметры в URL | |
const query = searchParams.get('q') || ''; | |
useEffect(() => { | |
if (query) { | |
setSearchTerm(query); | |
handleSearch(query); | |
} | |
}, [query, users]); | |
const handleSearch = (term) => { | |
if (!term.trim()) { | |
setFilteredUsers([]); | |
return; | |
} | |
setSearchLoading(true); | |
setTimeout(() => { | |
const results = users.filter(user => { | |
const firstName = user.name.first.toLowerCase(); | |
const lastName = user.name.last.toLowerCase(); | |
const username = user.login.username.toLowerCase(); | |
const searchTerm = term.toLowerCase(); | |
return firstName.includes(searchTerm) || | |
lastName.includes(searchTerm) || | |
username.includes(searchTerm); | |
}); | |
setFilteredUsers(results); | |
setSearchLoading(false); | |
}, 300); | |
}; | |
const handleInputChange = (e) => { | |
const value = e.target.value; | |
setSearchTerm(value); | |
const params = new URLSearchParams(); | |
if (value.trim()) | |
params.set('q', value); | |
const queryString = params.toString(); | |
navigate(`/search${queryString ? `?${queryString}` : ''}`, {replace: true}); | |
// useNavigate позволяет программно переходить между страницами | |
}; | |
if (usersLoading) { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title search-title">Поиск</h1> | |
<p>Загрузка базы пользователей...</p> | |
</div> | |
); | |
} | |
if (usersError) { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title search-title">Поиск</h1> | |
<p>Ошибка загрузки пользователей: {usersError}</p> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
</div> | |
</div> | |
); | |
} | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title search-title">Поиск</h1> | |
<div className="search-bar"> | |
<input | |
type="text" | |
placeholder="Поиск пользователей..." | |
value={searchTerm} | |
onChange={handleInputChange} | |
/> | |
</div> | |
{searchLoading && <p>Поиск...</p>} | |
{filteredUsers.length > 0 && ( | |
<div className="search-results"> | |
{filteredUsers.map((user, index) => { | |
const originalIndex = users.findIndex(u => u.login.uuid === user.login.uuid) + 1; | |
return ( | |
<div key={user.login.uuid} className="search-result"> | |
<div className="result-wrapper"> | |
<span className="result-avatar"> | |
<img src={user.picture.thumbnail} alt="Avatar" | |
style={{width: '40px', height: '40px', borderRadius: '50%'}}/> | |
</span> | |
<Link to={`/users/${originalIndex}`} | |
className="result-item">{user.login.username}</Link> | |
</div> | |
<span | |
className="result-bio">{user.name.first} {user.name.last} - {user.location.city}</span> | |
</div> | |
); | |
})} | |
</div> | |
)} | |
{searchTerm && !searchLoading && filteredUsers.length === 0 && users.length > 0 && ( | |
<p>Пользователи не найдены</p> | |
)} | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
<Link to="/profile" className="page-link">Профиль</Link> | |
</div> | |
</div> | |
); | |
} | |
function About() { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title about-title">Reactagram</h1> | |
<p className="about-text">Добро пожаловать в наше приложение! Делись моментами, находи друзей и | |
вдохновляйся!</p> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
<Link to="/profile" className="page-link">Профиль</Link> | |
<Link to="/search" className="page-link">Поиск</Link> | |
</div> | |
</div> | |
); | |
} | |
function NotFound() { | |
return ( | |
<div className="page-content"> | |
<h1 className="page-title">404 - Страница не найдена</h1> | |
<p className="about-text">Извините, запрашиваемая страница не существует.</p> | |
<div className="link-group"> | |
<Link to="/" className="page-link">Вернуться в ленту</Link> | |
<Link to="/profile" className="page-link">Профиль</Link> | |
<Link to="/search" className="page-link">Поиск</Link> | |
</div> | |
</div> | |
); | |
} | |
function App() { | |
return ( | |
<UsersProvider> | |
<BrowserRouter> | |
<nav className="nav"> | |
<ul className="nav-list"> | |
<li className="nav-item"> | |
<NavLink to="/" className={({isActive}) => isActive ? "nav-link active" : "nav-link"}> | |
<span className="nav-icon home-icon"></span> | |
<span className="nav-text">Лента</span> | |
</NavLink> | |
</li> | |
<li className="nav-item"> | |
<NavLink to="/profile" | |
className={({isActive}) => isActive ? "nav-link active" : "nav-link"}> | |
<span className="nav-icon profile-icon"></span> | |
<span className="nav-text">Профиль</span> | |
</NavLink> | |
</li> | |
<li className="nav-item"> | |
<NavLink to="/messages" | |
className={({isActive}) => isActive ? "nav-link active" : "nav-link"}> | |
<span className="nav-icon messages-icon"></span> | |
<span className="nav-text">Сообщения</span> | |
</NavLink> | |
</li> | |
<li className="nav-item"> | |
<NavLink to="/search" className={({isActive}) => isActive ? "nav-link active" : "nav-link"}> | |
<span className="nav-icon search-icon"></span> | |
<span className="nav-text">Поиск</span> | |
</NavLink> | |
</li> | |
<li className="nav-item"> | |
<NavLink to="/about" className={({isActive}) => isActive ? "nav-link active" : "nav-link"}> | |
<span className="nav-icon about-icon"></span> | |
<span className="nav-text">О нас</span> | |
</NavLink> | |
</li> | |
</ul> | |
</nav> | |
<main className="content"> | |
<Routes> | |
<Route path="/" element={<Home/>}/> | |
<Route path="/users/:id" element={<UserProfile/>}/> | |
{'параметры делают маршруты динамическими (например, /users/:id'} | |
<Route path="/profile" element={<Profile/>}> | |
<Route path="posts" element={<ProfilePosts/>}/> | |
<Route path="settings" element={<ProfileSettings/>}/> | |
<Route index element={<Navigate to="posts" replace/>}/> | |
</Route> | |
<Route path="/messages" element={<Messages/>}> | |
<Route path="inbox" element={<MessagesInbox/>}/> | |
<Route path="sent" element={<MessagesSent/>}/> | |
<Route index element={<Navigate to="inbox" replace/>}/> | |
</Route> | |
<Route path="/search" element={<Search/>}/> | |
<Route path="/about" element={<About/>}/> | |
<Route path="*" element={<NotFound/>}/> | |
</Routes> | |
</main> | |
</BrowserRouter> | |
</UsersProvider> | |
); | |
} | |
export default App; | |
========================================================================================================================== | |
App.css: | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
background: #fafafa; | |
min-height: 100vh; | |
color: #262626; | |
} | |
.nav { | |
background: #ffffff; | |
border-bottom: 1px solid #dbdbdb; | |
padding: 0.75rem 0; | |
position: fixed; | |
top: 0; | |
left: 0; | |
right: 0; | |
z-index: 1000; | |
} | |
.nav-list { | |
list-style: none; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
gap: 2rem; | |
max-width: 935px; | |
margin: 0 auto; | |
} | |
.nav-item { | |
position: relative; | |
} | |
.nav-link { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
color: #262626; | |
text-decoration: none; | |
font-size: 0.9rem; | |
font-weight: 500; | |
padding: 0.5rem 0.75rem; | |
border-radius: 8px; | |
transition: background-color 0.2s ease, color 0.2s ease; | |
} | |
.nav-link:hover, | |
.nav-link.active { | |
background-color: rgba(0, 149, 246, 0.1); | |
color: #0095f6; | |
} | |
.nav-icon { | |
display: inline-block; | |
width: 24px; | |
height: 24px; | |
background-size: cover; | |
background-position: center; | |
} | |
.home-icon { | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>'); | |
} | |
.profile-icon { | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>'); | |
} | |
.messages-icon { | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>'); | |
} | |
.search-icon { | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>'); | |
} | |
.about-icon { | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v.01"></path><path d="M12 8v4"></path></svg>'); | |
} | |
.nav-text { | |
display: inline; | |
} | |
.content { | |
max-width: 935px; | |
margin: 4rem auto 2rem; | |
padding: 1rem; | |
min-height: calc(100vh - 4rem); | |
} | |
.page-content { | |
display: flex; | |
flex-direction: column; | |
gap: 1.5rem; | |
padding: 1.5rem; | |
background: #ffffff; | |
border-radius: 8px; | |
border: 1px solid #dbdbdb; | |
} | |
.page-title { | |
font-size: 1.5rem; | |
font-weight: 600; | |
text-align: left; | |
color: #262626; | |
} | |
.home-title { | |
background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
} | |
.profile-title { | |
background: linear-gradient(45deg, #405de6, #5851db, #833ab4, #c13584, #e1306c, #fd1d1d); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
} | |
.messages-title { | |
background: linear-gradient(45deg, #ffdc80, #fcaf45, #f77737); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
} | |
.search-title { | |
background: linear-gradient(45deg, #833ab4, #fd1d1d, #fcb045); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
} | |
.about-title { | |
background: linear-gradient(45deg, #00ddeb, #26a0da, #0095f6); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
} | |
.post { | |
padding: 10px; | |
margin: 5px 0; | |
cursor: pointer; | |
background-color: #f9f9f9; | |
} | |
.post.active { | |
background-color: #e0f7fa; | |
border-left: 4px solid #007bff; | |
} | |
.post-header { | |
display: flex; | |
align-items: center; | |
gap: 0.75rem; | |
margin-bottom: 0.75rem; | |
} | |
.post-author-wrapper { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
} | |
.post-avatar { | |
width: 32px; | |
height: 32px; | |
border-radius: 50%; | |
background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888); | |
border: 2px solid #ffffff; | |
} | |
.post-author { | |
font-weight: 600; | |
color: #262626; | |
text-decoration: none; | |
} | |
.post-author:hover { | |
color: #0095f6; | |
} | |
.post-time { | |
color: #8e8e8e; | |
font-size: 0.8rem; | |
} | |
.post-content { | |
font-size: 0.9rem; | |
line-height: 1.4; | |
margin-bottom: 0.75rem; | |
} | |
.post-actions { | |
display: flex; | |
gap: 0.5rem; | |
} | |
.action-button { | |
padding: 0.5rem; | |
background: none; | |
border: none; | |
font-size: 0.85rem; | |
cursor: pointer; | |
color: #262626; | |
transition: color 0.2s ease; | |
} | |
.action-button:hover { | |
color: #0095f6; | |
} | |
.like::before { | |
content: '❤️'; | |
margin-right: 0.25rem; | |
} | |
.comment::before { | |
content: '💬'; | |
margin-right: 0.25rem; | |
} | |
.profile-info { | |
display: flex; | |
flex-direction: column; | |
gap: 1rem; | |
} | |
.profile-header { | |
display: flex; | |
align-items: center; | |
gap: 1rem; | |
} | |
.profile-avatar { | |
width: 80px; | |
height: 80px; | |
border-radius: 50%; | |
background: linear-gradient(45deg, #405de6, #5851db, #833ab4, #c13584, #e1306c, #fd1d1d); | |
border: 2px solid #ffffff; | |
} | |
.profile-details { | |
flex: 1; | |
} | |
.profile-username { | |
font-weight: 600; | |
font-size: 1.2rem; | |
} | |
.profile-edit { | |
padding: 0.5rem 1rem; | |
background: linear-gradient(45deg, #0095f6, #00ddeb); | |
color: #ffffff; | |
border: none; | |
border-radius: 8px; | |
cursor: pointer; | |
font-size: 0.85rem; | |
font-weight: 500; | |
transition: opacity 0.2s ease; | |
margin-left: 10px; | |
} | |
.profile-edit:hover { | |
opacity: 0.9; | |
} | |
.profile-stats { | |
display: flex; | |
gap: 1.5rem; | |
font-size: 0.9rem; | |
font-weight: 500; | |
} | |
.message { | |
display: flex; | |
align-items: center; | |
gap: 0.75rem; | |
padding: 0.75rem; | |
border-bottom: 1px solid #dbdbdb; | |
} | |
.message-sender-wrapper { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
} | |
.message-avatar { | |
width: 32px; | |
height: 32px; | |
border-radius: 50%; | |
background: linear-gradient(45deg, #f77737, #fcaf45, #ffdc80); | |
border: 2px solid #ffffff; | |
} | |
.message-sender { | |
font-weight: 600; | |
color: #262626; | |
text-decoration: none; | |
} | |
.message-sender:hover { | |
color: #0095f6; | |
} | |
.message-preview { | |
color: #8e8e8e; | |
font-size: 0.85rem; | |
} | |
.search-bar { | |
margin-bottom: 1rem; | |
} | |
.search-bar input { | |
width: 100%; | |
padding: 0.75rem 1rem; | |
border: 1px solid #dbdbdb; | |
border-radius: 12px; | |
font-size: 0.9rem; | |
background: #fafafa; | |
outline: none; | |
transition: border-color 0.2s ease, box-shadow 0.2s ease; | |
} | |
.search-bar input:focus { | |
border-color: #0095f6; | |
box-shadow: 0 0 0 3px rgba(0, 149, 246, 0.1); | |
} | |
.search-bar input::placeholder { | |
color: #8e8e8e; | |
} | |
.search-results { | |
display: flex; | |
flex-direction: column; | |
gap: 0.75rem; | |
animation: fadeInUp 0.4s ease-out; | |
} | |
@keyframes fadeInUp { | |
from { | |
opacity: 0; | |
transform: translateY(20px); | |
} | |
to { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
.search-result { | |
padding: 1rem; | |
border: 1px solid #dbdbdb; | |
border-radius: 12px; | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
background: #ffffff; | |
transition: all 0.3s ease; | |
animation: slideInLeft 0.5s ease-out; | |
animation-fill-mode: both; | |
transform: translateX(-20px); | |
opacity: 0; | |
} | |
.search-result:nth-child(1) { | |
animation-delay: 0.1s; | |
} | |
.search-result:nth-child(2) { | |
animation-delay: 0.15s; | |
} | |
.search-result:nth-child(3) { | |
animation-delay: 0.2s; | |
} | |
.search-result:nth-child(4) { | |
animation-delay: 0.25s; | |
} | |
.search-result:nth-child(5) { | |
animation-delay: 0.3s; | |
} | |
.search-result:nth-child(n+6) { | |
animation-delay: 0.35s; | |
} | |
@keyframes slideInLeft { | |
to { | |
opacity: 1; | |
transform: translateX(0); | |
} | |
} | |
.search-result:hover { | |
border-color: #0095f6; | |
box-shadow: 0 4px 12px rgba(0, 149, 246, 0.15); | |
transform: translateY(-2px); | |
} | |
.result-wrapper { | |
display: flex; | |
align-items: center; | |
gap: 0.75rem; | |
} | |
.result-avatar { | |
width: 48px; | |
height: 48px; | |
border-radius: 50%; | |
background: linear-gradient(45deg, #833ab4, #fd1d1d, #fcb045); | |
border: 2px solid #ffffff; | |
flex-shrink: 0; | |
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | |
} | |
.result-item { | |
font-weight: 600; | |
font-size: 1rem; | |
color: #262626; | |
text-decoration: none; | |
transition: color 0.2s ease; | |
} | |
.result-item:hover { | |
color: #0095f6; | |
} | |
.result-bio { | |
color: #8e8e8e; | |
font-size: 0.9rem; | |
margin-left: 60px; | |
padding-top: 0.25rem; | |
} | |
.about-text { | |
font-size: 0.9rem; | |
line-height: 1.5; | |
text-align: center; | |
color: #262626; | |
} | |
.link-group { | |
display: flex; | |
gap: 0.75rem; | |
flex-wrap: wrap; | |
justify-content: center; | |
} | |
.page-link { | |
display: inline-block; | |
padding: 0.6rem 1.2rem; | |
background: linear-gradient(45deg, #0095f6, #00ddeb); | |
color: #ffffff; | |
text-decoration: none; | |
font-size: 0.85rem; | |
font-weight: 500; | |
border-radius: 8px; | |
transition: opacity 0.2s ease, transform 0.2s ease; | |
} | |
.page-link:hover { | |
opacity: 0.9; | |
transform: translateY(-2px); | |
} | |
.search-loading { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
padding: 1rem; | |
color: #8e8e8e; | |
font-style: italic; | |
} | |
.search-loading::before { | |
content: ''; | |
width: 16px; | |
height: 16px; | |
border: 2px solid #dbdbdb; | |
border-top: 2px solid #0095f6; | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
} | |
@keyframes spin { | |
0% { | |
transform: rotate(0deg); | |
} | |
100% { | |
transform: rotate(360deg); | |
} | |
} | |
.no-results { | |
text-align: center; | |
padding: 2rem; | |
color: #8e8e8e; | |
font-style: italic; | |
animation: fadeIn 0.3s ease-out; | |
} | |
@keyframes fadeIn { | |
from { | |
opacity: 0; | |
} | |
to { | |
opacity: 1; | |
} | |
} | |
@media (max-width: 768px) { | |
.nav-list { | |
gap: 0.5rem; | |
flex-wrap: wrap; | |
padding: 0 0.5rem; | |
} | |
.nav-link { | |
flex-direction: column; | |
gap: 0.25rem; | |
padding: 0.5rem; | |
} | |
.nav-text { | |
font-size: 0.75rem; | |
} | |
.page-content { | |
padding: 1rem; | |
} | |
.page-title { | |
font-size: 1.25rem; | |
} | |
.profile-header { | |
flex-direction: column; | |
align-items: flex-start; | |
} | |
.profile-avatar { | |
width: 60px; | |
height: 60px; | |
} | |
.link-group { | |
flex-direction: column; | |
align-items: center; | |
} | |
.search-result { | |
padding: 0.75rem; | |
} | |
.result-avatar { | |
width: 40px; | |
height: 40px; | |
} | |
.result-bio { | |
margin-left: 52px; | |
} | |
} | |
@media (max-width: 480px) { | |
.nav-icon { | |
width: 20px; | |
height: 20px; | |
} | |
.nav-text { | |
display: none; | |
} | |
.post-avatar, .message-avatar { | |
width: 28px; | |
height: 28px; | |
} | |
.result-avatar { | |
width: 36px; | |
height: 36px; | |
} | |
.result-bio { | |
margin-left: 48px; | |
} | |
.post-content, .about-text { | |
font-size: 0.85rem; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment