feat: horizontal drag-scroll for all bio sections, fix tab resize
- Replace wrapping grid with horizontal ScrollRow (drag to scroll) - Apply to victories, education, and experience sections - Grid overlay for victory tabs so height stays stable across tabs - Fixed-width cards (w-44/w-48) with items-stretch for uniform height - Remove scrollbar, add grab cursor for drag interaction Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import Image from "next/image";
|
||||
import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, MapPin, Calendar, Award, Scale } from "lucide-react";
|
||||
import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, Award, Scale } from "lucide-react";
|
||||
import type { TeamMember, RichListItem, VictoryItem } from "@/types/content";
|
||||
|
||||
interface TeamProfileProps {
|
||||
@@ -132,17 +132,14 @@ export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid mt-5 pt-1" style={{ gridTemplateColumns: "1fr", gridTemplateRows: "1fr" }}>
|
||||
<div className="grid mt-4" style={{ gridTemplateColumns: "1fr", gridTemplateRows: "1fr" }}>
|
||||
{victoryTabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={`flex flex-col sm:flex-row sm:flex-wrap gap-4 col-start-1 row-start-1 ${
|
||||
activeTab === tab.key ? "" : "invisible"
|
||||
}`}
|
||||
>
|
||||
<div key={tab.key} className={`col-start-1 row-start-1 ${activeTab === tab.key ? "" : "invisible"}`}>
|
||||
<ScrollRow>
|
||||
{tab.items.map((item, i) => (
|
||||
<VictoryCard key={i} victory={item} />
|
||||
))}
|
||||
</ScrollRow>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -156,11 +153,11 @@ export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
||||
<GraduationCap size={14} />
|
||||
Образование
|
||||
</span>
|
||||
<div className="mt-4 space-y-2">
|
||||
<ScrollRow>
|
||||
{member.education!.map((item, i) => (
|
||||
<RichCard key={i} item={item} onImageClick={setLightbox} />
|
||||
))}
|
||||
</div>
|
||||
</ScrollRow>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -171,17 +168,13 @@ export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
||||
<Trophy size={15} />
|
||||
Опыт
|
||||
</span>
|
||||
<ul className="mt-4 space-y-2.5">
|
||||
<ScrollRow>
|
||||
{member.experience!.map((item, i) => (
|
||||
<li
|
||||
key={i}
|
||||
className="flex items-start gap-2.5 text-sm text-white/60"
|
||||
>
|
||||
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-gold/40" />
|
||||
{item}
|
||||
</li>
|
||||
<div key={i} className="w-48 shrink-0 rounded-xl border border-white/[0.08] bg-white/[0.03] p-3">
|
||||
<p className="text-sm text-white/60">{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</ul>
|
||||
</ScrollRow>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -230,11 +223,53 @@ export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function ScrollRow({ children }: { children: React.ReactNode }) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const dragState = useRef<{ startX: number; scrollLeft: number } | null>(null);
|
||||
const wasDragged = useRef(false);
|
||||
|
||||
const onPointerDown = useCallback((e: React.PointerEvent) => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
(e.target as HTMLElement).setPointerCapture(e.pointerId);
|
||||
dragState.current = { startX: e.clientX, scrollLeft: el.scrollLeft };
|
||||
wasDragged.current = false;
|
||||
}, []);
|
||||
|
||||
const onPointerMove = useCallback((e: React.PointerEvent) => {
|
||||
if (!dragState.current || !scrollRef.current) return;
|
||||
const dx = e.clientX - dragState.current.startX;
|
||||
if (Math.abs(dx) > 4) wasDragged.current = true;
|
||||
scrollRef.current.scrollLeft = dragState.current.scrollLeft - dx;
|
||||
}, []);
|
||||
|
||||
const onPointerUp = useCallback(() => {
|
||||
dragState.current = null;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative mt-4">
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex items-stretch gap-3 overflow-x-auto pb-2 pt-4 cursor-grab active:cursor-grabbing select-none"
|
||||
style={{ scrollbarWidth: "none", msOverflowStyle: "none", WebkitOverflowScrolling: "touch" }}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerUp}
|
||||
onLostPointerCapture={onPointerUp}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VictoryCard({ victory }: { victory: VictoryItem }) {
|
||||
const hasLink = !!victory.link;
|
||||
|
||||
return (
|
||||
<div className="group w-full sm:flex-1 sm:min-w-[180px] rounded-xl border border-white/[0.08] overflow-visible bg-white/[0.03] relative">
|
||||
<div className="group w-44 shrink-0 rounded-xl border border-white/[0.08] overflow-visible bg-white/[0.03] relative">
|
||||
<div className="absolute top-0 left-0 w-1 h-full bg-gold/40 rounded-l-xl" />
|
||||
{victory.place && (
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2 z-10">
|
||||
@@ -270,16 +305,16 @@ function RichCard({ item, onImageClick }: { item: RichListItem; onImageClick: (s
|
||||
|
||||
if (hasImage) {
|
||||
return (
|
||||
<div className="group flex rounded-xl border border-white/[0.08] overflow-hidden bg-white/[0.03]">
|
||||
<div className="group w-48 shrink-0 flex rounded-xl border border-white/[0.08] overflow-hidden bg-white/[0.03]">
|
||||
<button
|
||||
onClick={() => onImageClick(item.image!)}
|
||||
className="relative w-16 shrink-0 overflow-hidden cursor-pointer"
|
||||
className="relative w-14 shrink-0 overflow-hidden cursor-pointer"
|
||||
>
|
||||
<Image
|
||||
src={item.image!}
|
||||
alt={item.text}
|
||||
fill
|
||||
sizes="64px"
|
||||
sizes="56px"
|
||||
className="object-cover transition-transform group-hover:scale-105"
|
||||
/>
|
||||
</button>
|
||||
@@ -302,7 +337,7 @@ function RichCard({ item, onImageClick }: { item: RichListItem; onImageClick: (s
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="group rounded-xl border border-white/[0.08] overflow-hidden bg-white/[0.03]">
|
||||
<div className="group w-48 shrink-0 rounded-xl border border-white/[0.08] overflow-hidden bg-white/[0.03]">
|
||||
<div className="p-3">
|
||||
<p className="text-sm text-white/60">{item.text}</p>
|
||||
{hasLink && (
|
||||
|
||||
Reference in New Issue
Block a user