feat: clickable price cards open booking modal + smooth showcase transitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,12 +5,14 @@ import { CreditCard, Building2, ScrollText, Crown, Sparkles } from "lucide-react
|
|||||||
import { siteContent } from "@/data/content";
|
import { siteContent } from "@/data/content";
|
||||||
import { SectionHeading } from "@/components/ui/SectionHeading";
|
import { SectionHeading } from "@/components/ui/SectionHeading";
|
||||||
import { Reveal } from "@/components/ui/Reveal";
|
import { Reveal } from "@/components/ui/Reveal";
|
||||||
|
import { BookingModal } from "@/components/ui/BookingModal";
|
||||||
|
|
||||||
type Tab = "prices" | "rental" | "rules";
|
type Tab = "prices" | "rental" | "rules";
|
||||||
|
|
||||||
export function Pricing() {
|
export function Pricing() {
|
||||||
const { pricing } = siteContent;
|
const { pricing } = siteContent;
|
||||||
const [activeTab, setActiveTab] = useState<Tab>("prices");
|
const [activeTab, setActiveTab] = useState<Tab>("prices");
|
||||||
|
const [bookingOpen, setBookingOpen] = useState(false);
|
||||||
|
|
||||||
const tabs: { id: Tab; label: string; icon: React.ReactNode }[] = [
|
const tabs: { id: Tab; label: string; icon: React.ReactNode }[] = [
|
||||||
{ id: "prices", label: "Абонементы", icon: <CreditCard size={16} /> },
|
{ id: "prices", label: "Абонементы", icon: <CreditCard size={16} /> },
|
||||||
@@ -65,9 +67,10 @@ export function Pricing() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className={`group relative rounded-2xl border p-5 transition-all duration-300 ${
|
onClick={() => setBookingOpen(true)}
|
||||||
|
className={`group relative cursor-pointer rounded-2xl border p-5 transition-all duration-300 ${
|
||||||
isPopular
|
isPopular
|
||||||
? "border-[#c9a96e]/40 bg-gradient-to-br from-[#c9a96e]/10 via-transparent to-[#c9a96e]/5 dark:from-[#c9a96e]/[0.07] dark:to-[#c9a96e]/[0.02] shadow-lg shadow-[#c9a96e]/10"
|
? "border-[#c9a96e]/40 bg-gradient-to-br from-[#c9a96e]/10 via-transparent to-[#c9a96e]/5 dark:from-[#c9a96e]/[0.07] dark:to-[#c9a96e]/[0.02] shadow-lg shadow-[#c9a96e]/10 hover:shadow-xl hover:shadow-[#c9a96e]/20"
|
||||||
: "border-neutral-200 bg-white hover:border-neutral-300 dark:border-white/[0.06] dark:bg-[#0a0a0a] dark:hover:border-white/[0.12]"
|
: "border-neutral-200 bg-white hover:border-neutral-300 dark:border-white/[0.06] dark:bg-[#0a0a0a] dark:hover:border-white/[0.12]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -106,7 +109,7 @@ export function Pricing() {
|
|||||||
|
|
||||||
{/* Unlimited — featured card */}
|
{/* Unlimited — featured card */}
|
||||||
{unlimitedItem && (
|
{unlimitedItem && (
|
||||||
<div className="mt-6 team-card-glitter rounded-2xl border border-[#c9a96e]/30 bg-gradient-to-r from-[#c9a96e]/10 via-[#c9a96e]/5 to-[#c9a96e]/10 dark:from-[#c9a96e]/[0.06] dark:via-transparent dark:to-[#c9a96e]/[0.06] p-6 sm:p-8">
|
<div onClick={() => setBookingOpen(true)} className="mt-6 cursor-pointer team-card-glitter rounded-2xl border border-[#c9a96e]/30 bg-gradient-to-r from-[#c9a96e]/10 via-[#c9a96e]/5 to-[#c9a96e]/10 dark:from-[#c9a96e]/[0.06] dark:via-transparent dark:to-[#c9a96e]/[0.06] p-6 sm:p-8 transition-shadow duration-300 hover:shadow-xl hover:shadow-[#c9a96e]/20">
|
||||||
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-between">
|
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-between">
|
||||||
<div className="text-center sm:text-left">
|
<div className="text-center sm:text-left">
|
||||||
<div className="flex items-center justify-center gap-2 sm:justify-start">
|
<div className="flex items-center justify-center gap-2 sm:justify-start">
|
||||||
@@ -138,7 +141,8 @@ export function Pricing() {
|
|||||||
{pricing.rentalItems.map((item, i) => (
|
{pricing.rentalItems.map((item, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="flex items-center justify-between gap-4 rounded-2xl border border-neutral-200 bg-white px-6 py-5 transition-colors hover:border-neutral-300 dark:border-white/[0.06] dark:bg-[#0a0a0a] dark:hover:border-white/[0.12]"
|
onClick={() => setBookingOpen(true)}
|
||||||
|
className="cursor-pointer flex items-center justify-between gap-4 rounded-2xl border border-neutral-200 bg-white px-6 py-5 transition-colors hover:border-neutral-300 dark:border-white/[0.06] dark:bg-[#0a0a0a] dark:hover:border-white/[0.12]"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-neutral-900 dark:text-white">
|
<p className="font-medium text-neutral-900 dark:text-white">
|
||||||
@@ -180,6 +184,8 @@ export function Pricing() {
|
|||||||
</Reveal>
|
</Reveal>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<BookingModal open={bookingOpen} onClose={() => setBookingOpen(false)} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ export function ShowcaseLayout<T>({
|
|||||||
const activeItemRef = useRef<HTMLButtonElement>(null);
|
const activeItemRef = useRef<HTMLButtonElement>(null);
|
||||||
const detailRef = useRef<HTMLDivElement>(null);
|
const detailRef = useRef<HTMLDivElement>(null);
|
||||||
const [isUserInteracting, setIsUserInteracting] = useState(false);
|
const [isUserInteracting, setIsUserInteracting] = useState(false);
|
||||||
|
const [displayIndex, setDisplayIndex] = useState(activeIndex);
|
||||||
|
const [fading, setFading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeIndex === displayIndex) return;
|
||||||
|
setFading(true);
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setDisplayIndex(activeIndex);
|
||||||
|
setFading(false);
|
||||||
|
}, 250);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [activeIndex, displayIndex]);
|
||||||
|
|
||||||
// Auto-scroll selector only when item is out of view
|
// Auto-scroll selector only when item is out of view
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -105,12 +117,15 @@ export function ShowcaseLayout<T>({
|
|||||||
<div className="lg:w-[60%]">
|
<div className="lg:w-[60%]">
|
||||||
<div
|
<div
|
||||||
ref={detailRef}
|
ref={detailRef}
|
||||||
key={activeIndex}
|
className={`transition-all duration-300 ease-out ${
|
||||||
className="showcase-detail-enter"
|
fading
|
||||||
|
? "opacity-0 translate-y-2"
|
||||||
|
: "opacity-100 translate-y-0"
|
||||||
|
}`}
|
||||||
onTouchStart={handleTouchStart}
|
onTouchStart={handleTouchStart}
|
||||||
onTouchEnd={handleTouchEnd}
|
onTouchEnd={handleTouchEnd}
|
||||||
>
|
>
|
||||||
{renderDetail(items[activeIndex], activeIndex)}
|
{renderDetail(items[displayIndex], displayIndex)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Counter */}
|
{/* Counter */}
|
||||||
|
|||||||
Reference in New Issue
Block a user