104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
import { toComponent } from '../lib/cms/icon'
|
||
import { toGradient, toTextColor } from '../lib/cms/color'
|
||
import type { Enum_Componentsharedcolor_Value, Enum_Componentsharedicon_Value } from '../graphql/graphql'
|
||
import type { Maybe } from '../lib/types'
|
||
|
||
export interface ServiceCardCms {
|
||
id: string
|
||
title?: Maybe<string>
|
||
description?: Maybe<string>
|
||
icon?: Maybe<{ value?: Maybe<Enum_Componentsharedicon_Value> }>
|
||
color?: Maybe<{ value?: Maybe<Enum_Componentsharedcolor_Value> }>
|
||
category?: Maybe<{ slug: string }>
|
||
}
|
||
|
||
export interface ServicesCmsData {
|
||
title?: Maybe<string>
|
||
description?: Maybe<string>
|
||
cards?: Maybe<Array<Maybe<ServiceCardCms>>>
|
||
}
|
||
|
||
interface ServicesProps {
|
||
data?: Maybe<ServicesCmsData>
|
||
}
|
||
|
||
export function Services({ data }: ServicesProps) {
|
||
const title = data?.title ?? ''
|
||
const description = data?.description ?? ''
|
||
const cards = (data?.cards ?? []).filter((c): c is ServiceCardCms => Boolean(c))
|
||
|
||
if (!title && !description && cards.length === 0) {
|
||
return null
|
||
}
|
||
|
||
return (
|
||
<section id="services" className="py-24 bg-white">
|
||
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
|
||
<div className="max-w-3xl mb-16">
|
||
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">Услуги</p>
|
||
{title ? <h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">{title}</h2> : null}
|
||
{description ? <p className="text-xl text-gray-600">{description}</p> : null}
|
||
</div>
|
||
|
||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{cards.map((service, index) => {
|
||
const Icon = toComponent(service.icon?.value)
|
||
const gradient = toGradient(service.color?.value)
|
||
const iconColor = toTextColor(service.color?.value)
|
||
const floatClasses = [
|
||
'animate-float',
|
||
'animate-float-delay-1',
|
||
'animate-float-delay-2',
|
||
'animate-float-delay-3',
|
||
'animate-float-delay-4',
|
||
'animate-float-delay-5',
|
||
]
|
||
const floatClass = floatClasses[index % 6]
|
||
const slug = service.category?.slug
|
||
|
||
return (
|
||
<div
|
||
key={service.id}
|
||
className="group bg-white rounded-2xl border border-gray-100 hover:border-blue-200 hover:shadow-xl transition-all duration-300 overflow-hidden"
|
||
>
|
||
<div
|
||
className={`relative h-48 overflow-hidden bg-linear-to-br ${gradient} flex items-center justify-center`}
|
||
>
|
||
<div
|
||
className={`w-20 h-20 bg-white rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-md ${floatClass}`}
|
||
>
|
||
<Icon className={`w-10 h-10 ${iconColor}`} />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-6">
|
||
{service.title ? <h3 className="text-xl text-gray-900 mb-3">{service.title}</h3> : null}
|
||
|
||
{service.description ? (
|
||
<p className="text-gray-600 mb-4 leading-relaxed">{service.description}</p>
|
||
) : null}
|
||
|
||
{slug ? (
|
||
<a
|
||
href={`/${slug}`}
|
||
className="text-blue-600 hover:text-blue-700 transition-colors inline-flex items-center gap-2"
|
||
>
|
||
Подробнее
|
||
<span className="transition-all">→</span>
|
||
</a>
|
||
) : (
|
||
<span className="text-blue-600 inline-flex items-center gap-2">
|
||
Подробнее
|
||
<span className="transition-all">→</span>
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|