diff --git a/README.md b/README.md index 25269cb..8d6b9e9 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ - Support for Arbitrary React Components and Component Props! - Autogenerated multi-level table of content and anchor links for the different headers - Styling: [Tailwind CSS](https://tailwindcss.com/) - - Manually added support for scrollbar styling + - Manually added support for scrollbar styling to Tailwind CSS - Support for [Material Icons](https://fonts.google.com/icons) - Support for creating any arbitrary theming mode by swapping CSS variables - - Support for many screen sizes and resolutions + - Support for Container Queries (media queries at the element level) + - The website has a three-column layout, which turns into one-column + 2 toggleable side-menus if the screen is too narrow. - State Management: [React Context](https://reactjs.org/docs/context.html) - Persistent app state using LocalStorage - Accessibility @@ -56,7 +57,7 @@ - Performances are great, and possibility to deploy the app using a CDN - On-Demand ISR to continuously update the website when new content is added or existing content is modified/deleted - SEO - - Good defaults for the metadate and OpenGraph properties + - Good defaults for the metadata and OpenGraph properties - Each page can provide the thumbnail, title, description to be used - Automatic generation of the sitemap using [next-sitemap](https://www.npmjs.com/package/next-sitemap) - Data quality testing @@ -105,6 +106,6 @@ Run in dev mode: OR build and run in production mode ```bash -./run_accords_build.sh +npm run build ./run_accords_prod.sh ``` diff --git a/design.config.js b/design.config.js index 7451beb..6bbc07e 100644 --- a/design.config.js +++ b/design.config.js @@ -17,12 +17,6 @@ const colors = { }, }; -const breaks = { - thin: { raw: "(max-width: 25rem)" }, - mobile: { raw: "(max-width: 60rem)" }, - desktop: { raw: "(min-width: 60rem)" }, -}; - const fonts = { openDyslexic: "OpenDyslexic", vollkorn: "Vollkorn", @@ -43,9 +37,16 @@ const fontFamilies = { }, }; +const layout = { + // all values in rem + mainMenuReduced: 6, + mainMenu: 20, + subMenu: 20, +}; + module.exports = { colors, - breaks, + layout, fonts, fontFamilies, }; diff --git a/package-lock.json b/package-lock.json index 175fcbd..b3a1179 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "@graphql-codegen/typescript": "2.7.3", "@graphql-codegen/typescript-graphql-request": "^4.5.3", "@graphql-codegen/typescript-operations": "^2.5.3", - "@types/node": "18.7.8", + "@types/node": "18.7.9", "@types/nodemailer": "^6.4.5", "@types/react": "18.0.17", "@types/react-dom": "^18.0.6", @@ -46,7 +46,7 @@ "eslint-config-next": "12.2.5", "eslint-plugin-import": "^2.26.0", "graphql": "^16.6.0", - "next-sitemap": "^3.1.20", + "next-sitemap": "^3.1.21", "prettier": "^2.7.1", "prettier-plugin-tailwindcss": "^0.1.13", "tailwindcss": "^3.1.8", @@ -2411,9 +2411,9 @@ } }, "node_modules/@types/node": { - "version": "18.7.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.8.tgz", - "integrity": "sha512-/YP55EMK2341JkODUb8DM9O0x1SIz2aBvyF33Uf1c76St3VpsMXEIW0nxuKkq/5cxnbz0RD9cfwNZHEAZQD3ag==", + "version": "18.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.9.tgz", + "integrity": "sha512-0N5Y1XAdcl865nDdjbO0m3T6FdmQ4ijE89/urOHLREyTXbpMWbSafx9y7XIsgWGtwUP2iYTinLyyW3FatAxBLQ==", "dev": true }, "node_modules/@types/nodemailer": { @@ -6287,9 +6287,9 @@ } }, "node_modules/next-sitemap": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.20.tgz", - "integrity": "sha512-ugcSQpAtwc9fk7fsr5to0yrlA/RjY0kw2wHOUqcrRXnZDwFfBwGnEVvaYbI7C4ZlhvxjJTA3G15QMpvpmvPquw==", + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.21.tgz", + "integrity": "sha512-8upGCtI91FvjNBpeKDZzrzRBlY4BH6qjG7dMe89sdBzJ9B++PVLTzRPlpLHJYOEPukBROsxi2zVuAvePBtsrdw==", "dev": true, "funding": [ { @@ -10334,9 +10334,9 @@ } }, "@types/node": { - "version": "18.7.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.8.tgz", - "integrity": "sha512-/YP55EMK2341JkODUb8DM9O0x1SIz2aBvyF33Uf1c76St3VpsMXEIW0nxuKkq/5cxnbz0RD9cfwNZHEAZQD3ag==", + "version": "18.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.9.tgz", + "integrity": "sha512-0N5Y1XAdcl865nDdjbO0m3T6FdmQ4ijE89/urOHLREyTXbpMWbSafx9y7XIsgWGtwUP2iYTinLyyW3FatAxBLQ==", "dev": true }, "@types/nodemailer": { @@ -13263,9 +13263,9 @@ } }, "next-sitemap": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.20.tgz", - "integrity": "sha512-ugcSQpAtwc9fk7fsr5to0yrlA/RjY0kw2wHOUqcrRXnZDwFfBwGnEVvaYbI7C4ZlhvxjJTA3G15QMpvpmvPquw==", + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-3.1.21.tgz", + "integrity": "sha512-8upGCtI91FvjNBpeKDZzrzRBlY4BH6qjG7dMe89sdBzJ9B++PVLTzRPlpLHJYOEPukBROsxi2zVuAvePBtsrdw==", "dev": true, "requires": { "@corex/deepmerge": "^4.0.29", diff --git a/package.json b/package.json index 274315b..92ec690 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@graphql-codegen/typescript": "2.7.3", "@graphql-codegen/typescript-graphql-request": "^4.5.3", "@graphql-codegen/typescript-operations": "^2.5.3", - "@types/node": "18.7.8", + "@types/node": "18.7.9", "@types/nodemailer": "^6.4.5", "@types/react": "18.0.17", "@types/react-dom": "^18.0.6", @@ -56,7 +56,7 @@ "eslint-config-next": "12.2.5", "eslint-plugin-import": "^2.26.0", "graphql": "^16.6.0", - "next-sitemap": "^3.1.20", + "next-sitemap": "^3.1.21", "prettier": "^2.7.1", "prettier-plugin-tailwindcss": "^0.1.13", "tailwindcss": "^3.1.8", diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index ce1cbda..97cbb0a 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -4,6 +4,7 @@ import { useEffect, useLayoutEffect, useMemo, useState } from "react"; import { useSwipeable } from "react-swipeable"; import UAParser from "ua-parser-js"; import { useBoolean, useIsClient } from "usehooks-ts"; +import { layout } from "../../design.config"; import { Ico, Icon } from "./Ico"; import { ButtonGroup } from "./Inputs/ButtonGroup"; import { OrderableList } from "./Inputs/OrderableList"; @@ -12,7 +13,6 @@ import { TextInput } from "./Inputs/TextInput"; import { MainPanel } from "./Panels/MainPanel"; import { Popup } from "./Popup"; import { AnchorIds } from "hooks/useScrollTopOnChange"; -import { useMediaMobile } from "hooks/useMediaQuery"; import { filterHasAttributes, isDefined, @@ -27,6 +27,11 @@ import { useAppLayout } from "contexts/AppLayoutContext"; import { Button } from "components/Inputs/Button"; import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph"; import { getDefaultPreferredLanguages } from "helpers/locales"; +import { + useIs1ColumnLayout, + useIsScreenAtLeast, +} from "hooks/useContainerQuery"; +import { useOnResize } from "hooks/useOnResize"; /* * ╭─────────────╮ @@ -88,10 +93,18 @@ export const AppLayout = ({ setSubPanelOpen, toggleMainPanelOpen, toggleSubPanelOpen, + setScreenWidth, + setContentPanelWidth, + setSubPanelWidth, } = useAppLayout(); const router = useRouter(); - const isMobile = useMediaMobile(); + const is1ColumnLayout = useIs1ColumnLayout(); + const isScreenAtLeastXs = useIsScreenAtLeast("xs"); + + useOnResize(AnchorIds.Body, (width) => setScreenWidth(width)); + useOnResize(AnchorIds.ContentPanel, (width) => setContentPanelWidth(width)); + useOnResize(AnchorIds.SubPanel, (width) => setSubPanelWidth(width)); useEffect(() => { router.events.on("routeChangeStart", () => { @@ -178,18 +191,6 @@ export const AppLayout = ({ setPreferredLanguages, ]); - const gridCol = useMemo(() => { - if (isDefined(subPanel)) { - if (mainPanelReduced) { - return "grid-cols-[6rem_20rem_1fr]"; - } - return "grid-cols-[20rem_20rem_1fr]"; - } else if (mainPanelReduced) { - return "grid-cols-[6rem_0px_1fr]"; - } - return "grid-cols-[20rem_0px_1fr]"; - }, [mainPanelReduced, subPanel]); - const isClient = useIsClient(); const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } = useBoolean(false); @@ -212,12 +213,22 @@ export const AppLayout = ({ >
{openGraph.title} @@ -257,11 +268,11 @@ export const AppLayout = ({ {/* Background when navbar is opened */}
{subPanel} @@ -323,10 +344,14 @@ export const AppLayout = ({
@@ -334,8 +359,11 @@ export const AppLayout = ({ {/* Navbar */}
{langui.settings}
{router.locales && (
@@ -443,7 +473,12 @@ export const AppLayout = ({ )}
)} -
+

{langui.theme}

diff --git a/src/components/LightBox.tsx b/src/components/LightBox.tsx index f020f5f..22c4c15 100644 --- a/src/components/LightBox.tsx +++ b/src/components/LightBox.tsx @@ -5,6 +5,8 @@ import { Img } from "./Img"; import { Button } from "./Inputs/Button"; import { Popup } from "./Popup"; import { Icon } from "components/Ico"; +import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; +import { cIf, cJoin } from "helpers/className"; /* * ╭─────────────╮ @@ -40,6 +42,7 @@ export const LightBox = ({ const handlePrevious = useCallback(() => { if (index > 0) setIndex(index - 1); }, [index, setIndex]); + const is3ColumnsLayout = useIs3ColumnsLayout(); const handleNext = useCallback(() => { if (index < images.length - 1) setIndex(index + 1); @@ -78,11 +81,16 @@ export const LightBox = ({ >
-
+
{index > 0 && (
)} - > -
+ ); }; diff --git a/src/components/Panels/ContentPanel.tsx b/src/components/Panels/ContentPanel.tsx index d1dd58e..0fb7561 100644 --- a/src/components/Panels/ContentPanel.tsx +++ b/src/components/Panels/ContentPanel.tsx @@ -1,4 +1,5 @@ -import { cJoin } from "helpers/className"; +import { cIf, cJoin } from "helpers/className"; +import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; /* * ╭─────────────╮ @@ -23,20 +24,24 @@ export const ContentPanel = ({ width = ContentPanelWidthSizes.Default, children, className, -}: Props): JSX.Element => ( -
-
- {children} -
-
-); +}: Props): JSX.Element => { + const isContentPanelAtLeast3xl = useIsContentPanelAtLeast("3xl"); + return ( +
+
+ {children} +
+
+ ); +}; diff --git a/src/components/Panels/MainPanel.tsx b/src/components/Panels/MainPanel.tsx index 33b8f34..5917e4e 100644 --- a/src/components/Panels/MainPanel.tsx +++ b/src/components/Panels/MainPanel.tsx @@ -5,12 +5,11 @@ import { NavOption } from "components/PanelComponents/NavOption"; import { ToolTip } from "components/ToolTip"; import { useAppLayout } from "contexts/AppLayoutContext"; import { AppStaticProps } from "graphql/getAppStaticProps"; - -import { useMediaDesktop } from "hooks/useMediaQuery"; import { Icon } from "components/Ico"; import { cIf, cJoin } from "helpers/className"; import { isDefinedAndNotEmpty } from "helpers/others"; import { Link } from "components/Inputs/Link"; +import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; /* * ╭─────────────╮ @@ -24,7 +23,7 @@ interface Props { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ export const MainPanel = ({ langui }: Props): JSX.Element => { - const isDesktop = useMediaDesktop(); + const is3ColumnsLayout = useIs3ColumnsLayout(); const { mainPanelReduced = false, toggleMainPanelReduced, @@ -35,22 +34,24 @@ export const MainPanel = ({ langui }: Props): JSX.Element => {
{/* Reduce/expand main menu */} -
-
+ {is3ColumnsLayout && ( +
+
+ )}
@@ -60,19 +61,23 @@ export const MainPanel = ({ langui }: Props): JSX.Element => { `mb-4 aspect-square cursor-pointer bg-black transition-colors [mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark`, - cIf(mainPanelReduced && isDesktop, "w-12", "w-1/2") + cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2") )} >
- {(!mainPanelReduced || !isDesktop) && ( + {(!mainPanelReduced || !is3ColumnsLayout) && (

Accord’s Library

)}
{ icon={Icon.LibraryBooks} title={langui.library} subtitle={langui.library_short_description} - reduced={mainPanelReduced && isDesktop} + reduced={mainPanelReduced && is3ColumnsLayout} /> { icon={Icon.Workspaces} title={langui.contents} subtitle={langui.contents_short_description} - reduced={mainPanelReduced && isDesktop} + reduced={mainPanelReduced && is3ColumnsLayout} /> { icon={Icon.TravelExplore} title={langui.wiki} subtitle={langui.wiki_short_description} - reduced={mainPanelReduced && isDesktop} + reduced={mainPanelReduced && is3ColumnsLayout} /> { icon={Icon.WatchLater} title={langui.chronicles} subtitle={langui.chronicles_short_description} - reduced={mainPanelReduced && isDesktop} + reduced={mainPanelReduced && is3ColumnsLayout} /> @@ -146,7 +151,7 @@ export const MainPanel = ({ langui }: Props): JSX.Element => { url="/news" icon={Icon.Feed} title={langui.news} - reduced={mainPanelReduced && isDesktop} + reduced={mainPanelReduced && is3ColumnsLayout} /> {/* @@ -154,7 +159,7 @@ export const MainPanel = ({ langui }: Props): JSX.Element => { url="/merch" icon={Icon.Store} title={langui.merch} - reduced={mainPanelReduced && isDesktop} + reduced={mainPanelReduced && is3ColumnsLayout} /> */} @@ -162,29 +167,29 @@ export const MainPanel = ({ langui }: Props): JSX.Element => { url="https://gallery.accords-library.com/posts/" icon={Icon.Collections} title={langui.gallery} - reduced={mainPanelReduced && isDesktop} + reduced={mainPanelReduced && is3ColumnsLayout} /> - {mainPanelReduced && isDesktop ? "" : } + {mainPanelReduced && is3ColumnsLayout ? "" : }
{isDefinedAndNotEmpty(langui.licensing_notice) && ( diff --git a/src/components/Panels/SubPanel.tsx b/src/components/Panels/SubPanel.tsx index 4bc9823..e6df09e 100644 --- a/src/components/Panels/SubPanel.tsx +++ b/src/components/Panels/SubPanel.tsx @@ -1,3 +1,6 @@ +import { cIf, cJoin } from "helpers/className"; +import { useIsSubPanelAtLeast } from "hooks/useContainerQuery"; + /* * ╭─────────────╮ * ───────────────────────────────────────╯ COMPONENT ╰─────────────────────────────────────────── @@ -9,8 +12,16 @@ interface Props { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -export const SubPanel = ({ children }: Props): JSX.Element => ( -
- {children} -
-); +export const SubPanel = ({ children }: Props): JSX.Element => { + const isSubPanelAtLeastSm = useIsSubPanelAtLeast("2xs"); + return ( +
+ {children} +
+ ); +}; diff --git a/src/components/Popup.tsx b/src/components/Popup.tsx index 16eb4d7..8136ccc 100644 --- a/src/components/Popup.tsx +++ b/src/components/Popup.tsx @@ -56,12 +56,12 @@ export const Popup = ({
)} @@ -161,7 +161,7 @@ export const PostPage = ({ href={returnHref} title={returnTitle} langui={langui} - displayOn={ReturnButtonType.Mobile} + displayOnlyOn={"1ColumnLayout"} className="mb-10" /> )} @@ -180,8 +180,6 @@ export const PostPage = ({ ) : undefined } /> - - ) : ( <> @@ -199,7 +197,13 @@ export const PostPage = ({ )} {prependBody} - + {body && ( + <> + {displayThumbnailHeader && } + + + )} + {appendBody} ), diff --git a/src/components/PreviewCard.tsx b/src/components/PreviewCard.tsx index 9827821..06a29d6 100644 --- a/src/components/PreviewCard.tsx +++ b/src/components/PreviewCard.tsx @@ -19,7 +19,7 @@ import { prettyShortenNumber, } from "helpers/formatters"; import { ImageQuality } from "helpers/img"; -import { useMediaHoverable } from "hooks/useMediaQuery"; +import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { TranslatedProps } from "helpers/types/TranslatedProps"; @@ -81,7 +81,7 @@ export const PreviewCard = ({ infoAppend, }: Props): JSX.Element => { const { currency } = useAppLayout(); - const isHoverable = useMediaHoverable(); + const isHoverable = useDeviceSupportsHover(); const router = useRouter(); const metadataJSX = useMemo( @@ -90,7 +90,7 @@ export const PreviewCard = ({ {metadata && (metadata.releaseDate || metadata.price) && (
{metadata.releaseDate && ( -

+

)} {metadata.price && metadata.currencies && ( -

+

)} {metadata.views && ( -

+

)} {metadata.author && ( -

+

{ name: string; @@ -205,7 +206,7 @@ export const SmartList = ({

@@ -244,15 +245,22 @@ interface DefaultRenderWhenEmptyProps { langui: AppStaticProps["langui"]; } -const DefaultRenderWhenEmpty = ({ langui }: DefaultRenderWhenEmptyProps) => ( -
-
- -

{langui.no_results_message}

- +const DefaultRenderWhenEmpty = ({ langui }: DefaultRenderWhenEmptyProps) => { + const is3ColumnsLayout = useIs3ColumnsLayout(); + return ( +
+
+ {is3ColumnsLayout && ( + + )} +

{langui.no_results_message}

+ {!is3ColumnsLayout && ( + + )} +
-
-); + ); +}; diff --git a/src/components/Wiki/DefinitionCard.tsx b/src/components/Wiki/DefinitionCard.tsx index d0e6248..61261e6 100644 --- a/src/components/Wiki/DefinitionCard.tsx +++ b/src/components/Wiki/DefinitionCard.tsx @@ -5,6 +5,8 @@ import { AppStaticProps } from "graphql/getAppStaticProps"; import { getStatusDescription } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { Button } from "components/Inputs/Button"; +import { useIsContentPanelNoMoreThan } from "hooks/useContainerQuery"; +import { cIf, cJoin } from "helpers/className"; /* * ╭─────────────╮ @@ -37,6 +39,7 @@ const DefinitionCard = ({ index, categories, }: Props): JSX.Element => { + const isContentPanelNoMoreThanMd = useIsContentPanelNoMoreThan("md"); const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ items: translations, @@ -86,7 +89,12 @@ const DefinitionCard = ({

{selectedTranslation?.definition}

{source?.url && source.name && ( -
+

{langui.source}:

diff --git a/src/contexts/AppLayoutContext.tsx b/src/contexts/AppLayoutContext.tsx index f5b6c69..43d3e77 100644 --- a/src/contexts/AppLayoutContext.tsx +++ b/src/contexts/AppLayoutContext.tsx @@ -75,6 +75,21 @@ interface AppLayoutState { setLibraryItemUserStatus: React.Dispatch< React.SetStateAction >; + + screenWidth: number; + setScreenWidth: React.Dispatch< + React.SetStateAction + >; + + contentPanelWidth: number; + setContentPanelWidth: React.Dispatch< + React.SetStateAction + >; + + subPanelWidth: number; + setSubPanelWidth: React.Dispatch< + React.SetStateAction + >; } const initialState: RequiredNonNullable = { @@ -128,6 +143,15 @@ const initialState: RequiredNonNullable = { libraryItemUserStatus: {}, setLibraryItemUserStatus: () => null, + + screenWidth: 0, + setScreenWidth: () => null, + + contentPanelWidth: 0, + setContentPanelWidth: () => null, + + subPanelWidth: 0, + setSubPanelWidth: () => null, }; const AppContext = React.createContext(initialState); @@ -237,6 +261,10 @@ export const AppContextProvider = (props: Props): JSX.Element => { setDyslexic((current) => (isDefined(current) ? !current : current)); }; + const [screenWidth, setScreenWidth] = useState(0); + const [contentPanelWidth, setContentPanelWidth] = useState(0); + const [subPanelWidth, setSubPanelWidth] = useState(0); + return ( { preferredLanguages, menuGestures, libraryItemUserStatus, + screenWidth, + contentPanelWidth, + subPanelWidth, setSubPanelOpen, setConfigPanelOpen, setSearchPanelOpen, @@ -277,6 +308,9 @@ export const AppContextProvider = (props: Props): JSX.Element => { toggleMenuGestures, toggleSelectedThemeMode, toggleDyslexic, + setContentPanelWidth, + setScreenWidth, + setSubPanelWidth, }} > {props.children} diff --git a/src/graphql/operations/getVideoChannel.graphql b/src/graphql/operations/getVideoChannel.graphql index b3f9cbd..20fb705 100644 --- a/src/graphql/operations/getVideoChannel.graphql +++ b/src/graphql/operations/getVideoChannel.graphql @@ -23,9 +23,7 @@ query getVideoChannel($channel: String) { } } published_date { - year - month - day + ...datePicker } } } diff --git a/src/helpers/openGraph.ts b/src/helpers/openGraph.ts index 566507b..62b3502 100644 --- a/src/helpers/openGraph.ts +++ b/src/helpers/openGraph.ts @@ -16,7 +16,7 @@ const DEFAULT_OG_THUMBNAIL = { }; export const TITLE_PREFIX = "Accord’s Library"; -export const TITLE_SEPARATOR = " - " +export const TITLE_SEPARATOR = " - "; export interface OpenGraph { title: string; @@ -30,7 +30,9 @@ export const getOpenGraph = ( description?: string | null | undefined, thumbnail?: UploadImageFragment | null | undefined ): OpenGraph => ({ - title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""}`, + title: `${TITLE_PREFIX}${ + isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : "" + }`, description: isDefinedAndNotEmpty(description) ? description : langui.default_description ?? "", diff --git a/src/hooks/useContainerQuery.ts b/src/hooks/useContainerQuery.ts new file mode 100644 index 0000000..bed97cc --- /dev/null +++ b/src/hooks/useContainerQuery.ts @@ -0,0 +1,112 @@ +import { useAppLayout } from "contexts/AppLayoutContext"; + +type MediaQuery = { value: number; unit: "px" | "rem"; rule: "max" | "min" }; + +type Size = + | "2xl" + | "2xs" + | "3xl" + | "4xl" + | "5xl" + | "6xl" + | "7xl" + | "lg" + | "md" + | "sm" + | "xl" + | "xs"; + +const sizes: Record = { + "2xl": 42, + "3xl": 48, + "4xl": 56, + "5xl": 64, + "6xl": 72, + "7xl": 80, + lg: 32, + md: 28, + sm: 24, + xl: 36, + xs: 20, + "2xs": 16, +}; + +export const useIsScreenAtLeast = (size: Size): boolean => { + const { screenWidth } = useAppLayout(); + return useApplyContainerQuery(screenWidth, { + value: sizes[size], + unit: "rem", + rule: "min", + }); +}; + +// ts-unused-exports:disable-next-line +export const useIsScreenNoMoreThan = (size: Size): boolean => { + const { screenWidth } = useAppLayout(); + return useApplyContainerQuery(screenWidth, { + value: sizes[size], + unit: "rem", + rule: "max", + }); +}; + +export const useIsContentPanelAtLeast = (size: Size): boolean => { + const { contentPanelWidth } = useAppLayout(); + return useApplyContainerQuery(contentPanelWidth, { + value: sizes[size], + unit: "rem", + rule: "min", + }); +}; + +export const useIsContentPanelNoMoreThan = (size: Size): boolean => { + const { contentPanelWidth } = useAppLayout(); + return useApplyContainerQuery(contentPanelWidth, { + value: sizes[size], + unit: "rem", + rule: "max", + }); +}; + +export const useIsSubPanelAtLeast = (size: Size): boolean => { + const { subPanelWidth } = useAppLayout(); + return useApplyContainerQuery(subPanelWidth, { + value: sizes[size], + unit: "rem", + rule: "min", + }); +}; + +// ts-unused-exports:disable-next-line +export const useIsSubPanelNoMoreThan = (size: Size): boolean => { + const { subPanelWidth } = useAppLayout(); + return useApplyContainerQuery(subPanelWidth, { + value: sizes[size], + unit: "rem", + rule: "max", + }); +}; + +export const useIs3ColumnsLayout = (): boolean => { + const { screenWidth } = useAppLayout(); + return useApplyContainerQuery(screenWidth, { + value: sizes["5xl"], + unit: "rem", + rule: "min", + }); +}; + +export const useIs1ColumnLayout = (): boolean => { + const { screenWidth } = useAppLayout(); + return useApplyContainerQuery(screenWidth, { + value: sizes["5xl"], + unit: "rem", + rule: "max", + }); +}; + +const useApplyContainerQuery = (width: number, query: MediaQuery) => { + const { fontSize } = useAppLayout(); + const breakpoint = query.value * (query.unit === "rem" ? 16 : 1) * fontSize; + return query.rule === "min" ? width >= breakpoint : width < breakpoint; +}; diff --git a/src/hooks/useIntersectionList.ts b/src/hooks/useIntersectionList.ts index 7ca55e3..b368210 100644 --- a/src/hooks/useIntersectionList.ts +++ b/src/hooks/useIntersectionList.ts @@ -1,7 +1,8 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { throttle } from "throttle-debounce"; import { useIsClient } from "usehooks-ts"; -import { useOnScroll, AnchorIds } from "./useScrollTopOnChange"; +import { useOnScroll } from "./useOnScroll"; +import { AnchorIds } from "./useScrollTopOnChange"; import { isDefined } from "helpers/others"; export const useIntersectionList = (ids: string[]): number => { @@ -16,7 +17,7 @@ export const useIntersectionList = (ids: string[]): number => { const refreshCurrentIntersection = useCallback( (scroll: number) => { - console.log("update"); + console.log("useIntersectionList"); if (!isDefined(contentPanel)) { setCurrentIntersection(-1); diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts index 038a771..5036548 100644 --- a/src/hooks/useMediaQuery.ts +++ b/src/hooks/useMediaQuery.ts @@ -1,14 +1,7 @@ import { useMediaQuery } from "usehooks-ts"; -import { breaks } from "../../design.config"; -// ts-unused-exports:disable-next-line -export const useMediaThin = (): boolean => useMediaQuery(breaks.thin.raw); - -export const useMediaMobile = (): boolean => useMediaQuery(breaks.mobile.raw); - -export const useMediaDesktop = (): boolean => useMediaQuery(breaks.desktop.raw); - -export const useMediaHoverable = (): boolean => useMediaQuery("(hover: hover)"); +export const useDeviceSupportsHover = (): boolean => + useMediaQuery("(hover: hover)"); export const usePrefersDarkMode = (): boolean => useMediaQuery("(prefers-color-scheme: dark)"); diff --git a/src/hooks/useOnResize.ts b/src/hooks/useOnResize.ts new file mode 100644 index 0000000..fb46ef3 --- /dev/null +++ b/src/hooks/useOnResize.ts @@ -0,0 +1,29 @@ +import { useMemo, useCallback, useEffect } from "react"; +import { useIsClient } from "usehooks-ts"; +import { isDefined } from "helpers/others"; + +export const useOnResize = ( + id: string, + onResize: (width: number, height: number) => void +): void => { + const isClient = useIsClient(); + const elem = useMemo( + () => (isClient ? document.querySelector(`#${id}`) : null), + [id, isClient] + ); + + const listener = useCallback(() => { + console.log("useOnResize"); + if (elem?.clientWidth && elem.clientHeight) { + onResize(elem.clientWidth, elem.clientHeight); + } + }, [elem?.clientHeight, elem?.clientWidth, onResize]); + + useEffect(() => { + const ro = new ResizeObserver(listener); + if (isDefined(elem)) { + ro.observe(elem); + } + return () => ro.disconnect(); + }, [elem, listener]); +}; diff --git a/src/hooks/useOnScroll.ts b/src/hooks/useOnScroll.ts new file mode 100644 index 0000000..ee72066 --- /dev/null +++ b/src/hooks/useOnScroll.ts @@ -0,0 +1,23 @@ +import { useMemo, useCallback, useEffect } from "react"; +import { useIsClient } from "usehooks-ts"; +import { AnchorIds } from "./useScrollTopOnChange"; + +export const useOnScroll = ( + id: AnchorIds, + onScroll: (scroll: number) => void +): void => { + const isClient = useIsClient(); + const elem = useMemo( + () => (isClient ? document.querySelector(`#${id}`) : null), + [id, isClient] + ); + const listener = useCallback(() => { + if (elem?.scrollTop) { + onScroll(elem.scrollTop); + } + }, [elem?.scrollTop, onScroll]); + useEffect(() => { + elem?.addEventListener("scroll", listener); + return () => elem?.removeEventListener("scroll", listener); + }, [elem, listener]); +}; diff --git a/src/hooks/useScrollTopOnChange.ts b/src/hooks/useScrollTopOnChange.ts index 1ad675b..730e27c 100644 --- a/src/hooks/useScrollTopOnChange.ts +++ b/src/hooks/useScrollTopOnChange.ts @@ -1,8 +1,9 @@ -import { DependencyList, useCallback, useEffect, useMemo } from "react"; -import { useIsClient } from "usehooks-ts"; +import { DependencyList, useEffect } from "react"; export enum AnchorIds { + Body = "bodyqs65d4a98d56az48z64d", ContentPanel = "contentPanel495922447721572", + SubPanel = "subPanelz9e8rs2d3f18zer98ze", } // Scroll to top of element "id" when "deps" update. @@ -19,23 +20,3 @@ export const useScrollTopOnChange = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [id, ...deps, enabled]); }; - -export const useOnScroll = ( - id: AnchorIds, - onScroll: (scroll: number) => void -): void => { - const isClient = useIsClient(); - const elem = useMemo( - () => (isClient ? document.querySelector(`#${id}`) : null), - [id, isClient] - ); - const listener = useCallback(() => { - if (elem?.scrollTop) { - onScroll(elem.scrollTop); - } - }, [elem?.scrollTop, onScroll]); - useEffect(() => { - elem?.addEventListener("scroll", listener); - return () => elem?.removeEventListener("scrool", listener); - }, [elem, listener]); -}; diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 068d2ba..d49f52b 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,9 +1,6 @@ import { GetStaticProps } from "next"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Panels/ContentPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getOpenGraph } from "helpers/openGraph"; @@ -24,12 +21,7 @@ const FourOhFour = ({ contentPanel={

{openGraph.title}

- +
} openGraph={openGraph} diff --git a/src/pages/500.tsx b/src/pages/500.tsx index 3898699..9121fd3 100644 --- a/src/pages/500.tsx +++ b/src/pages/500.tsx @@ -1,9 +1,6 @@ import { GetStaticProps } from "next"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Panels/ContentPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getOpenGraph } from "helpers/openGraph"; @@ -24,12 +21,7 @@ const FiveHundred = ({ contentPanel={

{openGraph.title}

- +
} openGraph={openGraph} diff --git a/src/pages/about-us/contact.tsx b/src/pages/about-us/contact.tsx index d0196f5..bfb2dc1 100644 --- a/src/pages/about-us/contact.tsx +++ b/src/pages/about-us/contact.tsx @@ -9,6 +9,7 @@ import { import { cIf, cJoin } from "helpers/className"; import { randomInt } from "helpers/numbers"; import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; +import { useIs1ColumnLayout } from "hooks/useContainerQuery"; /* * ╭────────╮ @@ -17,6 +18,7 @@ import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; const AboutUs = ({ langui, ...otherProps }: PostStaticProps): JSX.Element => { const router = useRouter(); + const is1ColumnLayout = useIs1ColumnLayout(); const [formResponse, setFormResponse] = useState(""); const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">( "stale" @@ -100,7 +102,7 @@ const AboutUs = ({ langui, ...otherProps }: PostStaticProps): JSX.Element => { { ( title={langui.about_us} description={langui.about_us_description} /> + + + { title={langui.archives} description={langui.archives_description} /> + ), diff --git a/src/pages/archives/videos/c/[uid].tsx b/src/pages/archives/videos/c/[uid].tsx index 61ee08e..c5363d0 100644 --- a/src/pages/archives/videos/c/[uid].tsx +++ b/src/pages/archives/videos/c/[uid].tsx @@ -1,13 +1,10 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; -import { Fragment, useMemo } from "react"; +import { useMemo, useState } from "react"; import { useBoolean } from "usehooks-ts"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Switch } from "components/Inputs/Switch"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel, ContentPanelWidthSizes, @@ -19,10 +16,25 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { getVideoThumbnailURL } from "helpers/videos"; import { Icon } from "components/Ico"; -import { useMediaHoverable } from "hooks/useMediaQuery"; +import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { WithLabel } from "components/Inputs/WithLabel"; import { filterHasAttributes, isDefined } from "helpers/others"; import { getOpenGraph } from "helpers/openGraph"; +import { compareDate } from "helpers/date"; +import { HorizontalLine } from "components/HorizontalLine"; +import { SmartList } from "components/SmartList"; +import { cIf } from "helpers/className"; +import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; +import { TextInput } from "components/Inputs/TextInput"; + +/* + * ╭─────────────╮ + * ────────────────────────────────────────╯ CONSTANTS ╰────────────────────────────────────────── + */ + +const DEFAULT_FILTERS_STATE = { + searchName: "", +}; /* * ╭────────╮ @@ -38,7 +50,12 @@ interface Props extends AppStaticProps, AppLayoutRequired { const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true); - const hoverable = useMediaHoverable(); + const hoverable = useDeviceSupportsHover(); + const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); + + const [searchName, setSearchName] = useState( + DEFAULT_FILTERS_STATE.searchName + ); const subPanel = useMemo( () => ( @@ -47,7 +64,7 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { href="/archives/videos/" title={langui.videos} langui={langui} - displayOn={ReturnButtonType.Desktop} + displayOnlyOn={"3ColumnsLayout"} className="mb-10" /> @@ -57,6 +74,15 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { description={langui.archives_description} /> + + + + {hoverable && ( @@ -64,51 +90,58 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { )} ), - [hoverable, keepInfoVisible, langui, toggleKeepInfoVisible] + [hoverable, keepInfoVisible, langui, searchName, toggleKeepInfoVisible] ); const contentPanel = useMemo( () => ( -
-

{channel?.title}

-

{channel?.subscribers.toLocaleString()} subscribers

-
-
- {filterHasAttributes(channel?.videos?.data, [ + ( - - - - ))} -
+ ] as const)} + getItemId={(item) => item.id} + renderItem={({ item }) => ( + + )} + langui={langui} + className={cIf( + isContentPanelAtLeast4xl, + "grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8", + "grid-cols-2 gap-x-3 gap-y-5" + )} + groupingFunction={() => [channel?.title ?? ""]} + paginationItemPerPage={25} + searchingTerm={searchName} + searchingBy={(item) => item.attributes.title} + />
), [ - channel?.subscribers, channel?.title, channel?.videos?.data, + isContentPanelAtLeast4xl, keepInfoVisible, + langui, + searchName, ] ); @@ -137,6 +170,13 @@ export const getStaticProps: GetStaticProps = async (context) => { : "", }); if (!channel.videoChannels?.data[0].attributes) return { notFound: true }; + + channel.videoChannels.data[0].attributes.videos?.data + .sort((a, b) => + compareDate(a.attributes?.published_date, b.attributes?.published_date) + ) + .reverse(); + const appStaticProps = await getAppStaticProps(context); const props: Props = { ...appStaticProps, @@ -157,6 +197,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => { const sdk = getReadySdk(); const channels = await sdk.getVideoChannelsSlugs(); const paths: GetStaticPathsResult["paths"] = []; + if (channels.videoChannels?.data) filterHasAttributes(channels.videoChannels.data, [ "attributes", diff --git a/src/pages/archives/videos/index.tsx b/src/pages/archives/videos/index.tsx index cd7d8e5..6e07b27 100644 --- a/src/pages/archives/videos/index.tsx +++ b/src/pages/archives/videos/index.tsx @@ -8,10 +8,7 @@ import { Switch } from "components/Inputs/Switch"; import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel, ContentPanelWidthSizes, @@ -23,9 +20,12 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { filterHasAttributes } from "helpers/others"; import { getVideoThumbnailURL } from "helpers/videos"; -import { useMediaHoverable } from "hooks/useMediaQuery"; +import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { getOpenGraph } from "helpers/openGraph"; import { compareDate } from "helpers/date"; +import { HorizontalLine } from "components/HorizontalLine"; +import { cIf } from "helpers/className"; +import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; /* * ╭─────────────╮ @@ -46,7 +46,8 @@ interface Props extends AppStaticProps, AppLayoutRequired { } const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { - const hoverable = useMediaHoverable(); + const hoverable = useDeviceSupportsHover(); + const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true); @@ -62,7 +63,7 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { href="/archives/" title={"Archives"} langui={langui} - displayOn={ReturnButtonType.Desktop} + displayOnlyOn={"3ColumnsLayout"} className="mb-10" /> @@ -72,6 +73,8 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { description={langui.archives_description} /> + + { /> )} langui={langui} - className="desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2 - thin:grid-cols-1" + className={cIf( + isContentPanelAtLeast4xl, + "grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8", + "grid-cols-2 gap-x-3 gap-y-5" + )} paginationItemPerPage={25} searchingTerm={searchName} searchingBy={(item) => item.attributes.title} /> ), - [keepInfoVisible, langui, searchName, videos] + [isContentPanelAtLeast4xl, keepInfoVisible, langui, searchName, videos] ); return ( { - const isMobile = useMediaMobile(); + const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); const { setSubPanelOpen } = useAppLayout(); const router = useRouter(); @@ -49,8 +46,7 @@ const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => { href="/archives/videos/" title={langui.videos} langui={langui} - displayOn={ReturnButtonType.Desktop} - className="mb-10" + displayOnlyOn={"3ColumnsLayout"} /> @@ -87,7 +83,7 @@ const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => { href="/library/" title={langui.library} langui={langui} - displayOn={ReturnButtonType.Mobile} + displayOnlyOn={"1ColumnLayout"} className="mb-10" /> @@ -129,9 +125,9 @@ const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => { icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" /> - {isMobile - ? prettyShortenNumber(video.views) - : video.views.toLocaleString()} + {isContentPanelAtLeast4xl + ? video.views.toLocaleString() + : prettyShortenNumber(video.views)}

{video.channel?.data?.attributes && (

@@ -139,9 +135,9 @@ const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => { icon={Icon.ThumbUp} className="mr-1 translate-y-[.15em] !text-base" /> - {isMobile - ? prettyShortenNumber(video.likes) - : video.likes.toLocaleString()} + {isContentPanelAtLeast4xl + ? video.likes.toLocaleString() + : prettyShortenNumber(video.likes)}

)} { ), [ - isMobile, + isContentPanelAtLeast4xl, langui, router.locale, video.channel?.data?.attributes, diff --git a/src/pages/chronicles/[slug]/index.tsx b/src/pages/chronicles/[slug]/index.tsx index 472950e..d16bb13 100644 --- a/src/pages/chronicles/[slug]/index.tsx +++ b/src/pages/chronicles/[slug]/index.tsx @@ -13,10 +13,7 @@ import { ThumbnailHeader } from "components/ThumbnailHeader"; import { HorizontalLine } from "components/HorizontalLine"; import { GetChroniclesChaptersQuery } from "graphql/generated"; import { prettyInlineTitle, prettySlug } from "helpers/formatters"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { Icon } from "components/Ico"; import { getOpenGraph } from "helpers/openGraph"; import { @@ -93,7 +90,7 @@ const Chronicle = ({ () => ( - - {selectedContentTranslation.text_set?.text && ( - + <> + + + )} )} @@ -168,7 +166,7 @@ const Chronicle = ({ () => ( + + +
{filterHasAttributes(chapters, [ "attributes.chronicles", diff --git a/src/pages/contents/[slug].tsx b/src/pages/contents/[slug].tsx index 486ec8e..19baa42 100644 --- a/src/pages/contents/[slug].tsx +++ b/src/pages/contents/[slug].tsx @@ -5,10 +5,7 @@ import { Chip } from "components/Chip"; import { HorizontalLine } from "components/HorizontalLine"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; -import { - ReturnButtonType, - TranslatedReturnButton, -} from "components/PanelComponents/ReturnButton"; +import { TranslatedReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Panels/ContentPanel"; import { SubPanel } from "components/Panels/SubPanel"; import { PreviewCard } from "components/PreviewCard"; @@ -30,7 +27,6 @@ import { isDefinedAndNotEmpty, } from "helpers/others"; import { ContentWithTranslations } from "helpers/types"; -import { useMediaMobile } from "hooks/useMediaQuery"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { getOpenGraph } from "helpers/openGraph"; @@ -40,6 +36,11 @@ import { } from "helpers/locales"; import { getDescription } from "helpers/description"; import { TranslatedPreviewLine } from "components/PreviewLine"; +import { + useIs1ColumnLayout, + useIsContentPanelAtLeast, +} from "hooks/useContainerQuery"; +import { cIf } from "helpers/className"; /* * ╭────────╮ @@ -57,7 +58,8 @@ const Content = ({ currencies, ...otherProps }: Props): JSX.Element => { - const isMobile = useMediaMobile(); + const isContentPanelAtLeast2xl = useIsContentPanelAtLeast("2xl"); + const is1ColumnLayout = useIs1ColumnLayout(); const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ @@ -122,7 +124,7 @@ const Content = ({ {selectedTranslation?.text_set?.source_language?.data?.attributes @@ -282,7 +284,7 @@ const Content = ({ return (
@@ -394,9 +397,8 @@ const Content = ({ } thumbnailAspectRatio="3/2" topChips={ - isMobile - ? undefined - : previousContent.attributes.type?.data?.attributes + isContentPanelAtLeast2xl && + previousContent.attributes.type?.data?.attributes ? [ previousContent.attributes.type.data.attributes .titles?.[0] @@ -410,22 +412,25 @@ const Content = ({ : undefined } bottomChips={ - isMobile - ? undefined - : previousContent.attributes.categories?.data.map( + isContentPanelAtLeast2xl + ? previousContent.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" ) + : undefined } />
)} - - - + {selectedTranslation?.text_set?.text && ( + <> + + + + )} {nextContent?.attributes && ( <> @@ -448,9 +453,8 @@ const Content = ({ thumbnail={nextContent.attributes.thumbnail?.data?.attributes} thumbnailAspectRatio="3/2" topChips={ - isMobile - ? undefined - : nextContent.attributes.type?.data?.attributes + isContentPanelAtLeast2xl && + nextContent.attributes.type?.data?.attributes ? [ nextContent.attributes.type.data.attributes.titles?.[0] ? nextContent.attributes.type.data.attributes @@ -462,11 +466,11 @@ const Content = ({ : undefined } bottomChips={ - isMobile - ? undefined - : nextContent.attributes.categories?.data.map( + isContentPanelAtLeast2xl + ? nextContent.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" ) + : undefined } /> @@ -479,7 +483,7 @@ const Content = ({ content.categories, content.thumbnail?.data?.attributes, content.type, - isMobile, + isContentPanelAtLeast2xl, languageSwitcherProps, langui, nextContent?.attributes, diff --git a/src/pages/contents/all.tsx b/src/pages/contents/all.tsx index cd6fae0..46e9dcb 100644 --- a/src/pages/contents/all.tsx +++ b/src/pages/contents/all.tsx @@ -16,7 +16,7 @@ import { prettyInlineTitle, prettySlug } from "helpers/formatters"; import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; import { Button } from "components/Inputs/Button"; -import { useMediaHoverable } from "hooks/useMediaQuery"; +import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { Icon } from "components/Ico"; import { filterDefined, filterHasAttributes } from "helpers/others"; import { GetContentsQuery } from "graphql/generated"; @@ -25,6 +25,8 @@ import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; import { getOpenGraph } from "helpers/openGraph"; import { HorizontalLine } from "components/HorizontalLine"; import { TranslatedPreviewCard } from "components/PreviewCard"; +import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; +import { cJoin, cIf } from "helpers/className"; /* * ╭─────────────╮ @@ -52,7 +54,8 @@ const Contents = ({ languages, ...otherProps }: Props): JSX.Element => { - const hoverable = useMediaHoverable(); + const hoverable = useDeviceSupportsHover(); + const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); const [groupingMethod, setGroupingMethod] = useState( DEFAULT_FILTERS_STATE.groupingMethod @@ -135,6 +138,8 @@ const Contents = ({ description={langui.contents_description} /> + +
+
+ ); + } + return (
-
+
) : ( + /* TODO: Add to langui */ "The content is not available" )}
diff --git a/src/pages/library/[slug]/scans.tsx b/src/pages/library/[slug]/scans.tsx index 179f143..908a7e3 100644 --- a/src/pages/library/[slug]/scans.tsx +++ b/src/pages/library/[slug]/scans.tsx @@ -1,10 +1,7 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { Fragment, useCallback, useMemo } from "react"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel, ContentPanelWidthSizes, @@ -45,6 +42,11 @@ import { useSmartLanguage } from "hooks/useSmartLanguage"; import { TranslatedProps } from "helpers/types/TranslatedProps"; import { TranslatedNavOption } from "components/PanelComponents/NavOption"; import { useIntersectionList } from "hooks/useIntersectionList"; +import { + useIs1ColumnLayout, + useIsContentPanelNoMoreThan, +} from "hooks/useContainerQuery"; +import { cIf, cJoin } from "helpers/className"; /* * ╭────────╮ @@ -71,6 +73,7 @@ const LibrarySlug = ({ ...otherProps }: Props): JSX.Element => { const [openLightBox, LightBox] = useLightBox(); + const is1ColumnLayout = useIs1ColumnLayout(); const ids = useMemo( () => @@ -89,11 +92,11 @@ const LibrarySlug = ({ title={langui.item} langui={langui} className="mb-4" - displayOn={ReturnButtonType.Desktop} + displayOnlyOn="3ColumnsLayout" />
-
+
@@ -370,6 +374,7 @@ const ScanSet = ({ langui, content, }: ScanSetProps): JSX.Element => { + const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl"); const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ items: scanSet, @@ -533,8 +538,15 @@ const ScanSet = ({
{pages.map((page, index) => (
{ + const is1ColumnLayout = useIsContentPanelNoMoreThan("4xl"); const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ items: images, @@ -727,9 +740,15 @@ const ScanSetCover = ({
{coverImages.map((image, index) => (
{ - const hoverable = useMediaHoverable(); + const hoverable = useDeviceSupportsHover(); const { libraryItemUserStatus } = useAppLayout(); + const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); const [searchName, setSearchName] = useState( DEFAULT_FILTERS_STATE.searchName @@ -261,6 +265,8 @@ const Library = ({ description={langui.library_description} /> + + )} - className="grid-cols-2 items-end desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]" + className={cJoin( + "grid-cols-2 items-end", + cIf( + isContentPanelAtLeast4xl, + "grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]" + ) + )} searchingTerm={searchName} sortingFunction={sortingFunction} groupingFunction={groupingFunction} @@ -444,6 +456,7 @@ const Library = ({ currencies, filteringFunction, groupingFunction, + isContentPanelAtLeast4xl, items, keepInfoVisible, langui, diff --git a/src/pages/news/index.tsx b/src/pages/news/index.tsx index 6f62451..11c56c3 100644 --- a/src/pages/news/index.tsx +++ b/src/pages/news/index.tsx @@ -17,12 +17,15 @@ import { Icon } from "components/Ico"; import { WithLabel } from "components/Inputs/WithLabel"; import { TextInput } from "components/Inputs/TextInput"; import { Button } from "components/Inputs/Button"; -import { useMediaHoverable } from "hooks/useMediaQuery"; +import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { filterHasAttributes } from "helpers/others"; import { SmartList } from "components/SmartList"; import { getOpenGraph } from "helpers/openGraph"; import { compareDate } from "helpers/date"; import { TranslatedPreviewCard } from "components/PreviewCard"; +import { HorizontalLine } from "components/HorizontalLine"; +import { cIf } from "helpers/className"; +import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; /* * ╭─────────────╮ @@ -44,7 +47,8 @@ interface Props extends AppStaticProps, AppLayoutRequired { } const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { - const hoverable = useMediaHoverable(); + const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); + const hoverable = useDeviceSupportsHover(); const [searchName, setSearchName] = useState( DEFAULT_FILTERS_STATE.searchName ); @@ -63,6 +67,8 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { description={langui.news_description} /> + + { }} /> )} - className="grid-cols-1 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]" + className={cIf( + isContentPanelAtLeast4xl, + "grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8", + "grid-cols-2 gap-x-4 gap-y-6" + )} searchingTerm={searchName} searchingBy={(post) => `${prettySlug(post.attributes.slug)} ${post.attributes.translations @@ -140,7 +150,7 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { /> ), - [keepInfoVisible, langui, posts, searchName] + [keepInfoVisible, langui, posts, searchName, isContentPanelAtLeast4xl] ); return ( diff --git a/src/pages/wiki/[slug]/index.tsx b/src/pages/wiki/[slug]/index.tsx index f1f5ed1..57253bf 100644 --- a/src/pages/wiki/[slug]/index.tsx +++ b/src/pages/wiki/[slug]/index.tsx @@ -4,10 +4,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Chip } from "components/Chip"; import { HorizontalLine } from "components/HorizontalLine"; import { Img } from "components/Img"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel, ContentPanelWidthSizes, @@ -32,6 +29,8 @@ import { staticSmartLanguage, } from "helpers/locales"; import { getDescription } from "helpers/description"; +import { cIf, cJoin } from "helpers/className"; +import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; /* * ╭────────╮ @@ -60,6 +59,7 @@ const WikiPage = ({ }); const [openLightBox, LightBox] = useLightBox(); + const is3ColumnsLayout = useIs3ColumnsLayout(); const subPanel = useMemo( () => ( @@ -68,7 +68,7 @@ const WikiPage = ({ href={`/wiki`} title={langui.wiki} langui={langui} - displayOn={ReturnButtonType.Desktop} + displayOnlyOn={"3ColumnsLayout"} /> ), @@ -84,7 +84,7 @@ const WikiPage = ({ href={`/wiki`} title={langui.wiki} langui={langui} - displayOn={ReturnButtonType.Mobile} + displayOnlyOn={"1ColumnLayout"} className="mb-10" /> @@ -101,121 +101,131 @@ const WikiPage = ({
- - {selectedTranslation && ( -
-
- {page.thumbnail?.data?.attributes && ( - { - if (page.thumbnail?.data?.attributes?.url) { - openLightBox([ - getAssetURL( - page.thumbnail.data.attributes.url, - ImageQuality.Large - ), - ]); - } - }} - /> + <> + +
+
+ {page.thumbnail?.data?.attributes && ( + { + if (page.thumbnail?.data?.attributes?.url) { + openLightBox([ + getAssetURL( + page.thumbnail.data.attributes.url, + ImageQuality.Large + ), + ]); + } + }} + /> + )} +
+ {page.categories?.data && page.categories.data.length > 0 && ( + <> +

+ {langui.categories} +

+ +
+ {filterHasAttributes(page.categories.data, [ + "attributes", + ] as const).map((category) => ( + + ))} +
+ + )} + + {page.tags?.data && page.tags.data.length > 0 && ( + <> +

+ {langui.tags} +

+
+ {filterHasAttributes(page.tags.data, [ + "attributes", + ] as const).map((tag) => ( + + ))} +
+ + )} +
+
+ + {isDefinedAndNotEmpty(selectedTranslation.summary) && ( +
+

+ {langui.summary} +

+

{selectedTranslation.summary}

+
)} -
- {page.categories?.data && page.categories.data.length > 0 && ( - <> -

- {langui.categories} -

-
- {filterHasAttributes(page.categories.data, [ - "attributes", - ] as const).map((category) => ( - - ))} -
- - )} - - {page.tags?.data && page.tags.data.length > 0 && ( - <> -

- {langui.tags} -

-
- {filterHasAttributes(page.tags.data, [ - "attributes", - ] as const).map((tag) => ( - - ))} -
- - )} -
+ {filterHasAttributes(page.definitions, [ + "translations", + ] as const).map((definition, index) => ( +
+ ({ + language: translation?.language?.data?.attributes?.code, + definition: translation?.definition, + status: translation?.status, + }) + )} + index={index + 1} + languages={languages} + langui={langui} + categories={filterHasAttributes( + definition.categories?.data, + ["attributes"] as const + ).map((category) => category.attributes.short)} + /> +
+ ))}
- - {isDefinedAndNotEmpty(selectedTranslation.summary) && ( -
-

- {langui.summary} -

-

{selectedTranslation.summary}

-
- )} - - {filterHasAttributes(page.definitions, [ - "translations", - ] as const).map((definition, index) => ( -
- ({ - language: translation?.language?.data?.attributes?.code, - definition: translation?.definition, - status: translation?.status, - }))} - index={index + 1} - languages={languages} - langui={langui} - categories={filterHasAttributes(definition.categories?.data, [ - "attributes", - ] as const).map((category) => category.attributes.short)} - /> -
- ))} -
+ )} ), [ LanguageSwitcher, LightBox, + is3ColumnsLayout, languageSwitcherProps, languages, langui, openLightBox, - page, + page.categories?.data, + page.definitions, + page.tags?.data, + page.thumbnail?.data?.attributes, selectedTranslation, ] ); diff --git a/src/pages/wiki/chronology.tsx b/src/pages/wiki/chronology.tsx index bf366eb..9bbe5d6 100644 --- a/src/pages/wiki/chronology.tsx +++ b/src/pages/wiki/chronology.tsx @@ -3,10 +3,7 @@ import { Fragment, useCallback, useMemo } from "react"; import { useRouter } from "next/router"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { InsetBox } from "components/InsetBox"; -import { - ReturnButton, - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Panels/ContentPanel"; import { SubPanel } from "components/Panels/SubPanel"; import { @@ -71,7 +68,7 @@ const Chronology = ({ href="/wiki" title={langui.wiki} langui={langui} - displayOn={ReturnButtonType.Desktop} + displayOnlyOn="3ColumnsLayout" /> @@ -110,7 +107,7 @@ const Chronology = ({ href="/wiki" title={langui.wiki} langui={langui} - displayOn={ReturnButtonType.Mobile} + displayOnlyOn="1ColumnLayout" className="mb-10" /> diff --git a/src/pages/wiki/index.tsx b/src/pages/wiki/index.tsx index 1d2d636..bdb2d75 100644 --- a/src/pages/wiki/index.tsx +++ b/src/pages/wiki/index.tsx @@ -18,7 +18,7 @@ import { Button } from "components/Inputs/Button"; import { Switch } from "components/Inputs/Switch"; import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; -import { useMediaHoverable } from "hooks/useMediaQuery"; +import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { filterDefined, filterHasAttributes } from "helpers/others"; import { SmartList } from "components/SmartList"; import { Select } from "components/Inputs/Select"; @@ -26,6 +26,8 @@ import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; import { prettySlug } from "helpers/formatters"; import { getOpenGraph } from "helpers/openGraph"; import { TranslatedPreviewCard } from "components/PreviewCard"; +import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; +import { cIf } from "helpers/className"; /* * ╭─────────────╮ @@ -48,7 +50,8 @@ interface Props extends AppStaticProps, AppLayoutRequired { } const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => { - const hoverable = useMediaHoverable(); + const hoverable = useDeviceSupportsHover(); + const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); const [searchName, setSearchName] = useState( DEFAULT_FILTERS_STATE.searchName @@ -73,6 +76,8 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => { description={langui.wiki_description} /> + + { setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); }} /> +

@@ -193,7 +199,11 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => { /> )} langui={langui} - className="grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]" + className={cIf( + isContentPanelAtLeast4xl, + "grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8", + "grid-cols-2 gap-x-3 gap-y-5" + )} searchingTerm={searchName} searchingBy={(item) => filterDefined(item.attributes.translations) @@ -210,7 +220,14 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => { /> ), - [groupingFunction, keepInfoVisible, langui, pages, searchName] + [ + groupingFunction, + keepInfoVisible, + langui, + pages, + searchName, + isContentPanelAtLeast4xl, + ] ); return ( diff --git a/src/tailwind.css b/src/tailwind.css index 1a69c12..f59a309 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -35,7 +35,7 @@ mark { } *::-webkit-scrollbar { - @apply w-3 mobile:w-0; + @apply w-3; } *::-webkit-scrollbar-track { diff --git a/tailwind.config.js b/tailwind.config.js index 83ddbf6..5fd366e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,5 @@ const plugin = require("tailwindcss/plugin"); -const { breaks, colors, fonts, fontFamilies } = require("./design.config.js"); +const { colors, fonts, fontFamilies } = require("./design.config.js"); const rgb = (color) => [color.r, color.g, color.b].join(" "); @@ -23,9 +23,6 @@ module.exports = { ...fonts, }, screens: { - desktop: breaks.desktop, - mobile: breaks.mobile, - thin: breaks.thin, hoverable: { raw: "(hover: hover)" }, notHoverable: { raw: "(hover: none)" }, },