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", "patch-package": "^7.0.0",
"rc-slider": "^10.1.1", "rc-slider": "^10.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-collapsible": "^2.10.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hotkeys-hook": "^3.4.7", "react-hotkeys-hook": "^3.4.7",
"react-swipeable": "^7.0.0", "react-swipeable": "^7.0.0",
@ -9214,6 +9215,15 @@
"node": ">=0.10.0" "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": { "node_modules/react-dom": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@ -17699,6 +17709,12 @@
"loose-envify": "^1.1.0" "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": { "react-dom": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",

View File

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

View File

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

View File

@ -4,13 +4,11 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Containers/SubPanel"; import { SubPanel } from "components/Containers/SubPanel";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetChroniclesChaptersQuery } from "graphql/generated"; import { GetChroniclesChaptersQuery } from "graphql/generated";
import { filterHasAttributes } from "helpers/asserts";
import { prettySlug } from "helpers/formatters";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
import { useFormat } from "hooks/useFormat"; import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n"; 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 Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
const { format } = useFormat(); const { format } = useFormat();
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
@ -30,24 +29,8 @@ const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
title={format("chronicles")} title={format("chronicles")}
description={format("chronicles_description")} description={format("chronicles_description")}
/> />
<HorizontalLine /> <HorizontalLine />
<ChroniclesLists chapters={chapters} />
<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>
</SubPanel> </SubPanel>
); );