diff --git a/.changeset/shopify-assets-react-photo-album-v3.md b/.changeset/shopify-assets-react-photo-album-v3.md new file mode 100644 index 0000000000..2e22e5a748 --- /dev/null +++ b/.changeset/shopify-assets-react-photo-album-v3.md @@ -0,0 +1,5 @@ +--- +"sanity-plugin-shopify-assets": major +--- + +Upgrade `react-photo-album` to v3 (`RowsPhotoAlbum` + built-in infinite scroll) and align with the shared catalog dependency diff --git a/.changeset/unsplash-react-photo-album-3-6-1.md b/.changeset/unsplash-react-photo-album-3-6-1.md new file mode 100644 index 0000000000..36d2388558 --- /dev/null +++ b/.changeset/unsplash-react-photo-album-3-6-1.md @@ -0,0 +1,5 @@ +--- +"sanity-plugin-asset-source-unsplash": patch +--- + +fix(deps): update dependency react-photo-album to ^3.6.1 diff --git a/plugins/sanity-plugin-shopify-assets/package.json b/plugins/sanity-plugin-shopify-assets/package.json index a09cef4990..21e1cef239 100644 --- a/plugins/sanity-plugin-shopify-assets/package.json +++ b/plugins/sanity-plugin-shopify-assets/package.json @@ -46,9 +46,7 @@ "axios": "^1.18.1", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", - "react-infinite-scroll-component": "^6.1.1", - "react-photo-album": "^2.4.1", - "rxjs": "catalog:", + "react-photo-album": "catalog:", "video.js": "catalog:" }, "devDependencies": { diff --git a/plugins/sanity-plugin-shopify-assets/src/components/File.styled.tsx b/plugins/sanity-plugin-shopify-assets/src/components/File.styled.tsx index 50eeafe09f..cc590f4cab 100644 --- a/plugins/sanity-plugin-shopify-assets/src/components/File.styled.tsx +++ b/plugins/sanity-plugin-shopify-assets/src/components/File.styled.tsx @@ -2,35 +2,6 @@ import {Card} from '@sanity/ui' import {getTheme_v2} from '@sanity/ui/theme' import {styled} from 'styled-components' -export const Root = styled.div` - ${({theme}) => { - const v2 = getTheme_v2({sanity: theme.sanity}) - return ` - background-color: ${v2.color.muted.bg}; - border: 1px solid ${v2.color.border}; - ` - }}; - overflow: hidden; - background-origin: content-box; - background-repeat: no-repeat; - background-clip: border-box; - background-size: cover; - position: relative; - outline: none !important; - box-sizing: content-box; - user-drag: none; - - &:hover { - opacity: 0.85; - } - - &:focus, - &:active { - border: 1px solid var(--input-border-color-focus); - box-shadow: inset 0 0 0 3px var(--input-border-color-focus); - } -` - export const InfoLine = styled(Card)` ${({theme}) => { const v2 = getTheme_v2({sanity: theme.sanity}) @@ -46,6 +17,7 @@ export const InfoLine = styled(Card)` left: 0; max-width: 65%; overflow-wrap: break-word; + pointer-events: none; [data-ui='Text'] { color: var(--infoline-fg); @@ -65,6 +37,7 @@ export const DurationLine = styled(Card)` background-color: var(--durationline-bg); top: 0; right: 0; + pointer-events: none; [data-ui='Text'] { color: var(--durationline-fg); diff --git a/plugins/sanity-plugin-shopify-assets/src/components/File.tsx b/plugins/sanity-plugin-shopify-assets/src/components/File.tsx index 2cae5c8800..ea3789cd1d 100644 --- a/plugins/sanity-plugin-shopify-assets/src/components/File.tsx +++ b/plugins/sanity-plugin-shopify-assets/src/components/File.tsx @@ -1,54 +1,29 @@ import {Text} from '@sanity/ui' import prettyBytes from 'pretty-bytes' import prettyMilliseconds from 'pretty-ms' -import {useCallback, useRef} from 'react' -import type {Asset, ShopifyFile} from '../types' +import type {ShopifyFile} from '../types' import {extractName} from '../utils/helpers' -import {DurationLine, InfoLine, Root} from './File.styled' +import {DurationLine, InfoLine} from './File.styled' -type Props = { - data: ShopifyFile - width: number - height: number - onClick: (file: Asset) => void -} - -export default function File(props: Props) { - const {onClick, data, width, height} = props - const rootElm = useRef(null) - - const {preview, meta} = data +export default function File({data}: {data: ShopifyFile}) { const filename = extractName(data.url) - - const handleClick = useCallback(() => { - onClick({...data, filename}) - }, [onClick, data, filename]) + const {meta} = data return ( - + <> - {filename} {meta.fileSize && `(${prettyBytes(meta.fileSize)})`} + {filename} {meta.fileSize ? `(${prettyBytes(meta.fileSize)})` : null} - {meta.duration && ( + {meta.duration ? ( {prettyMilliseconds(meta.duration, {colonNotation: true, secondsDecimalDigits: 0})} - )} - + ) : null} + ) } diff --git a/plugins/sanity-plugin-shopify-assets/src/components/Loader.tsx b/plugins/sanity-plugin-shopify-assets/src/components/Loader.tsx new file mode 100644 index 0000000000..b36161d239 --- /dev/null +++ b/plugins/sanity-plugin-shopify-assets/src/components/Loader.tsx @@ -0,0 +1,9 @@ +import {Flex, Spinner} from '@sanity/ui' + +export function Loader() { + return ( + + + + ) +} diff --git a/plugins/sanity-plugin-shopify-assets/src/components/ShopifyAssetPicker.tsx b/plugins/sanity-plugin-shopify-assets/src/components/ShopifyAssetPicker.tsx index c8b2d4f6fa..f491e2d706 100644 --- a/plugins/sanity-plugin-shopify-assets/src/components/ShopifyAssetPicker.tsx +++ b/plugins/sanity-plugin-shopify-assets/src/components/ShopifyAssetPicker.tsx @@ -1,20 +1,78 @@ import {ErrorOutlineIcon} from '@sanity/icons/ErrorOutline' -import {Card, Dialog, Flex, Inline, Spinner, Stack, Text, TextInput} from '@sanity/ui' -import {type ChangeEvent, useCallback, useEffect, useMemo, useState} from 'react' -import InfiniteScroll from 'react-infinite-scroll-component' -import {PhotoAlbum} from 'react-photo-album' -import {BehaviorSubject, type Subscription} from 'rxjs' +import {Box, Card, Dialog, Flex, Inline, Stack, Text, TextInput} from '@sanity/ui' +import { + type ChangeEvent, + useEffect, + useMemo, + useRef, + useState, + use, + Suspense, + Activity, +} from 'react' + +import 'react-photo-album/rows.css' +import {RowsPhotoAlbum} from 'react-photo-album' +import InfiniteScroll from 'react-photo-album/scroll' import {type ObjectInputProps, PatchEvent, set, useClient, useDataset, useProjectId} from 'sanity' +import {styled} from 'styled-components' -import {search} from '../datastores/shopify' -import type {Asset, PageInfo, ShopifyAPIResponse, ShopifyFile} from '../types' +import {fetchAssets} from '../datastores/shopify' +import type {Asset, ShopifyAPIResponse, ShopifyFile} from '../types' +import {extractName} from '../utils/helpers' import DialogHeader from './DialogHeader' import File from './File' +import {Loader} from './Loader' import {Search} from './ShopifyAssetInput.styled' const RESULTS_PER_PAGE = 42 const PHOTO_SPACING = 2 const PHOTO_PADDING = 1 +const SEARCH_DEBOUNCE_MS = 500 + +const StyledDialog = styled(Dialog)` + & > [data-ui='DialogCard'] > [data-ui='Card'] { + height: 100%; + } +` + +function mapShopifyFileToPhoto(file: ShopifyFile) { + return { + src: file.preview?.url || file.url, + width: file.preview?.width || 2048, + height: file.preview?.height || 2048, + key: file.id, + alt: extractName(file.url), + data: file, + } +} + +type ShopifyPhoto = ReturnType + +function createFetcher(params: {projectId: string; dataset: string; shop: string; token?: string}) { + return async function fetcher(query: string, cursor: string): Promise { + return fetchAssets({ + ...params, + query, + cursor, + resultsPerPage: RESULTS_PER_PAGE, + }) + } +} + +function getErrorMessage(err: unknown): string { + if (err && typeof err === 'object') { + const maybeAxiosError = err as { + response?: {data?: {message?: string}} + message?: string + } + const message = maybeAxiosError.response?.data?.message || maybeAxiosError.message + if (message) { + return `${message} - check plugin configuration` + } + } + return 'An error occurred - check plugin configuration' +} export interface AssetPickerProps extends ObjectInputProps { shopifyDomain: string @@ -29,105 +87,78 @@ export default function ShopifyAssetPicker(props: AssetPickerProps) { const client = useClient({apiVersion: '2021-06-07'}) const token = client.config().token - const [apiError, setApiError] = useState('') const [query, setQuery] = useState('') - const [searchResults, setSearchResults] = useState([]) - const [pageInfo, setPageInfo] = useState() - const [isLoading, setIsLoading] = useState(true) + const [debouncedQuery, setDebouncedQuery] = useState('') + const [apiError, setApiError] = useState('') + const scrollContainerRef = useRef(null) const error = shopifyDomain ? apiError : 'Please configure your Shopify domain in the plugin config' - const searchSubject$ = useMemo(() => new BehaviorSubject(''), []) - const cursorSubject$ = useMemo(() => new BehaviorSubject(''), []) - useEffect(() => { - const searchSubscription: Subscription = search({ - projectId, - dataset, - shop: shopifyDomain, - query: searchSubject$, - cursor: cursorSubject$, - resultsPerPage: RESULTS_PER_PAGE, - token, - }).subscribe({ - next: (results: ShopifyAPIResponse) => { - setSearchResults((prevResults) => [...prevResults, ...results.assets]) - setPageInfo(results.pageInfo) - setIsLoading(false) - }, - error: (err) => { - setApiError( - `${ - err.response?.data?.message || err.message || 'An error occurred' - } - check plugin configuration`, - ) - }, - }) + const timeout = window.setTimeout(() => { + setApiError('') + setDebouncedQuery(query) + }, SEARCH_DEBOUNCE_MS) - return () => searchSubscription.unsubscribe() - }, [searchSubject$, cursorSubject$, shopifyDomain, projectId, dataset, token]) - - const handleSearchTermChanged = useCallback( - (event: ChangeEvent) => { - const newQuery = event.currentTarget.value - setQuery(newQuery) - setSearchResults([]) - setPageInfo(undefined) - setIsLoading(true) - - cursorSubject$.next('') - searchSubject$.next(newQuery) - }, - [cursorSubject$, searchSubject$], - ) + return () => window.clearTimeout(timeout) + }, [query]) - const handleScollerLoadMore = useCallback(() => { - setIsLoading(true) - if (pageInfo) cursorSubject$.next(pageInfo.cursor) - searchSubject$.next(query) - }, [cursorSubject$, pageInfo, searchSubject$, query]) - - const handleSelect = useCallback( - (file: Asset) => { - const nextValue: Asset = {...file, _key: value?._key, _type: schemaType.name} - onChange(PatchEvent.from([set(nextValue)])) - onClose() - }, - [onChange, onClose, schemaType.name, value?._key], + const fetcher = useMemo( + () => + createFetcher({ + projectId, + dataset, + shop: shopifyDomain, + token, + }), + [projectId, dataset, shopifyDomain, token], ) - const renderFile = useCallback( - (fileProps: any) => { - const {photo, layout} = fileProps - return ( - - ) - }, - [handleSelect], + const initialDataPromise = useMemo( + () => + fetcher(debouncedQuery, '').catch((err: unknown) => { + setApiError(getErrorMessage(err)) + return { + assets: [], + pageInfo: {cursor: '', hasNextPage: false}, + } satisfies ShopifyAPIResponse + }), + [debouncedQuery, fetcher], ) - const handleWidth = useCallback((width: number) => { - if (width < 300) return 150 - else if (width < 600) return 200 - return 300 - }, []) + const handleSearchTermChanged = (event: ChangeEvent) => { + setQuery(event.currentTarget.value) + } + + const handleSelect = (file: ShopifyFile) => { + const nextValue: Asset = { + ...file, + filename: extractName(file.url), + _key: value?._key, + _type: schemaType.name, + } + onChange(PatchEvent.from([set(nextValue)])) + onClose() + } return ( - } onClose={onClose} open={isOpen} + height="100%" width={4} > - + {error ? ( @@ -141,66 +172,126 @@ export default function ShopifyAssetPicker(props: AssetPickerProps) { ) : ( <> - - - - Search Shopify for assets - - - - - {!isLoading && searchResults.length === 0 && ( - - No results found - - )} - - - - } - endMessage={ - - - No more results + + + + + Search Shopify for assets - - } - > - {searchResults && ( - ({ - src: file?.preview?.url, - width: file?.preview?.width || 2048, - height: file?.preview?.height || 2048, - key: file.id, - data: file, - }))} - renderPhoto={renderFile} - componentsProps={{ - containerProps: {style: {marginBottom: `${PHOTO_SPACING}px`}}, - }} - /> - )} - + + + + + }> + + )} - + + ) +} + +function ShopifyAssetGallery({ + query, + fetcher, + scrollContainerRef, + onSelect, + onError, + initialDataPromise, +}: { + query: string + fetcher: (query: string, cursor: string) => Promise + scrollContainerRef: React.RefObject + onSelect: (file: ShopifyFile) => void + onError: (message: string) => void + initialDataPromise: Promise +}) { + const data = use(initialDataPromise) + // Remounted per query via `key`, so the initial pageInfo is the pagination baseline. + const paginationRef = useRef({ + cursor: data.pageInfo.cursor, + hasNextPage: data.pageInfo.hasNextPage, + }) + + const initialPhotos = data.assets.map(mapShopifyFileToPhoto) + + return ( + <> + + + + {query ? `No results found for "${query}"` : 'No results found'} + + + + + scrollContainerRef.current} + fetch={async () => { + if (!paginationRef.current.hasNextPage) { + return null + } + + try { + const results = await fetcher(query, paginationRef.current.cursor) + paginationRef.current = { + cursor: results.pageInfo.cursor, + hasNextPage: results.pageInfo.hasNextPage, + } + + if (results.assets.length === 0) { + return null + } + + return results.assets.map(mapShopifyFileToPhoto) + } catch (err) { + onError(getErrorMessage(err)) + return null + } + }} + loading={} + finished={ + + + No more results + + + } + onClick={({photo}) => { + onSelect(photo.data) + }} + > + + photos={[]} + spacing={PHOTO_SPACING} + padding={PHOTO_PADDING} + targetRowHeight={(width) => { + if (width < 300) return 150 + if (width < 600) return 200 + return 300 + }} + render={{ + extras: (_, context) => , + }} + componentsProps={{container: {style: {marginBottom: PHOTO_SPACING}}}} + /> + + + ) } diff --git a/plugins/sanity-plugin-shopify-assets/src/css.d.ts b/plugins/sanity-plugin-shopify-assets/src/css.d.ts new file mode 100644 index 0000000000..503bef694e --- /dev/null +++ b/plugins/sanity-plugin-shopify-assets/src/css.d.ts @@ -0,0 +1 @@ +declare module 'react-photo-album/rows.css' diff --git a/plugins/sanity-plugin-shopify-assets/src/datastores/shopify.ts b/plugins/sanity-plugin-shopify-assets/src/datastores/shopify.ts index 62f94465aa..32c9cb25bf 100644 --- a/plugins/sanity-plugin-shopify-assets/src/datastores/shopify.ts +++ b/plugins/sanity-plugin-shopify-assets/src/datastores/shopify.ts @@ -1,95 +1,42 @@ import axios from 'axios' -import {BehaviorSubject, Observable, concat, defer} from 'rxjs' -import {debounceTime, distinctUntilChanged, map, switchMap, withLatestFrom} from 'rxjs/operators' -type SearchSubject = BehaviorSubject -type CursorSubject = BehaviorSubject +import type {ShopifyAPIResponse} from '../types' -interface fetchProps { +export interface FetchAssetsProps { projectId: string dataset: string shop: string - query: SearchSubject - cursor: CursorSubject - resultsPerPage: number - token?: string -} - -interface searchProps extends Omit { query: string cursor: string -} -interface listProps extends Omit { - cursor: string + resultsPerPage: number + token?: string } -const fetchSearch = (props: searchProps): Observable => { +export async function fetchAssets(props: FetchAssetsProps): Promise { const {projectId, dataset, shop, query, cursor, resultsPerPage, token} = props - const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : '' - const url = `https://${projectId}.api.sanity.io/v1/shopify/assets/${dataset}?shop=${encodeURIComponent( + const searchParams = new URLSearchParams({ shop, - )}&query=${encodeURIComponent(query)}${cursorParam}&limit=${resultsPerPage}` - - return defer(() => - axios.get(url, { - withCredentials: true, - method: 'GET', - headers: token - ? { - Authorization: `Bearer ${token}`, - } - : {}, - }), - ).pipe(map((result) => result.data)) -} - -const fetchList = (props: listProps): Observable => { - const {projectId, dataset, shop, cursor, resultsPerPage, token} = props - - const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : '' - const url = `https://${projectId}.api.sanity.io/v1/shopify/assets/${dataset}?shop=${encodeURIComponent( - shop, - )}${cursorParam}&limit=${resultsPerPage}` - - return defer(() => - axios.get(url, { - withCredentials: true, - method: 'GET', - headers: token - ? { - Authorization: `Bearer ${token}`, - } - : {}, - }), - ).pipe(map((result) => result.data)) -} - -export const search = (props: fetchProps): Observable => { - const {projectId, dataset, shop, query, cursor, resultsPerPage, token} = props - - // No value-based dedupe here on purpose: callers clear results and set their - // loading state before pushing to these subjects, so suppressing a repeated - // [query, cursor] pair would leave them loading forever. debounceTime already - // collapses rapid input and switchMap cancels superseded requests. - return concat( - query.pipe( - withLatestFrom(cursor), - debounceTime(500), - switchMap(([q, c]) => { - if (q) { - return fetchSearch({ - projectId, - dataset, - shop, - query: q, - cursor: c, - resultsPerPage, - token, - }).pipe(distinctUntilChanged()) + limit: `${resultsPerPage}`, + }) + if (query.trim()) { + searchParams.set('query', query.trim()) + } + if (cursor) { + searchParams.set('cursor', cursor) + } + + const url = `https://${projectId}.api.sanity.io/v1/shopify/assets/${dataset}?${searchParams}` + + const result = await axios.get(url, { + withCredentials: true, + method: 'GET', + headers: token + ? { + Authorization: `Bearer ${token}`, } - return fetchList({projectId, dataset, shop, cursor: c, resultsPerPage, token}) - }), - ), - ) + : {}, + }) + + return result.data } diff --git a/plugins/sanity-plugin-shopify-assets/src/shopify-search.regression.test.ts b/plugins/sanity-plugin-shopify-assets/src/shopify-search.regression.test.ts index 02f6ba7461..1b4ee7385f 100644 --- a/plugins/sanity-plugin-shopify-assets/src/shopify-search.regression.test.ts +++ b/plugins/sanity-plugin-shopify-assets/src/shopify-search.regression.test.ts @@ -1,49 +1,54 @@ -import {BehaviorSubject} from 'rxjs' +import type {AxiosRequestConfig} from 'axios' import {expect, test, vi} from 'vitest' -import {search} from './datastores/shopify' +import {fetchAssets} from './datastores/shopify' + +const axiosGet = vi.hoisted(() => + vi.fn((_url: string, _config?: AxiosRequestConfig) => + Promise.resolve({data: {assets: [], pageInfo: {cursor: '', hasNextPage: false}}}), + ), +) vi.mock('axios', () => ({ default: { - get: vi.fn(() => - Promise.resolve({data: {assets: [], pageInfo: {cursor: '', hasNextPage: false}}}), - ), + get: axiosGet, }, })) -// The picker clears its results and sets its loading state before pushing to -// these subjects, so `search` must emit for every push. Deduplicating repeated -// [query, cursor] pairs would leave the picker stuck loading on an empty grid. -test('emits again when a query settles back to the previous value', async () => { - vi.useFakeTimers() - - const query = new BehaviorSubject('') - const cursor = new BehaviorSubject('') - const emissions: unknown[] = [] - - const subscription = search({ +test('fetchAssets includes query and cursor search params', async () => { + await fetchAssets({ projectId: 'project', dataset: 'dataset', shop: 'example.myshopify.com', - query, - cursor, + query: 'abc', + cursor: 'cursor-1', resultsPerPage: 42, - }).subscribe((results) => emissions.push(results)) - - cursor.next('') - query.next('abc') - await vi.advanceTimersByTimeAsync(600) - expect(emissions).toHaveLength(1) + token: 'token', + }) + + expect(axiosGet).toHaveBeenCalledTimes(1) + expect(axiosGet.mock.calls[0]?.[0]).toContain('shop=example.myshopify.com') + expect(axiosGet.mock.calls[0]?.[0]).toContain('query=abc') + expect(axiosGet.mock.calls[0]?.[0]).toContain('cursor=cursor-1') + expect(axiosGet.mock.calls[0]?.[0]).toContain('limit=42') + expect(axiosGet.mock.calls[0]?.[1]?.headers).toMatchObject({ + Authorization: 'Bearer token', + }) +}) - // A typo corrected back to the previous query within the debounce window - cursor.next('') - query.next('abcd') - cursor.next('') - query.next('abc') - await vi.advanceTimersByTimeAsync(600) +test('fetchAssets omits empty query and cursor params', async () => { + axiosGet.mockClear() - expect(emissions).toHaveLength(2) + await fetchAssets({ + projectId: 'project', + dataset: 'dataset', + shop: 'example.myshopify.com', + query: ' ', + cursor: '', + resultsPerPage: 42, + }) - subscription.unsubscribe() - vi.useRealTimers() + expect(axiosGet.mock.calls[0]?.[0]).not.toContain('query=') + expect(axiosGet.mock.calls[0]?.[0]).not.toContain('cursor=') + expect(axiosGet.mock.calls[0]?.[1]?.headers).toEqual({}) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db0f143dfd..e97894e24e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,8 +146,8 @@ catalogs: specifier: ^19.2.8 version: 19.2.8 react-photo-album: - specifier: ^3.6.0 - version: 3.6.0 + specifier: ^3.6.1 + version: 3.6.1 react-rx: specifier: ^4.2.5 version: 4.2.5 @@ -1914,7 +1914,7 @@ importers: version: 4.18.1 react-photo-album: specifier: 'catalog:' - version: 3.6.0(@types/react@19.2.18)(react@19.2.8) + version: 3.6.1(@types/react@19.2.18)(react@19.2.8) devDependencies: '@sanity/tsconfig': specifier: 'catalog:' @@ -2858,15 +2858,9 @@ importers: pretty-ms: specifier: ^8.0.0 version: 8.0.0 - react-infinite-scroll-component: - specifier: ^6.1.1 - version: 6.1.1(react@19.2.8) react-photo-album: - specifier: ^2.4.1 - version: 2.4.1(react@19.2.8) - rxjs: specifier: 'catalog:' - version: 7.8.2 + version: 3.6.1(@types/react@19.2.18)(react@19.2.8) video.js: specifier: 'catalog:' version: 7.21.7 @@ -10395,11 +10389,6 @@ packages: peerDependencies: react: '*' - react-infinite-scroll-component@6.1.1: - resolution: {integrity: sha512-R8YoOyiNDynSWmfVme5LHslsKrP+/xcRUWR2ies8UgUab9dtyw5ECnMCVPPmnmjjF4MWQmfVdRwRWcWaDgeyMA==} - peerDependencies: - react: '>=16.0.0' - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -10415,14 +10404,8 @@ packages: peerDependencies: react: '>=16.13.1' - react-photo-album@2.4.1: - resolution: {integrity: sha512-dzqP5QbYAugA0uZTl3qsVldckzDXYDkDOvA8CpACl51hSEfhJmCfwhbnI4WBHnETQHv48nnNQ1jhrulst8njLA==} - engines: {node: '>=12'} - peerDependencies: - react: '>=16.8.0' - - react-photo-album@3.6.0: - resolution: {integrity: sha512-W9NgI+0XxOYF/FLQJ/ZiKsizNQtGtgDdiFgojmTmpBKDGeGiWKfSzmbw3v9WAqzimPeiFJ2sEb9DO0nHRHP/OA==} + react-photo-album@3.6.1: + resolution: {integrity: sha512-m4pTIpgEpdAJgRnf3DfISHfIHMjJkouWJdKUyGy9UaFBBePv1kolqKTzAmZrfQcoZBVfT/g9vvcc39o1RfwZgA==} engines: {node: '>=18'} peerDependencies: '@types/react': ^18 || ^19 @@ -11097,10 +11080,6 @@ packages: text-decoder@1.2.7: resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} - throttle-debounce@2.3.0: - resolution: {integrity: sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==} - engines: {node: '>=8'} - throttleit@2.1.0: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} @@ -19861,11 +19840,6 @@ snapshots: dependencies: react: 19.2.8 - react-infinite-scroll-component@6.1.1(react@19.2.8): - dependencies: - react: 19.2.8 - throttle-debounce: 2.3.0 - react-is@16.13.1: {} react-is@17.0.2: {} @@ -19877,11 +19851,7 @@ snapshots: jerrypick: 1.1.2 react: 19.2.8 - react-photo-album@2.4.1(react@19.2.8): - dependencies: - react: 19.2.8 - - react-photo-album@3.6.0(@types/react@19.2.18)(react@19.2.8): + react-photo-album@3.6.1(@types/react@19.2.18)(react@19.2.8): dependencies: react: 19.2.8 optionalDependencies: @@ -21062,8 +21032,6 @@ snapshots: transitivePeerDependencies: - react-native-b4a - throttle-debounce@2.3.0: {} - throttleit@2.1.0: {} through2@2.0.5: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7f25269857..442643d534 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -65,7 +65,7 @@ catalog: react-hook-form: ^7.84.0 react-icons: ^5.7.0 react-is: ^19.2.8 - react-photo-album: ^3.6.0 + react-photo-album: ^3.6.1 react-rx: ^4.2.5 rxjs: ^7.8.2 sanity: ^6.7.0