mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 12:47:50 +00:00
improve YMM experience
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
4fec476285
commit
589efe5699
@ -9,10 +9,8 @@ import {
|
|||||||
} from '@headlessui/react';
|
} from '@headlessui/react';
|
||||||
import { ChevronDownIcon } from '@heroicons/react/16/solid';
|
import { ChevronDownIcon } from '@heroicons/react/16/solid';
|
||||||
import Spinner from 'components/spinner';
|
import Spinner from 'components/spinner';
|
||||||
import { useDebounce } from 'hooks/use-debounce';
|
|
||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useInView } from 'react-intersection-observer';
|
|
||||||
|
|
||||||
type FilterFieldProps<T extends { [key: string]: unknown }> = {
|
type FilterFieldProps<T extends { [key: string]: unknown }> = {
|
||||||
options: T[];
|
options: T[];
|
||||||
@ -26,9 +24,6 @@ type FilterFieldProps<T extends { [key: string]: unknown }> = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
loadMore?: (reset?: boolean) => void;
|
|
||||||
hasNextPage?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const FilterField = <T extends { [key: string]: unknown }>({
|
const FilterField = <T extends { [key: string]: unknown }>({
|
||||||
@ -40,9 +35,7 @@ const FilterField = <T extends { [key: string]: unknown }>({
|
|||||||
getId,
|
getId,
|
||||||
disabled,
|
disabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
autoFocus = false,
|
autoFocus = false
|
||||||
loadMore,
|
|
||||||
hasNextPage
|
|
||||||
}: FilterFieldProps<T>) => {
|
}: FilterFieldProps<T>) => {
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const getDisplayValue = useCallback(
|
const getDisplayValue = useCallback(
|
||||||
@ -57,7 +50,6 @@ const FilterField = <T extends { [key: string]: unknown }>({
|
|||||||
},
|
},
|
||||||
[displayKey]
|
[displayKey]
|
||||||
);
|
);
|
||||||
const [scrollTrigger, isInView] = useInView();
|
|
||||||
|
|
||||||
const filteredOptions =
|
const filteredOptions =
|
||||||
query === ''
|
query === ''
|
||||||
@ -66,26 +58,6 @@ const FilterField = <T extends { [key: string]: unknown }>({
|
|||||||
return getDisplayValue(option).toLocaleLowerCase().includes(query.toLowerCase());
|
return getDisplayValue(option).toLocaleLowerCase().includes(query.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadMoreFnRef = useRef<Function>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadMoreFnRef.current = loadMore;
|
|
||||||
}, [loadMore]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isInView && hasNextPage) {
|
|
||||||
loadMoreFnRef.current?.();
|
|
||||||
}
|
|
||||||
}, [isInView, hasNextPage]);
|
|
||||||
|
|
||||||
const debouncedQuery = useDebounce(query);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedQuery && !filteredOptions.length) {
|
|
||||||
loadMoreFnRef.current?.(true);
|
|
||||||
}
|
|
||||||
}, [debouncedQuery, filteredOptions.length]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Combobox
|
<Combobox
|
||||||
@ -94,7 +66,7 @@ const FilterField = <T extends { [key: string]: unknown }>({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onClose={() => setQuery('')}
|
onClose={() => setQuery('')}
|
||||||
immediate
|
immediate
|
||||||
disabled={disabled || isLoading}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<ComboboxInput
|
<ComboboxInput
|
||||||
@ -109,7 +81,7 @@ const FilterField = <T extends { [key: string]: unknown }>({
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Spinner className="fill-black/60" />
|
<Spinner className="fill-black/60" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronDownIcon className="fill-black/60 group-data-[hover]:fill-black size-5" />
|
<ChevronDownIcon className="size-5 fill-black/60 group-data-[hover]:fill-black" />
|
||||||
)}
|
)}
|
||||||
</ComboboxButton>
|
</ComboboxButton>
|
||||||
</div>
|
</div>
|
||||||
@ -122,7 +94,6 @@ const FilterField = <T extends { [key: string]: unknown }>({
|
|||||||
key={getId(option)}
|
key={getId(option)}
|
||||||
value={option}
|
value={option}
|
||||||
className="flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-1.5 text-sm/6 data-[focus]:bg-secondary/10"
|
className="flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-1.5 text-sm/6 data-[focus]:bg-secondary/10"
|
||||||
ref={scrollTrigger}
|
|
||||||
>
|
>
|
||||||
{getDisplayValue(option)}
|
{getDisplayValue(option)}
|
||||||
</ComboboxOption>
|
</ComboboxOption>
|
||||||
|
@ -6,7 +6,7 @@ import { Menu, Metaobject, PageInfo } from 'lib/shopify/types';
|
|||||||
import { createUrl, findParentCollection } from 'lib/utils';
|
import { createUrl, findParentCollection } from 'lib/utils';
|
||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
import { useParams, useRouter, useSearchParams } from 'next/navigation';
|
import { useParams, useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState, useTransition } from 'react';
|
||||||
import { fetchMetaobjectReferences } from './actions';
|
import { fetchMetaobjectReferences } from './actions';
|
||||||
import FilterField from './field';
|
import FilterField from './field';
|
||||||
|
|
||||||
@ -35,13 +35,14 @@ const sortOptions = (options: Metaobject[], displayField: string) => {
|
|||||||
return modelA.localeCompare(modelB);
|
return modelA.localeCompare(modelB);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) => {
|
const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) => {
|
||||||
const params = useParams<{ collection?: string }>();
|
const params = useParams<{ collection?: string }>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const makeIdFromSearchParams = searchParams.get(MAKE_FILTER_ID);
|
const makeIdFromSearchParams = searchParams.get(MAKE_FILTER_ID);
|
||||||
// const modelIdFromSearchParams = searchParams.get(MODEL_FILTER_ID);
|
const modelIdFromSearchParams = searchParams.get(MODEL_FILTER_ID);
|
||||||
// const yearIdFromSearchParams = searchParams.get(YEAR_FILTER_ID);
|
const yearIdFromSearchParams = searchParams.get(YEAR_FILTER_ID);
|
||||||
|
|
||||||
const parentCollection = params.collection ? findParentCollection(menu, params.collection) : null;
|
const parentCollection = params.collection ? findParentCollection(menu, params.collection) : null;
|
||||||
// get the active collection (if any) to identify the default part type.
|
// get the active collection (if any) to identify the default part type.
|
||||||
@ -54,6 +55,8 @@ const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) =>
|
|||||||
(partTypeCollection && partTypeCollection.includes(type.value))
|
(partTypeCollection && partTypeCollection.includes(type.value))
|
||||||
) || null
|
) || null
|
||||||
);
|
);
|
||||||
|
const [, initialMake, modelFromHandle, yearFromHandle] = params.collection?.split('_') || [];
|
||||||
|
|
||||||
const [make, setMake] = useState<Metaobject | null>(null);
|
const [make, setMake] = useState<Metaobject | null>(null);
|
||||||
const [model, setModel] = useState<Metaobject | null>(null);
|
const [model, setModel] = useState<Metaobject | null>(null);
|
||||||
const [year, setYear] = useState<Metaobject | null>(null);
|
const [year, setYear] = useState<Metaobject | null>(null);
|
||||||
@ -61,58 +64,10 @@ const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) =>
|
|||||||
const [models, setModels] = useState<Options>({ options: [], pageInfo: null });
|
const [models, setModels] = useState<Options>({ options: [], pageInfo: null });
|
||||||
const [years, setYears] = useState<Options>({ options: [], pageInfo: null });
|
const [years, setYears] = useState<Options>({ options: [], pageInfo: null });
|
||||||
|
|
||||||
const [loadingAttribute, setLoadingAttribute] = useState<'models' | 'years'>();
|
|
||||||
|
|
||||||
const disabled = !partType || !make || !model || !year;
|
const disabled = !partType || !make || !model || !year;
|
||||||
const [, initialMake] = params.collection?.split('_') || [];
|
|
||||||
|
|
||||||
const handleFetchModels = useCallback(
|
const [isLoadingModels, startLoadingModels] = useTransition();
|
||||||
async (params: { makeId?: string; reset?: boolean; after?: string }) => {
|
const [isLoadingYears, startLoadingYears] = useTransition();
|
||||||
const { makeId, reset, after } = params;
|
|
||||||
setLoadingAttribute('models');
|
|
||||||
const modelsResponse = await fetchMetaobjectReferences(makeId, after);
|
|
||||||
|
|
||||||
setModels((models) => {
|
|
||||||
if (reset) {
|
|
||||||
return {
|
|
||||||
options: modelsResponse?.references || [],
|
|
||||||
pageInfo: modelsResponse?.pageInfo || null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
options: models.options.concat(modelsResponse?.references || []),
|
|
||||||
pageInfo: modelsResponse?.pageInfo || models.pageInfo
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setLoadingAttribute(undefined);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleFetchYears = useCallback(
|
|
||||||
async (params: { modelId?: string; after?: string; reset?: boolean }) => {
|
|
||||||
const { modelId, after, reset } = params;
|
|
||||||
setLoadingAttribute('years');
|
|
||||||
const yearsResponse = await fetchMetaobjectReferences(modelId, after);
|
|
||||||
|
|
||||||
setYears((years) => {
|
|
||||||
if (reset) {
|
|
||||||
return {
|
|
||||||
options: yearsResponse?.references || [],
|
|
||||||
pageInfo: yearsResponse?.pageInfo || null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
options: years.options.concat(yearsResponse?.references || []),
|
|
||||||
pageInfo: yearsResponse?.pageInfo || years.pageInfo
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setLoadingAttribute(undefined);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (partType) {
|
if (partType) {
|
||||||
@ -134,17 +89,152 @@ const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) =>
|
|||||||
}
|
}
|
||||||
}, [initialMake, makeIdFromSearchParams, makes, partType]);
|
}, [initialMake, makeIdFromSearchParams, makes, partType]);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadRestModels = useCallback(
|
||||||
if (make?.id) {
|
async (pageInfo?: PageInfo | null) => {
|
||||||
handleFetchModels({ makeId: make?.id, reset: true });
|
let _pageInfo = pageInfo;
|
||||||
}
|
const results = [] as Metaobject[];
|
||||||
}, [make?.id, handleFetchModels]);
|
while (_pageInfo?.hasNextPage) {
|
||||||
|
const modelsResponse = await fetchMetaobjectReferences(make?.id, _pageInfo?.endCursor);
|
||||||
|
results.push(...(modelsResponse?.references || []));
|
||||||
|
_pageInfo = modelsResponse?.pageInfo || null;
|
||||||
|
}
|
||||||
|
setModels((models) => {
|
||||||
|
return {
|
||||||
|
options: models.options.concat(results),
|
||||||
|
pageInfo: _pageInfo || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[make?.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadRestYears = useCallback(
|
||||||
|
async (pageInfo?: PageInfo | null) => {
|
||||||
|
let _pageInfo = pageInfo;
|
||||||
|
const results = [] as Metaobject[];
|
||||||
|
while (_pageInfo?.hasNextPage) {
|
||||||
|
const yearsResponse = await fetchMetaobjectReferences(model?.id, _pageInfo?.endCursor);
|
||||||
|
results.push(...(yearsResponse?.references || []));
|
||||||
|
_pageInfo = yearsResponse?.pageInfo || null;
|
||||||
|
}
|
||||||
|
setYears((years) => {
|
||||||
|
return {
|
||||||
|
options: years.options.concat(results),
|
||||||
|
pageInfo: _pageInfo || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[model?.id]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (model?.id) {
|
const handleFetchModels = async (params: {
|
||||||
handleFetchYears({ modelId: model?.id, reset: true });
|
makeId?: string;
|
||||||
|
reset?: boolean;
|
||||||
|
after?: string;
|
||||||
|
}) => {
|
||||||
|
const { makeId, reset, after } = params;
|
||||||
|
const modelsResponse = await fetchMetaobjectReferences(makeId, after);
|
||||||
|
|
||||||
|
setModels((models) => {
|
||||||
|
if (reset) {
|
||||||
|
return {
|
||||||
|
options: modelsResponse?.references || [],
|
||||||
|
pageInfo: modelsResponse?.pageInfo || null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
options: models.options.concat(modelsResponse?.references || []),
|
||||||
|
pageInfo: modelsResponse?.pageInfo || models.pageInfo
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (modelsResponse?.pageInfo?.hasNextPage) {
|
||||||
|
loadRestModels(modelsResponse?.pageInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (make?.id) {
|
||||||
|
startLoadingModels(async () => {
|
||||||
|
await handleFetchModels({ makeId: make?.id, reset: true });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [handleFetchYears, model?.id]);
|
}, [make?.id, loadRestModels]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFetchYears = async (params: {
|
||||||
|
modelId?: string;
|
||||||
|
after?: string;
|
||||||
|
reset?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { modelId, after, reset } = params;
|
||||||
|
const yearsResponse = await fetchMetaobjectReferences(modelId, after);
|
||||||
|
|
||||||
|
setYears((years) => {
|
||||||
|
if (reset) {
|
||||||
|
return {
|
||||||
|
options: yearsResponse?.references || [],
|
||||||
|
pageInfo: yearsResponse?.pageInfo || null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
options: years.options.concat(yearsResponse?.references || []),
|
||||||
|
pageInfo: yearsResponse?.pageInfo || years.pageInfo
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (yearsResponse?.pageInfo?.hasNextPage) {
|
||||||
|
loadRestYears(yearsResponse?.pageInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (model?.id) {
|
||||||
|
startLoadingYears(async () => {
|
||||||
|
await handleFetchYears({ modelId: model?.id, reset: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loadRestYears, model?.id]);
|
||||||
|
|
||||||
|
// compute the initial model from the url
|
||||||
|
useEffect(() => {
|
||||||
|
if (modelIdFromSearchParams || modelFromHandle) {
|
||||||
|
const selectedModel =
|
||||||
|
models.options.find((model) =>
|
||||||
|
modelIdFromSearchParams
|
||||||
|
? model.id === modelIdFromSearchParams
|
||||||
|
: modelFromHandle === model.name!.toLowerCase()
|
||||||
|
) || null;
|
||||||
|
|
||||||
|
setModel((currentModel) =>
|
||||||
|
currentModel?.id !== selectedModel?.id ? selectedModel : currentModel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [modelFromHandle, modelIdFromSearchParams, models.options]);
|
||||||
|
|
||||||
|
// compute the initial year from the url
|
||||||
|
useEffect(() => {
|
||||||
|
if (yearIdFromSearchParams || yearFromHandle) {
|
||||||
|
const selectedYear =
|
||||||
|
years.options.find((year) =>
|
||||||
|
yearIdFromSearchParams
|
||||||
|
? year.id === yearIdFromSearchParams
|
||||||
|
: yearFromHandle === year.name!.toLowerCase()
|
||||||
|
) || null;
|
||||||
|
|
||||||
|
setYear((currentYear) => (currentYear?.id !== selectedYear?.id ? selectedYear : currentYear));
|
||||||
|
}
|
||||||
|
}, [yearFromHandle, yearIdFromSearchParams, years.options]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const selectedModel = models.options.find((model) =>
|
||||||
|
modelIdFromSearchParams
|
||||||
|
? model.id === modelIdFromSearchParams
|
||||||
|
: modelFromHandle === model.name!.toLowerCase()
|
||||||
|
);
|
||||||
|
setModel(selectedModel || null);
|
||||||
|
}, [modelFromHandle, modelIdFromSearchParams, models.options]);
|
||||||
|
|
||||||
const onChangeMake = async (value: Metaobject | null) => {
|
const onChangeMake = async (value: Metaobject | null) => {
|
||||||
setMake(value);
|
setMake(value);
|
||||||
@ -176,14 +266,6 @@ const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) =>
|
|||||||
router.push(createUrl(`/search/${partType?.value}`, newSearchParams), { scroll: false });
|
router.push(createUrl(`/search/${partType?.value}`, newSearchParams), { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoadMoreModels = (reset?: boolean) => {
|
|
||||||
return handleFetchModels({ makeId: make?.id, after: models.pageInfo?.endCursor, reset });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLoadMoreYears = (reset?: boolean) => {
|
|
||||||
return handleFetchYears({ modelId: model?.id, after: years.pageInfo?.endCursor, reset });
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedyear = sortYears(years.options);
|
const sortedyear = sortYears(years.options);
|
||||||
const sortedModels = sortOptions(models.options, 'name');
|
const sortedModels = sortOptions(models.options, 'name');
|
||||||
|
|
||||||
@ -216,9 +298,7 @@ const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) =>
|
|||||||
getId={(option) => option.id}
|
getId={(option) => option.id}
|
||||||
disabled={!make}
|
disabled={!make}
|
||||||
autoFocus={autoFocusField === 'model'}
|
autoFocus={autoFocusField === 'model'}
|
||||||
isLoading={loadingAttribute === 'models'}
|
isLoading={isLoadingModels}
|
||||||
loadMore={handleLoadMoreModels}
|
|
||||||
hasNextPage={models.pageInfo?.hasNextPage}
|
|
||||||
/>
|
/>
|
||||||
<FilterField
|
<FilterField
|
||||||
label="Year"
|
label="Year"
|
||||||
@ -228,9 +308,7 @@ const FiltersList = ({ makes = [], menu, autoFocusField }: FiltersListProps) =>
|
|||||||
getId={(option) => option.id}
|
getId={(option) => option.id}
|
||||||
disabled={!model || !make}
|
disabled={!model || !make}
|
||||||
autoFocus={autoFocusField === 'year'}
|
autoFocus={autoFocusField === 'year'}
|
||||||
isLoading={loadingAttribute === 'years'}
|
isLoading={isLoadingYears}
|
||||||
loadMore={handleLoadMoreYears}
|
|
||||||
hasNextPage={years.pageInfo?.hasNextPage}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={onSearch}
|
onClick={onSearch}
|
||||||
|
@ -37,7 +37,6 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.51.5",
|
"react-hook-form": "^7.51.5",
|
||||||
"react-intersection-observer": "^9.10.3",
|
|
||||||
"react-tooltip": "^5.26.3",
|
"react-tooltip": "^5.26.3",
|
||||||
"tailwind-merge": "^2.2.2",
|
"tailwind-merge": "^2.2.2",
|
||||||
"tailwind-variants": "^0.2.1",
|
"tailwind-variants": "^0.2.1",
|
||||||
|
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -53,9 +53,6 @@ importers:
|
|||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.51.5
|
specifier: ^7.51.5
|
||||||
version: 7.51.5(react@18.2.0)
|
version: 7.51.5(react@18.2.0)
|
||||||
react-intersection-observer:
|
|
||||||
specifier: ^9.10.3
|
|
||||||
version: 9.10.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
|
||||||
react-tooltip:
|
react-tooltip:
|
||||||
specifier: ^5.26.3
|
specifier: ^5.26.3
|
||||||
version: 5.26.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 5.26.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
@ -1918,15 +1915,6 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18
|
react: ^16.8.0 || ^17 || ^18
|
||||||
|
|
||||||
react-intersection-observer@9.10.3:
|
|
||||||
resolution: {integrity: sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
|
||||||
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
react-dom:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
react-is@16.13.1:
|
react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
@ -4339,12 +4327,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
|
||||||
react-intersection-observer@9.10.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
optionalDependencies:
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
|
|
||||||
react-is@16.13.1: {}
|
react-is@16.13.1: {}
|
||||||
|
|
||||||
react-tooltip@5.26.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
react-tooltip@5.26.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user