Removed now unused SmartList component

This commit is contained in:
DrMint 2023-02-22 06:23:39 +01:00
parent 5677fb180f
commit 75de7c5f2a
2 changed files with 85 additions and 301 deletions

View File

@ -1,233 +0,0 @@
import { Fragment, useCallback, useEffect, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import naturalCompare from "string-natural-compare";
import { Chip } from "./Chip";
import { PageSelector } from "./Inputs/PageSelector";
import { Ico } from "./Ico";
import { cJoin } from "helpers/className";
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { Ids } from "types/ids";
import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms";
import { useFormat } from "hooks/useFormat";
interface Group<T> {
name: string;
items: T[];
totalCount: number;
}
const defaultGroupSortingFunction = <T,>(a: Group<T>, b: Group<T>) =>
naturalCompare(a.name, b.name);
const defaultGroupCountingFunction = () => 1;
const defaultFilteringFunction = () => true;
const defaultSortingFunction = () => 0;
const defaultGroupingFunction = () => [""];
interface Props<T> {
// Items
items: T[];
getItemId: (item: T) => string;
renderItem: (props: { item: T }) => JSX.Element;
renderWhenEmpty?: () => JSX.Element;
// Pagination
paginationItemPerPage?: number;
paginationSelectorTop?: boolean;
paginationSelectorBottom?: boolean;
paginationScroolTop?: boolean;
// Searching
searchingTerm?: string;
searchingBy?: (item: T) => string;
searchingCaseInsensitive?: boolean;
// Grouping
groupingFunction?: (item: T) => string[];
groupSortingFunction?: (a: Group<T>, b: Group<T>) => number;
groupCountingFunction?: (item: T) => number;
// Filtering
filteringFunction?: (item: T) => boolean;
// Sorting
sortingFunction?: (a: T, b: T) => number;
// Other
className?: string;
}
export const SmartList = <T,>({
items,
getItemId,
renderItem: RenderItem,
renderWhenEmpty: RenderWhenEmpty,
paginationItemPerPage = Infinity,
paginationSelectorTop = true,
paginationSelectorBottom = true,
paginationScroolTop = true,
searchingTerm,
searchingBy,
searchingCaseInsensitive = true,
groupingFunction = defaultGroupingFunction,
groupSortingFunction = defaultGroupSortingFunction,
groupCountingFunction = defaultGroupCountingFunction,
filteringFunction = defaultFilteringFunction,
sortingFunction = defaultSortingFunction,
className,
}: Props<T>): JSX.Element => {
const [page, setPage] = useState(1);
const { format } = useFormat();
useScrollTopOnChange(Ids.ContentPanel, [page], paginationScroolTop);
useEffect(() => setPage(1), [searchingTerm, groupingFunction, groupSortingFunction]);
const searchFilter = useCallback(() => {
if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) {
if (searchingCaseInsensitive) {
return items.filter((item) =>
searchingBy(item).toLowerCase().includes(searchingTerm.toLowerCase())
);
}
return items.filter((item) => searchingBy(item).includes(searchingTerm));
}
return items;
}, [items, searchingBy, searchingCaseInsensitive, searchingTerm]);
const filteredItems = searchFilter().filter(filteringFunction);
const sortedItem = filteredItems.sort(sortingFunction);
const groups = (() => {
const memo: Group<T>[] = [];
sortedItem.forEach((item) => {
groupingFunction(item).forEach((groupName) => {
const group = memo.find((elem) => elem.name === groupName);
if (isDefined(group)) {
group.items.push(item);
group.totalCount += groupCountingFunction(item);
} else {
memo.push({
name: groupName,
items: [item],
totalCount: groupCountingFunction(item),
});
}
});
});
return memo.sort(groupSortingFunction);
})();
const pages = (() => {
const memo: Group<T>[][] = [];
let currentPage: Group<T>[] = [];
let remainingSlots = paginationItemPerPage;
let loopSafeguard = 1000;
const newPage = () => {
memo.push(currentPage);
currentPage = [];
remainingSlots = paginationItemPerPage;
};
for (const group of groups) {
let remainingItems = group.items.length;
while (remainingItems > 0 && loopSafeguard >= 0) {
loopSafeguard--;
const currentIndex = group.items.length - remainingItems;
if (
remainingSlots <= 0 ||
(currentIndex === 0 &&
remainingItems > remainingSlots &&
remainingItems <= paginationItemPerPage)
) {
newPage();
}
const slicedGroup: Group<T> = {
name: group.name,
items: group.items.slice(currentIndex, currentIndex + remainingSlots),
totalCount: group.totalCount,
};
remainingItems -= slicedGroup.items.length;
remainingSlots -= slicedGroup.items.length;
currentPage.push(slicedGroup);
}
}
if (currentPage.length > 0) {
newPage();
}
return memo;
})();
useHotkeys("left", () => setPage((current) => current - 1), { enabled: page > 1 });
useHotkeys("right", () => setPage((current) => current + 1), {
enabled: page < pages.length,
});
return (
<>
{pages.length > 1 && paginationSelectorTop && (
<PageSelector className="mb-12" page={page} pagesCount={pages.length} onChange={setPage} />
)}
<div className="mb-8">
{(pages[page - 1]?.length ?? 0) > 0 ? (
pages[page - 1]?.map(
(group) =>
group.items.length > 0 && (
<Fragment key={group.name}>
{group.name.length > 0 && (
<h2
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
first-of-type:pt-0">
{group.name}
<Chip text={format("x_results", { x: group.totalCount })} />
</h2>
)}
<div
className={cJoin(
`grid items-start gap-8 border-b-2 border-dotted pb-12
last-of-type:border-0`,
className
)}>
{group.items.map((item) => (
<RenderItem item={item} key={getItemId(item)} />
))}
</div>
</Fragment>
)
)
) : isDefined(RenderWhenEmpty) ? (
<RenderWhenEmpty />
) : (
<DefaultRenderWhenEmpty />
)}
</div>
{pages.length > 1 && paginationSelectorBottom && (
<PageSelector className="mb-12" page={page} pagesCount={pages.length} onChange={setPage} />
)}
</>
);
};
/*
*
* PRIVATE COMPONENTS
*/
const DefaultRenderWhenEmpty = () => {
const { format } = useFormat();
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
return (
<div className="grid h-full place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
border-dark p-8 text-dark opacity-40">
{is3ColumnsLayout && <Ico icon="chevron_left" className="!text-[300%]" />}
<p className="max-w-xs text-2xl">{format("no_results_message")}</p>
{!is3ColumnsLayout && <Ico icon="chevron_right" className="!text-[300%]" />}
</div>
</div>
);
};

View File

@ -8,7 +8,6 @@ import { filterHasAttributes } from "helpers/asserts";
import { GetContentsFolderQuery } from "graphql/generated";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { prettySlug } from "helpers/formatters";
import { SmartList } from "components/SmartList";
import { Ico } from "components/Ico";
import { Button, TranslatedButton } from "components/Inputs/Button";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
@ -21,6 +20,7 @@ import { useAtomGetter, useAtomSetter } from "helpers/atoms";
import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { Chip } from "components/Chip";
/*
*
@ -100,74 +100,91 @@ const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Elemen
)}
</div>
<SmartList
items={filterHasAttributes(folder.subfolders?.data, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<TranslatedPreviewFolder
href={`/contents/folder/${item.attributes.slug}`}
translations={filterHasAttributes(item.attributes.titles, [
"language.data.attributes.code",
] as const).map((title) => ({
title: title.title,
language: title.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(item.attributes.slug) }}
/>
)}
className={cJoin(
"items-end",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-4"
)
)}
renderWhenEmpty={() => <></>}
groupingFunction={() => [format("folders")]}
/>
<SmartList
items={filterHasAttributes(folder.contents?.data, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<TranslatedPreviewCard
href={`/contents/${item.attributes.slug}`}
translations={filterHasAttributes(item.attributes.translations, [
"language.data.attributes.code",
] as const).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(item.attributes.slug) }}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="3/2"
thumbnailForceAspectRatio
topChips={
item.attributes.type?.data?.attributes
? [
item.attributes.type.data.attributes.titles?.[0]
? item.attributes.type.data.attributes.titles[0]?.title
: prettySlug(item.attributes.type.data.attributes.slug),
]
: undefined
}
bottomChips={item.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
{folder.subfolders?.data && folder.subfolders.data.length > 0 && (
<div className="mb-8">
<h2 className="flex flex-row place-items-center gap-2 pb-2 text-2xl">
{format("folders")}
<Chip text={format("x_results", { x: folder.subfolders.data.length })} />
</h2>
<div
className={cJoin(
"grid items-start gap-8 pb-12",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-4"
)
)}>
{filterHasAttributes(folder.subfolders.data, ["id", "attributes"] as const).map(
(subfolder) => (
<TranslatedPreviewFolder
key={subfolder.id}
href={`/contents/folder/${subfolder.attributes.slug}`}
translations={filterHasAttributes(subfolder.attributes.titles, [
"language.data.attributes.code",
] as const).map((title) => ({
title: title.title,
language: title.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(subfolder.attributes.slug) }}
/>
)
)}
keepInfoVisible
/>
)}
className={cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-x-3 gap-y-5"
)}
renderWhenEmpty={() => <></>}
groupingFunction={() => [format("contents")]}
/>
</div>
</div>
)}
{folder.contents?.data && folder.contents.data.length > 0 && (
<div className="mb-8">
<h2 className="flex flex-row place-items-center gap-2 pb-2 text-2xl">
{format("contents")}
<Chip text={format("x_results", { x: folder.contents.data.length })} />
</h2>
<div
className={cJoin(
"grid items-start gap-8 pb-12",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-4"
)
)}>
{filterHasAttributes(folder.contents.data, ["id", "attributes"] as const).map(
(item) => (
<TranslatedPreviewCard
key={item.id}
href={`/contents/${item.attributes.slug}`}
translations={filterHasAttributes(item.attributes.translations, [
"language.data.attributes.code",
] as const).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(item.attributes.slug) }}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="3/2"
thumbnailForceAspectRatio
topChips={
item.attributes.type?.data?.attributes
? [
item.attributes.type.data.attributes.titles?.[0]
? item.attributes.type.data.attributes.titles[0]?.title
: prettySlug(item.attributes.type.data.attributes.slug),
]
: undefined
}
bottomChips={item.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
keepInfoVisible
/>
)
)}
</div>
</div>
)}
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
<NoContentNorFolderMessage />