From 43126a69203aad89b11d300a2bbc1de9299535b7 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 14 Apr 2026 16:18:47 -0500 Subject: [PATCH] Remove old code --- .../images/Gallery/index-old-2.web.tsx | 391 ------------------ src/components/images/Gallery/index-old.tsx | 332 --------------- .../images/Gallery/index-old.web.tsx | 378 ----------------- 3 files changed, 1101 deletions(-) delete mode 100644 src/components/images/Gallery/index-old-2.web.tsx delete mode 100644 src/components/images/Gallery/index-old.tsx delete mode 100644 src/components/images/Gallery/index-old.web.tsx diff --git a/src/components/images/Gallery/index-old-2.web.tsx b/src/components/images/Gallery/index-old-2.web.tsx deleted file mode 100644 index 2a19c8c2be..0000000000 --- a/src/components/images/Gallery/index-old-2.web.tsx +++ /dev/null @@ -1,391 +0,0 @@ -import {useCallback, useEffect, useRef, useState} from 'react' -import {Pressable, View} from 'react-native' -import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' -import {Image} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' -import {Trans, useLingui} from '@lingui/react/macro' -import useEmblaCarousel from 'embla-carousel-react' - -import {type Dimensions} from '#/lib/media/types' -import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' -import {atoms as a, useTheme} from '#/alf' -import {MediaInsetBorder} from '#/components/MediaInsetBorder' -import {PostEmbedViewContext} from '#/components/Post/Embed/types' -import {Text} from '#/components/Typography' -import {useAnalytics} from '#/analytics' - -/** - * No-op on web. The web Gallery uses its own DOM-based bleed measurement - * (getBoundingClientRect + DOM tree walk), so the native ref/context approach - * isn't needed. These exports exist to match the native module's API. - */ -export function GalleryBleed({children}: {children: React.ReactNode}) { - return <>{children} -} - -export function useGalleryBleed() { - return {bleedRef: {current: null}, bleedWidth: 0} -} - -const CONTAINER_ASPECT_RATIO = 3 / 2 -const ITEM_GAP = 8 -const MIN_PEEK = 40 - -interface GalleryProps { - images: AppBskyEmbedImages.ViewImage[] - onPress?: ( - index: number, - containerRefs: AnimatedRef[], - fetchedDims: (Dimensions | null)[], - ) => void - onPressIn?: (index: number) => void - viewContext?: PostEmbedViewContext -} - -export function Gallery({ - images, - onPress, - onPressIn, - viewContext, -}: GalleryProps) { - const t = useTheme() - const {t: l} = useLingui() - const ax = useAnalytics() - const largeAltBadge = useLargeAltBadgeEnabled() - const currentPageRef = useRef(0) - const containerRef = useRef(null) - const [containerWidth, setContainerWidth] = useState(0) - const [insetLeft, setInsetLeft] = useState(0) - const [insetRight, setInsetRight] = useState(0) - const insetLeftRef = useRef(0) - - const containerRefs = useRef[]>([]).current - const thumbDimsRef = useRef<(Dimensions | null)[]>([]) - - const ref0 = useAnimatedRef() - const ref1 = useAnimatedRef() - const ref2 = useAnimatedRef() - const ref3 = useAnimatedRef() - const refs = [ref0, ref1, ref2, ref3] - for (let i = 0; i < images.length; i++) { - containerRefs[i] = refs[i] - } - - const isWithinQuote = - viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia - const hideBadges = isWithinQuote - - const QUOTE_PADDING = 12 - const containerHeight = - containerWidth > 0 ? containerWidth / CONTAINER_ASPECT_RATIO : 0 - - const getItemWidth = (image: AppBskyEmbedImages.ViewImage, index: number) => { - const ar = image.aspectRatio - let width = containerHeight - if (ar && ar.width > 0 && ar.height > 0) { - const ratio = ar.width / ar.height - const clamped = Math.max(2 / 3, Math.min(ratio, 3 / 2)) - width = containerHeight * clamped - } - // Ensure the first image leaves room for a peek of the next - if (index === 0 && images.length > 1) { - width = Math.min(width, containerWidth - MIN_PEEK) - } - return width - } - - // Embla carousel - const [emblaRef, emblaApi] = useEmblaCarousel({ - align: () => insetLeftRef.current, - containScroll: 'trimSnaps', - dragFree: true, - }) - - // Track page changes for analytics - useEffect(() => { - if (!emblaApi) return - const onSelect = () => { - const page = emblaApi.selectedScrollSnap() - if (page !== currentPageRef.current) { - ax.metric('post:gallery:swipe', { - fromIndex: currentPageRef.current, - toIndex: page, - totalImages: images.length, - }) - currentPageRef.current = page - } - } - const onDragStart = () => setIsDragging(true) - const onDragEnd = () => setIsDragging(false) - emblaApi.on('select', onSelect) - emblaApi.on('pointerDown', onDragStart) - emblaApi.on('pointerUp', onDragEnd) - emblaApi.on('settle', onDragEnd) - return () => { - emblaApi.off('select', onSelect) - emblaApi.off('pointerDown', onDragStart) - emblaApi.off('pointerUp', onDragEnd) - emblaApi.off('settle', onDragEnd) - } - }, [emblaApi, ax, images.length]) - - // Re-initialize Embla when bleed measurements change - useEffect(() => { - if (emblaApi) emblaApi.reInit() - }, [emblaApi, insetLeft, insetRight]) - - // Suppress click after drag - const pointerDown = useRef(false) - const dragged = useRef(false) - const [isDragging, setIsDragging] = useState(false) - - useEffect(() => { - if (!emblaApi) return - const root = emblaApi.rootNode() - - const onPointerDown = () => { - pointerDown.current = true - dragged.current = false - } - const onPointerMove = () => { - if (pointerDown.current) dragged.current = true - } - const onPointerUp = () => { - pointerDown.current = false - } - const onClick = (e: MouseEvent) => { - if (dragged.current) { - e.stopPropagation() - e.preventDefault() - } - } - - root.addEventListener('pointerdown', onPointerDown) - root.addEventListener('pointermove', onPointerMove) - root.addEventListener('pointerup', onPointerUp) - root.addEventListener('click', onClick, true) - return () => { - root.removeEventListener('pointerdown', onPointerDown) - root.removeEventListener('pointermove', onPointerMove) - root.removeEventListener('pointerup', onPointerUp) - root.removeEventListener('click', onClick, true) - } - }, [emblaApi]) - - // Bleed measurement - const measureBleed = useCallback(() => { - if (isWithinQuote) return - requestAnimationFrame(() => { - const el = containerRef.current as unknown as HTMLElement - if (!el) return - const galleryRect = el.getBoundingClientRect() - let parent: HTMLElement | null = el.parentElement - while (parent) { - const ps = window.getComputedStyle(parent) - const pl = parseFloat(ps.paddingLeft) - const pr = parseFloat(ps.paddingRight) - if (pl >= 8 && pr >= 8) { - const parentRect = parent.getBoundingClientRect() - const il = galleryRect.left - parentRect.left - insetLeftRef.current = il - setInsetLeft(il) - setInsetRight(parentRect.right - galleryRect.right) - break - } - parent = parent.parentElement - } - }) - }, [isWithinQuote]) - - const isBleed = !isWithinQuote && (insetLeft > 0 || insetRight > 0) - - return ( - 0 - ? isWithinQuote - ? { - height: containerHeight, - overflow: 'hidden' as const, - width: containerWidth + QUOTE_PADDING * 2, - marginLeft: -QUOTE_PADDING, - } - : {height: containerHeight, overflow: 'visible' as const} - : {aspectRatio: CONTAINER_ASPECT_RATIO} - } - ref={containerRef} - onLayout={e => { - const w = e.nativeEvent.layout.width - if (w > 0 && containerWidth === 0) setContainerWidth(w) - measureBleed() - }} - role="group" - aria-roledescription="carousel" - aria-label={l`Image gallery, ${images.length} images`}> - {containerWidth > 0 && ( -
-
-
- {images.map((image, index) => ( - - - { - if (dragged.current) return - ax.metric('post:gallery:openLightbox', { - imageIndex: index, - totalImages: images.length, - }) - onPress( - index, - containerRefs.slice(0, images.length), - thumbDimsRef.current.slice(), - ) - } - : undefined - } - onPressIn={onPressIn ? () => onPressIn(index) : undefined} - accessibilityRole="button" - accessibilityLabel={ - image.alt || l`Image ${index + 1} of ${images.length}` - } - accessibilityHint={l`Opens full image`} - style={[ - a.flex_1, - a.rounded_md, - a.overflow_hidden, - t.atoms.bg_contrast_25, - ]}> - { - thumbDimsRef.current[index] = { - width: e.source.width, - height: e.source.height, - } - }} - loading={index === 0 ? 'eager' : 'lazy'} - /> - - - {image.alt && !hideBadges ? ( - - - ALT - - - ) : null} - - - ))} - {(isBleed ? insetRight > 0 : isWithinQuote) && ( -
- )} -
-
-
- )} - - ) -} - -function Slide({ - width, - height, - children, -}: { - width: number - height: number - children: React.ReactNode -}) { - const [pressed, setPressed] = useState(false) - return ( -
setPressed(true)} - onPointerUp={() => setPressed(false)} - onPointerLeave={() => setPressed(false)} - onPointerMove={() => setPressed(false)} - style={{ - flex: `0 0 ${width}px`, - minWidth: 0, - height, - transition: 'transform 0.15s ease-out', - transform: pressed ? 'scale(0.975)' : undefined, - }}> - {children} -
- ) -} diff --git a/src/components/images/Gallery/index-old.tsx b/src/components/images/Gallery/index-old.tsx deleted file mode 100644 index 3b04a55b0e..0000000000 --- a/src/components/images/Gallery/index-old.tsx +++ /dev/null @@ -1,332 +0,0 @@ -import { - cloneElement, - createContext, - useContext, - useMemo, - useRef, - useState, - isValidElement, -} from 'react' -import {FlatList, Pressable, useWindowDimensions, View} from 'react-native' -import {DrawerGestureContext} from 'react-native-drawer-layout' -import {Gesture, GestureDetector} from 'react-native-gesture-handler' -import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' -import {Image} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' -import {utils} from '@bsky.app/alf' -import {Trans, useLingui} from '@lingui/react/macro' - -import {mergeRefs} from '#/lib/merge-refs' -import {type Dimensions} from '#/lib/media/types' -import {useA11y} from '#/state/a11y' -import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' -import {atoms as a, useTheme} from '#/alf' -import {AutoSizedImage} from '#/components/images/AutoSizedImage' -import {MediaInsetBorder} from '#/components/MediaInsetBorder' -import {PostEmbedViewContext} from '#/components/Post/Embed/types' -import {Text} from '#/components/Typography' -import {useAnalytics} from '#/analytics' - -const CONTAINER_ASPECT_RATIO = 3 / 2 -const ITEM_GAP = 8 // tokens.space.sm -const MIN_PEEK = 40 - -interface GalleryProps { - images: AppBskyEmbedImages.ViewImage[] - onPress?: ( - index: number, - containerRefs: AnimatedRef[], - fetchedDims: (Dimensions | null)[], - ) => void - onPressIn?: (index: number) => void - viewContext?: PostEmbedViewContext -} - -const Context = createContext<{ - ref: React.RefObject -}>({ - ref: {current: null}, -}) - -export function GalleryBleed({children}: {children: React.ReactNode}) { - const ref = useRef(null) - - if (!isValidElement(children)) { - throw new Error('GalleryBleed children must be a single React element') - } - - const node = children as React.ReactElement - - return ( - - {cloneElement(node, { - ref: mergeRefs([ref, node?.props?.ref]), - })} - - ) -} - -export function Gallery({ - images, - onPress, - onPressIn, - viewContext, -}: GalleryProps) { - const t = useTheme() - const {t: l} = useLingui() - const ax = useAnalytics() - const {screenReaderEnabled} = useA11y() - const largeAltBadge = useLargeAltBadgeEnabled() - const currentPageRef = useRef(0) - const {width: windowWidth} = useWindowDimensions() - const [leftOffset, setLeftOffset] = useState(0) - const [containerWidth, setContainerWidth] = useState(0) - - const containerRefs = useRef[]>([]).current - const thumbDimsRef = useRef<(Dimensions | null)[]>([]) - - const ref0 = useAnimatedRef() - const ref1 = useAnimatedRef() - const ref2 = useAnimatedRef() - const ref3 = useAnimatedRef() - const refs = [ref0, ref1, ref2, ref3] - for (let i = 0; i < images.length; i++) { - containerRefs[i] = refs[i] - } - - const isWithinQuote = - viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia - const hideBadges = isWithinQuote - - const containerHeight = - containerWidth > 0 ? containerWidth / CONTAINER_ASPECT_RATIO : 0 - // Bleed: full-width carousel that extends to screen edges - // In quotes: small bleed to the quote card border (p_md = 12px) - const QUOTE_PADDING = 12 - const bleed = !isWithinQuote - const insetLeft = bleed - ? leftOffset || windowWidth - containerWidth - : QUOTE_PADDING - const insetRight = bleed - ? windowWidth - insetLeft - containerWidth - : QUOTE_PADDING - - const getItemWidth = (image: AppBskyEmbedImages.ViewImage, index: number) => { - const ar = image.aspectRatio - let width = containerHeight // default to square-ish - if (ar && ar.width > 0 && ar.height > 0) { - const ratio = ar.width / ar.height - // Width derived from image's own aspect ratio at the fixed container height - // Clamp aspect ratio between 2:3 (portrait) and 3:2 (landscape) - const clamped = Math.max(2 / 3, Math.min(ratio, 3 / 2)) - width = containerHeight * clamped - } - // Ensure the first image leaves room for a peek of the next - if (index === 0 && images.length > 1) { - width = Math.min(width, containerWidth - MIN_PEEK) - } - return width - } - - if (screenReaderEnabled) { - return ( - - {images.map((image, index) => ( - - onPress?.(index, [containerRef], [dims]) - } - onPressIn={() => onPressIn?.(index)} - hideBadge={ - viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia - } - /> - ))} - - ) - } - - return ( - 0 - ? {height: containerHeight, overflow: 'visible'} - : {aspectRatio: CONTAINER_ASPECT_RATIO} - } - onLayout={e => { - const w = e.nativeEvent.layout.width - if (w > 0) { - setContainerWidth(w) - } - e.target.measureInWindow((x: number) => { - if (x > 0) { - setLeftOffset(x) - } - }) - }}> - {containerWidth > 0 && ( - - { - const offsetX = e.nativeEvent.contentOffset.x - // Determine which item is most visible based on scroll position - let accumulated = insetLeft // account for left content padding - let page = 0 - for (let i = 0; i < images.length; i++) { - const w = getItemWidth(images[i], i) + ITEM_GAP - if (offsetX < accumulated + w / 2) { - page = i - break - } - accumulated += w - page = i - } - if (page !== currentPageRef.current) { - ax.metric('post:gallery:swipe', { - fromIndex: currentPageRef.current, - toIndex: page, - totalImages: images.length, - }) - currentPageRef.current = page - } - }} - scrollEventThrottle={16} - keyExtractor={(_, index) => String(index)} - renderItem={({item: image, index}) => ( - - { - ax.metric('post:gallery:openLightbox', { - imageIndex: index, - totalImages: images.length, - }) - onPress( - index, - containerRefs.slice(0, images.length), - thumbDimsRef.current.slice(), - ) - } - : undefined - } - onPressIn={onPressIn ? () => onPressIn(index) : undefined} - android_ripple={{ - color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), - foreground: true, - }} - accessibilityRole="button" - accessibilityLabel={ - image.alt || l`Image ${index + 1} of ${images.length}` - } - accessibilityHint={l`Opens full image`} - style={[ - a.flex_1, - a.rounded_md, - a.overflow_hidden, - t.atoms.bg_contrast_25, - ]}> - { - thumbDimsRef.current[index] = { - width: e.source.width, - height: e.source.height, - } - }} - loading={index === 0 ? 'eager' : 'lazy'} - /> - - - {image.alt && !hideBadges ? ( - - - ALT - - - ) : null} - - )} - /> - - )} - - ) -} - -function DrawerGestureBlocker({children}: {children: React.ReactNode}) { - const drawerGesture = useContext(DrawerGestureContext) - - const nativeGesture = useMemo(() => { - const gesture = Gesture.Native() - if (drawerGesture) { - gesture.blocksExternalGesture(drawerGesture) - } - return gesture - }, [drawerGesture]) - - return {children} -} diff --git a/src/components/images/Gallery/index-old.web.tsx b/src/components/images/Gallery/index-old.web.tsx deleted file mode 100644 index 3c882ce6b9..0000000000 --- a/src/components/images/Gallery/index-old.web.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import {useCallback, useEffect, useRef, useState} from 'react' -import {Pressable, View} from 'react-native' -import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' -import {Image} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' -import {Trans, useLingui} from '@lingui/react/macro' -import useEmblaCarousel from 'embla-carousel-react' - -import {type Dimensions} from '#/lib/media/types' -import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' -import {atoms as a, useTheme} from '#/alf' -import {MediaInsetBorder} from '#/components/MediaInsetBorder' -import {PostEmbedViewContext} from '#/components/Post/Embed/types' -import {Text} from '#/components/Typography' -import {useAnalytics} from '#/analytics' - -const CONTAINER_ASPECT_RATIO = 3 / 2 -const ITEM_GAP = 8 -const MIN_PEEK = 40 - -interface GalleryProps { - images: AppBskyEmbedImages.ViewImage[] - onPress?: ( - index: number, - containerRefs: AnimatedRef[], - fetchedDims: (Dimensions | null)[], - ) => void - onPressIn?: (index: number) => void - viewContext?: PostEmbedViewContext -} - -export function Gallery({ - images, - onPress, - onPressIn, - viewContext, -}: GalleryProps) { - const t = useTheme() - const {t: l} = useLingui() - const ax = useAnalytics() - const largeAltBadge = useLargeAltBadgeEnabled() - const currentPageRef = useRef(0) - const containerRef = useRef(null) - const [containerWidth, setContainerWidth] = useState(0) - const [insetLeft, setInsetLeft] = useState(0) - const [insetRight, setInsetRight] = useState(0) - const insetLeftRef = useRef(0) - - const containerRefs = useRef[]>([]).current - const thumbDimsRef = useRef<(Dimensions | null)[]>([]) - - const ref0 = useAnimatedRef() - const ref1 = useAnimatedRef() - const ref2 = useAnimatedRef() - const ref3 = useAnimatedRef() - const refs = [ref0, ref1, ref2, ref3] - for (let i = 0; i < images.length; i++) { - containerRefs[i] = refs[i] - } - - const isWithinQuote = - viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia - const hideBadges = isWithinQuote - - const QUOTE_PADDING = 12 - const containerHeight = - containerWidth > 0 ? containerWidth / CONTAINER_ASPECT_RATIO : 0 - - const getItemWidth = (image: AppBskyEmbedImages.ViewImage, index: number) => { - const ar = image.aspectRatio - let width = containerHeight - if (ar && ar.width > 0 && ar.height > 0) { - const ratio = ar.width / ar.height - const clamped = Math.max(2 / 3, Math.min(ratio, 3 / 2)) - width = containerHeight * clamped - } - // Ensure the first image leaves room for a peek of the next - if (index === 0 && images.length > 1) { - width = Math.min(width, containerWidth - MIN_PEEK) - } - return width - } - - // Embla carousel - const [emblaRef, emblaApi] = useEmblaCarousel({ - align: () => insetLeftRef.current, - containScroll: 'trimSnaps', - dragFree: true, - }) - - // Track page changes for analytics - useEffect(() => { - if (!emblaApi) return - const onSelect = () => { - const page = emblaApi.selectedScrollSnap() - if (page !== currentPageRef.current) { - ax.metric('post:gallery:swipe', { - fromIndex: currentPageRef.current, - toIndex: page, - totalImages: images.length, - }) - currentPageRef.current = page - } - } - const onDragStart = () => setIsDragging(true) - const onDragEnd = () => setIsDragging(false) - emblaApi.on('select', onSelect) - emblaApi.on('pointerDown', onDragStart) - emblaApi.on('pointerUp', onDragEnd) - emblaApi.on('settle', onDragEnd) - return () => { - emblaApi.off('select', onSelect) - emblaApi.off('pointerDown', onDragStart) - emblaApi.off('pointerUp', onDragEnd) - emblaApi.off('settle', onDragEnd) - } - }, [emblaApi, ax, images.length]) - - // Re-initialize Embla when bleed measurements change - useEffect(() => { - if (emblaApi) emblaApi.reInit() - }, [emblaApi, insetLeft, insetRight]) - - // Suppress click after drag - const pointerDown = useRef(false) - const dragged = useRef(false) - const [isDragging, setIsDragging] = useState(false) - - useEffect(() => { - if (!emblaApi) return - const root = emblaApi.rootNode() - - const onPointerDown = () => { - pointerDown.current = true - dragged.current = false - } - const onPointerMove = () => { - if (pointerDown.current) dragged.current = true - } - const onPointerUp = () => { - pointerDown.current = false - } - const onClick = (e: MouseEvent) => { - if (dragged.current) { - e.stopPropagation() - e.preventDefault() - } - } - - root.addEventListener('pointerdown', onPointerDown) - root.addEventListener('pointermove', onPointerMove) - root.addEventListener('pointerup', onPointerUp) - root.addEventListener('click', onClick, true) - return () => { - root.removeEventListener('pointerdown', onPointerDown) - root.removeEventListener('pointermove', onPointerMove) - root.removeEventListener('pointerup', onPointerUp) - root.removeEventListener('click', onClick, true) - } - }, [emblaApi]) - - // Bleed measurement - const measureBleed = useCallback(() => { - if (isWithinQuote) return - requestAnimationFrame(() => { - const el = containerRef.current as unknown as HTMLElement - if (!el) return - const galleryRect = el.getBoundingClientRect() - let parent: HTMLElement | null = el.parentElement - while (parent) { - const ps = window.getComputedStyle(parent) - const pl = parseFloat(ps.paddingLeft) - const pr = parseFloat(ps.paddingRight) - if (pl >= 8 && pr >= 8) { - const parentRect = parent.getBoundingClientRect() - const il = galleryRect.left - parentRect.left - insetLeftRef.current = il - setInsetLeft(il) - setInsetRight(parentRect.right - galleryRect.right) - break - } - parent = parent.parentElement - } - }) - }, [isWithinQuote]) - - const isBleed = !isWithinQuote && (insetLeft > 0 || insetRight > 0) - - return ( - 0 - ? isWithinQuote - ? { - height: containerHeight, - overflow: 'hidden' as const, - width: containerWidth + QUOTE_PADDING * 2, - marginLeft: -QUOTE_PADDING, - } - : {height: containerHeight, overflow: 'visible' as const} - : {aspectRatio: CONTAINER_ASPECT_RATIO} - } - ref={containerRef} - onLayout={e => { - const w = e.nativeEvent.layout.width - if (w > 0 && containerWidth === 0) setContainerWidth(w) - measureBleed() - }} - role="group" - aria-roledescription="carousel" - aria-label={l`Image gallery, ${images.length} images`}> - {containerWidth > 0 && ( -
-
-
- {images.map((image, index) => ( - - - { - if (dragged.current) return - ax.metric('post:gallery:openLightbox', { - imageIndex: index, - totalImages: images.length, - }) - onPress( - index, - containerRefs.slice(0, images.length), - thumbDimsRef.current.slice(), - ) - } - : undefined - } - onPressIn={onPressIn ? () => onPressIn(index) : undefined} - accessibilityRole="button" - accessibilityLabel={ - image.alt || l`Image ${index + 1} of ${images.length}` - } - accessibilityHint={l`Opens full image`} - style={[ - a.flex_1, - a.rounded_md, - a.overflow_hidden, - t.atoms.bg_contrast_25, - ]}> - { - thumbDimsRef.current[index] = { - width: e.source.width, - height: e.source.height, - } - }} - loading={index === 0 ? 'eager' : 'lazy'} - /> - - - {image.alt && !hideBadges ? ( - - - ALT - - - ) : null} - - - ))} - {(isBleed ? insetRight > 0 : isWithinQuote) && ( -
- )} -
-
-
- )} - - ) -} - -function Slide({ - width, - height, - children, -}: { - width: number - height: number - children: React.ReactNode -}) { - const [pressed, setPressed] = useState(false) - return ( -
setPressed(true)} - onPointerUp={() => setPressed(false)} - onPointerLeave={() => setPressed(false)} - onPointerMove={() => setPressed(false)} - style={{ - flex: `0 0 ${width}px`, - minWidth: 0, - height, - transition: 'transform 0.15s ease-out', - transform: pressed ? 'scale(0.975)' : undefined, - }}> - {children} -
- ) -}