Use react-collapsible for chronicles

This commit is contained in:
DrMint 2023-05-03 03:49:14 +02:00
parent bf6bf2e8a8
commit a52cb1fe54
6 changed files with 116 additions and 65 deletions

16
package-lock.json generated
View File

@ -28,6 +28,7 @@
"patch-package": "^7.0.0",
"rc-slider": "^10.1.1",
"react": "^18.2.0",
"react-collapsible": "^2.10.0",
"react-dom": "18.2.0",
"react-hotkeys-hook": "^3.4.7",
"react-swipeable": "^7.0.0",
@ -9214,6 +9215,15 @@
"node": ">=0.10.0"
}
},
"node_modules/react-collapsible": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/react-collapsible/-/react-collapsible-2.10.0.tgz",
"integrity": "sha512-kEVsmlFfXBMTCnU5gwIv19MdmPAhbIPzz5Er37TiJSzRKS0IHrqAKQyQeHEmtoGIQMTcVI46FzE4z3NlVTx77A==",
"peerDependencies": {
"react": "~15 || ~16 || ~17 || ~18",
"react-dom": "~15 || ~16 || ~17 || ~18"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@ -17699,6 +17709,12 @@
"loose-envify": "^1.1.0"
}
},
"react-collapsible": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/react-collapsible/-/react-collapsible-2.10.0.tgz",
"integrity": "sha512-kEVsmlFfXBMTCnU5gwIv19MdmPAhbIPzz5Er37TiJSzRKS0IHrqAKQyQeHEmtoGIQMTcVI46FzE4z3NlVTx77A==",
"requires": {}
},
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",

View File

@ -41,6 +41,7 @@
"patch-package": "^7.0.0",
"rc-slider": "^10.1.1",
"react": "^18.2.0",
"react-collapsible": "^2.10.0",
"react-dom": "18.2.0",
"react-hotkeys-hook": "^3.4.7",
"react-swipeable": "^7.0.0",

View File

@ -1,15 +1,15 @@
import { useCallback } from "react";
import { useBoolean } from "usehooks-ts";
import Collapsible from "react-collapsible";
import { TranslatedChroniclePreview } from "./ChroniclePreview";
import { GetChroniclesChaptersQuery } from "graphql/generated";
import { filterHasAttributes } from "helpers/asserts";
import { prettyInlineTitle, prettySlug, sJoin } from "helpers/formatters";
import { Ico } from "components/Ico";
import { compareDate } from "helpers/date";
import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { useAtomSetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
import { Button } from "components/Inputs/Button";
/*
*
@ -24,27 +24,41 @@ interface Props {
>["data"];
currentSlug?: string;
title: string;
open?: boolean;
onTriggerClosing?: () => void;
onOpening?: () => void;
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element => {
const ChroniclesList = ({
chronicles,
currentSlug,
title,
open,
onTriggerClosing,
onOpening,
}: Props): JSX.Element => {
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const { value: isOpen, toggle: toggleOpen } = useBoolean(
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
);
return (
<div>
<div className="grid place-content-center">
<div className="grid cursor-pointer grid-cols-[1em_1fr] gap-4" onClick={toggleOpen}>
<Ico className="!text-xl" icon={isOpen ? "arrow_drop_up" : "arrow_drop_down"} />
<p className="mb-4 font-headers text-xl">{title}</p>
<Collapsible
open={open}
accordionPosition={title}
contentInnerClassName="grid gap-4 pt-4"
onTriggerClosing={onTriggerClosing}
onOpening={onOpening}
easing="ease-in-out"
transitionTime={400}
lazyRender
contentHiddenWhenClosed
trigger={
<div className="flex place-content-center place-items-center gap-4">
<h2 className="text-center text-xl">{title}</h2>
<Button icon={open ? "expand_less" : "expand_more"} active={open} size="small" />
</div>
</div>
<div
className="grid gap-4 overflow-hidden transition-height duration-500"
style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}>
}>
{filterHasAttributes(chronicles, ["attributes.contents", "attributes.translations"])
.sort((a, b) => compareDate(a.attributes.date_start, b.attributes.date_start))
.map((chronicle) => (
@ -104,7 +118,7 @@ const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element
)}
</div>
))}
</div>
</Collapsible>
</div>
);
};

View File

@ -0,0 +1,53 @@
import { useState } from "react";
import { GetChroniclesChaptersQuery } from "graphql/generated";
import { filterHasAttributes } from "helpers/asserts";
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
import { prettySlug } from "helpers/formatters";
/*
*
* COMPONENT
*/
interface Props {
chapters: NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"];
currentChronicleSlug?: string;
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ChroniclesLists = ({ chapters, currentChronicleSlug }: Props): JSX.Element => {
const [openedIndex, setOpenedIndex] = useState(
currentChronicleSlug
? chapters.findIndex((chapter) =>
chapter.attributes?.chronicles?.data.some(
(chronicle) => chronicle.attributes?.slug === currentChronicleSlug
)
)
: -1
);
return (
<div className="grid gap-16">
{filterHasAttributes(chapters, ["attributes.chronicles", "id"]).map(
(chapter, chapterIndex) => (
<TranslatedChroniclesList
currentSlug={currentChronicleSlug}
open={openedIndex === chapterIndex}
onOpening={() => setOpenedIndex(chapterIndex)}
onTriggerClosing={() => setOpenedIndex(-1)}
key={chapter.id}
chronicles={chapter.attributes.chronicles.data}
translations={filterHasAttributes(chapter.attributes.titles, [
"language.data.attributes.code",
]).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(chapter.attributes.slug) }}
/>
)
)}
</div>
);
};

View File

@ -22,6 +22,7 @@ import { Ids } from "types/ids";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { ElementsSeparator } from "helpers/component";
import { ChroniclesLists } from "components/Chronicles/ChroniclesLists";
/*
*
@ -65,6 +66,18 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
),
});
const subPanel = (
<SubPanel>
<ReturnButton
displayOnlyOn={"3ColumnsLayout"}
href="/chronicles"
title={format("chronicles")}
/>
<HorizontalLine />
<ChroniclesLists chapters={chapters} currentChronicleSlug={chronicle.slug} />
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
@ -116,35 +129,6 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
</ContentPanel>
);
const subPanel = (
<SubPanel>
<ReturnButton
displayOnlyOn={"3ColumnsLayout"}
href="/chronicles"
title={format("chronicles")}
/>
<HorizontalLine />
<div className="grid gap-16">
{filterHasAttributes(chapters, ["attributes.chronicles", "id"]).map((chapter) => (
<TranslatedChroniclesList
key={chapter.id}
chronicles={chapter.attributes.chronicles.data}
translations={filterHasAttributes(chapter.attributes.titles, [
"language.data.attributes.code",
]).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(chapter.attributes.slug) }}
currentSlug={chronicle.slug}
/>
))}
</div>
</SubPanel>
);
return (
<AppLayout
contentPanel={contentPanel}

View File

@ -4,13 +4,11 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Containers/SubPanel";
import { getReadySdk } from "graphql/sdk";
import { GetChroniclesChaptersQuery } from "graphql/generated";
import { filterHasAttributes } from "helpers/asserts";
import { prettySlug } from "helpers/formatters";
import { getOpenGraph } from "helpers/openGraph";
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
import { HorizontalLine } from "components/HorizontalLine";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { ChroniclesLists } from "components/Chronicles/ChroniclesLists";
/*
*
@ -23,6 +21,7 @@ interface Props extends AppLayoutRequired {
const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
const { format } = useFormat();
const subPanel = (
<SubPanel>
<PanelHeader
@ -30,24 +29,8 @@ const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
title={format("chronicles")}
description={format("chronicles_description")}
/>
<HorizontalLine />
<div className="grid gap-16">
{filterHasAttributes(chapters, ["attributes.chronicles", "id"]).map((chapter) => (
<TranslatedChroniclesList
key={chapter.id}
chronicles={chapter.attributes.chronicles.data}
translations={filterHasAttributes(chapter.attributes.titles, [
"language.data.attributes.code",
]).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(chapter.attributes.slug) }}
/>
))}
</div>
<ChroniclesLists chapters={chapters} />
</SubPanel>
);