Skip to content

Commit 15825e4

Browse files
committed
feat: improve swipecard
1 parent 072e926 commit 15825e4

2 files changed

Lines changed: 56 additions & 24 deletions

File tree

src/components/cards/EditionUseCaseCard.tsx renamed to src/components/cards/SwipeCard.tsx

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {Media} from "@/payload-types"
66
import Image from "next/image"
77
import {LinkButton} from "../ui/LinkButton"
88

9-
interface EditionUseCaseCardProps {
9+
interface SwipeCardProps {
1010
title: string
1111
description: string
1212
image?: Media | number | null
@@ -18,26 +18,19 @@ interface EditionUseCaseCardProps {
1818
className?: string
1919
}
2020

21-
export function EditionUseCaseCard({
22-
title,
23-
description,
24-
image,
25-
link,
26-
isFocused = false,
27-
className
28-
}: EditionUseCaseCardProps) {
21+
export function SwipeCard({ title, description, image, link, className }: SwipeCardProps) {
2922
const imageUrl = typeof image === 'object' ? getMediaUrl(image?.url) : ""
3023

3124
return (
3225
<div
3326
className={cn(
34-
"glass-card-shell group relative flex h-full min-h-0 flex-col rounded-3xl transition-all duration-500 ease-out before:pointer-events-none before:absolute before:inset-1px before:rounded-[calc(1.6rem-1px)] before:border before:border-white/6 before:content-['']",
27+
"glass-card-shell group relative flex flex-col rounded-3xl transition-all duration-500 ease-out before:pointer-events-none before:absolute before:inset-1px before:rounded-[calc(1.6rem-1px)] before:border before:border-white/6 before:content-['']",
3528
className
3629
)}
3730
>
3831
<div aria-hidden="true" className="glass-card-topline"/>
3932
<div
40-
className="relative flex h-full flex-col items-stretch justify-start overflow-hidden rounded-2xl">
33+
className="relative flex flex-col items-stretch justify-start rounded-2xl">
4134
<div className={"p-2"}>
4235
{imageUrl ? (
4336
<div
@@ -56,18 +49,20 @@ export function EditionUseCaseCard({
5649
</div>
5750

5851

59-
<div className="flex min-h-0 flex-1 flex-col gap-2 p-4">
60-
<h3 className="line-clamp-2 text-xl font-semibold text-white">
52+
<div className="flex flex-col gap-2 p-3 sm:p-4">
53+
<h3 className="line-clamp-2 flex-none text-lg font-semibold leading-[1.35] text-white sm:text-xl">
6154
{title}
6255
</h3>
63-
<p className="mb-2 text-sm text-white/75 leading-relaxed">
56+
<p className="text-sm leading-relaxed text-white/75">
6457
{description}
6558
</p>
6659

6760
{link?.url && link?.label && (
68-
<div className="mt-auto pt-2">
69-
<LinkButton href={link.url} className="my-2">
70-
{link.label}
61+
<div className="min-w-0 flex-none pt-1">
62+
<LinkButton href={link.url} className="max-w-full min-w-0">
63+
<span className="min-w-0 truncate">
64+
{link.label}
65+
</span>
7166
</LinkButton>
7267
</div>
7368
)}

src/components/sections/SwipeCardSection.tsx

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"use client"
22

3-
import { EditionUseCaseCard } from "@/components/cards/EditionUseCaseCard"
3+
import { SwipeCard } from "@/components/cards/SwipeCard"
44
import { Section } from "@/components/ui/Section"
55
import { cn } from "@/lib/utils"
66
import type { SwipeCardsLayoutBlock } from "@/lib/cms"
77
import { Button } from "@code0-tech/pictor"
88
import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"
99
import { m as motion, type PanInfo, type Variants } from "motion/react"
10-
import { useState } from "react"
10+
import { type TouchEvent as ReactTouchEvent, useRef, useState } from "react"
1111
import { useWebHaptics } from "web-haptics/react"
1212

1313
interface SwipeCardSectionProps {
@@ -16,6 +16,8 @@ interface SwipeCardSectionProps {
1616

1717
export function SwipeCardSection({ content }: SwipeCardSectionProps) {
1818
const [focusedIndex, setFocusedIndex] = useState(0)
19+
const touchStartX = useRef<number | null>(null)
20+
const handledTouchSwipe = useRef(false)
1921
const { trigger } = useWebHaptics()
2022

2123
if (!content?.cards?.length) return null
@@ -34,6 +36,11 @@ export function SwipeCardSection({ content }: SwipeCardSectionProps) {
3436
}
3537

3638
const handleDragEnd = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
39+
if (handledTouchSwipe.current) {
40+
handledTouchSwipe.current = false
41+
return
42+
}
43+
3744
const swipeThreshold = 48
3845

3946
if (Math.abs(info.offset.x) < swipeThreshold) return
@@ -46,6 +53,34 @@ export function SwipeCardSection({ content }: SwipeCardSectionProps) {
4653
handleNext()
4754
}
4855

56+
const handleTouchStart = (event: ReactTouchEvent<HTMLDivElement>) => {
57+
handledTouchSwipe.current = true
58+
touchStartX.current = event.touches[0]?.clientX ?? null
59+
}
60+
61+
const handleTouchEnd = (event: ReactTouchEvent<HTMLDivElement>) => {
62+
window.setTimeout(() => {
63+
handledTouchSwipe.current = false
64+
}, 250)
65+
66+
if (touchStartX.current === null) return
67+
68+
const touchEndX = event.changedTouches[0]?.clientX
69+
if (touchEndX === undefined) return
70+
71+
const offsetX = touchEndX - touchStartX.current
72+
touchStartX.current = null
73+
74+
if (Math.abs(offsetX) < 48) return
75+
76+
if (offsetX > 0) {
77+
handlePrevious()
78+
return
79+
}
80+
81+
handleNext()
82+
}
83+
4984
const staggerContainer: Variants = {
5085
hidden: {},
5186
show: {
@@ -118,12 +153,14 @@ export function SwipeCardSection({ content }: SwipeCardSectionProps) {
118153
dragConstraints={{ left: 0, right: 0 }}
119154
dragElastic={0.08}
120155
onDragEnd={handleDragEnd}
156+
onTouchStart={handleTouchStart}
157+
onTouchEnd={handleTouchEnd}
121158
>
122159
<div className="relative flex w-full items-stretch justify-center">
123-
<div className="invisible pointer-events-none grid w-[60%]">
160+
<div className="invisible pointer-events-none grid w-full sm:w-[80%] lg:w-[60%]">
124161
{cards.map((card, index) => (
125162
<div key={card.id || index} className="col-start-1 row-start-1">
126-
<EditionUseCaseCard
163+
<SwipeCard
127164
title={card.title}
128165
description={card.description}
129166
image={card.image}
@@ -143,16 +180,16 @@ export function SwipeCardSection({ content }: SwipeCardSectionProps) {
143180
<div
144181
key={card.id || index}
145182
className={cn(
146-
"absolute inset-y-0 h-full w-full transition-all duration-500 ease-out",
147-
"left-1/2 w-[60%]",
183+
"absolute top-0 w-full transition-all duration-500 ease-out",
184+
"left-1/2 w-full sm:w-[80%] lg:w-[60%]",
148185
!isVisibleMobile && "lg:opacity-100 lg:pointer-events-auto opacity-0 pointer-events-none",
149186
!isVisibleDesktop && "lg:opacity-0 lg:pointer-events-none",
150187
)}
151188
style={{
152189
transform: `translateX(calc(-50% + ${offset * 104}%))`,
153190
}}
154191
>
155-
<EditionUseCaseCard
192+
<SwipeCard
156193
title={card.title}
157194
description={card.description}
158195
image={card.image}

0 commit comments

Comments
 (0)