create accordion and news component
This commit is contained in:
@@ -71,7 +71,7 @@ const SearchCard = ({
|
||||
</div>
|
||||
<Button
|
||||
text="Откликнуться"
|
||||
className="bg-orange text-white px-10 py-3 text-base font-semibold hover:bg-orange/80 transition-colors cursor-pointer"
|
||||
className="bg-orange hover:bg-orange/80 text-white px-10 py-3 text-base font-semibold transition-colors cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,58 +2,16 @@ import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import AddressSelector from '@/components/AddressSelector'
|
||||
import SearchCard from '@/app/(urls)/search/components/SearchCard'
|
||||
import avatar from '../public/images/avatar.png'
|
||||
import belarusIcon from '../public/images/belarus.png'
|
||||
import russiaIcon from '../public/images/russia.png'
|
||||
|
||||
import FAQ from '@/components/FAQ'
|
||||
import { faqs } from '@/app/staticData'
|
||||
import { data } from '@/app/staticData'
|
||||
import { routes } from '@/app/staticData'
|
||||
import Button from '@/components/ui/Button'
|
||||
import News from '@/components/News'
|
||||
export default function Home() {
|
||||
const routes = 12845
|
||||
const userImg = avatar
|
||||
const blIcon = belarusIcon
|
||||
const ruIcon = russiaIcon
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1123,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Нужен перевозчик',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
moving_type: 'Авиатранспорт',
|
||||
estimated_date: new Date(2025, 4, 15),
|
||||
},
|
||||
{
|
||||
id: 2423,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Могу перевезти',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
moving_type: 'Автоперевозка',
|
||||
estimated_date: new Date(2025, 5, 18),
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
day_out: new Date(2025, 5, 21),
|
||||
day_in: new Date(2025, 5, 25),
|
||||
},
|
||||
]
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center max-w-[93%] mx-auto">
|
||||
{/* main */}
|
||||
<div className="flex items-center justify-center space-x-16">
|
||||
<div>
|
||||
<Image
|
||||
@@ -89,6 +47,7 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* форма на серч */}
|
||||
<AddressSelector />
|
||||
|
||||
<div className="text-lg font-normal text-[#272424] underline decoration-orange underline-offset-4 mb-20 hover:text-orange transition-colors cursor-pointer">
|
||||
@@ -104,13 +63,180 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* первые пять серч карточек -- бекенд??? */}
|
||||
<div className="w-full max-w-[1250px] mx-auto">
|
||||
<div className="grid grid-cols-1 gap-4 w-full">
|
||||
{data.map((card) => (
|
||||
<SearchCard key={card.id} {...card} />
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-center py-4">
|
||||
<Button
|
||||
text="Разместить объявление"
|
||||
className=" bg-orange text-white text-xl font-semibold px-6 py-3 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* how it works */}
|
||||
<div className="w-full max-w-[1250px] mx-auto my-20">
|
||||
<h2 className="text-4xl text-center font-bold">Как это работает</h2>
|
||||
<div className="flex flex-col items-center justify-center text-base font-medium text-center py-4 mb-4">
|
||||
<span>
|
||||
TWB - это сервис, созданный для того, чтобы отправитель и перевозчик
|
||||
нашли друг-друга!
|
||||
</span>
|
||||
<span>
|
||||
Наш сервис предлагает вам прямые контакты, а не является посредником
|
||||
!
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center sm:items-start sm:space-x-4 md:space-x-8 lg:space-x-20 space-y-8 sm:space-y-0">
|
||||
<div className="flex flex-col items-center w-full sm:w-1/3 px-4 sm:px-2">
|
||||
<div className="flex flex-col items-center h-[260px] sm:h-[300px]">
|
||||
<Image
|
||||
src="/images/laptop.png"
|
||||
alt="laptop"
|
||||
width={230}
|
||||
height={230}
|
||||
className="mb-6 w-[180px] h-[180px] sm:w-[230px] sm:h-[230px]"
|
||||
/>
|
||||
<div className="text-xl sm:text-2xl font-semibold text-center">
|
||||
Найдите перевозчика
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm sm:text-base text-gray-600 text-center px-2 sm:px-4 md:px-7">
|
||||
В форме поиска укажите откуда и куда Вам нужно доставить посылку,
|
||||
нажмите кнопку "найти перевозчика". Если по вашему запросу ничего
|
||||
не найдено - Вы можете сами разместить объявление и тогда
|
||||
перевозчики Вас найдут.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center w-full sm:w-1/3 px-4 sm:px-2">
|
||||
<div className="flex flex-col items-center h-[260px] sm:h-[300px]">
|
||||
<Image
|
||||
src="/images/phone.png"
|
||||
alt="phone"
|
||||
width={230}
|
||||
height={230}
|
||||
className="mb-6 w-[180px] h-[180px] sm:w-[230px] sm:h-[230px]"
|
||||
/>
|
||||
<div className="text-xl sm:text-2xl font-semibold text-center">
|
||||
Свяжитесь с перевозчиком
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm sm:text-base text-gray-600 text-center px-2 sm:px-4 md:px-7">
|
||||
Нажмите на кнопку «ОТКЛИКНУТЬСЯ», свяжитесь и договоритесь о месте
|
||||
встречи и условиях перевозки
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center w-full sm:w-1/3 px-4 sm:px-2">
|
||||
<div className="flex flex-col items-center h-[260px] sm:h-[300px]">
|
||||
<Image
|
||||
src="/images/package.png"
|
||||
alt="package"
|
||||
width={230}
|
||||
height={230}
|
||||
className="mb-6 w-[180px] h-[180px] sm:w-[230px] sm:h-[230px]"
|
||||
/>
|
||||
<div className="text-xl sm:text-2xl font-semibold text-center">
|
||||
Передайте посылку
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm sm:text-base text-gray-600 text-center px-2 sm:px-4 md:px-7">
|
||||
Встречайтесь, знакомьтесь и передавайте посылку
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center pt-8 sm:pt-10">
|
||||
<Button
|
||||
text="Отправить посылку"
|
||||
className="bg-orange hover:bg-orange/80 text-white text-base sm:text-lg px-8 sm:px-12 py-2.5 sm:py-3 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* преимущества */}
|
||||
<div className="w-full max-w-[1250px] mx-auto my-10 px-4 sm:px-6">
|
||||
<h2 className="text-3xl sm:text-4xl text-center font-bold mb-12">
|
||||
Преимущества сервиса
|
||||
</h2>
|
||||
<div className="flex flex-col lg:flex-row items-center justify-between gap-6 lg:gap-4">
|
||||
<div className="flex flex-col w-full lg:w-1/4 space-y-4">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Прямой контакт
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Общаешься напрямую с перевозчиком, никаких посредников
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Своя цена
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Стоимость перевозки самостоятельно обговариваете с перевозчиком.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Нет доп. расходов
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Никаких комиссий, переплат и дополнительных расходов за
|
||||
отправку.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-auto px-4 sm:px-0">
|
||||
<Image
|
||||
src="/images/advantage.svg"
|
||||
alt="advantages"
|
||||
width={648}
|
||||
height={403}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full lg:w-1/4 space-y-4">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Уведомления
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Можешь самостоятельно найти перевозчиков или разместить
|
||||
объявление на сайте.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Удобный поиск
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Как только по твоему объявлению найдется перевозчик мы сообщим
|
||||
на E-mail.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Экономия времени
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Не нужно искать группы, чаты, и кидать "клич", а просто
|
||||
достаточно разместить объявление на сайте.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* FAQ */}
|
||||
<FAQ faqs={faqs} />
|
||||
|
||||
{/* новости */}
|
||||
<News />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
137
frontend/app/staticData/index.ts
Normal file
137
frontend/app/staticData/index.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import avatar from '../../public/images/avatar.png'
|
||||
import belarusIcon from '../../public/images/belarus.png'
|
||||
import russiaIcon from '../../public/images/russia.png'
|
||||
|
||||
const userImg = avatar
|
||||
const blIcon = belarusIcon
|
||||
const ruIcon = russiaIcon
|
||||
|
||||
export const routes = 12845
|
||||
|
||||
export const faqs = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Как это работает?',
|
||||
content:
|
||||
'Наш сервис это своего рода доска объявлений для тех, кто хочет отправить посылку в другой город или страну и тех, кто может эту посылку захватить с собой. Благодаря сайту Trip With Bonus - вы сможете найти перевозчика для своей посылки быстро, а также связаться с ним напрямую, без всяких посредников.',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'TripWB - это новый сервис доставки посылок?',
|
||||
content:
|
||||
'Нет, мы не почтовый сервис. Наш сайт - это платформа, которая позволяет отправителю и перевозчику быстро найти друг друга, как доска объявлений.',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Сервис платный?',
|
||||
content:
|
||||
'Все основные функции нашего сервиса бесплатные. Для того, чтобы разместить своё объявление, вам необходимо лишь <a href="/login" class="text-orange hover:underline">зарегистрироваться</a> на сайте. Однако, у нас есть дополнительные тарифы, которые включат в себя полезные функции связанные с уведомлением, чтобы вы ничего не пропустили. Ознакомиться с ними Вы можете в личном кабинете сервиса.',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Кому платить за перевозку?',
|
||||
content:
|
||||
'Наш сервис не взымает никаких оплат или комиссий. Вы договариваетесь о перевозке напрямую с человеком, а все условия обговариваете индивидуально. Мы лишь предоставляем функцию поиска.',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'Чем TripWB отличается от почтовых сервисов и служб доставки?',
|
||||
content:
|
||||
'Однозначно, явные признаки победы институционализации ограничены исключительно образом мышления. Следует отметить, что новая модель организационной деятельности обеспечивает широкому кругу (специалистов) участие в формировании как самодостаточных, так и внешне зависимых концептуальных решений. ',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: 'Я перевозчик, могу взять с собой посылку, куда мне обратиться?',
|
||||
content: `
|
||||
<ol>
|
||||
<li>
|
||||
Вы можете воспользоваться системой поиска на сайте:
|
||||
<ul>
|
||||
<li>Указать пункт А (откуда можете забрать посылку) и пункт Б (куда можете её доставить)</li>
|
||||
<li>Нажать кнопку "найти посылку"</li>
|
||||
<li>Сайт покажет Вам все доступные объявления</li>
|
||||
</ul>
|
||||
<p>Это могут быть как города, так и страны, авто- или авиа- перевозка.</p>
|
||||
</li>
|
||||
<li>
|
||||
Разместите своё объявление на сайте:
|
||||
<ul>
|
||||
<li><a href="/login" class="text-orange hover:underline">Зарегистрируйтесь</a> на сайте и войдите в личный кабинет</li>
|
||||
<li>Разместите своё объявление как перевозчик, заполнив небольшую форму</li>
|
||||
<li>Укажите пункт А, пункт Б, а также какой тип посылки готовы взять</li>
|
||||
<li>Опубликуйте своё объявление</li>
|
||||
</ul>
|
||||
<p>Отправители сами будут откликаться.</p>
|
||||
</li>
|
||||
</ol>
|
||||
`,
|
||||
},
|
||||
]
|
||||
|
||||
export const data = [
|
||||
{
|
||||
id: 1123,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Нужен перевозчик',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
moving_type: 'Авиатранспорт',
|
||||
estimated_date: new Date(2025, 4, 15),
|
||||
},
|
||||
{
|
||||
id: 2423,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Могу перевезти',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
moving_type: 'Автоперевозка',
|
||||
estimated_date: new Date(2025, 5, 18),
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
day_out: new Date(2025, 5, 21),
|
||||
day_in: new Date(2025, 5, 25),
|
||||
},
|
||||
]
|
||||
|
||||
export const news = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Высокий уровень вовлечения представителей целевой аудитории',
|
||||
description:
|
||||
'Значимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса для',
|
||||
image: '/images/news.svg',
|
||||
slug: 'vysoki-uroven-vlezheniya-predstaviteley-tselyovoi-auditorii',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Высокий уровень вовлечения представителей целевой аудитории',
|
||||
description:
|
||||
'Значимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса для',
|
||||
image: '/images/news.svg',
|
||||
slug: 'vysoki-uroven-vlezheniya-predstaviteley-tselyovoi-auditorii',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Высокий уровень вовлечения представителей целевой аудитории',
|
||||
description:
|
||||
'Значимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса дляЗначимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса дляЗначимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса дляЗначимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса для',
|
||||
image: '/images/news.svg',
|
||||
slug: 'vysoki-uroven-vlezheniya-predstaviteley-tselyovoi-auditorii',
|
||||
},
|
||||
]
|
||||
@@ -1,11 +0,0 @@
|
||||
export interface TextInputProps {
|
||||
value: string
|
||||
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||
label?: string
|
||||
placeholder?: string
|
||||
name: string
|
||||
type?: string
|
||||
className?: string
|
||||
maxLength?: number
|
||||
tooltip?: string | React.ReactNode
|
||||
}
|
||||
@@ -39,3 +39,12 @@ export interface SearchCardProps {
|
||||
day_out?: Date
|
||||
day_in?: Date
|
||||
}
|
||||
|
||||
export interface AccordionProps {
|
||||
title: string
|
||||
content: string | React.ReactNode
|
||||
}
|
||||
|
||||
export interface FAQProps {
|
||||
faqs: { id: number; title: string; content: string }[]
|
||||
}
|
||||
|
||||
25
frontend/components/FAQ.tsx
Normal file
25
frontend/components/FAQ.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import Accordion from './ui/Accordion'
|
||||
import { FAQProps } from '@/app/types'
|
||||
|
||||
const FAQ: React.FC<FAQProps> = ({ faqs }) => {
|
||||
try {
|
||||
return (
|
||||
<div className="w-full max-w-[1250px] mx-auto">
|
||||
<div className="pl-4 py-5 sticky">
|
||||
<h2 className="text-3xl font-bold text-center py-4">
|
||||
Часто задаваемые вопросы:
|
||||
</h2>
|
||||
{faqs.map((faq) => (
|
||||
<Accordion key={faq.id} title={faq.title} content={faq.content} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error fetching FAQs:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default FAQ
|
||||
44
frontend/components/News.tsx
Normal file
44
frontend/components/News.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { news } from '@/app/staticData'
|
||||
import ShowMore from './ui/ShowMore'
|
||||
|
||||
interface NewsItem {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
image: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
export default function News() {
|
||||
return (
|
||||
<div className="w-full max-w-[1250px] mx-auto px-4 sm:px-6 mb-20">
|
||||
<h2 className="text-3xl sm:text-4xl text-center font-bold mb-10">
|
||||
Последние новости
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{news.map((item) => (
|
||||
<Link href={`/news/${item.slug}`} key={item.id}>
|
||||
<div className="flex flex-col bg-white rounded-2xl shadow-md overflow-hidden hover:shadow-2xl transition-shadow duration-500 p-6">
|
||||
<div className="relative h-[200px]">
|
||||
<Image
|
||||
src={item.image}
|
||||
alt={item.title}
|
||||
fill
|
||||
className="object-cover rounded-2xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-6">
|
||||
<h3 className="text-base font-semibold mb-3">{item.title}</h3>
|
||||
<ShowMore text={item.description} />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
75
frontend/components/ui/Accordion.tsx
Normal file
75
frontend/components/ui/Accordion.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { IoIosAdd, IoIosClose } from 'react-icons/io'
|
||||
import { AccordionProps } from '@/app/types'
|
||||
|
||||
const Accordion: React.FC<AccordionProps> = ({ title, content }) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const [contentHeight, setContentHeight] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setContentHeight(contentRef.current.scrollHeight)
|
||||
}
|
||||
}, [content])
|
||||
|
||||
const renderContent = () => {
|
||||
if (typeof content === 'string') {
|
||||
return (
|
||||
<div
|
||||
className="text-md font-medium [&>ol]:list-decimal [&>ol]:pl-4 [&_ul]:list-disc [&_ul]:py-2 [&_ul]:pl-4 [&_li]:mb-2 [&_p]:mt-1 [&_a]:text-orange [&_a]:hover:underline"
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return <div className="text-md font-medium">{content}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative bg-white rounded-2xl my-4">
|
||||
<button
|
||||
className="flex justify-between items-center py-4 px-6 hover:bg-gray-50 rounded-2xl space-x-9 w-full"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<h3 className="text-xl font-bold text-left">{title}</h3>
|
||||
<div className="w-6 h-6 flex items-center justify-center rounded-full border border-orange">
|
||||
<div
|
||||
className={`transition-all duration-500 ${
|
||||
isOpen ? 'rotate-180' : 'rotate-0'
|
||||
}`}
|
||||
>
|
||||
{isOpen ? (
|
||||
<IoIosClose className="w-5 h-5 text-orange" />
|
||||
) : (
|
||||
<IoIosAdd className="w-5 h-5 text-orange" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
ref={contentRef}
|
||||
style={
|
||||
{ '--accordion-height': `${contentHeight}px` } as React.CSSProperties
|
||||
}
|
||||
className={`
|
||||
grid
|
||||
transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)]
|
||||
${
|
||||
isOpen ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className=" rounded-xl px-6 mt-2">
|
||||
<div className="py-4">{renderContent()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Accordion
|
||||
48
frontend/components/ui/ShowMore.tsx
Normal file
48
frontend/components/ui/ShowMore.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
|
||||
interface ShowMoreProps {
|
||||
text?: string
|
||||
}
|
||||
|
||||
const ShowMore = ({ text }: ShowMoreProps) => {
|
||||
const [isExpandedMobile, setIsExpandedMobile] = useState(false)
|
||||
const [isExpandedDesktop, setIsExpandedDesktop] = useState(false)
|
||||
|
||||
if (!text) return null
|
||||
|
||||
const maxLength = {
|
||||
mobile: 100,
|
||||
desktop: 100,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pb-5">
|
||||
{/* мобила */}
|
||||
<div
|
||||
onClick={() => setIsExpandedMobile(!isExpandedMobile)}
|
||||
className={`lg:hidden text-justify relative cursor-pointer overflow-hidden
|
||||
${isExpandedMobile ? 'max-h-[2000px]' : 'max-h-[300px]'}`}
|
||||
>
|
||||
{isExpandedMobile ? text : text.slice(0, maxLength.mobile)}
|
||||
{!isExpandedMobile && text.length > maxLength.mobile && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white to-transparent" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* десктоп */}
|
||||
<div
|
||||
onClick={() => setIsExpandedDesktop(!isExpandedDesktop)}
|
||||
className={`hidden lg:block text-justify relative cursor-pointer overflow-hidden
|
||||
${isExpandedDesktop ? 'max-h-[2000px]' : 'max-h-[300px]'}`}
|
||||
>
|
||||
{isExpandedDesktop ? text : text.slice(0, maxLength.desktop)}
|
||||
{!isExpandedDesktop && text.length > maxLength.desktop && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white to-transparent" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowMore
|
||||
9
frontend/public/images/advantage.svg
Normal file
9
frontend/public/images/advantage.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.8 MiB |
BIN
frontend/public/images/laptop.png
Normal file
BIN
frontend/public/images/laptop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
frontend/public/images/leftArrow.png
Normal file
BIN
frontend/public/images/leftArrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
9
frontend/public/images/news.svg
Normal file
9
frontend/public/images/news.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 13 MiB |
BIN
frontend/public/images/package.png
Normal file
BIN
frontend/public/images/package.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
frontend/public/images/phone.png
Normal file
BIN
frontend/public/images/phone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Reference in New Issue
Block a user