updated gallery
This commit is contained in:
@@ -157,23 +157,3 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.nb-react-drag-zone {
|
||||
position: absolute;
|
||||
left: 48px;
|
||||
right: 48px;
|
||||
bottom: 0;
|
||||
height: 12px;
|
||||
z-index: 1;
|
||||
cursor: grab;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.nb-react-drag-zone:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.nb-react-drag-zone {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,18 @@ export default function CategoryPillCarousel({
|
||||
}) {
|
||||
const viewportRef = useRef(null);
|
||||
const stripRef = useRef(null);
|
||||
const dragZoneRef = useRef(null);
|
||||
const animationRef = useRef(0);
|
||||
const suppressClickRef = useRef(false);
|
||||
const dragStateRef = useRef({
|
||||
active: false,
|
||||
started: false,
|
||||
captured: false,
|
||||
pointerId: null,
|
||||
pointerType: 'mouse',
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
startOffset: 0,
|
||||
startedOnLink: false,
|
||||
});
|
||||
|
||||
const [offset, setOffset] = useState(0);
|
||||
@@ -157,13 +159,10 @@ export default function CategoryPillCarousel({
|
||||
|
||||
useEffect(() => {
|
||||
const strip = stripRef.current;
|
||||
const dragZone = dragZoneRef.current;
|
||||
if (!strip || !dragZone) return;
|
||||
if (!strip) return;
|
||||
|
||||
const onPointerDown = (event) => {
|
||||
const isMouse = event.pointerType === 'mouse';
|
||||
const fromDragZone = event.currentTarget === dragZone;
|
||||
if (isMouse && !fromDragZone) return;
|
||||
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
||||
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
@@ -172,16 +171,15 @@ export default function CategoryPillCarousel({
|
||||
|
||||
dragStateRef.current.active = true;
|
||||
dragStateRef.current.started = false;
|
||||
dragStateRef.current.captured = false;
|
||||
dragStateRef.current.pointerId = event.pointerId;
|
||||
dragStateRef.current.pointerType = event.pointerType || 'mouse';
|
||||
dragStateRef.current.startX = event.clientX;
|
||||
dragStateRef.current.startY = event.clientY;
|
||||
dragStateRef.current.startOffset = offset;
|
||||
dragStateRef.current.startedOnLink = !!event.target.closest('.nb-react-pill');
|
||||
|
||||
setDragging(false);
|
||||
|
||||
if (strip.setPointerCapture) {
|
||||
try { strip.setPointerCapture(event.pointerId); } catch (_) { /* no-op */ }
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerMove = (event) => {
|
||||
@@ -189,13 +187,24 @@ export default function CategoryPillCarousel({
|
||||
if (!state.active || state.pointerId !== event.pointerId) return;
|
||||
|
||||
const dx = event.clientX - state.startX;
|
||||
const threshold = state.pointerType === 'touch' ? 12 : 8;
|
||||
const dy = event.clientY - state.startY;
|
||||
const threshold = state.pointerType === 'touch'
|
||||
? 12
|
||||
: (state.startedOnLink ? 24 : 12);
|
||||
if (!state.started) {
|
||||
if (Math.abs(dx) <= threshold) {
|
||||
if (Math.abs(dx) <= threshold || Math.abs(dx) <= Math.abs(dy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.started = true;
|
||||
if (!state.captured && strip.setPointerCapture) {
|
||||
try {
|
||||
strip.setPointerCapture(event.pointerId);
|
||||
state.captured = true;
|
||||
} catch (_) {
|
||||
state.captured = false;
|
||||
}
|
||||
}
|
||||
setDragging(true);
|
||||
}
|
||||
|
||||
@@ -213,12 +222,14 @@ export default function CategoryPillCarousel({
|
||||
suppressClickRef.current = state.started;
|
||||
state.active = false;
|
||||
state.started = false;
|
||||
state.startedOnLink = false;
|
||||
state.pointerId = null;
|
||||
setDragging(false);
|
||||
|
||||
if (strip.releasePointerCapture) {
|
||||
if (state.captured && strip.releasePointerCapture) {
|
||||
try { strip.releasePointerCapture(event.pointerId); } catch (_) { /* no-op */ }
|
||||
}
|
||||
state.captured = false;
|
||||
};
|
||||
|
||||
const onClickCapture = (event) => {
|
||||
@@ -234,22 +245,12 @@ export default function CategoryPillCarousel({
|
||||
strip.addEventListener('pointercancel', onPointerUpOrCancel);
|
||||
strip.addEventListener('click', onClickCapture, true);
|
||||
|
||||
dragZone.addEventListener('pointerdown', onPointerDown);
|
||||
dragZone.addEventListener('pointermove', onPointerMove);
|
||||
dragZone.addEventListener('pointerup', onPointerUpOrCancel);
|
||||
dragZone.addEventListener('pointercancel', onPointerUpOrCancel);
|
||||
|
||||
return () => {
|
||||
strip.removeEventListener('pointerdown', onPointerDown);
|
||||
strip.removeEventListener('pointermove', onPointerMove);
|
||||
strip.removeEventListener('pointerup', onPointerUpOrCancel);
|
||||
strip.removeEventListener('pointercancel', onPointerUpOrCancel);
|
||||
strip.removeEventListener('click', onClickCapture, true);
|
||||
|
||||
dragZone.removeEventListener('pointerdown', onPointerDown);
|
||||
dragZone.removeEventListener('pointermove', onPointerMove);
|
||||
dragZone.removeEventListener('pointerup', onPointerUpOrCancel);
|
||||
dragZone.removeEventListener('pointercancel', onPointerUpOrCancel);
|
||||
};
|
||||
}, [moveTo, offset]);
|
||||
|
||||
@@ -307,12 +308,6 @@ export default function CategoryPillCarousel({
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" className="w-[18px] h-[18px]" aria-hidden="true"><path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd"/></svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
ref={dragZoneRef}
|
||||
className="nb-react-drag-zone"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {
|
||||
useState, useEffect, useRef, useCallback, memo,
|
||||
} from 'react';
|
||||
import ArtworkCard from './ArtworkCard';
|
||||
import ArtworkGallery from '../artwork/ArtworkGallery';
|
||||
import './MasonryGallery.css';
|
||||
|
||||
// ── Masonry helpers ────────────────────────────────────────────────────────
|
||||
@@ -132,6 +132,8 @@ function mapRankApiArtwork(item) {
|
||||
uname: item.author?.name ?? '',
|
||||
username: item.author?.username ?? item.author?.name ?? '',
|
||||
avatar_url: item.author?.avatar_url ?? null,
|
||||
content_type_name: item.category?.content_type_name ?? item.category?.content_type_slug ?? item.category?.content_type ?? '',
|
||||
content_type_slug: item.category?.content_type_slug ?? item.category?.content_type ?? '',
|
||||
category_name: item.category?.name ?? '',
|
||||
category_slug: item.category?.slug ?? '',
|
||||
slug: item.slug ?? '',
|
||||
@@ -164,6 +166,36 @@ async function fetchRankApiArtworks(endpoint, rankType) {
|
||||
|
||||
const SKELETON_COUNT = 10;
|
||||
|
||||
function getMasonryCardProps(art, idx) {
|
||||
const title = (art.name || art.title || 'Untitled artwork').trim();
|
||||
const hasDimensions = Number(art.width) > 0 && Number(art.height) > 0;
|
||||
const aspectRatio = hasDimensions ? Number(art.width) / Number(art.height) : null;
|
||||
const categorySlug = (art.category_slug || '').toLowerCase();
|
||||
const categoryName = (art.category_name || art.category || '').toLowerCase();
|
||||
const wideCategories = ['photography', 'wallpapers', 'photography-digital', 'wallpaper'];
|
||||
const wideCategoryNames = ['photography', 'wallpapers'];
|
||||
const isWideEligible =
|
||||
aspectRatio !== null &&
|
||||
aspectRatio > 2.0 &&
|
||||
(wideCategories.includes(categorySlug) || wideCategoryNames.includes(categoryName));
|
||||
|
||||
return {
|
||||
articleClassName: `nova-card gallery-item artwork relative${isWideEligible ? ' nova-card--wide' : ''}`,
|
||||
articleStyle: isWideEligible ? { gridColumn: 'span 2' } : undefined,
|
||||
frameClassName: 'rounded-2xl ring-1 ring-white/5 bg-black/20 shadow-lg shadow-black/40 hover:ring-white/15 hover:shadow-[0_8px_30px_rgba(0,0,0,0.6),0_0_0_1px_rgba(255,255,255,0.08)]',
|
||||
mediaClassName: 'nova-card-media relative w-full overflow-hidden bg-neutral-900',
|
||||
mediaStyle: hasDimensions ? { aspectRatio: `${art.width} / ${art.height}` } : undefined,
|
||||
imageSrcSet: art.thumb_srcset || undefined,
|
||||
imageSizes: '(max-width: 768px) 50vw, (max-width: 1280px) 33vw, 20vw',
|
||||
imageWidth: hasDimensions ? art.width : undefined,
|
||||
imageHeight: hasDimensions ? art.height : undefined,
|
||||
loading: idx < 8 ? 'eager' : 'lazy',
|
||||
decoding: idx < 8 ? 'sync' : 'async',
|
||||
fetchPriority: idx === 0 ? 'high' : undefined,
|
||||
imageClassName: 'nova-card-main-image absolute inset-0 h-full w-full object-cover group-hover:scale-[1.03]',
|
||||
};
|
||||
}
|
||||
|
||||
// ── Main component ────────────────────────────────────────────────────────
|
||||
/**
|
||||
* MasonryGallery
|
||||
@@ -309,7 +341,7 @@ function MasonryGallery({
|
||||
// ── Render ─────────────────────────────────────────────────────────────
|
||||
return (
|
||||
<section
|
||||
className="px-6 pb-10 pt-2 md:px-10 is-enhanced"
|
||||
className="pb-10 pt-2 is-enhanced"
|
||||
data-nova-gallery
|
||||
data-gallery-type={galleryType}
|
||||
data-react-masonry-gallery
|
||||
@@ -321,17 +353,14 @@ function MasonryGallery({
|
||||
<>
|
||||
<div
|
||||
ref={gridRef}
|
||||
className={gridClass}
|
||||
data-gallery-grid
|
||||
>
|
||||
{artworks.map((art, idx) => (
|
||||
<ArtworkCard
|
||||
key={`${art.id}-${idx}`}
|
||||
art={art}
|
||||
loading={idx < 8 ? 'eager' : 'lazy'}
|
||||
fetchPriority={idx === 0 ? 'high' : undefined}
|
||||
/>
|
||||
))}
|
||||
<ArtworkGallery
|
||||
items={artworks}
|
||||
layout="masonry"
|
||||
className={gridClass}
|
||||
containerProps={{ 'data-gallery-grid': true }}
|
||||
resolveCardProps={getMasonryCardProps}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Infinite scroll sentinel – placed after the grid */}
|
||||
|
||||
Reference in New Issue
Block a user