Fix subpanel closing on mobile+ improvements
This commit is contained in:
parent
fe52ded606
commit
df8a7f820d
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"css.lint.unknownAtRules": "ignore",
|
"css.lint.unknownAtRules": "ignore",
|
||||||
"editor.rulers": [100]
|
"editor.rulers": [100],
|
||||||
|
"typescript.preferences.importModuleSpecifier": "non-relative"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"cuid": "^2.1.8",
|
"cuid": "^2.1.8",
|
||||||
"intl-messageformat": "^10.3.0",
|
"intl-messageformat": "^10.3.0",
|
||||||
"isomorphic-dompurify": "^0.26.0",
|
"isomorphic-dompurify": "^0.26.0",
|
||||||
"jotai": "^2.0.0",
|
"jotai": "^2.0.1",
|
||||||
"markdown-to-jsx": "^7.1.9",
|
"markdown-to-jsx": "^7.1.9",
|
||||||
"marked": "^4.2.12",
|
"marked": "^4.2.12",
|
||||||
"material-symbols": "^0.4.4",
|
"material-symbols": "^0.4.4",
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
"turndown": "^7.1.1",
|
"turndown": "^7.1.1",
|
||||||
"ua-parser-js": "^1.0.33",
|
"ua-parser-js": "^1.0.33",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"zod": "^3.20.5"
|
"zod": "^3.20.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@digitak/esrun": "^3.2.19",
|
"@digitak/esrun": "^3.2.19",
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"graphql-request": "^5.1.0",
|
"graphql-request": "^5.1.0",
|
||||||
"next-sitemap": "^3.1.50",
|
"next-sitemap": "^3.1.52",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||||
"tailwindcss": "^3.2.6",
|
"tailwindcss": "^3.2.6",
|
||||||
|
@ -7496,9 +7496,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jotai": {
|
"node_modules/jotai": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.0.1.tgz",
|
||||||
"integrity": "sha512-04G0CRZQgp3xrFAezd6X14psZ2TRGekHeYMBcbDJ/BR8ZJQPS+j0YkMTxUxyG58HJnN2+adfj5sWQWoqgtp1XQ==",
|
"integrity": "sha512-b/BpBFkv3nq8HgT6YX5h5/y9VfKIn9OL1dO6gd9bWTgKt6LLe24VIMURTDwSYS888XfubuRQlbepb5IQGAtmcQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20.0"
|
"node": ">=12.20.0"
|
||||||
},
|
},
|
||||||
|
@ -8150,9 +8150,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next-sitemap": {
|
"node_modules/next-sitemap": {
|
||||||
"version": "3.1.50",
|
"version": "3.1.52",
|
||||||
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.50.tgz",
|
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.52.tgz",
|
||||||
"integrity": "sha512-BnxAbjOK1zVcYvpZ4sYfhPXcL3ajLh/AIJLR39YKrhFxrD92KkiAGuVaKhfpoQLUf+ldsGBkGpdml2N5Qdd1KA==",
|
"integrity": "sha512-tY469i4QRV1PwM9BoL+HdKYBCJ83IQl3PmUNapG/Hxp0MIYIw1hINU8E+Edf5Kr8vHXfVzPqDoul/Abu2P0vkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -10741,9 +10741,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.20.5",
|
"version": "3.20.6",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.20.6.tgz",
|
||||||
"integrity": "sha512-BTAAliwfoB9dWf2hC+TXlyWKk/YTqRGZjHQR0WLC2A2pzierWo7KuQ1ebjS4SNaFaxg/lDItzl9/QTgLjcHbgw==",
|
"integrity": "sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
@ -16385,9 +16385,9 @@
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"jotai": {
|
"jotai": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.0.1.tgz",
|
||||||
"integrity": "sha512-04G0CRZQgp3xrFAezd6X14psZ2TRGekHeYMBcbDJ/BR8ZJQPS+j0YkMTxUxyG58HJnN2+adfj5sWQWoqgtp1XQ==",
|
"integrity": "sha512-b/BpBFkv3nq8HgT6YX5h5/y9VfKIn9OL1dO6gd9bWTgKt6LLe24VIMURTDwSYS888XfubuRQlbepb5IQGAtmcQ==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"js-sdsl": {
|
"js-sdsl": {
|
||||||
|
@ -16871,9 +16871,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"next-sitemap": {
|
"next-sitemap": {
|
||||||
"version": "3.1.50",
|
"version": "3.1.52",
|
||||||
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.50.tgz",
|
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.52.tgz",
|
||||||
"integrity": "sha512-BnxAbjOK1zVcYvpZ4sYfhPXcL3ajLh/AIJLR39YKrhFxrD92KkiAGuVaKhfpoQLUf+ldsGBkGpdml2N5Qdd1KA==",
|
"integrity": "sha512-tY469i4QRV1PwM9BoL+HdKYBCJ83IQl3PmUNapG/Hxp0MIYIw1hINU8E+Edf5Kr8vHXfVzPqDoul/Abu2P0vkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@corex/deepmerge": "^4.0.29",
|
"@corex/deepmerge": "^4.0.29",
|
||||||
|
@ -18719,9 +18719,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"zod": {
|
"zod": {
|
||||||
"version": "3.20.5",
|
"version": "3.20.6",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.20.6.tgz",
|
||||||
"integrity": "sha512-BTAAliwfoB9dWf2hC+TXlyWKk/YTqRGZjHQR0WLC2A2pzierWo7KuQ1ebjS4SNaFaxg/lDItzl9/QTgLjcHbgw=="
|
"integrity": "sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"cuid": "^2.1.8",
|
"cuid": "^2.1.8",
|
||||||
"intl-messageformat": "^10.3.0",
|
"intl-messageformat": "^10.3.0",
|
||||||
"isomorphic-dompurify": "^0.26.0",
|
"isomorphic-dompurify": "^0.26.0",
|
||||||
"jotai": "^2.0.0",
|
"jotai": "^2.0.1",
|
||||||
"markdown-to-jsx": "^7.1.9",
|
"markdown-to-jsx": "^7.1.9",
|
||||||
"marked": "^4.2.12",
|
"marked": "^4.2.12",
|
||||||
"material-symbols": "^0.4.4",
|
"material-symbols": "^0.4.4",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"turndown": "^7.1.1",
|
"turndown": "^7.1.1",
|
||||||
"ua-parser-js": "^1.0.33",
|
"ua-parser-js": "^1.0.33",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"zod": "^3.20.5"
|
"zod": "^3.20.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@digitak/esrun": "^3.2.19",
|
"@digitak/esrun": "^3.2.19",
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"graphql-request": "^5.1.0",
|
"graphql-request": "^5.1.0",
|
||||||
"next-sitemap": "^3.1.50",
|
"next-sitemap": "^3.1.52",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||||
"tailwindcss": "^3.2.6",
|
"tailwindcss": "^3.2.6",
|
||||||
|
|
|
@ -77,15 +77,19 @@ export const AppLayout = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const turnSubIntoContent = isDefined(subPanel) && isUndefined(contentPanel);
|
const turnSubIntoContent = isDefined(subPanel) && isUndefined(contentPanel) && is1ColumnLayout;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...handlers}
|
{...handlers}
|
||||||
id={Ids.Body}
|
id={Ids.Body}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"fixed inset-0 m-0 grid touch-pan-y bg-light p-0 [grid-template-areas:'main_sub_content']",
|
"fixed inset-0 m-0 grid touch-pan-y bg-light p-0",
|
||||||
cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']")
|
cIf(
|
||||||
|
is1ColumnLayout,
|
||||||
|
"grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']",
|
||||||
|
"[grid-template-areas:'main_sub_content']"
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns: is1ColumnLayout
|
gridTemplateColumns: is1ColumnLayout
|
||||||
|
@ -113,6 +117,22 @@ export const AppLayout = ({
|
||||||
<meta property="og:image:type" content="image/jpeg" />
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
|
{/* Content panel */}
|
||||||
|
<div
|
||||||
|
id={Ids.ContentPanel}
|
||||||
|
className={cJoin(
|
||||||
|
"bg-light texture-paper-dots [grid-area:content]",
|
||||||
|
cIf(contentPanelScroolbar, "overflow-y-scroll")
|
||||||
|
)}>
|
||||||
|
{isDefined(contentPanel) ? (
|
||||||
|
contentPanel
|
||||||
|
) : turnSubIntoContent ? (
|
||||||
|
subPanel
|
||||||
|
) : (
|
||||||
|
<ContentPlaceholder message={format("select_option_sidebar")} icon={"chevron_left"} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Background when navbar is opened */}
|
{/* Background when navbar is opened */}
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
|
@ -120,7 +140,7 @@ export const AppLayout = ({
|
||||||
[grid-area:content]`,
|
[grid-area:content]`,
|
||||||
cIf(
|
cIf(
|
||||||
(isMainPanelOpened || isSubPanelOpened) && is1ColumnLayout,
|
(isMainPanelOpened || isSubPanelOpened) && is1ColumnLayout,
|
||||||
"z-10 backdrop-blur",
|
"backdrop-blur",
|
||||||
"pointer-events-none touch-none"
|
"pointer-events-none touch-none"
|
||||||
)
|
)
|
||||||
)}>
|
)}>
|
||||||
|
@ -140,56 +160,10 @@ export const AppLayout = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content panel */}
|
|
||||||
<div
|
|
||||||
id={Ids.ContentPanel}
|
|
||||||
className={cJoin(
|
|
||||||
"bg-light texture-paper-dots [grid-area:content]",
|
|
||||||
cIf(contentPanelScroolbar, "overflow-y-scroll")
|
|
||||||
)}>
|
|
||||||
{isDefined(contentPanel) ? (
|
|
||||||
contentPanel
|
|
||||||
) : (
|
|
||||||
<ContentPlaceholder message={format("select_option_sidebar")} icon={"chevron_left"} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sub panel */}
|
|
||||||
{isDefined(subPanel) && (
|
|
||||||
<div
|
|
||||||
id={Ids.SubPanel}
|
|
||||||
className={cJoin(
|
|
||||||
`z-20 overflow-y-scroll border-r border-dark/50 bg-light
|
|
||||||
transition-transform duration-300 scrollbar-none texture-paper-dots`,
|
|
||||||
cIf(
|
|
||||||
is1ColumnLayout,
|
|
||||||
"justify-self-end border-r-0 [grid-area:content]",
|
|
||||||
"[grid-area:sub]"
|
|
||||||
),
|
|
||||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l"),
|
|
||||||
cIf(is1ColumnLayout && !isSubPanelOpened && !turnSubIntoContent, "translate-x-[100vw]"),
|
|
||||||
cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0")
|
|
||||||
)}>
|
|
||||||
{subPanel}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Main panel */}
|
|
||||||
<div
|
|
||||||
className={cJoin(
|
|
||||||
`z-30 overflow-y-scroll border-r border-dark/50 bg-light
|
|
||||||
transition-transform duration-300 scrollbar-none texture-paper-dots`,
|
|
||||||
cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"),
|
|
||||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
|
||||||
cIf(!isMainPanelOpened && is1ColumnLayout, "-translate-x-full")
|
|
||||||
)}>
|
|
||||||
<MainPanel />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Navbar */}
|
{/* Navbar */}
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center border-t
|
`z-40 grid grid-cols-[5rem_1fr_5rem] place-items-center border-t
|
||||||
border-dotted border-black bg-light texture-paper-dots [grid-area:navbar]`,
|
border-dotted border-black bg-light texture-paper-dots [grid-area:navbar]`,
|
||||||
cIf(!is1ColumnLayout, "hidden")
|
cIf(!is1ColumnLayout, "hidden")
|
||||||
)}>
|
)}>
|
||||||
|
@ -221,6 +195,37 @@ export const AppLayout = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Sub panel */}
|
||||||
|
{isDefined(subPanel) && !turnSubIntoContent && (
|
||||||
|
<div
|
||||||
|
id={Ids.SubPanel}
|
||||||
|
className={cJoin(
|
||||||
|
`z-40 overflow-y-scroll border-r border-dark/50 bg-light
|
||||||
|
transition-transform duration-300 scrollbar-none texture-paper-dots`,
|
||||||
|
cIf(
|
||||||
|
is1ColumnLayout,
|
||||||
|
"justify-self-end border-r-0 [grid-area:content]",
|
||||||
|
"[grid-area:sub]"
|
||||||
|
),
|
||||||
|
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l"),
|
||||||
|
cIf(is1ColumnLayout && !isSubPanelOpened, "translate-x-[100vw]")
|
||||||
|
)}>
|
||||||
|
{subPanel}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Main panel */}
|
||||||
|
<div
|
||||||
|
className={cJoin(
|
||||||
|
`z-40 overflow-y-scroll border-r border-dark/50 bg-light
|
||||||
|
transition-transform duration-300 scrollbar-none texture-paper-dots`,
|
||||||
|
cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"),
|
||||||
|
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
||||||
|
cIf(!isMainPanelOpened && is1ColumnLayout, "-translate-x-full")
|
||||||
|
)}>
|
||||||
|
<MainPanel />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback } from "react";
|
import { MouseEventHandler, useCallback } from "react";
|
||||||
import { DatePickerFragment } from "graphql/generated";
|
import { DatePickerFragment } from "graphql/generated";
|
||||||
import { TranslatedProps } from "types/TranslatedProps";
|
import { TranslatedProps } from "types/TranslatedProps";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
@ -17,12 +17,21 @@ interface Props {
|
||||||
url: string;
|
url: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChroniclePreview = ({ date, url, title, active, disabled }: Props): JSX.Element => (
|
export const ChroniclePreview = ({
|
||||||
|
date,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
active,
|
||||||
|
disabled,
|
||||||
|
onClick,
|
||||||
|
}: Props): JSX.Element => (
|
||||||
<DownPressable
|
<DownPressable
|
||||||
className="flex w-full gap-4 py-4 px-5"
|
className="flex w-full gap-4 py-4 px-5"
|
||||||
href={url}
|
href={url}
|
||||||
|
onClick={onClick}
|
||||||
active={active}
|
active={active}
|
||||||
border
|
border
|
||||||
disabled={disabled}>
|
disabled={disabled}>
|
||||||
|
|
|
@ -8,6 +8,8 @@ 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 { atoms } from "contexts/atoms";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -25,6 +27,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element => {
|
const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element => {
|
||||||
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||||
const { value: isOpen, toggle: toggleOpen } = useBoolean(
|
const { value: isOpen, toggle: toggleOpen } = useBoolean(
|
||||||
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
|
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
|
||||||
);
|
);
|
||||||
|
@ -75,6 +78,7 @@ const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element
|
||||||
"/#chronicle-",
|
"/#chronicle-",
|
||||||
chronicle.attributes.slug
|
chronicle.attributes.slug
|
||||||
)}
|
)}
|
||||||
|
onClick={() => setSubPanelOpened(false)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: chronicle.attributes.translations.length > 0 && (
|
: chronicle.attributes.translations.length > 0 && (
|
||||||
|
|
|
@ -19,6 +19,12 @@ export enum ContentPanelWidthSizes {
|
||||||
Full = "full",
|
Full = "full",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contentPanelWidthSizesToClassName: Record<ContentPanelWidthSizes, string> = {
|
||||||
|
default: "max-w-2xl",
|
||||||
|
large: "max-w-4xl",
|
||||||
|
full: "w-full",
|
||||||
|
};
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const ContentPanel = ({
|
export const ContentPanel = ({
|
||||||
|
@ -31,13 +37,9 @@ export const ContentPanel = ({
|
||||||
<div className="grid h-full">
|
<div className="grid h-full">
|
||||||
<main
|
<main
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"relative justify-self-center px-4 pt-10 pb-20",
|
"relative justify-self-center",
|
||||||
cIf(isContentPanelAtLeast3xl, "px-10 pt-20 pb-32"),
|
cIf(isContentPanelAtLeast3xl, "px-10 pt-20 pb-32", "px-4 pt-10 pb-20"),
|
||||||
width === ContentPanelWidthSizes.Default
|
contentPanelWidthSizesToClassName[width],
|
||||||
? "max-w-2xl"
|
|
||||||
: width === ContentPanelWidthSizes.Large
|
|
||||||
? "max-w-4xl"
|
|
||||||
: "w-full",
|
|
||||||
className
|
className
|
||||||
)}>
|
)}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
onFocusChanged?: (isFocused: boolean) => void;
|
onFocusChanged?: (isFocused: boolean) => void;
|
||||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { MouseEventHandler, useState } from "react";
|
||||||
import { Link } from "components/Inputs/Link";
|
import { Link } from "components/Inputs/Link";
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
noBackground?: boolean;
|
noBackground?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpPressable = ({
|
export const UpPressable = ({
|
||||||
|
@ -16,12 +17,14 @@ export const UpPressable = ({
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
noBackground = false,
|
noBackground = false,
|
||||||
|
onClick,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const [isFocused, setFocused] = useState(false);
|
const [isFocused, setFocused] = useState(false);
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
onFocusChanged={setFocused}
|
onFocusChanged={setFocused}
|
||||||
|
onClick={onClick}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`drop-shadow-lg transition-all duration-300 shadow-shade`,
|
`drop-shadow-lg transition-all duration-300 shadow-shade`,
|
||||||
cIf(!noBackground, "overflow-hidden rounded-md bg-highlight"),
|
cIf(!noBackground, "overflow-hidden rounded-md bg-highlight"),
|
||||||
|
|
|
@ -55,9 +55,9 @@ export const Button = ({
|
||||||
onFocus={(event) => event.target.blur()}
|
onFocus={(event) => event.target.blur()}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`group grid cursor-pointer select-none grid-flow-col place-content-center
|
`group grid cursor-pointer select-none grid-flow-col place-content-center
|
||||||
place-items-center gap-2 rounded-full border border-dark py-3 px-4
|
place-items-center gap-2 rounded-full border border-dark
|
||||||
leading-none text-dark transition-all`,
|
leading-none text-dark transition-all`,
|
||||||
cIf(size === "small", "px-3 py-1 text-xs"),
|
cIf(size === "small", "px-3 py-1 text-xs", "py-3 px-4"),
|
||||||
cIf(active, "!border-black bg-black !text-light drop-shadow-lg shadow-black"),
|
cIf(active, "!border-black bg-black !text-light drop-shadow-lg shadow-black"),
|
||||||
cIf(
|
cIf(
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -74,16 +74,16 @@ export const Button = ({
|
||||||
{isDefined(badgeNumber) && (
|
{isDefined(badgeNumber) && (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full bg-dark
|
`absolute grid place-items-center rounded-full bg-dark
|
||||||
font-bold text-light transition-opacity group-hover:opacity-0`,
|
font-bold text-light transition-opacity group-hover:opacity-0`,
|
||||||
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
|
cIf(size === "small", "-top-2 -right-2 h-5 w-5", "-top-3 -right-2 h-8 w-8")
|
||||||
)}>
|
)}>
|
||||||
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isDefinedAndNotEmpty(icon) && (
|
{isDefinedAndNotEmpty(icon) && (
|
||||||
<Ico
|
<Ico
|
||||||
className="[font-size:150%] [line-height:0.66]"
|
className="![font-size:150%] ![line-height:0.66]"
|
||||||
icon={icon}
|
icon={icon}
|
||||||
isFilled={active}
|
isFilled={active}
|
||||||
opticalSize={size === "normal" ? 24 : 20}
|
opticalSize={size === "normal" ? 24 : 20}
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
alwaysNewTab?: boolean;
|
alwaysNewTab?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
onFocusChanged?: (isFocused: boolean) => void;
|
onFocusChanged?: (isFocused: boolean) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
linkStyled?: boolean;
|
linkStyled?: boolean;
|
||||||
|
@ -22,6 +22,7 @@ export const Link = ({
|
||||||
alwaysNewTab,
|
alwaysNewTab,
|
||||||
disabled,
|
disabled,
|
||||||
linkStyled = false,
|
linkStyled = false,
|
||||||
|
onClick,
|
||||||
onFocusChanged,
|
onFocusChanged,
|
||||||
}: Props): JSX.Element => (
|
}: Props): JSX.Element => (
|
||||||
<ConditionalWrapper
|
<ConditionalWrapper
|
||||||
|
@ -29,6 +30,7 @@ export const Link = ({
|
||||||
wrapperProps={{
|
wrapperProps={{
|
||||||
href: href ?? "",
|
href: href ?? "",
|
||||||
alwaysNewTab,
|
alwaysNewTab,
|
||||||
|
onClick,
|
||||||
onFocusChanged,
|
onFocusChanged,
|
||||||
className: cJoin(
|
className: cJoin(
|
||||||
cIf(
|
cIf(
|
||||||
|
@ -51,12 +53,14 @@ interface LinkWrapperProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
alwaysNewTab?: boolean;
|
alwaysNewTab?: boolean;
|
||||||
onFocusChanged?: (isFocused: boolean) => void;
|
onFocusChanged?: (isFocused: boolean) => void;
|
||||||
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkWrapper = ({
|
const LinkWrapper = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
onFocusChanged,
|
onFocusChanged,
|
||||||
|
onClick,
|
||||||
alwaysNewTab = false,
|
alwaysNewTab = false,
|
||||||
href,
|
href,
|
||||||
}: LinkWrapperProps & Wrapper) => (
|
}: LinkWrapperProps & Wrapper) => (
|
||||||
|
@ -65,6 +69,7 @@ const LinkWrapper = ({
|
||||||
className={className}
|
className={className}
|
||||||
target={alwaysNewTab ? "_blank" : "_self"}
|
target={alwaysNewTab ? "_blank" : "_self"}
|
||||||
replace={href.startsWith("#")}
|
replace={href.startsWith("#")}
|
||||||
|
onClick={onClick}
|
||||||
onMouseLeave={() => onFocusChanged?.(false)}
|
onMouseLeave={() => onFocusChanged?.(false)}
|
||||||
onMouseDown={() => onFocusChanged?.(true)}
|
onMouseDown={() => onFocusChanged?.(true)}
|
||||||
onMouseUp={() => onFocusChanged?.(false)}>
|
onMouseUp={() => onFocusChanged?.(false)}>
|
||||||
|
|
|
@ -58,13 +58,12 @@ export const Select = ({
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`grid cursor-pointer select-none grid-flow-col grid-cols-[1fr_auto_auto]
|
`grid cursor-pointer select-none grid-flow-col grid-cols-[1fr_auto_auto]
|
||||||
place-items-center rounded-3xl p-1 outline outline-1 -outline-offset-1
|
place-items-center rounded-3xl p-1 outline outline-1 -outline-offset-1`,
|
||||||
outline-mid`,
|
|
||||||
cIf(isOpened, "rounded-b-none bg-highlight outline-transparent"),
|
cIf(isOpened, "rounded-b-none bg-highlight outline-transparent"),
|
||||||
cIf(
|
cIf(
|
||||||
disabled,
|
disabled,
|
||||||
"cursor-not-allowed text-dark opacity-50 outline-dark/60 grayscale",
|
"cursor-not-allowed text-dark opacity-50 outline-dark/60 grayscale",
|
||||||
"transition-all hover:bg-mid hover:outline-transparent"
|
"outline-mid transition-all hover:bg-mid hover:outline-transparent"
|
||||||
)
|
)
|
||||||
)}>
|
)}>
|
||||||
<p onClick={tryToggling} className="w-full px-4 py-1">
|
<p onClick={tryToggling} className="w-full px-4 py-1">
|
||||||
|
|
|
@ -21,10 +21,14 @@ export const Switch = ({ value, onClick, className, disabled = false }: Props):
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`relative grid h-6 w-12 content-center rounded-full border-mid outline
|
`relative grid h-6 w-12 content-center rounded-full border-mid outline
|
||||||
outline-1 -outline-offset-1 outline-mid transition-colors`,
|
outline-1 -outline-offset-1 transition-colors`,
|
||||||
cIf(value, "border-none bg-mid shadow-inner-sm outline-transparent shadow-shade"),
|
cIf(value, "border-none shadow-inner-sm shadow-shade"),
|
||||||
cIf(disabled, "cursor-not-allowed opacity-50 grayscale", "cursor-pointer"),
|
cIf(disabled, "cursor-not-allowed opacity-50 grayscale", "cursor-pointer outline-mid"),
|
||||||
cIf(disabled, cIf(value, "bg-dark/40 outline-transparent", "outline-dark/60")),
|
cIf(
|
||||||
|
disabled,
|
||||||
|
cIf(value, "bg-dark/40 outline-transparent", "outline-dark/60"),
|
||||||
|
cIf(value, "bg-mid outline-transparent")
|
||||||
|
),
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Markdown from "markdown-to-jsx";
|
import Markdown from "markdown-to-jsx";
|
||||||
import React, { Fragment, useMemo } from "react";
|
import React, { Fragment, MouseEventHandler, useMemo } from "react";
|
||||||
import ReactDOMServer from "react-dom/server";
|
import ReactDOMServer from "react-dom/server";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Img } from "components/Img";
|
import { Img } from "components/Img";
|
||||||
|
@ -218,13 +218,13 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
|
||||||
interface TableOfContentsProps {
|
interface TableOfContentsProps {
|
||||||
text: string;
|
text: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
horizontalLine?: boolean;
|
onContentClicked?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TableOfContents = ({
|
export const TableOfContents = ({
|
||||||
text,
|
text,
|
||||||
title,
|
title,
|
||||||
horizontalLine = false,
|
onContentClicked,
|
||||||
}: TableOfContentsProps): JSX.Element => {
|
}: TableOfContentsProps): JSX.Element => {
|
||||||
const { format } = useFormat();
|
const { format } = useFormat();
|
||||||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
||||||
|
@ -233,17 +233,20 @@ export const TableOfContents = ({
|
||||||
<>
|
<>
|
||||||
{toc.children.length > 0 && (
|
{toc.children.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{horizontalLine && <HorizontalLine />}
|
|
||||||
<h3 className="text-xl">{format("table_of_contents")}</h3>
|
<h3 className="text-xl">{format("table_of_contents")}</h3>
|
||||||
<div className="max-w-[14.5rem] text-left">
|
<div className="max-w-[14.5rem] text-left">
|
||||||
<p
|
<p
|
||||||
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
|
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
|
||||||
text-left">
|
text-left">
|
||||||
<Link href={`#${toc.slug}`} linkStyled>
|
<Link href={`#${toc.slug}`} linkStyled onClick={onContentClicked}>
|
||||||
{<abbr title={toc.title}>{toc.title}</abbr>}
|
{<abbr title={toc.title}>{toc.title}</abbr>}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
<TocLevel tocchildren={toc.children} parentNumbering="" />
|
<TocLevel
|
||||||
|
tocchildren={toc.children}
|
||||||
|
parentNumbering=""
|
||||||
|
onContentClicked={onContentClicked}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -334,12 +337,14 @@ interface LevelProps {
|
||||||
tocchildren: TocInterface[];
|
tocchildren: TocInterface[];
|
||||||
parentNumbering: string;
|
parentNumbering: string;
|
||||||
allowIntersection?: boolean;
|
allowIntersection?: boolean;
|
||||||
|
onContentClicked?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TocLevel = ({
|
const TocLevel = ({
|
||||||
tocchildren,
|
tocchildren,
|
||||||
parentNumbering,
|
parentNumbering,
|
||||||
allowIntersection = true,
|
allowIntersection = true,
|
||||||
|
onContentClicked,
|
||||||
}: LevelProps): JSX.Element => {
|
}: LevelProps): JSX.Element => {
|
||||||
const ids = useMemo(() => tocchildren.map((child) => child.slug), [tocchildren]);
|
const ids = useMemo(() => tocchildren.map((child) => child.slug), [tocchildren]);
|
||||||
const currentIntersection = useIntersectionList(ids);
|
const currentIntersection = useIntersectionList(ids);
|
||||||
|
@ -354,7 +359,7 @@ const TocLevel = ({
|
||||||
cIf(allowIntersection && currentIntersection === childIndex, "text-dark")
|
cIf(allowIntersection && currentIntersection === childIndex, "text-dark")
|
||||||
)}>
|
)}>
|
||||||
<span className="text-dark">{`${parentNumbering}${childIndex + 1}.`}</span>{" "}
|
<span className="text-dark">{`${parentNumbering}${childIndex + 1}.`}</span>{" "}
|
||||||
<Link href={`#${child.slug}`} linkStyled>
|
<Link href={`#${child.slug}`} linkStyled onClick={onContentClicked}>
|
||||||
{<abbr title={child.title}>{child.title}</abbr>}
|
{<abbr title={child.title}>{child.title}</abbr>}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
@ -362,6 +367,7 @@ const TocLevel = ({
|
||||||
tocchildren={child.children}
|
tocchildren={child.children}
|
||||||
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
||||||
allowIntersection={allowIntersection && currentIntersection === childIndex}
|
allowIntersection={allowIntersection && currentIntersection === childIndex}
|
||||||
|
onContentClicked={onContentClicked}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -23,7 +23,7 @@ interface Props {
|
||||||
reduced?: boolean;
|
reduced?: boolean;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useCallback } from "react";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { NavOption } from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
|
@ -21,6 +22,8 @@ export const MainPanel = (): JSX.Element => {
|
||||||
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
||||||
const { format } = useFormat();
|
const { format } = useFormat();
|
||||||
const [isMainPanelReduced, setMainPanelReduced] = useAtomPair(atoms.layout.mainPanelReduced);
|
const [isMainPanelReduced, setMainPanelReduced] = useAtomPair(atoms.layout.mainPanelReduced);
|
||||||
|
const setMainPanelOpened = useAtomSetter(atoms.layout.mainPanelOpened);
|
||||||
|
const closeMainPanel = useCallback(() => setMainPanelOpened(false), [setMainPanelOpened]);
|
||||||
const setSettingsOpened = useAtomSetter(atoms.layout.settingsOpened);
|
const setSettingsOpened = useAtomSetter(atoms.layout.settingsOpened);
|
||||||
const setSearchOpened = useAtomSetter(atoms.layout.searchOpened);
|
const setSearchOpened = useAtomSetter(atoms.layout.searchOpened);
|
||||||
|
|
||||||
|
@ -53,7 +56,10 @@ export const MainPanel = (): JSX.Element => {
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<div className="grid place-items-center">
|
<div className="grid place-items-center">
|
||||||
<Link href="/" className="flex w-full cursor-pointer justify-center">
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="flex w-full cursor-pointer justify-center"
|
||||||
|
onClick={closeMainPanel}>
|
||||||
<ColoredSvg
|
<ColoredSvg
|
||||||
src="/icons/accords.svg"
|
src="/icons/accords.svg"
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
|
@ -77,6 +83,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
placement={isMainPanelReduced ? "right" : "top"}>
|
placement={isMainPanelReduced ? "right" : "top"}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
closeMainPanel();
|
||||||
setSettingsOpened(true);
|
setSettingsOpened(true);
|
||||||
sendAnalytics("Settings", "Open settings");
|
sendAnalytics("Settings", "Open settings");
|
||||||
}}
|
}}
|
||||||
|
@ -88,6 +95,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
placement={isMainPanelReduced ? "right" : "top"}>
|
placement={isMainPanelReduced ? "right" : "top"}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
closeMainPanel();
|
||||||
setSearchOpened(true);
|
setSearchOpened(true);
|
||||||
sendAnalytics("Search", "Open search");
|
sendAnalytics("Search", "Open search");
|
||||||
}}
|
}}
|
||||||
|
@ -106,6 +114,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
title={format("library")}
|
title={format("library")}
|
||||||
subtitle={format("library_short_description")}
|
subtitle={format("library_short_description")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
|
@ -114,6 +123,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
title={format("contents")}
|
title={format("contents")}
|
||||||
subtitle={format("contents_short_description")}
|
subtitle={format("contents_short_description")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
|
@ -122,6 +132,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
title={format("wiki")}
|
title={format("wiki")}
|
||||||
subtitle={format("wiki_short_description")}
|
subtitle={format("wiki_short_description")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
|
@ -130,6 +141,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
title={format("chronicles")}
|
title={format("chronicles")}
|
||||||
subtitle={format("chronicles_short_description")}
|
subtitle={format("chronicles_short_description")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
@ -139,22 +151,15 @@ export const MainPanel = (): JSX.Element => {
|
||||||
icon="newspaper"
|
icon="newspaper"
|
||||||
title={format("news")}
|
title={format("news")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*
|
|
||||||
<NavOption
|
|
||||||
url="/merch"
|
|
||||||
icon="store"
|
|
||||||
title={format("merch")}
|
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
|
||||||
/>
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
url="https://gallery.accords-library.com/posts/"
|
url="https://gallery.accords-library.com/posts/"
|
||||||
icon="perm_media"
|
icon="perm_media"
|
||||||
title={format("gallery")}
|
title={format("gallery")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
|
@ -162,6 +167,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
icon="save"
|
icon="save"
|
||||||
title={format("archives")}
|
title={format("archives")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
|
@ -169,6 +175,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
icon="info"
|
icon="info"
|
||||||
title={format("about_us")}
|
title={format("about_us")}
|
||||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||||
|
onClick={closeMainPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(!isMainPanelReduced || !is3ColumnsLayout) && <HorizontalLine />}
|
{(!isMainPanelReduced || !is3ColumnsLayout) && <HorizontalLine />}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { MaterialSymbol } from "material-symbols";
|
||||||
import { Popup } from "components/Containers/Popup";
|
import { Popup } from "components/Containers/Popup";
|
||||||
import { sendAnalytics } from "helpers/analytics";
|
import { sendAnalytics } from "helpers/analytics";
|
||||||
import { atoms } from "contexts/atoms";
|
import { atoms } from "contexts/atoms";
|
||||||
import { useAtomPair } from "helpers/atoms";
|
import { useAtomPair, useAtomSetter } from "helpers/atoms";
|
||||||
import { TextInput } from "components/Inputs/TextInput";
|
import { TextInput } from "components/Inputs/TextInput";
|
||||||
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||||
import { PreviewCard, TranslatedPreviewCard } from "components/PreviewCard";
|
import { PreviewCard, TranslatedPreviewCard } from "components/PreviewCard";
|
||||||
|
@ -197,6 +197,7 @@ export const SearchPopup = (): JSX.Element => {
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="w-56"
|
className="w-56"
|
||||||
href={`/library/${item.slug}`}
|
href={`/library/${item.slug}`}
|
||||||
|
onClick={() => setSearchOpened(false)}
|
||||||
translations={filterHasAttributes(item._formatted.descriptions, [
|
translations={filterHasAttributes(item._formatted.descriptions, [
|
||||||
"language.data.attributes.code",
|
"language.data.attributes.code",
|
||||||
] as const).map((translation) => ({
|
] as const).map((translation) => ({
|
||||||
|
@ -243,6 +244,7 @@ export const SearchPopup = (): JSX.Element => {
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="w-56"
|
className="w-56"
|
||||||
href={`/contents/${item.slug}`}
|
href={`/contents/${item.slug}`}
|
||||||
|
onClick={() => setSearchOpened(false)}
|
||||||
translations={filterHasAttributes(item._formatted.translations, [
|
translations={filterHasAttributes(item._formatted.translations, [
|
||||||
"language.data.attributes.code",
|
"language.data.attributes.code",
|
||||||
] as const).map(({ displayable_description, language, ...otherAttributes }) => ({
|
] as const).map(({ displayable_description, language, ...otherAttributes }) => ({
|
||||||
|
@ -287,6 +289,7 @@ export const SearchPopup = (): JSX.Element => {
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="w-56"
|
className="w-56"
|
||||||
href={`/wiki/${item.slug}`}
|
href={`/wiki/${item.slug}`}
|
||||||
|
onClick={() => setSearchOpened(false)}
|
||||||
translations={filterHasAttributes(item._formatted.translations, [
|
translations={filterHasAttributes(item._formatted.translations, [
|
||||||
"language.data.attributes.code",
|
"language.data.attributes.code",
|
||||||
] as const).map(
|
] as const).map(
|
||||||
|
@ -338,6 +341,7 @@ export const SearchPopup = (): JSX.Element => {
|
||||||
className="w-56"
|
className="w-56"
|
||||||
key={item.id}
|
key={item.id}
|
||||||
href={`/news/${item.slug}`}
|
href={`/news/${item.slug}`}
|
||||||
|
onClick={() => setSearchOpened(false)}
|
||||||
translations={filterHasAttributes(item._formatted.translations, [
|
translations={filterHasAttributes(item._formatted.translations, [
|
||||||
"language.data.attributes.code",
|
"language.data.attributes.code",
|
||||||
] as const).map(({ excerpt, body, language, ...otherAttributes }) => ({
|
] as const).map(({ excerpt, body, language, ...otherAttributes }) => ({
|
||||||
|
@ -380,6 +384,7 @@ export const SearchPopup = (): JSX.Element => {
|
||||||
className="w-56"
|
className="w-56"
|
||||||
key={item.uid}
|
key={item.uid}
|
||||||
href={`/archives/videos/v/${item.uid}`}
|
href={`/archives/videos/v/${item.uid}`}
|
||||||
|
onClick={() => setSearchOpened(false)}
|
||||||
title={item._formatted.title}
|
title={item._formatted.title}
|
||||||
thumbnail={getVideoThumbnailURL(item.uid)}
|
thumbnail={getVideoThumbnailURL(item.uid)}
|
||||||
thumbnailAspectRatio="16/9"
|
thumbnailAspectRatio="16/9"
|
||||||
|
@ -427,6 +432,7 @@ const SearchResultSection = ({
|
||||||
children,
|
children,
|
||||||
}: SearchResultSectionProps) => {
|
}: SearchResultSectionProps) => {
|
||||||
const { format } = useFormat();
|
const { format } = useFormat();
|
||||||
|
const setSearchOpened = useAtomSetter(atoms.layout.searchOpened);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isDefined(totalHits) && totalHits > 0 && (
|
{isDefined(totalHits) && totalHits > 0 && (
|
||||||
|
@ -434,7 +440,8 @@ const SearchResultSection = ({
|
||||||
<div className="mb-6 grid place-content-start">
|
<div className="mb-6 grid place-content-start">
|
||||||
<UpPressable
|
<UpPressable
|
||||||
className="grid grid-cols-[auto_1fr] place-items-center gap-6 px-6 py-4"
|
className="grid grid-cols-[auto_1fr] place-items-center gap-6 px-6 py-4"
|
||||||
href={href}>
|
href={href}
|
||||||
|
onClick={() => setSearchOpened(false)}>
|
||||||
<Ico icon={icon} className="!text-3xl" isFilled />
|
<Ico icon={icon} className="!text-3xl" isFilled />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers text-lg">{title}</p>
|
<p className="font-headers text-lg">{title}</p>
|
||||||
|
|
|
@ -11,9 +11,12 @@ import { ThumbnailHeader } from "./ThumbnailHeader";
|
||||||
import { ToolTip } from "./ToolTip";
|
import { ToolTip } from "./ToolTip";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { PostWithTranslations } from "types/types";
|
import { PostWithTranslations } from "types/types";
|
||||||
import { filterHasAttributes } from "helpers/asserts";
|
import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||||
import { prettySlug } from "helpers/formatters";
|
import { prettySlug } from "helpers/formatters";
|
||||||
import { useFormat } from "hooks/useFormat";
|
import { useFormat } from "hooks/useFormat";
|
||||||
|
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
|
||||||
|
import { atoms } from "contexts/atoms";
|
||||||
|
import { ElementsSeparator } from "helpers/component";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -49,6 +52,8 @@ export const PostPage = ({
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const { format, formatStatusDescription } = useFormat();
|
const { format, formatStatusDescription } = useFormat();
|
||||||
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||||
|
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||||
|
|
||||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
items: post.translations,
|
items: post.translations,
|
||||||
|
@ -65,17 +70,16 @@ export const PostPage = ({
|
||||||
const title = selectedTranslation?.title ?? prettySlug(post.slug);
|
const title = selectedTranslation?.title ?? prettySlug(post.slug);
|
||||||
const excerpt = selectedTranslation?.excerpt ?? "";
|
const excerpt = selectedTranslation?.excerpt ?? "";
|
||||||
|
|
||||||
const subPanel =
|
const subPanel = (
|
||||||
returnHref || returnTitle || displayCredits || displayToc ? (
|
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
{returnHref && returnTitle && (
|
<ElementsSeparator>
|
||||||
<ReturnButton href={returnHref} title={returnTitle} displayOnlyOn={"3ColumnsLayout"} />
|
{[
|
||||||
)}
|
returnHref && returnTitle && !is1ColumnLayout && (
|
||||||
|
<ReturnButton href={returnHref} title={returnTitle} />
|
||||||
|
),
|
||||||
|
|
||||||
{displayCredits && (
|
displayCredits && (
|
||||||
<>
|
<>
|
||||||
<HorizontalLine />
|
|
||||||
|
|
||||||
{selectedTranslation && (
|
{selectedTranslation && (
|
||||||
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
||||||
<p className="font-headers font-bold">{format("status")}:</p>
|
<p className="font-headers font-bold">{format("status")}:</p>
|
||||||
|
@ -103,11 +107,19 @@ export const PostPage = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
),
|
||||||
|
|
||||||
{displayToc && <TableOfContents text={body} title={title} horizontalLine />}
|
displayToc && (
|
||||||
|
<TableOfContents
|
||||||
|
text={body}
|
||||||
|
title={title}
|
||||||
|
onContentClicked={() => setSubPanelOpened(false)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
]}
|
||||||
|
</ElementsSeparator>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
) : undefined;
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel>
|
<ContentPanel>
|
||||||
|
@ -133,6 +145,7 @@ export const PostPage = ({
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{(isDefined(prependBody) || isDefined(body)) && <HorizontalLine />}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -148,12 +161,7 @@ export const PostPage = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{prependBody}
|
{prependBody}
|
||||||
{body && (
|
{body && <Markdawn text={body} />}
|
||||||
<>
|
|
||||||
{displayThumbnailHeader && <HorizontalLine />}
|
|
||||||
<Markdawn text={body} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{appendBody}
|
{appendBody}
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback } from "react";
|
import { MouseEventHandler, useCallback } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { Markdown } from "./Markdown/Markdown";
|
import { Markdown } from "./Markdown/Markdown";
|
||||||
import { Chip } from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
|
@ -50,6 +50,7 @@ interface Props {
|
||||||
| { __typename: "anotherHoverlayName" };
|
| { __typename: "anotherHoverlayName" };
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
@ -72,6 +73,7 @@ export const PreviewCard = ({
|
||||||
infoAppend,
|
infoAppend,
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
onClick,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const currency = useAtomGetter(atoms.settings.currency);
|
const currency = useAtomGetter(atoms.settings.currency);
|
||||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||||
|
@ -115,6 +117,7 @@ export const PreviewCard = ({
|
||||||
<UpPressable
|
<UpPressable
|
||||||
className={cJoin("grid items-end text-left", className)}
|
className={cJoin("grid items-end text-left", className)}
|
||||||
href={href}
|
href={href}
|
||||||
|
onClick={onClick}
|
||||||
noBackground
|
noBackground
|
||||||
disabled={disabled}>
|
disabled={disabled}>
|
||||||
<div className={cJoin("group", cIf(disabled, "pointer-events-none touch-none select-none"))}>
|
<div className={cJoin("group", cIf(disabled, "pointer-events-none touch-none select-none"))}>
|
||||||
|
@ -139,17 +142,15 @@ export const PreviewCard = ({
|
||||||
{hoverlay && hoverlay.__typename === "Video" && (
|
{hoverlay && hoverlay.__typename === "Video" && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 grid place-content-center bg-shade/0
|
className="absolute inset-0 grid place-content-center rounded-t-md
|
||||||
text-light transition-colors group-hover:bg-shade/50">
|
bg-shade/0 text-light transition-colors group-hover:bg-shade/50">
|
||||||
<Ico
|
<Ico
|
||||||
icon="play_circle"
|
icon="play_circle"
|
||||||
className="!text-6xl text-light opacity-0 drop-shadow-lg transition-opacity
|
className="!text-6xl text-light opacity-0 drop-shadow-lg transition-opacity
|
||||||
shadow-shade group-hover:opacity-100 dark:text-black"
|
shadow-shade group-hover:opacity-100 dark:text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="absolute right-2 bottom-2 rounded-full bg-black/60 px-2 text-light">
|
||||||
className="absolute right-2 bottom-2 rounded-full bg-black/60 px-2
|
|
||||||
text-light">
|
|
||||||
{prettyDuration(hoverlay.duration)}
|
{prettyDuration(hoverlay.duration)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -166,14 +167,14 @@ export const PreviewCard = ({
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"z-20 grid gap-2 p-4 transition-opacity linearbg-obi",
|
"z-20 gap-2 p-4 transition-opacity linearbg-obi",
|
||||||
cIf(
|
cIf(
|
||||||
!keepInfoVisible && isHoverable,
|
!keepInfoVisible && isHoverable,
|
||||||
`-inset-x-0.5 bottom-2 opacity-0 shadow-shade
|
`-inset-x-0.5 bottom-2 opacity-0 shadow-shade
|
||||||
[border-radius:10%_10%_10%_10%_/_1%_1%_3%_3%]
|
[border-radius:10%_10%_10%_10%_/_1%_1%_3%_3%]
|
||||||
group-hover:opacity-100 hoverable:absolute hoverable:drop-shadow-lg
|
group-hover:opacity-100 hoverable:absolute hoverable:drop-shadow-lg
|
||||||
notHoverable:rounded-b-md notHoverable:opacity-100`,
|
notHoverable:rounded-b-md notHoverable:opacity-100`,
|
||||||
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
|
"grid [border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
|
||||||
)
|
)
|
||||||
)}>
|
)}>
|
||||||
{metadata?.position === "Top" && metadataJSX}
|
{metadata?.position === "Top" && metadataJSX}
|
||||||
|
|
|
@ -1,28 +1,19 @@
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useEffectOnce } from "usehooks-ts";
|
import { useEffectOnce } from "usehooks-ts";
|
||||||
import { atom } from "jotai";
|
|
||||||
import { UploadImageFragment } from "graphql/generated";
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
import { LightBox } from "components/LightBox";
|
import { LightBox } from "components/LightBox";
|
||||||
import { filterDefined } from "helpers/asserts";
|
import { filterDefined } from "helpers/asserts";
|
||||||
import { atomPairing, useAtomSetter } from "helpers/atoms";
|
import { useAtomSetter } from "helpers/atoms";
|
||||||
|
import { internalAtoms } from "contexts/atoms";
|
||||||
const lightBoxAtom = atomPairing(
|
|
||||||
atom<{
|
|
||||||
showLightBox: (
|
|
||||||
images: (UploadImageFragment | string | null | undefined)[],
|
|
||||||
index?: number
|
|
||||||
) => void;
|
|
||||||
}>({ showLightBox: () => null })
|
|
||||||
);
|
|
||||||
|
|
||||||
export const lightBox = lightBoxAtom[0];
|
|
||||||
|
|
||||||
export const LightBoxProvider = (): JSX.Element => {
|
export const LightBoxProvider = (): JSX.Element => {
|
||||||
|
const router = useRouter();
|
||||||
const [isLightBoxVisible, setLightBoxVisibility] = useState(false);
|
const [isLightBoxVisible, setLightBoxVisibility] = useState(false);
|
||||||
const [lightBoxImages, setLightBoxImages] = useState<(UploadImageFragment | string)[]>([]);
|
const [lightBoxImages, setLightBoxImages] = useState<(UploadImageFragment | string)[]>([]);
|
||||||
const [lightBoxIndex, setLightBoxIndex] = useState(0);
|
const [lightBoxIndex, setLightBoxIndex] = useState(0);
|
||||||
|
|
||||||
const setShowLightBox = useAtomSetter(lightBoxAtom);
|
const setShowLightBox = useAtomSetter(internalAtoms.lightBox);
|
||||||
|
|
||||||
useEffectOnce(() =>
|
useEffectOnce(() =>
|
||||||
setShowLightBox({
|
setShowLightBox({
|
||||||
|
@ -40,6 +31,8 @@ export const LightBoxProvider = (): JSX.Element => {
|
||||||
setTimeout(() => setLightBoxImages([]), 100);
|
setTimeout(() => setLightBoxImages([]), 100);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => router.events.on("routeChangeStart", closeLightBox));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LightBox
|
<LightBox
|
||||||
isVisible={isLightBoxVisible}
|
isVisible={isLightBoxVisible}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useScrollIntoView } from "hooks/useScrollIntoView";
|
|
||||||
import { useAtomSetter } from "helpers/atoms";
|
|
||||||
import { atoms } from "contexts/atoms";
|
|
||||||
|
|
||||||
export const useAppLayout = (): void => {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const setSearchOpened = useAtomSetter(atoms.layout.searchOpened);
|
|
||||||
const setSettingsOpened = useAtomSetter(atoms.layout.settingsOpened);
|
|
||||||
const setMainPanelOpened = useAtomSetter(atoms.layout.mainPanelOpened);
|
|
||||||
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
router.events.on("routeChangeStart", () => {
|
|
||||||
console.log("[Router Events] on routeChangeStart");
|
|
||||||
setSearchOpened(false);
|
|
||||||
setSettingsOpened(false);
|
|
||||||
setMainPanelOpened(false);
|
|
||||||
setSubPanelOpened(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.events.on("hashChangeStart", () => {
|
|
||||||
console.log("[Router Events] on hashChangeStart");
|
|
||||||
setSubPanelOpened(false);
|
|
||||||
});
|
|
||||||
}, [router, setSettingsOpened, setMainPanelOpened, setSubPanelOpened, setSearchOpened]);
|
|
||||||
|
|
||||||
useScrollIntoView();
|
|
||||||
};
|
|
|
@ -1,15 +1,37 @@
|
||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { atomWithStorage } from "jotai/utils";
|
import { atomWithStorage } from "jotai/utils";
|
||||||
import { localData } from "contexts/localData";
|
|
||||||
import { containerQueries } from "contexts/containerQueries";
|
import { containerQueries } from "contexts/containerQueries";
|
||||||
import { atomPairing } from "helpers/atoms";
|
import { atomPairing } from "helpers/atoms";
|
||||||
import { settings } from "contexts/settings";
|
import { settings } from "contexts/settings";
|
||||||
import { lightBox } from "contexts/LightBoxProvider";
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
|
import { Languages, Currencies, Langui } from "helpers/localData";
|
||||||
|
|
||||||
/*
|
/* [ LOCAL DATA ATOMS ] */
|
||||||
* I'm getting a weird error if I put those atoms in appLayout.ts
|
|
||||||
* So I'm putting the atoms here. Sucks, I know.
|
const languages = atomPairing(atom<Languages>([]));
|
||||||
*/
|
const currencies = atomPairing(atom<Currencies>([]));
|
||||||
|
const langui = atomPairing(atom<Langui>({}));
|
||||||
|
const fallbackLangui = atomPairing(atom<Langui>({}));
|
||||||
|
|
||||||
|
const localData = {
|
||||||
|
languages: languages[0],
|
||||||
|
currencies: currencies[0],
|
||||||
|
langui: langui[0],
|
||||||
|
fallbackLangui: fallbackLangui[0],
|
||||||
|
};
|
||||||
|
|
||||||
|
/* [ LIGHTBOX ATOMS ] */
|
||||||
|
|
||||||
|
const lightBoxAtom = atomPairing(
|
||||||
|
atom<{
|
||||||
|
showLightBox: (
|
||||||
|
images: (UploadImageFragment | string | null | undefined)[],
|
||||||
|
index?: number
|
||||||
|
) => void;
|
||||||
|
}>({ showLightBox: () => null })
|
||||||
|
);
|
||||||
|
|
||||||
|
const lightBox = lightBoxAtom[0];
|
||||||
|
|
||||||
/* [ APPLAYOUT ATOMS ] */
|
/* [ APPLAYOUT ATOMS ] */
|
||||||
|
|
||||||
|
@ -49,3 +71,9 @@ export const atoms = {
|
||||||
lightBox,
|
lightBox,
|
||||||
containerQueries,
|
containerQueries,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Do not import outside of the "contexts" folder
|
||||||
|
export const internalAtoms = {
|
||||||
|
lightBox: lightBoxAtom,
|
||||||
|
localData: { languages, currencies, langui, fallbackLangui },
|
||||||
|
};
|
||||||
|
|
|
@ -1,42 +1,24 @@
|
||||||
import { atom } from "jotai";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useFetch } from "usehooks-ts";
|
import { useFetch } from "usehooks-ts";
|
||||||
import { atomPairing, useAtomSetter } from "helpers/atoms";
|
import { useAtomSetter } from "helpers/atoms";
|
||||||
import {
|
|
||||||
Languages,
|
|
||||||
Currencies,
|
|
||||||
Langui,
|
|
||||||
processLangui,
|
|
||||||
processCurrencies,
|
|
||||||
processLanguages,
|
|
||||||
} from "helpers/localData";
|
|
||||||
import {
|
import {
|
||||||
LocalDataGetWebsiteInterfacesQuery,
|
LocalDataGetWebsiteInterfacesQuery,
|
||||||
LocalDataGetCurrenciesQuery,
|
LocalDataGetCurrenciesQuery,
|
||||||
LocalDataGetLanguagesQuery,
|
LocalDataGetLanguagesQuery,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
import { LocalDataFile } from "graphql/fetchLocalData";
|
import { LocalDataFile } from "graphql/fetchLocalData";
|
||||||
|
import { internalAtoms } from "contexts/atoms";
|
||||||
const languages = atomPairing(atom<Languages>([]));
|
import { processLanguages, processCurrencies, processLangui } from "helpers/localData";
|
||||||
const currencies = atomPairing(atom<Currencies>([]));
|
|
||||||
const langui = atomPairing(atom<Langui>({}));
|
|
||||||
const fallbackLangui = atomPairing(atom<Langui>({}));
|
|
||||||
|
|
||||||
export const localData = {
|
|
||||||
languages: languages[0],
|
|
||||||
currencies: currencies[0],
|
|
||||||
langui: langui[0],
|
|
||||||
fallbackLangui: fallbackLangui[0],
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFileName = (name: LocalDataFile): string => `/local-data/${name}.json`;
|
const getFileName = (name: LocalDataFile): string => `/local-data/${name}.json`;
|
||||||
|
|
||||||
export const useLocalData = (): void => {
|
export const useLocalData = (): void => {
|
||||||
const setLanguages = useAtomSetter(languages);
|
const setLanguages = useAtomSetter(internalAtoms.localData.languages);
|
||||||
const setCurrencies = useAtomSetter(currencies);
|
const setCurrencies = useAtomSetter(internalAtoms.localData.currencies);
|
||||||
const setLangui = useAtomSetter(langui);
|
const setLangui = useAtomSetter(internalAtoms.localData.langui);
|
||||||
const setFallbackLangui = useAtomSetter(fallbackLangui);
|
const setFallbackLangui = useAtomSetter(internalAtoms.localData.fallbackLangui);
|
||||||
|
|
||||||
const { locale } = useRouter();
|
const { locale } = useRouter();
|
||||||
const { data: rawLanguages } = useFetch<LocalDataGetLanguagesQuery>(getFileName("languages"));
|
const { data: rawLanguages } = useFetch<LocalDataGetLanguagesQuery>(getFileName("languages"));
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { isDefined } from "./asserts";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
|
import { insertInBetweenArray } from "helpers/others";
|
||||||
|
import { isDefined } from "helpers/asserts";
|
||||||
|
|
||||||
export interface Wrapper {
|
export interface Wrapper {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -28,3 +30,15 @@ export const ConditionalWrapper = <T, U>({
|
||||||
) : (
|
) : (
|
||||||
<>{children}</>
|
<>{children}</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface ElementsSeparatorProps {
|
||||||
|
children: React.ReactNode[];
|
||||||
|
separator?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ElementsSeparator = ({
|
||||||
|
children,
|
||||||
|
separator = <HorizontalLine />,
|
||||||
|
}: ElementsSeparatorProps): JSX.Element => (
|
||||||
|
<>{insertInBetweenArray(children.filter(Boolean), separator)}</>
|
||||||
|
);
|
||||||
|
|
|
@ -46,3 +46,17 @@ export const cartesianProduct = <T, U>(arrayA: T[], arrayB: U[]): [T, U][] => {
|
||||||
arrayA.forEach((a) => arrayB.forEach((b) => result.push([a, b])));
|
arrayA.forEach((a) => arrayB.forEach((b) => result.push([a, b])));
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const insertInBetweenArray = <T>(elems: T[], elemToInsert: T): T[] => {
|
||||||
|
if (elems.length < 2) return elems;
|
||||||
|
const elemsCopy = [...elems];
|
||||||
|
const lastElem = elemsCopy.pop() as T;
|
||||||
|
|
||||||
|
const result: T[] = [];
|
||||||
|
for (const elem of elemsCopy) {
|
||||||
|
result.push(elem, elemToInsert);
|
||||||
|
}
|
||||||
|
result.push(lastElem);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
|
@ -16,20 +16,20 @@ import "styles/rc-slider.css";
|
||||||
import "styles/tippy.css";
|
import "styles/tippy.css";
|
||||||
|
|
||||||
import { useLocalData } from "contexts/localData";
|
import { useLocalData } from "contexts/localData";
|
||||||
import { useAppLayout } from "contexts/appLayout";
|
|
||||||
import { LightBoxProvider } from "contexts/LightBoxProvider";
|
import { LightBoxProvider } from "contexts/LightBoxProvider";
|
||||||
import { SettingsPopup } from "components/Panels/SettingsPopup";
|
import { SettingsPopup } from "components/Panels/SettingsPopup";
|
||||||
import { useSettings } from "contexts/settings";
|
import { useSettings } from "contexts/settings";
|
||||||
import { useContainerQueries } from "contexts/containerQueries";
|
import { useContainerQueries } from "contexts/containerQueries";
|
||||||
import { useWebkitFixes } from "contexts/webkitFixes";
|
import { useWebkitFixes } from "contexts/webkitFixes";
|
||||||
import { SearchPopup } from "components/Panels/SearchPopup";
|
import { SearchPopup } from "components/Panels/SearchPopup";
|
||||||
|
import { useScrollIntoView } from "hooks/useScrollIntoView";
|
||||||
|
|
||||||
const AccordsLibraryApp = (props: AppProps): JSX.Element => {
|
const AccordsLibraryApp = (props: AppProps): JSX.Element => {
|
||||||
useLocalData();
|
useLocalData();
|
||||||
useAppLayout();
|
|
||||||
useSettings();
|
useSettings();
|
||||||
useContainerQueries();
|
useContainerQueries();
|
||||||
useWebkitFixes();
|
useWebkitFixes();
|
||||||
|
useScrollIntoView();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useCallback } from "react";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Ico } from "components/Ico";
|
import { Ico } from "components/Ico";
|
||||||
|
@ -16,7 +17,7 @@ import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||||
import { getVideoFile } from "helpers/videos";
|
import { getVideoFile } from "helpers/videos";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import { atoms } from "contexts/atoms";
|
import { atoms } from "contexts/atoms";
|
||||||
import { useAtomGetter } from "helpers/atoms";
|
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
|
||||||
import { Link } from "components/Inputs/Link";
|
import { Link } from "components/Inputs/Link";
|
||||||
import { useFormat } from "hooks/useFormat";
|
import { useFormat } from "hooks/useFormat";
|
||||||
import { getFormat } from "helpers/i18n";
|
import { getFormat } from "helpers/i18n";
|
||||||
|
@ -32,6 +33,8 @@ interface Props extends AppLayoutRequired {
|
||||||
|
|
||||||
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||||
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
||||||
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||||
|
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
|
||||||
const { format } = useFormat();
|
const { format } = useFormat();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -45,9 +48,9 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<NavOption title={format("video")} url="#video" border />
|
<NavOption title={format("video")} url="#video" border onClick={closeSubPanel} />
|
||||||
<NavOption title={format("channel")} url="#channel" border />
|
<NavOption title={format("channel")} url="#channel" border onClick={closeSubPanel} />
|
||||||
<NavOption title={format("description")} url="#description" border />
|
<NavOption title={format("description")} url="#description" border onClick={closeSubPanel} />
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -69,7 +72,6 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||||
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
|
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
|
||||||
className="aspect-video w-full"
|
className="aspect-video w-full"
|
||||||
title="YouTube video player"
|
title="YouTube video player"
|
||||||
frameBorder="0"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write;
|
allow="accelerometer; autoplay; clipboard-write;
|
||||||
encrypted-media; gyroscope; picture-in-picture"
|
encrypted-media; gyroscope; picture-in-picture"
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||||
import { Ids } from "types/ids";
|
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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -87,8 +88,10 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{selectedContentTranslation && (
|
{selectedContentTranslation && (
|
||||||
<>
|
<ElementsSeparator>
|
||||||
|
{[
|
||||||
<ThumbnailHeader
|
<ThumbnailHeader
|
||||||
|
key="thumbnailHeader"
|
||||||
pre_title={selectedContentTranslation.pre_title}
|
pre_title={selectedContentTranslation.pre_title}
|
||||||
title={selectedContentTranslation.title}
|
title={selectedContentTranslation.title}
|
||||||
subtitle={selectedContentTranslation.subtitle}
|
subtitle={selectedContentTranslation.subtitle}
|
||||||
|
@ -101,15 +104,13 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
|
||||||
type={primaryContent?.type}
|
type={primaryContent?.type}
|
||||||
description={selectedContentTranslation.description}
|
description={selectedContentTranslation.description}
|
||||||
thumbnail={primaryContent?.thumbnail?.data?.attributes}
|
thumbnail={primaryContent?.thumbnail?.data?.attributes}
|
||||||
/>
|
/>,
|
||||||
|
|
||||||
{selectedContentTranslation.text_set?.text && (
|
selectedContentTranslation.text_set?.text && (
|
||||||
<>
|
|
||||||
<HorizontalLine />
|
|
||||||
<Markdawn text={selectedContentTranslation.text_set.text} />
|
<Markdawn text={selectedContentTranslation.text_set.text} />
|
||||||
</>
|
),
|
||||||
)}
|
]}
|
||||||
</>
|
</ElementsSeparator>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { Fragment, useCallback } from "react";
|
||||||
import naturalCompare from "string-natural-compare";
|
import naturalCompare from "string-natural-compare";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Chip } from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
|
||||||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||||
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
||||||
import { TranslatedReturnButton } from "components/PanelComponents/ReturnButton";
|
import { TranslatedReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
|
@ -32,9 +31,10 @@ import { TranslatedPreviewLine } from "components/PreviewLine";
|
||||||
import { cIf } from "helpers/className";
|
import { cIf } from "helpers/className";
|
||||||
import { Ids } from "types/ids";
|
import { Ids } from "types/ids";
|
||||||
import { atoms } from "contexts/atoms";
|
import { atoms } from "contexts/atoms";
|
||||||
import { useAtomGetter } from "helpers/atoms";
|
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
|
||||||
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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -47,6 +47,7 @@ interface Props extends AppLayoutRequired {
|
||||||
|
|
||||||
const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
const isContentPanelAtLeast2xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast2xl);
|
const isContentPanelAtLeast2xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast2xl);
|
||||||
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||||
|
|
||||||
const { format, formatStatusDescription } = useFormat();
|
const { format, formatStatusDescription } = useFormat();
|
||||||
|
@ -92,11 +93,11 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<TranslatedReturnButton {...returnButtonProps} displayOnlyOn="3ColumnsLayout" />
|
<ElementsSeparator>
|
||||||
|
{[
|
||||||
|
!is1ColumnLayout && <TranslatedReturnButton {...returnButtonProps} />,
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.source_language?.data?.attributes?.code !== undefined && (
|
selectedTranslation?.text_set?.source_language?.data?.attributes?.code !== undefined && (
|
||||||
<>
|
|
||||||
<HorizontalLine />
|
|
||||||
<div className="grid gap-5">
|
<div className="grid gap-5">
|
||||||
<h2 className="text-xl">
|
<h2 className="text-xl">
|
||||||
{selectedTranslation.text_set.source_language.data.attributes.code ===
|
{selectedTranslation.text_set.source_language.data.attributes.code ===
|
||||||
|
@ -188,11 +189,9 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
),
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.text && (
|
selectedTranslation?.text_set?.text && (
|
||||||
<>
|
|
||||||
<TableOfContents
|
<TableOfContents
|
||||||
text={selectedTranslation.text_set.text}
|
text={selectedTranslation.text_set.text}
|
||||||
title={prettyInlineTitle(
|
title={prettyInlineTitle(
|
||||||
|
@ -200,14 +199,11 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
selectedTranslation.title,
|
selectedTranslation.title,
|
||||||
selectedTranslation.subtitle
|
selectedTranslation.subtitle
|
||||||
)}
|
)}
|
||||||
horizontalLine
|
onContentClicked={() => setSubPanelOpened(false)}
|
||||||
/>
|
/>
|
||||||
</>
|
),
|
||||||
)}
|
|
||||||
|
|
||||||
{content.ranged_contents?.data && content.ranged_contents.data.length > 0 && (
|
content.ranged_contents?.data && content.ranged_contents.data.length > 0 && (
|
||||||
<>
|
|
||||||
<HorizontalLine />
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers text-2xl font-bold">{format("source")}</p>
|
<p className="font-headers text-2xl font-bold">{format("source")}</p>
|
||||||
<div className="mt-6 grid place-items-center gap-6">
|
<div className="mt-6 grid place-items-center gap-6">
|
||||||
|
@ -217,7 +213,9 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
] as const).map((rangedContent) => {
|
] as const).map((rangedContent) => {
|
||||||
const libraryItem = rangedContent.attributes.library_item.data;
|
const libraryItem = rangedContent.attributes.library_item.data;
|
||||||
return (
|
return (
|
||||||
<div key={libraryItem.attributes.slug} className={cIf(is1ColumnLayout, "w-3/4")}>
|
<div
|
||||||
|
key={libraryItem.attributes.slug}
|
||||||
|
className={cIf(is1ColumnLayout, "w-3/4")}>
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/library/${libraryItem.attributes.slug}`}
|
href={`/library/${libraryItem.attributes.slug}`}
|
||||||
title={libraryItem.attributes.title}
|
title={libraryItem.attributes.title}
|
||||||
|
@ -251,8 +249,9 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
),
|
||||||
)}
|
]}
|
||||||
|
</ElementsSeparator>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -265,7 +264,10 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid place-items-center">
|
<div className="grid place-items-center">
|
||||||
|
<ElementsSeparator>
|
||||||
|
{[
|
||||||
<ThumbnailHeader
|
<ThumbnailHeader
|
||||||
|
key="thumbnailHeader"
|
||||||
thumbnail={content.thumbnail?.data?.attributes}
|
thumbnail={content.thumbnail?.data?.attributes}
|
||||||
pre_title={selectedTranslation?.pre_title}
|
pre_title={selectedTranslation?.pre_title}
|
||||||
title={selectedTranslation?.title}
|
title={selectedTranslation?.title}
|
||||||
|
@ -278,10 +280,10 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
<LanguageSwitcher {...languageSwitcherProps} />
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>,
|
||||||
|
<>
|
||||||
{previousContent?.attributes && (
|
{previousContent?.attributes && (
|
||||||
<div className="mt-12 mb-8 w-full">
|
<div className="mb-6 w-full">
|
||||||
<h2 className="mb-4 text-center text-2xl">{format("previous_content")}</h2>
|
<h2 className="mb-4 text-center text-2xl">{format("previous_content")}</h2>
|
||||||
<TranslatedPreviewLine
|
<TranslatedPreviewLine
|
||||||
href={`/contents/${previousContent.attributes.slug}`}
|
href={`/contents/${previousContent.attributes.slug}`}
|
||||||
|
@ -316,17 +318,14 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>,
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.text && (
|
selectedTranslation?.text_set?.text && (
|
||||||
<>
|
|
||||||
<HorizontalLine />
|
|
||||||
<Markdawn text={selectedTranslation.text_set.text} />
|
<Markdawn text={selectedTranslation.text_set.text} />
|
||||||
</>
|
),
|
||||||
)}
|
|
||||||
|
|
||||||
{nextContent?.attributes && (
|
nextContent?.attributes && (
|
||||||
<>
|
<>
|
||||||
<HorizontalLine />
|
|
||||||
<h2 className="mb-4 text-center text-2xl">{format("followup_content")}</h2>
|
<h2 className="mb-4 text-center text-2xl">{format("followup_content")}</h2>
|
||||||
<TranslatedPreviewLine
|
<TranslatedPreviewLine
|
||||||
href={`/contents/${nextContent.attributes.slug}`}
|
href={`/contents/${nextContent.attributes.slug}`}
|
||||||
|
@ -358,7 +357,9 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
),
|
||||||
|
]}
|
||||||
|
</ElementsSeparator>
|
||||||
</div>
|
</div>
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,6 +29,8 @@ import { prettySlug } from "helpers/formatters";
|
||||||
import { Paginator } from "components/Containers/Paginator";
|
import { Paginator } from "components/Containers/Paginator";
|
||||||
import { useFormat } from "hooks/useFormat";
|
import { useFormat } from "hooks/useFormat";
|
||||||
import { getFormat } from "helpers/i18n";
|
import { getFormat } from "helpers/i18n";
|
||||||
|
import { useAtomSetter } from "helpers/atoms";
|
||||||
|
import { atoms } from "contexts/atoms";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -59,6 +61,7 @@ const Contents = (props: Props): JSX.Element => {
|
||||||
const hoverable = useDeviceSupportsHover();
|
const hoverable = useDeviceSupportsHover();
|
||||||
const { format } = useFormat();
|
const { format } = useFormat();
|
||||||
const router = useTypedRouter(queryParamSchema);
|
const router = useTypedRouter(queryParamSchema);
|
||||||
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||||
|
|
||||||
const sortingMethods = useMemo(
|
const sortingMethods = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
@ -136,7 +139,12 @@ const Contents = (props: Props): JSX.Element => {
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<Button href="/contents" text={format("switch_to_folder_view")} icon="folder" />
|
<Button
|
||||||
|
href="/contents"
|
||||||
|
text={format("switch_to_folder_view")}
|
||||||
|
icon="folder"
|
||||||
|
onClick={() => setSubPanelOpened(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { cJoin, cIf } from "helpers/className";
|
import { cJoin, cIf } from "helpers/className";
|
||||||
import { atoms } from "contexts/atoms";
|
import { atoms } from "contexts/atoms";
|
||||||
import { useAtomGetter } from "helpers/atoms";
|
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
|
||||||
import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
|
import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
|
||||||
import { useFormat } from "hooks/useFormat";
|
import { useFormat } from "hooks/useFormat";
|
||||||
import { getFormat } from "helpers/i18n";
|
import { getFormat } from "helpers/i18n";
|
||||||
|
@ -35,6 +35,7 @@ interface Props extends AppLayoutRequired {
|
||||||
|
|
||||||
const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => {
|
const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => {
|
||||||
const { format } = useFormat();
|
const { format } = useFormat();
|
||||||
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||||
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
|
@ -47,7 +48,12 @@ const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Elemen
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<Button href="/contents/all" text={format("switch_to_grid_view")} icon="apps" />
|
<Button
|
||||||
|
href="/contents/all"
|
||||||
|
text={format("switch_to_grid_view")}
|
||||||
|
icon="apps"
|
||||||
|
onClick={() => setSubPanelOpened(false)}
|
||||||
|
/>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ const Home = (props: PostStaticProps): JSX.Element => {
|
||||||
prependBody={
|
prependBody={
|
||||||
<div className="grid w-full place-content-center place-items-center gap-5 text-center">
|
<div className="grid w-full place-content-center place-items-center gap-5 text-center">
|
||||||
<div
|
<div
|
||||||
className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')]
|
className="aspect-square w-32 bg-black ![mask-size:contain]
|
||||||
[mask-size:contain] [mask-repeat:no-repeat] [mask-position:center]"
|
[mask:url('/icons/accords.svg')]"
|
||||||
/>
|
/>
|
||||||
<h1 className="mb-0 text-5xl">Accord’s Library</h1>
|
<h1 className="mb-0 text-5xl">Accord’s Library</h1>
|
||||||
<h2 className="-mt-5 text-xl">Discover • Analyze • Translate • Archive</h2>
|
<h2 className="-mt-5 text-xl">Discover • Analyze • Translate • Archive</h2>
|
||||||
|
|
|
@ -50,7 +50,7 @@ import { useIntersectionList } from "hooks/useIntersectionList";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Ids } from "types/ids";
|
import { Ids } from "types/ids";
|
||||||
import { atoms } from "contexts/atoms";
|
import { atoms } from "contexts/atoms";
|
||||||
import { useAtomGetter } from "helpers/atoms";
|
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
|
||||||
import { Link } from "components/Inputs/Link";
|
import { Link } from "components/Inputs/Link";
|
||||||
import { useFormat } from "hooks/useFormat";
|
import { useFormat } from "hooks/useFormat";
|
||||||
import { getFormat } from "helpers/i18n";
|
import { getFormat } from "helpers/i18n";
|
||||||
|
@ -85,6 +85,8 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
|
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
|
||||||
|
|
||||||
const { showLightBox } = useAtomGetter(atoms.lightBox);
|
const { showLightBox } = useAtomGetter(atoms.lightBox);
|
||||||
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||||
|
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
|
||||||
|
|
||||||
useScrollTopOnChange(Ids.ContentPanel, [item]);
|
useScrollTopOnChange(Ids.ContentPanel, [item]);
|
||||||
const currentIntersection = useIntersectionList(intersectionIds);
|
const currentIntersection = useIntersectionList(intersectionIds);
|
||||||
|
@ -109,6 +111,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
url={`#${intersectionIds[0]}`}
|
url={`#${intersectionIds[0]}`}
|
||||||
border
|
border
|
||||||
active={currentIntersection === 0}
|
active={currentIntersection === 0}
|
||||||
|
onClick={closeSubPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{item.gallery && item.gallery.data.length > 0 && (
|
{item.gallery && item.gallery.data.length > 0 && (
|
||||||
|
@ -117,6 +120,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
url={`#${intersectionIds[1]}`}
|
url={`#${intersectionIds[1]}`}
|
||||||
border
|
border
|
||||||
active={currentIntersection === 1}
|
active={currentIntersection === 1}
|
||||||
|
onClick={closeSubPanel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -125,6 +129,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
url={`#${intersectionIds[2]}`}
|
url={`#${intersectionIds[2]}`}
|
||||||
border
|
border
|
||||||
active={currentIntersection === 2}
|
active={currentIntersection === 2}
|
||||||
|
onClick={closeSubPanel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{item.subitems && item.subitems.data.length > 0 && (
|
{item.subitems && item.subitems.data.length > 0 && (
|
||||||
|
@ -133,6 +138,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
url={`#${intersectionIds[3]}`}
|
url={`#${intersectionIds[3]}`}
|
||||||
border
|
border
|
||||||
active={currentIntersection === 3}
|
active={currentIntersection === 3}
|
||||||
|
onClick={closeSubPanel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -142,6 +148,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
url={`#${intersectionIds[4]}`}
|
url={`#${intersectionIds[4]}`}
|
||||||
border
|
border
|
||||||
active={currentIntersection === 4}
|
active={currentIntersection === 4}
|
||||||
|
onClick={closeSubPanel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -931,7 +931,7 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
|
||||||
last-of-type:border-0`,
|
last-of-type:border-0`,
|
||||||
cIf(
|
cIf(
|
||||||
is1ColumnLayout,
|
is1ColumnLayout,
|
||||||
"grid-cols-2 gap-[4vmin]",
|
"grid-cols-3 gap-[4vmin]",
|
||||||
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
||||||
)
|
)
|
||||||
)}>
|
)}>
|
||||||
|
|
|
@ -50,11 +50,11 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
});
|
});
|
||||||
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = is3ColumnsLayout ? (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<ReturnButton href={`/wiki`} title={format("wiki")} displayOnlyOn={"3ColumnsLayout"} />
|
<ReturnButton href={`/wiki`} title={format("wiki")} displayOnlyOn={"3ColumnsLayout"} />
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
);
|
) : undefined;
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Large}>
|
<ContentPanel width={ContentPanelWidthSizes.Large}>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
max-width: calc(100vw - 10px);
|
max-width: calc(100vw - 10px);
|
||||||
}
|
}
|
||||||
.tippy-box {
|
.tippy-box {
|
||||||
@apply relative rounded-lg bg-light shadow-xl
|
@apply relative rounded-lg bg-highlight shadow-xl
|
||||||
transition-[transform,visibility,opacity] shadow-shade;
|
transition-[transform,visibility,opacity] shadow-shade;
|
||||||
}
|
}
|
||||||
.tippy-box[data-placement^="top"] > .tippy-arrow {
|
.tippy-box[data-placement^="top"] > .tippy-arrow {
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);
|
transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);
|
||||||
}
|
}
|
||||||
.tippy-arrow {
|
.tippy-arrow {
|
||||||
@apply h-4 w-4 text-light;
|
@apply h-4 w-4 text-highlight;
|
||||||
}
|
}
|
||||||
.tippy-arrow:before {
|
.tippy-arrow:before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
Loading…
Reference in New Issue